diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/adc.rs | 73 | ||||
| -rw-r--r-- | src/board.rs | 14 | ||||
| -rw-r--r-- | src/clocks.rs | 134 | ||||
| -rw-r--r-- | src/clocks/config.rs | 199 | ||||
| -rw-r--r-- | src/clocks/mod.rs | 887 | ||||
| -rw-r--r-- | src/clocks/periph_helpers.rs | 393 | ||||
| -rw-r--r-- | src/config.rs | 3 | ||||
| -rw-r--r-- | src/lib.rs | 10 | ||||
| -rw-r--r-- | src/lpuart/buffered.rs | 11 | ||||
| -rw-r--r-- | src/lpuart/mod.rs | 156 | ||||
| -rw-r--r-- | src/ostimer.rs | 73 | ||||
| -rw-r--r-- | src/reset.rs | 112 | ||||
| -rw-r--r-- | src/rtc.rs | 30 | ||||
| -rw-r--r-- | src/uart.rs | 316 |
14 files changed, 1663 insertions, 748 deletions
diff --git a/src/adc.rs b/src/adc.rs index 655bf934f..b5ec5983f 100644 --- a/src/adc.rs +++ b/src/adc.rs | |||
| @@ -1,6 +1,10 @@ | |||
| 1 | //! ADC driver | 1 | //! ADC driver |
| 2 | use core::sync::atomic::{AtomicBool, Ordering}; | 2 | use core::sync::atomic::{AtomicBool, Ordering}; |
| 3 | 3 | ||
| 4 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 5 | |||
| 6 | use crate::clocks::periph_helpers::{AdcClockSel, AdcConfig, Div4}; | ||
| 7 | use crate::clocks::{enable_and_reset, Gate, PoweredClock}; | ||
| 4 | use crate::pac; | 8 | use crate::pac; |
| 5 | use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; | 9 | use crate::pac::adc1::cfg::{HptExdi, Pwrsel, Refsel, Tcmdres, Tprictrl, Tres}; |
| 6 | use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; | 10 | use crate::pac::adc1::cmdh1::{Avgs, Cmpen, Next, Sts}; |
| @@ -12,7 +16,7 @@ type Regs = pac::adc1::RegisterBlock; | |||
| 12 | 16 | ||
| 13 | static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); | 17 | static INTERRUPT_TRIGGERED: AtomicBool = AtomicBool::new(false); |
| 14 | // Token-based instance pattern like embassy-imxrt | 18 | // Token-based instance pattern like embassy-imxrt |
| 15 | pub trait Instance { | 19 | pub trait Instance: Gate<MrccPeriphConfig = AdcConfig> + PeripheralType { |
| 16 | fn ptr() -> *const Regs; | 20 | fn ptr() -> *const Regs; |
| 17 | } | 21 | } |
| 18 | 22 | ||
| @@ -26,12 +30,12 @@ impl Instance for crate::peripherals::ADC1 { | |||
| 26 | } | 30 | } |
| 27 | 31 | ||
| 28 | // Also implement Instance for the Peri wrapper type | 32 | // Also implement Instance for the Peri wrapper type |
| 29 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { | 33 | // impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::ADC1> { |
| 30 | #[inline(always)] | 34 | // #[inline(always)] |
| 31 | fn ptr() -> *const Regs { | 35 | // fn ptr() -> *const Regs { |
| 32 | pac::Adc1::ptr() | 36 | // pac::Adc1::ptr() |
| 33 | } | 37 | // } |
| 34 | } | 38 | // } |
| 35 | 39 | ||
| 36 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 40 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 37 | #[repr(u8)] | 41 | #[repr(u8)] |
| @@ -60,6 +64,29 @@ pub struct LpadcConfig { | |||
| 60 | pub enable_conv_pause: bool, | 64 | pub enable_conv_pause: bool, |
| 61 | pub conv_pause_delay: u16, | 65 | pub conv_pause_delay: u16, |
| 62 | pub fifo_watermark: u8, | 66 | pub fifo_watermark: u8, |
| 67 | pub power: PoweredClock, | ||
| 68 | pub source: AdcClockSel, | ||
| 69 | pub div: Div4, | ||
| 70 | } | ||
| 71 | |||
| 72 | impl Default for LpadcConfig { | ||
| 73 | fn default() -> Self { | ||
| 74 | LpadcConfig { | ||
| 75 | enable_in_doze_mode: true, | ||
| 76 | conversion_average_mode: CalAvgs::NoAverage, | ||
| 77 | enable_analog_preliminary: false, | ||
| 78 | power_up_delay: 0x80, | ||
| 79 | reference_voltage_source: Refsel::Option1, | ||
| 80 | power_level_mode: Pwrsel::Lowest, | ||
| 81 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 82 | enable_conv_pause: false, | ||
| 83 | conv_pause_delay: 0, | ||
| 84 | fifo_watermark: 0, | ||
| 85 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 86 | source: AdcClockSel::FroLfDiv, | ||
| 87 | div: Div4::no_div(), | ||
| 88 | } | ||
| 89 | } | ||
| 63 | } | 90 | } |
| 64 | 91 | ||
| 65 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 92 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| @@ -94,15 +121,24 @@ pub struct ConvResult { | |||
| 94 | pub conv_value: u16, | 121 | pub conv_value: u16, |
| 95 | } | 122 | } |
| 96 | 123 | ||
| 97 | pub struct Adc<I: Instance> { | 124 | pub struct Adc<'a, I: Instance> { |
| 98 | _inst: core::marker::PhantomData<I>, | 125 | _inst: core::marker::PhantomData<&'a mut I>, |
| 99 | } | 126 | } |
| 100 | 127 | ||
| 101 | impl<I: Instance> Adc<I> { | 128 | impl<'a, I: Instance> Adc<'a, I> { |
| 102 | /// initialize ADC | 129 | /// initialize ADC |
| 103 | pub fn new(_inst: impl Instance, config: LpadcConfig) -> Self { | 130 | pub fn new(_inst: Peri<'a, I>, config: LpadcConfig) -> Self { |
| 104 | let adc = unsafe { &*I::ptr() }; | 131 | let adc = unsafe { &*I::ptr() }; |
| 105 | 132 | ||
| 133 | let _clock_freq = unsafe { | ||
| 134 | enable_and_reset::<I>(&AdcConfig { | ||
| 135 | power: config.power, | ||
| 136 | source: config.source, | ||
| 137 | div: config.div, | ||
| 138 | }) | ||
| 139 | .expect("Adc Init should not fail") | ||
| 140 | }; | ||
| 141 | |||
| 106 | /* Reset the module. */ | 142 | /* Reset the module. */ |
| 107 | adc.ctrl().modify(|_, w| w.rst().held_in_reset()); | 143 | adc.ctrl().modify(|_, w| w.rst().held_in_reset()); |
| 108 | adc.ctrl().modify(|_, w| w.rst().released_from_reset()); | 144 | adc.ctrl().modify(|_, w| w.rst().released_from_reset()); |
| @@ -194,21 +230,6 @@ impl<I: Instance> Adc<I> { | |||
| 194 | adc.ctrl().modify(|_, w| w.adcen().disabled()); | 230 | adc.ctrl().modify(|_, w| w.adcen().disabled()); |
| 195 | } | 231 | } |
| 196 | 232 | ||
| 197 | pub fn get_default_config() -> LpadcConfig { | ||
| 198 | LpadcConfig { | ||
| 199 | enable_in_doze_mode: true, | ||
| 200 | conversion_average_mode: CalAvgs::NoAverage, | ||
| 201 | enable_analog_preliminary: false, | ||
| 202 | power_up_delay: 0x80, | ||
| 203 | reference_voltage_source: Refsel::Option1, | ||
| 204 | power_level_mode: Pwrsel::Lowest, | ||
| 205 | trigger_priority_policy: TriggerPriorityPolicy::ConvPreemptImmediatelyNotAutoResumed, | ||
| 206 | enable_conv_pause: false, | ||
| 207 | conv_pause_delay: 0, | ||
| 208 | fifo_watermark: 0, | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | pub fn do_offset_calibration(&self) { | 233 | pub fn do_offset_calibration(&self) { |
| 213 | let adc = unsafe { &*I::ptr() }; | 234 | let adc = unsafe { &*I::ptr() }; |
| 214 | // Enable calibration mode | 235 | // Enable calibration mode |
diff --git a/src/board.rs b/src/board.rs deleted file mode 100644 index fa679e82c..000000000 --- a/src/board.rs +++ /dev/null | |||
| @@ -1,14 +0,0 @@ | |||
| 1 | use crate::{clocks, pac, pins}; | ||
| 2 | |||
| 3 | /// Initialize clocks and pin muxing for UART2 debug console. | ||
| 4 | pub unsafe fn init_uart2(p: &pac::Peripherals) { | ||
| 5 | clocks::ensure_frolf_running(p); | ||
| 6 | clocks::enable_uart2_port2(p); | ||
| 7 | pins::configure_uart2_pins_port2(); | ||
| 8 | clocks::select_uart2_clock(p); | ||
| 9 | } | ||
| 10 | |||
| 11 | /// Initialize clocks for the LED GPIO/PORT used by the blink example. | ||
| 12 | pub unsafe fn init_led(p: &pac::Peripherals) { | ||
| 13 | clocks::enable_led_port(p); | ||
| 14 | } | ||
diff --git a/src/clocks.rs b/src/clocks.rs deleted file mode 100644 index 65a17cef6..000000000 --- a/src/clocks.rs +++ /dev/null | |||
| @@ -1,134 +0,0 @@ | |||
| 1 | //! Clock control helpers (no magic numbers, PAC field access only). | ||
| 2 | //! Provides reusable gate abstractions for peripherals used by the examples. | ||
| 3 | use crate::pac; | ||
| 4 | |||
| 5 | /// Trait describing an AHB clock gate that can be toggled through MRCC. | ||
| 6 | pub trait Gate { | ||
| 7 | /// Enable the clock gate. | ||
| 8 | unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 9 | |||
| 10 | /// Return whether the clock gate is currently enabled. | ||
| 11 | fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool; | ||
| 12 | } | ||
| 13 | |||
| 14 | /// Enable a clock gate for the given peripheral set. | ||
| 15 | #[inline] | ||
| 16 | pub unsafe fn enable<G: Gate>(peripherals: &pac::Peripherals) { | ||
| 17 | let mrcc = &peripherals.mrcc0; | ||
| 18 | G::enable(mrcc); | ||
| 19 | while !G::is_enabled(mrcc) {} | ||
| 20 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Check whether a gate is currently enabled. | ||
| 24 | #[inline] | ||
| 25 | pub fn is_enabled<G: Gate>(peripherals: &pac::Peripherals) -> bool { | ||
| 26 | G::is_enabled(&peripherals.mrcc0) | ||
| 27 | } | ||
| 28 | |||
| 29 | macro_rules! impl_cc_gate { | ||
| 30 | ($name:ident, $reg:ident, $field:ident) => { | ||
| 31 | pub struct $name; | ||
| 32 | |||
| 33 | impl Gate for $name { | ||
| 34 | #[inline] | ||
| 35 | unsafe fn enable(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 36 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 37 | } | ||
| 38 | |||
| 39 | #[inline] | ||
| 40 | fn is_enabled(mrcc: &pac::mrcc0::RegisterBlock) -> bool { | ||
| 41 | mrcc.$reg().read().$field().is_enabled() | ||
| 42 | } | ||
| 43 | } | ||
| 44 | }; | ||
| 45 | } | ||
| 46 | |||
| 47 | pub mod gate { | ||
| 48 | use super::*; | ||
| 49 | |||
| 50 | impl_cc_gate!(Port2, mrcc_glb_cc1, port2); | ||
| 51 | impl_cc_gate!(Port3, mrcc_glb_cc1, port3); | ||
| 52 | impl_cc_gate!(Ostimer0, mrcc_glb_cc1, ostimer0); | ||
| 53 | impl_cc_gate!(Lpuart2, mrcc_glb_cc0, lpuart2); | ||
| 54 | impl_cc_gate!(Gpio3, mrcc_glb_cc2, gpio3); | ||
| 55 | impl_cc_gate!(Port1, mrcc_glb_cc1, port1); | ||
| 56 | impl_cc_gate!(Adc1, mrcc_glb_cc1, adc1); | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Convenience helper enabling the PORT2 and LPUART2 gates required for the debug UART. | ||
| 60 | pub unsafe fn enable_uart2_port2(peripherals: &pac::Peripherals) { | ||
| 61 | enable::<gate::Port2>(peripherals); | ||
| 62 | enable::<gate::Lpuart2>(peripherals); | ||
| 63 | } | ||
| 64 | |||
| 65 | /// Convenience helper enabling the PORT3 and GPIO3 gates used by the LED in the examples. | ||
| 66 | pub unsafe fn enable_led_port(peripherals: &pac::Peripherals) { | ||
| 67 | enable::<gate::Port3>(peripherals); | ||
| 68 | enable::<gate::Gpio3>(peripherals); | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Convenience helper enabling the OSTIMER0 clock gate. | ||
| 72 | pub unsafe fn enable_ostimer0(peripherals: &pac::Peripherals) { | ||
| 73 | enable::<gate::Ostimer0>(peripherals); | ||
| 74 | } | ||
| 75 | |||
| 76 | pub unsafe fn select_uart2_clock(peripherals: &pac::Peripherals) { | ||
| 77 | // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 78 | let mrcc = &peripherals.mrcc0; | ||
| 79 | mrcc.mrcc_lpuart2_clksel().write(|w| w.mux().clkroot_func_0()); | ||
| 80 | mrcc.mrcc_lpuart2_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 81 | } | ||
| 82 | |||
| 83 | pub unsafe fn ensure_frolf_running(peripherals: &pac::Peripherals) { | ||
| 84 | // Ensure FRO_LF divider clock is running (reset default HALT=1 stops it) | ||
| 85 | let sys = &peripherals.syscon; | ||
| 86 | sys.frolfdiv().modify(|_, w| { | ||
| 87 | // DIV defaults to 0; keep it explicit and clear HALT | ||
| 88 | unsafe { w.div().bits(0) }.halt().run() | ||
| 89 | }); | ||
| 90 | } | ||
| 91 | |||
| 92 | /// Compute the FRO_LF_DIV output frequency currently selected for LPUART2. | ||
| 93 | /// Assumes select_uart2_clock() has chosen MUX=0 (FRO_LF_DIV) and DIV is set in SYSCON.FRO_LF_DIV. | ||
| 94 | pub unsafe fn uart2_src_hz(peripherals: &pac::Peripherals) -> u32 { | ||
| 95 | // SYSCON.FRO_LF_DIV: DIV field is simple divider: freq_out = 12_000_000 / (DIV+1) for many NXP parts. | ||
| 96 | // On MCXA276 FRO_LF base is 12 MHz; our init keeps DIV=0, so result=12_000_000. | ||
| 97 | // Read it anyway for future generality. | ||
| 98 | let div = peripherals.syscon.frolfdiv().read().div().bits() as u32; | ||
| 99 | let base = 12_000_000u32; | ||
| 100 | base / (div + 1) | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Enable clock gate and release reset for OSTIMER0. | ||
| 104 | /// Select OSTIMER0 clock source = 1 MHz root (working bring-up configuration). | ||
| 105 | pub unsafe fn select_ostimer0_clock_1m(peripherals: &pac::Peripherals) { | ||
| 106 | let mrcc = &peripherals.mrcc0; | ||
| 107 | mrcc.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); | ||
| 108 | } | ||
| 109 | |||
| 110 | pub unsafe fn init_fro16k(peripherals: &pac::Peripherals) { | ||
| 111 | let vbat = &peripherals.vbat0; | ||
| 112 | // Enable FRO16K oscillator | ||
| 113 | vbat.froctla().modify(|_, w| w.fro_en().set_bit()); | ||
| 114 | |||
| 115 | // Lock the control register | ||
| 116 | vbat.frolcka().modify(|_, w| w.lock().set_bit()); | ||
| 117 | |||
| 118 | // Enable clock outputs to both VSYS and VDD_CORE domains | ||
| 119 | // Bit 0: clk_16k0 to VSYS domain | ||
| 120 | // Bit 1: clk_16k1 to VDD_CORE domain | ||
| 121 | vbat.froclke().modify(|_, w| unsafe { w.clke().bits(0x3) }); | ||
| 122 | } | ||
| 123 | |||
| 124 | pub unsafe fn enable_adc(peripherals: &pac::Peripherals) { | ||
| 125 | enable::<gate::Port1>(peripherals); | ||
| 126 | enable::<gate::Adc1>(peripherals); | ||
| 127 | } | ||
| 128 | |||
| 129 | pub unsafe fn select_adc_clock(peripherals: &pac::Peripherals) { | ||
| 130 | // Use FRO_LF_DIV (already running) MUX=0 DIV=0 | ||
| 131 | let mrcc = &peripherals.mrcc0; | ||
| 132 | mrcc.mrcc_adc_clksel().write(|w| w.mux().clkroot_func_0()); | ||
| 133 | mrcc.mrcc_adc_clkdiv().write(|w| unsafe { w.bits(0) }); | ||
| 134 | } | ||
diff --git a/src/clocks/config.rs b/src/clocks/config.rs new file mode 100644 index 000000000..a517afcca --- /dev/null +++ b/src/clocks/config.rs | |||
| @@ -0,0 +1,199 @@ | |||
| 1 | //! Clock Configuration | ||
| 2 | //! | ||
| 3 | //! This module holds configuration types used for the system clocks. For | ||
| 4 | //! configuration of individual peripherals, see [`super::periph_helpers`]. | ||
| 5 | |||
| 6 | use super::PoweredClock; | ||
| 7 | |||
| 8 | /// This type represents a divider in the range 1..=256. | ||
| 9 | /// | ||
| 10 | /// At a hardware level, this is an 8-bit register from 0..=255, | ||
| 11 | /// which adds one. | ||
| 12 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 13 | pub struct Div8(pub(super) u8); | ||
| 14 | |||
| 15 | impl Div8 { | ||
| 16 | /// Store a "raw" divisor value that will divide the source by | ||
| 17 | /// `(n + 1)`, e.g. `Div8::from_raw(0)` will divide the source | ||
| 18 | /// by 1, and `Div8::from_raw(255)` will divide the source by | ||
| 19 | /// 256. | ||
| 20 | pub const fn from_raw(n: u8) -> Self { | ||
| 21 | Self(n) | ||
| 22 | } | ||
| 23 | |||
| 24 | /// Store a specific divisor value that will divide the source | ||
| 25 | /// by `n`. e.g. `Div8::from_divisor(1)` will divide the source | ||
| 26 | /// by 1, and `Div8::from_divisor(256)` will divide the source | ||
| 27 | /// by 256. | ||
| 28 | /// | ||
| 29 | /// Will return `None` if `n` is not in the range `1..=256`. | ||
| 30 | /// Consider [`Self::from_raw`] for an infallible version. | ||
| 31 | pub const fn from_divisor(n: u16) -> Option<Self> { | ||
| 32 | let Some(n) = n.checked_sub(1) else { | ||
| 33 | return None; | ||
| 34 | }; | ||
| 35 | if n > (u8::MAX as u16) { | ||
| 36 | return None; | ||
| 37 | } | ||
| 38 | Some(Self(n as u8)) | ||
| 39 | } | ||
| 40 | |||
| 41 | /// Convert into "raw" bits form | ||
| 42 | #[inline(always)] | ||
| 43 | pub const fn into_bits(self) -> u8 { | ||
| 44 | self.0 | ||
| 45 | } | ||
| 46 | |||
| 47 | /// Convert into "divisor" form, as a u32 for convenient frequency math | ||
| 48 | #[inline(always)] | ||
| 49 | pub const fn into_divisor(self) -> u32 { | ||
| 50 | self.0 as u32 + 1 | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// ```text | ||
| 55 | /// ┌─────────────────────────────────────────────────────────┐ | ||
| 56 | /// │ │ | ||
| 57 | /// │ ┌───────────┐ clk_out ┌─────────┐ │ | ||
| 58 | /// XTAL ──────┼──▷│ System │───────────▷│ │ clk_in │ | ||
| 59 | /// │ │ OSC │ clkout_byp │ MUX │──────────────────┼──────▷ | ||
| 60 | /// EXTAL ──────┼──▷│ │───────────▷│ │ │ | ||
| 61 | /// │ └───────────┘ └─────────┘ │ | ||
| 62 | /// │ │ | ||
| 63 | /// │ ┌───────────┐ fro_hf_root ┌────┐ fro_hf │ | ||
| 64 | /// │ │ FRO180 ├───────┬─────▷│ CG │─────────────────────┼──────▷ | ||
| 65 | /// │ │ │ │ ├────┤ clk_45m │ | ||
| 66 | /// │ │ │ └─────▷│ CG │─────────────────────┼──────▷ | ||
| 67 | /// │ └───────────┘ └────┘ │ | ||
| 68 | /// │ ┌───────────┐ fro_12m_root ┌────┐ fro_12m │ | ||
| 69 | /// │ │ FRO12M │────────┬─────▷│ CG │────────────────────┼──────▷ | ||
| 70 | /// │ │ │ │ ├────┤ clk_1m │ | ||
| 71 | /// │ │ │ └─────▷│1/12│────────────────────┼──────▷ | ||
| 72 | /// │ └───────────┘ └────┘ │ | ||
| 73 | /// │ │ | ||
| 74 | /// │ ┌──────────┐ │ | ||
| 75 | /// │ │000 │ │ | ||
| 76 | /// │ clk_in │ │ │ | ||
| 77 | /// │ ───────────────▷│001 │ │ | ||
| 78 | /// │ fro_12m │ │ │ | ||
| 79 | /// │ ───────────────▷│010 │ │ | ||
| 80 | /// │ fro_hf_root │ │ │ | ||
| 81 | /// │ ───────────────▷│011 │ main_clk │ | ||
| 82 | /// │ │ │───────────────────────────┼──────▷ | ||
| 83 | /// clk_16k ──────┼─────────────────▷│100 │ │ | ||
| 84 | /// │ none │ │ │ | ||
| 85 | /// │ ───────────────▷│101 │ │ | ||
| 86 | /// │ pll1_clk │ │ │ | ||
| 87 | /// │ ───────────────▷│110 │ │ | ||
| 88 | /// │ none │ │ │ | ||
| 89 | /// │ ───────────────▷│111 │ │ | ||
| 90 | /// │ └──────────┘ │ | ||
| 91 | /// │ ▲ │ | ||
| 92 | /// │ │ │ | ||
| 93 | /// │ SCG SCS │ | ||
| 94 | /// │ SCG-Lite │ | ||
| 95 | /// └─────────────────────────────────────────────────────────┘ | ||
| 96 | /// | ||
| 97 | /// | ||
| 98 | /// clk_in ┌─────┐ | ||
| 99 | /// ───────────────▷│00 │ | ||
| 100 | /// clk_45m │ │ | ||
| 101 | /// ───────────────▷│01 │ ┌───────────┐ pll1_clk | ||
| 102 | /// none │ │─────▷│ SPLL │───────────────▷ | ||
| 103 | /// ───────────────▷│10 │ └───────────┘ | ||
| 104 | /// fro_12m │ │ | ||
| 105 | /// ───────────────▷│11 │ | ||
| 106 | /// └─────┘ | ||
| 107 | /// ``` | ||
| 108 | #[non_exhaustive] | ||
| 109 | pub struct ClocksConfig { | ||
| 110 | /// FIRC, FRO180, 45/60/90/180M clock source | ||
| 111 | pub firc: Option<FircConfig>, | ||
| 112 | /// SIRC, FRO12M, clk_12m clock source | ||
| 113 | // NOTE: I don't think we *can* disable the SIRC? | ||
| 114 | pub sirc: SircConfig, | ||
| 115 | /// FRO16K clock source | ||
| 116 | pub fro16k: Option<Fro16KConfig>, | ||
| 117 | } | ||
| 118 | |||
| 119 | // FIRC/FRO180M | ||
| 120 | |||
| 121 | /// ```text | ||
| 122 | /// ┌───────────┐ fro_hf_root ┌────┐ fro_hf | ||
| 123 | /// │ FRO180M ├───────┬─────▷│GATE│──────────▷ | ||
| 124 | /// │ │ │ ├────┤ clk_45m | ||
| 125 | /// │ │ └─────▷│GATE│──────────▷ | ||
| 126 | /// └───────────┘ └────┘ | ||
| 127 | /// ``` | ||
| 128 | #[non_exhaustive] | ||
| 129 | pub struct FircConfig { | ||
| 130 | /// Selected clock frequency | ||
| 131 | pub frequency: FircFreqSel, | ||
| 132 | /// Selected power state of the clock | ||
| 133 | pub power: PoweredClock, | ||
| 134 | /// Is the "fro_hf" gated clock enabled? | ||
| 135 | pub fro_hf_enabled: bool, | ||
| 136 | /// Is the "clk_45m" gated clock enabled? | ||
| 137 | pub clk_45m_enabled: bool, | ||
| 138 | /// Is the "fro_hf_div" clock enabled? Requires `fro_hf`! | ||
| 139 | pub fro_hf_div: Option<Div8>, | ||
| 140 | } | ||
| 141 | |||
| 142 | /// Selected FIRC frequency | ||
| 143 | pub enum FircFreqSel { | ||
| 144 | /// 45MHz Output | ||
| 145 | Mhz45, | ||
| 146 | /// 60MHz Output | ||
| 147 | Mhz60, | ||
| 148 | /// 90MHz Output | ||
| 149 | Mhz90, | ||
| 150 | /// 180MHz Output | ||
| 151 | Mhz180, | ||
| 152 | } | ||
| 153 | |||
| 154 | // SIRC/FRO12M | ||
| 155 | |||
| 156 | /// ```text | ||
| 157 | /// ┌───────────┐ fro_12m_root ┌────┐ fro_12m | ||
| 158 | /// │ FRO12M │────────┬─────▷│ CG │──────────▷ | ||
| 159 | /// │ │ │ ├────┤ clk_1m | ||
| 160 | /// │ │ └─────▷│1/12│──────────▷ | ||
| 161 | /// └───────────┘ └────┘ | ||
| 162 | /// ``` | ||
| 163 | #[non_exhaustive] | ||
| 164 | pub struct SircConfig { | ||
| 165 | pub power: PoweredClock, | ||
| 166 | // peripheral output, aka sirc_12mhz | ||
| 167 | pub fro_12m_enabled: bool, | ||
| 168 | /// Is the "fro_lf_div" clock enabled? Requires `fro_12m`! | ||
| 169 | pub fro_lf_div: Option<Div8>, | ||
| 170 | } | ||
| 171 | |||
| 172 | #[non_exhaustive] | ||
| 173 | pub struct Fro16KConfig { | ||
| 174 | pub vsys_domain_active: bool, | ||
| 175 | pub vdd_core_domain_active: bool, | ||
| 176 | } | ||
| 177 | |||
| 178 | impl Default for ClocksConfig { | ||
| 179 | fn default() -> Self { | ||
| 180 | Self { | ||
| 181 | firc: Some(FircConfig { | ||
| 182 | frequency: FircFreqSel::Mhz45, | ||
| 183 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 184 | fro_hf_enabled: true, | ||
| 185 | clk_45m_enabled: true, | ||
| 186 | fro_hf_div: None, | ||
| 187 | }), | ||
| 188 | sirc: SircConfig { | ||
| 189 | power: PoweredClock::AlwaysEnabled, | ||
| 190 | fro_12m_enabled: true, | ||
| 191 | fro_lf_div: None, | ||
| 192 | }, | ||
| 193 | fro16k: Some(Fro16KConfig { | ||
| 194 | vsys_domain_active: true, | ||
| 195 | vdd_core_domain_active: true, | ||
| 196 | }), | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs new file mode 100644 index 000000000..e02840592 --- /dev/null +++ b/src/clocks/mod.rs | |||
| @@ -0,0 +1,887 @@ | |||
| 1 | //! # Clock Module | ||
| 2 | //! | ||
| 3 | //! For the MCX-A, we separate clock and peripheral control into two main stages: | ||
| 4 | //! | ||
| 5 | //! 1. At startup, e.g. when `embassy_mcxa::init()` is called, we configure the | ||
| 6 | //! core system clocks, including external and internal oscillators. This | ||
| 7 | //! configuration is then largely static for the duration of the program. | ||
| 8 | //! 2. When HAL drivers are created, e.g. `Lpuart::new()` is called, the driver | ||
| 9 | //! is responsible for two main things: | ||
| 10 | //! * Ensuring that any required "upstream" core system clocks necessary for | ||
| 11 | //! clocking the peripheral is active and configured to a reasonable value | ||
| 12 | //! * Enabling the clock gates for that peripheral, and resetting the peripheral | ||
| 13 | //! | ||
| 14 | //! From a user perspective, only step 1 is visible. Step 2 is automatically handled | ||
| 15 | //! by HAL drivers, using interfaces defined in this module. | ||
| 16 | //! | ||
| 17 | //! It is also possible to *view* the state of the clock configuration after [`init()`] | ||
| 18 | //! has been called, using the [`with_clocks()`] function, which provides a view of the | ||
| 19 | //! [`Clocks`] structure. | ||
| 20 | //! | ||
| 21 | //! ## For HAL driver implementors | ||
| 22 | //! | ||
| 23 | //! The majority of peripherals in the MCXA chip are fed from either a "hard-coded" or | ||
| 24 | //! configurable clock source, e.g. selecting the FROM12M or `clk_1m` as a source. This | ||
| 25 | //! selection, as well as often any pre-scaler division from that source clock, is made | ||
| 26 | //! through MRCC registers. | ||
| 27 | //! | ||
| 28 | //! Any peripheral that is controlled through the MRCC register can automatically implement | ||
| 29 | //! the necessary APIs using the `impl_cc_gate!` macro in this module. You will also need | ||
| 30 | //! to define the configuration surface and steps necessary to fully configure that peripheral | ||
| 31 | //! from a clocks perspective by: | ||
| 32 | //! | ||
| 33 | //! 1. Defining a configuration type in the [`periph_helpers`] module that contains any selects | ||
| 34 | //! or divisions available to the HAL driver | ||
| 35 | //! 2. Implementing the [`periph_helpers::SPConfHelper`] trait, which should check that the | ||
| 36 | //! necessary input clocks are reasonable | ||
| 37 | |||
| 38 | use core::cell::RefCell; | ||
| 39 | |||
| 40 | use config::{ClocksConfig, FircConfig, FircFreqSel, Fro16KConfig, SircConfig}; | ||
| 41 | use mcxa_pac::scg0::firccsr::{FircFclkPeriphEn, FircSclkPeriphEn, Fircsten}; | ||
| 42 | use mcxa_pac::scg0::sirccsr::{SircClkPeriphEn, Sircsten}; | ||
| 43 | use periph_helpers::SPConfHelper; | ||
| 44 | |||
| 45 | use crate::pac; | ||
| 46 | pub mod config; | ||
| 47 | pub mod periph_helpers; | ||
| 48 | |||
| 49 | // | ||
| 50 | // Statics/Consts | ||
| 51 | // | ||
| 52 | |||
| 53 | /// The state of system core clocks. | ||
| 54 | /// | ||
| 55 | /// Initialized by [`init()`], and then unchanged for the remainder of the program. | ||
| 56 | static CLOCKS: critical_section::Mutex<RefCell<Option<Clocks>>> = critical_section::Mutex::new(RefCell::new(None)); | ||
| 57 | |||
| 58 | // | ||
| 59 | // Free functions | ||
| 60 | // | ||
| 61 | |||
| 62 | /// Initialize the core system clocks with the given [`ClocksConfig`]. | ||
| 63 | /// | ||
| 64 | /// This function should be called EXACTLY once at start-up, usually via a | ||
| 65 | /// call to [`embassy_mcxa::init()`](crate::init()). Subsequent calls will | ||
| 66 | /// return an error. | ||
| 67 | pub fn init(settings: ClocksConfig) -> Result<(), ClockError> { | ||
| 68 | critical_section::with(|cs| { | ||
| 69 | if CLOCKS.borrow_ref(cs).is_some() { | ||
| 70 | Err(ClockError::AlreadyInitialized) | ||
| 71 | } else { | ||
| 72 | Ok(()) | ||
| 73 | } | ||
| 74 | })?; | ||
| 75 | |||
| 76 | let mut clocks = Clocks::default(); | ||
| 77 | let mut operator = ClockOperator { | ||
| 78 | clocks: &mut clocks, | ||
| 79 | config: &settings, | ||
| 80 | |||
| 81 | _mrcc0: unsafe { pac::Mrcc0::steal() }, | ||
| 82 | scg0: unsafe { pac::Scg0::steal() }, | ||
| 83 | syscon: unsafe { pac::Syscon::steal() }, | ||
| 84 | vbat0: unsafe { pac::Vbat0::steal() }, | ||
| 85 | }; | ||
| 86 | |||
| 87 | operator.configure_firc_clocks()?; | ||
| 88 | operator.configure_sirc_clocks()?; | ||
| 89 | operator.configure_fro16k_clocks()?; | ||
| 90 | |||
| 91 | // For now, just use FIRC as the main/cpu clock, which should already be | ||
| 92 | // the case on reset | ||
| 93 | assert!(operator.scg0.rccr().read().scs().is_firc()); | ||
| 94 | assert_eq!(operator.syscon.ahbclkdiv().read().div().bits(), 0); | ||
| 95 | operator.clocks.main_clk = Some(operator.clocks.fro_hf_root.clone().unwrap()); | ||
| 96 | |||
| 97 | critical_section::with(|cs| { | ||
| 98 | let mut clks = CLOCKS.borrow_ref_mut(cs); | ||
| 99 | assert!(clks.is_none(), "Clock setup race!"); | ||
| 100 | *clks = Some(clocks); | ||
| 101 | }); | ||
| 102 | |||
| 103 | Ok(()) | ||
| 104 | } | ||
| 105 | |||
| 106 | /// Obtain the full clocks structure, calling the given closure in a critical section. | ||
| 107 | /// | ||
| 108 | /// The given closure will be called with read-only access to the state of the system | ||
| 109 | /// clocks. This can be used to query and return the state of a given clock. | ||
| 110 | /// | ||
| 111 | /// As the caller's closure will be called in a critical section, care must be taken | ||
| 112 | /// not to block or cause any other undue delays while accessing. | ||
| 113 | /// | ||
| 114 | /// Calls to this function will not succeed until after a successful call to `init()`, | ||
| 115 | /// and will always return None. | ||
| 116 | pub fn with_clocks<R: 'static, F: FnOnce(&Clocks) -> R>(f: F) -> Option<R> { | ||
| 117 | critical_section::with(|cs| { | ||
| 118 | let c = CLOCKS.borrow_ref(cs); | ||
| 119 | let c = c.as_ref()?; | ||
| 120 | Some(f(c)) | ||
| 121 | }) | ||
| 122 | } | ||
| 123 | |||
| 124 | // | ||
| 125 | // Structs/Enums | ||
| 126 | // | ||
| 127 | |||
| 128 | /// The `Clocks` structure contains the initialized state of the core system clocks | ||
| 129 | /// | ||
| 130 | /// These values are configured by providing [`config::ClocksConfig`] to the [`init()`] function | ||
| 131 | /// at boot time. | ||
| 132 | #[derive(Default, Debug, Clone)] | ||
| 133 | #[non_exhaustive] | ||
| 134 | pub struct Clocks { | ||
| 135 | /// The `clk_in` is a clock provided by an external oscillator | ||
| 136 | pub clk_in: Option<Clock>, | ||
| 137 | |||
| 138 | // FRO180M stuff | ||
| 139 | // | ||
| 140 | /// `fro_hf_root` is the direct output of the `FRO180M` internal oscillator | ||
| 141 | /// | ||
| 142 | /// It is used to feed downstream clocks, such as `fro_hf`, `clk_45m`, | ||
| 143 | /// and `fro_hf_div`. | ||
| 144 | pub fro_hf_root: Option<Clock>, | ||
| 145 | |||
| 146 | /// `fro_hf` is the same frequency as `fro_hf_root`, but behind a gate. | ||
| 147 | pub fro_hf: Option<Clock>, | ||
| 148 | |||
| 149 | /// `clk_45` is a 45MHz clock, sourced from `fro_hf`. | ||
| 150 | pub clk_45m: Option<Clock>, | ||
| 151 | |||
| 152 | /// `fro_hf_div` is a configurable frequency clock, sourced from `fro_hf`. | ||
| 153 | pub fro_hf_div: Option<Clock>, | ||
| 154 | |||
| 155 | // | ||
| 156 | // End FRO180M | ||
| 157 | |||
| 158 | // FRO12M stuff | ||
| 159 | // | ||
| 160 | /// `fro_12m_root` is the direct output of the `FRO12M` internal oscillator | ||
| 161 | /// | ||
| 162 | /// It is used to feed downstream clocks, such as `fro_12m`, `clk_1m`, | ||
| 163 | /// `and `fro_lf_div`. | ||
| 164 | pub fro_12m_root: Option<Clock>, | ||
| 165 | |||
| 166 | /// `fro_12m` is the same frequency as `fro_12m_root`, but behind a gate. | ||
| 167 | pub fro_12m: Option<Clock>, | ||
| 168 | |||
| 169 | /// `clk_1m` is a 1MHz clock, sourced from `fro_12m` | ||
| 170 | pub clk_1m: Option<Clock>, | ||
| 171 | |||
| 172 | /// `fro_lf_div` is a configurable frequency clock, sourced from `fro_12m` | ||
| 173 | pub fro_lf_div: Option<Clock>, | ||
| 174 | // | ||
| 175 | // End FRO12M stuff | ||
| 176 | /// `clk_16k_vsys` is one of two outputs of the `FRO16K` internal oscillator. | ||
| 177 | /// | ||
| 178 | /// Also referred to as `clk_16k[0]` in the datasheet, it feeds peripherals in | ||
| 179 | /// the system domain, such as the CMP and RTC. | ||
| 180 | pub clk_16k_vsys: Option<Clock>, | ||
| 181 | |||
| 182 | /// `clk_16k_vdd_core` is one of two outputs of the `FRO16K` internal oscillator. | ||
| 183 | /// | ||
| 184 | /// Also referred to as `clk_16k[1]` in the datasheet, it feeds peripherals in | ||
| 185 | /// the VDD Core domain, such as the OSTimer or LPUarts. | ||
| 186 | pub clk_16k_vdd_core: Option<Clock>, | ||
| 187 | |||
| 188 | /// `main_clk` is the main clock used by the CPU, AHB, APB, IPS bus, and some | ||
| 189 | /// peripherals. | ||
| 190 | pub main_clk: Option<Clock>, | ||
| 191 | |||
| 192 | /// `pll1_clk` is the output of the main system PLL, `pll1`. | ||
| 193 | pub pll1_clk: Option<Clock>, | ||
| 194 | } | ||
| 195 | |||
| 196 | /// `ClockError` is the main error returned when configuring or checking clock state | ||
| 197 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 198 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 199 | #[non_exhaustive] | ||
| 200 | pub enum ClockError { | ||
| 201 | /// The system clocks were never initialized by calling [`init()`] | ||
| 202 | NeverInitialized, | ||
| 203 | /// The [`init()`] function was called more than once | ||
| 204 | AlreadyInitialized, | ||
| 205 | /// The requested configuration was not possible to fulfill, as the system clocks | ||
| 206 | /// were not configured in a compatible way | ||
| 207 | BadConfig { clock: &'static str, reason: &'static str }, | ||
| 208 | /// The requested configuration was not possible to fulfill, as the required system | ||
| 209 | /// clocks have not yet been implemented. | ||
| 210 | NotImplemented { clock: &'static str }, | ||
| 211 | /// The requested peripheral could not be configured, as the steps necessary to | ||
| 212 | /// enable it have not yet been implemented. | ||
| 213 | UnimplementedConfig, | ||
| 214 | } | ||
| 215 | |||
| 216 | /// Information regarding a system clock | ||
| 217 | #[derive(Debug, Clone)] | ||
| 218 | pub struct Clock { | ||
| 219 | /// The frequency, in Hz, of the given clock | ||
| 220 | pub frequency: u32, | ||
| 221 | /// The power state of the clock, e.g. whether it is active in deep sleep mode | ||
| 222 | /// or not. | ||
| 223 | pub power: PoweredClock, | ||
| 224 | } | ||
| 225 | |||
| 226 | /// The power state of a given clock. | ||
| 227 | /// | ||
| 228 | /// On the MCX-A, when Deep-Sleep is entered, any clock not configured for Deep Sleep | ||
| 229 | /// mode will be stopped. This means that any downstream usage, e.g. by peripherals, | ||
| 230 | /// will also stop. | ||
| 231 | /// | ||
| 232 | /// In the future, we will provide an API for entering Deep Sleep, and if there are | ||
| 233 | /// any peripherals that are NOT using an `AlwaysEnabled` clock active, entry into | ||
| 234 | /// Deep Sleep will be prevented, in order to avoid misbehaving peripherals. | ||
| 235 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 236 | pub enum PoweredClock { | ||
| 237 | /// The given clock will NOT continue running in Deep Sleep mode | ||
| 238 | NormalEnabledDeepSleepDisabled, | ||
| 239 | /// The given clock WILL continue running in Deep Sleep mode | ||
| 240 | AlwaysEnabled, | ||
| 241 | } | ||
| 242 | |||
| 243 | /// The ClockOperator is a private helper type that contains the methods used | ||
| 244 | /// during system clock initialization. | ||
| 245 | /// | ||
| 246 | /// # SAFETY | ||
| 247 | /// | ||
| 248 | /// Concurrent access to clock-relevant peripheral registers, such as `MRCC`, `SCG`, | ||
| 249 | /// `SYSCON`, and `VBAT` should not be allowed for the duration of the [`init()`] function. | ||
| 250 | struct ClockOperator<'a> { | ||
| 251 | /// A mutable reference to the current state of system clocks | ||
| 252 | clocks: &'a mut Clocks, | ||
| 253 | /// A reference to the requested configuration provided by the caller of [`init()`] | ||
| 254 | config: &'a ClocksConfig, | ||
| 255 | |||
| 256 | // We hold on to stolen peripherals | ||
| 257 | _mrcc0: pac::Mrcc0, | ||
| 258 | scg0: pac::Scg0, | ||
| 259 | syscon: pac::Syscon, | ||
| 260 | vbat0: pac::Vbat0, | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Trait describing an AHB clock gate that can be toggled through MRCC. | ||
| 264 | pub trait Gate { | ||
| 265 | type MrccPeriphConfig: SPConfHelper; | ||
| 266 | |||
| 267 | /// Enable the clock gate. | ||
| 268 | /// | ||
| 269 | /// # SAFETY | ||
| 270 | /// | ||
| 271 | /// The current peripheral must be disabled prior to calling this method | ||
| 272 | unsafe fn enable_clock(); | ||
| 273 | |||
| 274 | /// Disable the clock gate. | ||
| 275 | /// | ||
| 276 | /// # SAFETY | ||
| 277 | /// | ||
| 278 | /// There must be no active user of this peripheral when calling this method | ||
| 279 | unsafe fn disable_clock(); | ||
| 280 | |||
| 281 | /// Drive the peripheral into reset. | ||
| 282 | /// | ||
| 283 | /// # SAFETY | ||
| 284 | /// | ||
| 285 | /// There must be no active user of this peripheral when calling this method | ||
| 286 | unsafe fn assert_reset(); | ||
| 287 | |||
| 288 | /// Drive the peripheral out of reset. | ||
| 289 | /// | ||
| 290 | /// # SAFETY | ||
| 291 | /// | ||
| 292 | /// There must be no active user of this peripheral when calling this method | ||
| 293 | unsafe fn release_reset(); | ||
| 294 | |||
| 295 | /// Return whether the clock gate for this peripheral is currently enabled. | ||
| 296 | fn is_clock_enabled() -> bool; | ||
| 297 | |||
| 298 | /// Return whether the peripheral is currently held in reset. | ||
| 299 | fn is_reset_released() -> bool; | ||
| 300 | } | ||
| 301 | |||
| 302 | /// This is the primary helper method HAL drivers are expected to call when creating | ||
| 303 | /// an instance of the peripheral. | ||
| 304 | /// | ||
| 305 | /// This method: | ||
| 306 | /// | ||
| 307 | /// 1. Enables the MRCC clock gate for this peripheral | ||
| 308 | /// 2. Calls the `G::MrccPeriphConfig::post_enable_config()` method, returning an error | ||
| 309 | /// and re-disabling the peripheral if this fails. | ||
| 310 | /// 3. Pulses the MRCC reset line, to reset the peripheral to the default state | ||
| 311 | /// 4. Returns the frequency, in Hz that is fed into the peripheral, taking into account | ||
| 312 | /// the selected upstream clock, as well as any division specified by `cfg`. | ||
| 313 | /// | ||
| 314 | /// NOTE: if a clock is disabled, sourced from an "ambient" clock source, this method | ||
| 315 | /// may return `Ok(0)`. In the future, this might be updated to return the correct | ||
| 316 | /// "ambient" clock, e.g. the AHB/APB frequency. | ||
| 317 | /// | ||
| 318 | /// # SAFETY | ||
| 319 | /// | ||
| 320 | /// This peripheral must not yet be in use prior to calling `enable_and_reset`. | ||
| 321 | #[inline] | ||
| 322 | pub unsafe fn enable_and_reset<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { | ||
| 323 | let freq = enable::<G>(cfg).inspect_err(|_| disable::<G>())?; | ||
| 324 | pulse_reset::<G>(); | ||
| 325 | Ok(freq) | ||
| 326 | } | ||
| 327 | |||
| 328 | /// Enable the clock gate for the given peripheral. | ||
| 329 | /// | ||
| 330 | /// Prefer [`enable_and_reset`] unless you are specifically avoiding a pulse of the reset, or need | ||
| 331 | /// to control the duration of the pulse more directly. | ||
| 332 | /// | ||
| 333 | /// # SAFETY | ||
| 334 | /// | ||
| 335 | /// This peripheral must not yet be in use prior to calling `enable`. | ||
| 336 | #[inline] | ||
| 337 | pub unsafe fn enable<G: Gate>(cfg: &G::MrccPeriphConfig) -> Result<u32, ClockError> { | ||
| 338 | G::enable_clock(); | ||
| 339 | while !G::is_clock_enabled() {} | ||
| 340 | core::arch::asm!("dsb sy; isb sy", options(nomem, nostack, preserves_flags)); | ||
| 341 | |||
| 342 | let freq = critical_section::with(|cs| { | ||
| 343 | let clocks = CLOCKS.borrow_ref(cs); | ||
| 344 | let clocks = clocks.as_ref().ok_or(ClockError::NeverInitialized)?; | ||
| 345 | cfg.post_enable_config(clocks) | ||
| 346 | }); | ||
| 347 | |||
| 348 | freq.inspect_err(|_e| { | ||
| 349 | G::disable_clock(); | ||
| 350 | }) | ||
| 351 | } | ||
| 352 | |||
| 353 | /// Disable the clock gate for the given peripheral. | ||
| 354 | /// | ||
| 355 | /// # SAFETY | ||
| 356 | /// | ||
| 357 | /// This peripheral must no longer be in use prior to calling `enable`. | ||
| 358 | #[allow(dead_code)] | ||
| 359 | #[inline] | ||
| 360 | pub unsafe fn disable<G: Gate>() { | ||
| 361 | G::disable_clock(); | ||
| 362 | } | ||
| 363 | |||
| 364 | /// Check whether a gate is currently enabled. | ||
| 365 | #[allow(dead_code)] | ||
| 366 | #[inline] | ||
| 367 | pub fn is_clock_enabled<G: Gate>() -> bool { | ||
| 368 | G::is_clock_enabled() | ||
| 369 | } | ||
| 370 | |||
| 371 | /// Release a reset line for the given peripheral set. | ||
| 372 | /// | ||
| 373 | /// Prefer [`enable_and_reset`]. | ||
| 374 | /// | ||
| 375 | /// # SAFETY | ||
| 376 | /// | ||
| 377 | /// This peripheral must not yet be in use prior to calling `release_reset`. | ||
| 378 | #[inline] | ||
| 379 | pub unsafe fn release_reset<G: Gate>() { | ||
| 380 | G::release_reset(); | ||
| 381 | } | ||
| 382 | |||
| 383 | /// Assert a reset line for the given peripheral set. | ||
| 384 | /// | ||
| 385 | /// Prefer [`enable_and_reset`]. | ||
| 386 | /// | ||
| 387 | /// # SAFETY | ||
| 388 | /// | ||
| 389 | /// This peripheral must not yet be in use prior to calling `assert_reset`. | ||
| 390 | #[inline] | ||
| 391 | pub unsafe fn assert_reset<G: Gate>() { | ||
| 392 | G::assert_reset(); | ||
| 393 | } | ||
| 394 | |||
| 395 | /// Check whether the peripheral is held in reset. | ||
| 396 | #[inline] | ||
| 397 | pub unsafe fn is_reset_released<G: Gate>() -> bool { | ||
| 398 | G::is_reset_released() | ||
| 399 | } | ||
| 400 | |||
| 401 | /// Pulse a reset line (assert then release) with a short delay. | ||
| 402 | /// | ||
| 403 | /// Prefer [`enable_and_reset`]. | ||
| 404 | /// | ||
| 405 | /// # SAFETY | ||
| 406 | /// | ||
| 407 | /// This peripheral must not yet be in use prior to calling `release_reset`. | ||
| 408 | #[inline] | ||
| 409 | pub unsafe fn pulse_reset<G: Gate>() { | ||
| 410 | G::assert_reset(); | ||
| 411 | cortex_m::asm::nop(); | ||
| 412 | cortex_m::asm::nop(); | ||
| 413 | G::release_reset(); | ||
| 414 | } | ||
| 415 | |||
| 416 | // | ||
| 417 | // `impl`s for structs/enums | ||
| 418 | // | ||
| 419 | |||
| 420 | /// The [`Clocks`] type's methods generally take the form of "ensure X clock is active". | ||
| 421 | /// | ||
| 422 | /// These methods are intended to be used by HAL peripheral implementors to ensure that their | ||
| 423 | /// selected clocks are active at a suitable level at time of construction. These methods | ||
| 424 | /// return the frequency of the requested clock, in Hertz, or a [`ClockError`]. | ||
| 425 | impl Clocks { | ||
| 426 | /// Ensure the `fro_lf_div` clock is active and valid at the given power state. | ||
| 427 | pub fn ensure_fro_lf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 428 | let Some(clk) = self.fro_lf_div.as_ref() else { | ||
| 429 | return Err(ClockError::BadConfig { | ||
| 430 | clock: "fro_lf_div", | ||
| 431 | reason: "required but not active", | ||
| 432 | }); | ||
| 433 | }; | ||
| 434 | if !clk.power.meets_requirement_of(at_level) { | ||
| 435 | return Err(ClockError::BadConfig { | ||
| 436 | clock: "fro_lf_div", | ||
| 437 | reason: "not low power active", | ||
| 438 | }); | ||
| 439 | } | ||
| 440 | Ok(clk.frequency) | ||
| 441 | } | ||
| 442 | |||
| 443 | /// Ensure the `fro_hf` clock is active and valid at the given power state. | ||
| 444 | pub fn ensure_fro_hf_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 445 | let Some(clk) = self.fro_hf.as_ref() else { | ||
| 446 | return Err(ClockError::BadConfig { | ||
| 447 | clock: "fro_hf", | ||
| 448 | reason: "required but not active", | ||
| 449 | }); | ||
| 450 | }; | ||
| 451 | if !clk.power.meets_requirement_of(at_level) { | ||
| 452 | return Err(ClockError::BadConfig { | ||
| 453 | clock: "fro_hf", | ||
| 454 | reason: "not low power active", | ||
| 455 | }); | ||
| 456 | } | ||
| 457 | Ok(clk.frequency) | ||
| 458 | } | ||
| 459 | |||
| 460 | /// Ensure the `fro_hf_div` clock is active and valid at the given power state. | ||
| 461 | pub fn ensure_fro_hf_div_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 462 | let Some(clk) = self.fro_hf_div.as_ref() else { | ||
| 463 | return Err(ClockError::BadConfig { | ||
| 464 | clock: "fro_hf_div", | ||
| 465 | reason: "required but not active", | ||
| 466 | }); | ||
| 467 | }; | ||
| 468 | if !clk.power.meets_requirement_of(at_level) { | ||
| 469 | return Err(ClockError::BadConfig { | ||
| 470 | clock: "fro_hf_div", | ||
| 471 | reason: "not low power active", | ||
| 472 | }); | ||
| 473 | } | ||
| 474 | Ok(clk.frequency) | ||
| 475 | } | ||
| 476 | |||
| 477 | /// Ensure the `clk_in` clock is active and valid at the given power state. | ||
| 478 | pub fn ensure_clk_in_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 479 | Err(ClockError::NotImplemented { clock: "clk_in" }) | ||
| 480 | } | ||
| 481 | |||
| 482 | /// Ensure the `clk_16k_vsys` clock is active and valid at the given power state. | ||
| 483 | pub fn ensure_clk_16k_vsys_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 484 | // NOTE: clk_16k is always active in low power mode | ||
| 485 | Ok(self | ||
| 486 | .clk_16k_vsys | ||
| 487 | .as_ref() | ||
| 488 | .ok_or(ClockError::BadConfig { | ||
| 489 | clock: "clk_16k_vsys", | ||
| 490 | reason: "required but not active", | ||
| 491 | })? | ||
| 492 | .frequency) | ||
| 493 | } | ||
| 494 | |||
| 495 | /// Ensure the `clk_16k_vdd_core` clock is active and valid at the given power state. | ||
| 496 | pub fn ensure_clk_16k_vdd_core_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 497 | // NOTE: clk_16k is always active in low power mode | ||
| 498 | Ok(self | ||
| 499 | .clk_16k_vdd_core | ||
| 500 | .as_ref() | ||
| 501 | .ok_or(ClockError::BadConfig { | ||
| 502 | clock: "clk_16k_vdd_core", | ||
| 503 | reason: "required but not active", | ||
| 504 | })? | ||
| 505 | .frequency) | ||
| 506 | } | ||
| 507 | |||
| 508 | /// Ensure the `clk_1m` clock is active and valid at the given power state. | ||
| 509 | pub fn ensure_clk_1m_active(&self, at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 510 | let Some(clk) = self.clk_1m.as_ref() else { | ||
| 511 | return Err(ClockError::BadConfig { | ||
| 512 | clock: "clk_1m", | ||
| 513 | reason: "required but not active", | ||
| 514 | }); | ||
| 515 | }; | ||
| 516 | if !clk.power.meets_requirement_of(at_level) { | ||
| 517 | return Err(ClockError::BadConfig { | ||
| 518 | clock: "clk_1m", | ||
| 519 | reason: "not low power active", | ||
| 520 | }); | ||
| 521 | } | ||
| 522 | Ok(clk.frequency) | ||
| 523 | } | ||
| 524 | |||
| 525 | /// Ensure the `pll1_clk_div` clock is active and valid at the given power state. | ||
| 526 | pub fn ensure_pll1_clk_div_active(&self, _at_level: &PoweredClock) -> Result<u32, ClockError> { | ||
| 527 | Err(ClockError::NotImplemented { clock: "pll1_clk_div" }) | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | impl PoweredClock { | ||
| 532 | /// Does THIS clock meet the power requirements of the OTHER clock? | ||
| 533 | pub fn meets_requirement_of(&self, other: &Self) -> bool { | ||
| 534 | match (self, other) { | ||
| 535 | (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::AlwaysEnabled) => false, | ||
| 536 | (PoweredClock::NormalEnabledDeepSleepDisabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, | ||
| 537 | (PoweredClock::AlwaysEnabled, PoweredClock::NormalEnabledDeepSleepDisabled) => true, | ||
| 538 | (PoweredClock::AlwaysEnabled, PoweredClock::AlwaysEnabled) => true, | ||
| 539 | } | ||
| 540 | } | ||
| 541 | } | ||
| 542 | |||
| 543 | impl ClockOperator<'_> { | ||
| 544 | /// Configure the FIRC/FRO180M clock family | ||
| 545 | /// | ||
| 546 | /// NOTE: Currently we require this to be a fairly hardcoded value, as this clock is used | ||
| 547 | /// as the main clock used for the CPU, AHB, APB, etc. | ||
| 548 | fn configure_firc_clocks(&mut self) -> Result<(), ClockError> { | ||
| 549 | const HARDCODED_ERR: Result<(), ClockError> = Err(ClockError::BadConfig { | ||
| 550 | clock: "firc", | ||
| 551 | reason: "For now, FIRC must be enabled and in default state!", | ||
| 552 | }); | ||
| 553 | |||
| 554 | // Did the user give us a FIRC config? | ||
| 555 | let Some(firc) = self.config.firc.as_ref() else { | ||
| 556 | return HARDCODED_ERR; | ||
| 557 | }; | ||
| 558 | // Is the FIRC set to 45MHz (should be reset default) | ||
| 559 | if !matches!(firc.frequency, FircFreqSel::Mhz45) { | ||
| 560 | return HARDCODED_ERR; | ||
| 561 | } | ||
| 562 | let base_freq = 45_000_000; | ||
| 563 | |||
| 564 | // Now, check if the FIRC as expected for our hardcoded value | ||
| 565 | let mut firc_ok = true; | ||
| 566 | |||
| 567 | // Is the hardware currently set to the default 45MHz? | ||
| 568 | // | ||
| 569 | // NOTE: the SVD currently has the wrong(?) values for these: | ||
| 570 | // 45 -> 48 | ||
| 571 | // 60 -> 64 | ||
| 572 | // 90 -> 96 | ||
| 573 | // 180 -> 192 | ||
| 574 | // Probably correct-ish, but for a different trim value? | ||
| 575 | firc_ok &= self.scg0.firccfg().read().freq_sel().is_firc_48mhz_192s(); | ||
| 576 | |||
| 577 | // Check some values in the CSR | ||
| 578 | let csr = self.scg0.firccsr().read(); | ||
| 579 | // Is it enabled? | ||
| 580 | firc_ok &= csr.fircen().is_enabled(); | ||
| 581 | // Is it accurate? | ||
| 582 | firc_ok &= csr.fircacc().is_enabled_and_valid(); | ||
| 583 | // Is there no error? | ||
| 584 | firc_ok &= csr.fircerr().is_error_not_detected(); | ||
| 585 | // Is the FIRC the system clock? | ||
| 586 | firc_ok &= csr.fircsel().is_firc(); | ||
| 587 | // Is it valid? | ||
| 588 | firc_ok &= csr.fircvld().is_enabled_and_valid(); | ||
| 589 | |||
| 590 | // Are we happy with the current (hardcoded) state? | ||
| 591 | if !firc_ok { | ||
| 592 | return HARDCODED_ERR; | ||
| 593 | } | ||
| 594 | |||
| 595 | // Note that the fro_hf_root is active | ||
| 596 | self.clocks.fro_hf_root = Some(Clock { | ||
| 597 | frequency: base_freq, | ||
| 598 | power: firc.power, | ||
| 599 | }); | ||
| 600 | |||
| 601 | // Okay! Now we're past that, let's enable all the downstream clocks. | ||
| 602 | let FircConfig { | ||
| 603 | frequency: _, | ||
| 604 | power, | ||
| 605 | fro_hf_enabled, | ||
| 606 | clk_45m_enabled, | ||
| 607 | fro_hf_div, | ||
| 608 | } = firc; | ||
| 609 | |||
| 610 | // When is the FRO enabled? | ||
| 611 | let pow_set = match power { | ||
| 612 | PoweredClock::NormalEnabledDeepSleepDisabled => Fircsten::DisabledInStopModes, | ||
| 613 | PoweredClock::AlwaysEnabled => Fircsten::EnabledInStopModes, | ||
| 614 | }; | ||
| 615 | |||
| 616 | // Do we enable the `fro_hf` output? | ||
| 617 | let fro_hf_set = if *fro_hf_enabled { | ||
| 618 | self.clocks.fro_hf = Some(Clock { | ||
| 619 | frequency: base_freq, | ||
| 620 | power: *power, | ||
| 621 | }); | ||
| 622 | FircFclkPeriphEn::Enabled | ||
| 623 | } else { | ||
| 624 | FircFclkPeriphEn::Disabled | ||
| 625 | }; | ||
| 626 | |||
| 627 | // Do we enable the `clk_45m` output? | ||
| 628 | let clk_45m_set = if *clk_45m_enabled { | ||
| 629 | self.clocks.clk_45m = Some(Clock { | ||
| 630 | frequency: 45_000_000, | ||
| 631 | power: *power, | ||
| 632 | }); | ||
| 633 | FircSclkPeriphEn::Enabled | ||
| 634 | } else { | ||
| 635 | FircSclkPeriphEn::Disabled | ||
| 636 | }; | ||
| 637 | |||
| 638 | self.scg0.firccsr().modify(|_r, w| { | ||
| 639 | w.fircsten().variant(pow_set); | ||
| 640 | w.firc_fclk_periph_en().variant(fro_hf_set); | ||
| 641 | w.firc_sclk_periph_en().variant(clk_45m_set); | ||
| 642 | w | ||
| 643 | }); | ||
| 644 | |||
| 645 | // Do we enable the `fro_hf_div` output? | ||
| 646 | if let Some(d) = fro_hf_div.as_ref() { | ||
| 647 | // We need `fro_hf` to be enabled | ||
| 648 | if !*fro_hf_enabled { | ||
| 649 | return Err(ClockError::BadConfig { | ||
| 650 | clock: "fro_hf_div", | ||
| 651 | reason: "fro_hf not enabled", | ||
| 652 | }); | ||
| 653 | } | ||
| 654 | |||
| 655 | // Halt and reset the div | ||
| 656 | self.syscon.frohfdiv().write(|w| { | ||
| 657 | w.halt().halt(); | ||
| 658 | w.reset().asserted(); | ||
| 659 | w | ||
| 660 | }); | ||
| 661 | // Then change the div, unhalt it, and reset it | ||
| 662 | self.syscon.frohfdiv().write(|w| { | ||
| 663 | unsafe { | ||
| 664 | w.div().bits(d.into_bits()); | ||
| 665 | } | ||
| 666 | w.halt().run(); | ||
| 667 | w.reset().released(); | ||
| 668 | w | ||
| 669 | }); | ||
| 670 | |||
| 671 | // Wait for clock to stabilize | ||
| 672 | while self.syscon.frohfdiv().read().unstab().is_ongoing() {} | ||
| 673 | |||
| 674 | // Store off the clock info | ||
| 675 | self.clocks.fro_hf_div = Some(Clock { | ||
| 676 | frequency: base_freq / d.into_divisor(), | ||
| 677 | power: *power, | ||
| 678 | }); | ||
| 679 | } | ||
| 680 | |||
| 681 | Ok(()) | ||
| 682 | } | ||
| 683 | |||
| 684 | /// Configure the SIRC/FRO12M clock family | ||
| 685 | fn configure_sirc_clocks(&mut self) -> Result<(), ClockError> { | ||
| 686 | let SircConfig { | ||
| 687 | power, | ||
| 688 | fro_12m_enabled, | ||
| 689 | fro_lf_div, | ||
| 690 | } = &self.config.sirc; | ||
| 691 | let base_freq = 12_000_000; | ||
| 692 | |||
| 693 | // Allow writes | ||
| 694 | self.scg0.sirccsr().modify(|_r, w| w.lk().write_enabled()); | ||
| 695 | self.clocks.fro_12m_root = Some(Clock { | ||
| 696 | frequency: base_freq, | ||
| 697 | power: *power, | ||
| 698 | }); | ||
| 699 | |||
| 700 | let deep = match power { | ||
| 701 | PoweredClock::NormalEnabledDeepSleepDisabled => Sircsten::Disabled, | ||
| 702 | PoweredClock::AlwaysEnabled => Sircsten::Enabled, | ||
| 703 | }; | ||
| 704 | let pclk = if *fro_12m_enabled { | ||
| 705 | self.clocks.fro_12m = Some(Clock { | ||
| 706 | frequency: base_freq, | ||
| 707 | power: *power, | ||
| 708 | }); | ||
| 709 | self.clocks.clk_1m = Some(Clock { | ||
| 710 | frequency: base_freq / 12, | ||
| 711 | power: *power, | ||
| 712 | }); | ||
| 713 | SircClkPeriphEn::Enabled | ||
| 714 | } else { | ||
| 715 | SircClkPeriphEn::Disabled | ||
| 716 | }; | ||
| 717 | |||
| 718 | // Set sleep/peripheral usage | ||
| 719 | self.scg0.sirccsr().modify(|_r, w| { | ||
| 720 | w.sircsten().variant(deep); | ||
| 721 | w.sirc_clk_periph_en().variant(pclk); | ||
| 722 | w | ||
| 723 | }); | ||
| 724 | |||
| 725 | while self.scg0.sirccsr().read().sircvld().is_disabled_or_not_valid() {} | ||
| 726 | if self.scg0.sirccsr().read().sircerr().is_error_detected() { | ||
| 727 | return Err(ClockError::BadConfig { | ||
| 728 | clock: "sirc", | ||
| 729 | reason: "error set", | ||
| 730 | }); | ||
| 731 | } | ||
| 732 | |||
| 733 | // reset lock | ||
| 734 | self.scg0.sirccsr().modify(|_r, w| w.lk().write_disabled()); | ||
| 735 | |||
| 736 | // Do we enable the `fro_lf_div` output? | ||
| 737 | if let Some(d) = fro_lf_div.as_ref() { | ||
| 738 | // We need `fro_lf` to be enabled | ||
| 739 | if !*fro_12m_enabled { | ||
| 740 | return Err(ClockError::BadConfig { | ||
| 741 | clock: "fro_lf_div", | ||
| 742 | reason: "fro_12m not enabled", | ||
| 743 | }); | ||
| 744 | } | ||
| 745 | |||
| 746 | // Halt and reset the div | ||
| 747 | self.syscon.frolfdiv().write(|w| { | ||
| 748 | w.halt().halt(); | ||
| 749 | w.reset().asserted(); | ||
| 750 | w | ||
| 751 | }); | ||
| 752 | // Then change the div, unhalt it, and reset it | ||
| 753 | self.syscon.frolfdiv().write(|w| { | ||
| 754 | unsafe { | ||
| 755 | w.div().bits(d.into_bits()); | ||
| 756 | } | ||
| 757 | w.halt().run(); | ||
| 758 | w.reset().released(); | ||
| 759 | w | ||
| 760 | }); | ||
| 761 | |||
| 762 | // Wait for clock to stabilize | ||
| 763 | while self.syscon.frolfdiv().read().unstab().is_ongoing() {} | ||
| 764 | |||
| 765 | // Store off the clock info | ||
| 766 | self.clocks.fro_lf_div = Some(Clock { | ||
| 767 | frequency: base_freq / d.into_divisor(), | ||
| 768 | power: *power, | ||
| 769 | }); | ||
| 770 | } | ||
| 771 | |||
| 772 | Ok(()) | ||
| 773 | } | ||
| 774 | |||
| 775 | /// Configure the FRO16K/clk_16k clock family | ||
| 776 | fn configure_fro16k_clocks(&mut self) -> Result<(), ClockError> { | ||
| 777 | let Some(fro16k) = self.config.fro16k.as_ref() else { | ||
| 778 | return Ok(()); | ||
| 779 | }; | ||
| 780 | // Enable FRO16K oscillator | ||
| 781 | self.vbat0.froctla().modify(|_, w| w.fro_en().set_bit()); | ||
| 782 | |||
| 783 | // Lock the control register | ||
| 784 | self.vbat0.frolcka().modify(|_, w| w.lock().set_bit()); | ||
| 785 | |||
| 786 | let Fro16KConfig { | ||
| 787 | vsys_domain_active, | ||
| 788 | vdd_core_domain_active, | ||
| 789 | } = fro16k; | ||
| 790 | |||
| 791 | // Enable clock outputs to both VSYS and VDD_CORE domains | ||
| 792 | // Bit 0: clk_16k0 to VSYS domain | ||
| 793 | // Bit 1: clk_16k1 to VDD_CORE domain | ||
| 794 | // | ||
| 795 | // TODO: Define sub-fields for this register with a PAC patch? | ||
| 796 | let mut bits = 0; | ||
| 797 | if *vsys_domain_active { | ||
| 798 | bits |= 0b01; | ||
| 799 | self.clocks.clk_16k_vsys = Some(Clock { | ||
| 800 | frequency: 16_384, | ||
| 801 | power: PoweredClock::AlwaysEnabled, | ||
| 802 | }); | ||
| 803 | } | ||
| 804 | if *vdd_core_domain_active { | ||
| 805 | bits |= 0b10; | ||
| 806 | self.clocks.clk_16k_vdd_core = Some(Clock { | ||
| 807 | frequency: 16_384, | ||
| 808 | power: PoweredClock::AlwaysEnabled, | ||
| 809 | }); | ||
| 810 | } | ||
| 811 | self.vbat0.froclke().modify(|_r, w| unsafe { w.clke().bits(bits) }); | ||
| 812 | |||
| 813 | Ok(()) | ||
| 814 | } | ||
| 815 | } | ||
| 816 | |||
| 817 | // | ||
| 818 | // Macros/macro impls | ||
| 819 | // | ||
| 820 | |||
| 821 | /// This macro is used to implement the [`Gate`] trait for a given peripheral | ||
| 822 | /// that is controlled by the MRCC peripheral. | ||
| 823 | macro_rules! impl_cc_gate { | ||
| 824 | ($name:ident, $clk_reg:ident, $rst_reg:ident, $field:ident, $config:ty) => { | ||
| 825 | impl Gate for crate::peripherals::$name { | ||
| 826 | type MrccPeriphConfig = $config; | ||
| 827 | |||
| 828 | #[inline] | ||
| 829 | unsafe fn enable_clock() { | ||
| 830 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 831 | mrcc.$clk_reg().modify(|_, w| w.$field().enabled()); | ||
| 832 | } | ||
| 833 | |||
| 834 | #[inline] | ||
| 835 | unsafe fn disable_clock() { | ||
| 836 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 837 | mrcc.$clk_reg().modify(|_r, w| w.$field().disabled()); | ||
| 838 | } | ||
| 839 | |||
| 840 | #[inline] | ||
| 841 | fn is_clock_enabled() -> bool { | ||
| 842 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 843 | mrcc.$clk_reg().read().$field().is_enabled() | ||
| 844 | } | ||
| 845 | |||
| 846 | #[inline] | ||
| 847 | unsafe fn release_reset() { | ||
| 848 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 849 | mrcc.$rst_reg().modify(|_, w| w.$field().enabled()); | ||
| 850 | } | ||
| 851 | |||
| 852 | #[inline] | ||
| 853 | unsafe fn assert_reset() { | ||
| 854 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 855 | mrcc.$rst_reg().modify(|_, w| w.$field().disabled()); | ||
| 856 | } | ||
| 857 | |||
| 858 | #[inline] | ||
| 859 | fn is_reset_released() -> bool { | ||
| 860 | let mrcc = unsafe { pac::Mrcc0::steal() }; | ||
| 861 | mrcc.$rst_reg().read().$field().is_enabled() | ||
| 862 | } | ||
| 863 | } | ||
| 864 | }; | ||
| 865 | } | ||
| 866 | |||
| 867 | /// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, | ||
| 868 | /// for various low level peripherals. | ||
| 869 | pub(crate) mod gate { | ||
| 870 | use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig, OsTimerConfig}; | ||
| 871 | use super::*; | ||
| 872 | |||
| 873 | // These peripherals have no additional upstream clocks or configuration required | ||
| 874 | // other than enabling through the MRCC gate. Currently, these peripherals will | ||
| 875 | // ALWAYS return `Ok(0)` when calling [`enable_and_reset()`] and/or | ||
| 876 | // [`SPConfHelper::post_enable_config()`]. | ||
| 877 | impl_cc_gate!(PORT1, mrcc_glb_cc1, mrcc_glb_rst1, port1, NoConfig); | ||
| 878 | impl_cc_gate!(PORT2, mrcc_glb_cc1, mrcc_glb_rst1, port2, NoConfig); | ||
| 879 | impl_cc_gate!(PORT3, mrcc_glb_cc1, mrcc_glb_rst1, port3, NoConfig); | ||
| 880 | impl_cc_gate!(GPIO3, mrcc_glb_cc2, mrcc_glb_rst2, gpio3, NoConfig); | ||
| 881 | |||
| 882 | // These peripherals DO have meaningful configuration, and could fail if the system | ||
| 883 | // clocks do not match their needs. | ||
| 884 | impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); | ||
| 885 | impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); | ||
| 886 | impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); | ||
| 887 | } | ||
diff --git a/src/clocks/periph_helpers.rs b/src/clocks/periph_helpers.rs new file mode 100644 index 000000000..e5b234c5b --- /dev/null +++ b/src/clocks/periph_helpers.rs | |||
| @@ -0,0 +1,393 @@ | |||
| 1 | //! Peripheral Helpers | ||
| 2 | //! | ||
| 3 | //! The purpose of this module is to define the per-peripheral special handling | ||
| 4 | //! required from a clocking perspective. Different peripherals have different | ||
| 5 | //! selectable source clocks, and some peripherals have additional pre-dividers | ||
| 6 | //! that can be used. | ||
| 7 | //! | ||
| 8 | //! See the docs of [`SPConfHelper`] for more details. | ||
| 9 | |||
| 10 | use super::{ClockError, Clocks, PoweredClock}; | ||
| 11 | use crate::pac; | ||
| 12 | |||
| 13 | /// Sealed Peripheral Configuration Helper | ||
| 14 | /// | ||
| 15 | /// NOTE: the name "sealed" doesn't *totally* make sense because its not sealed yet in the | ||
| 16 | /// embassy-mcxa project, but it derives from embassy-imxrt where it is. We should | ||
| 17 | /// fix the name, or actually do the sealing of peripherals. | ||
| 18 | /// | ||
| 19 | /// This trait serves to act as a per-peripheral customization for clocking behavior. | ||
| 20 | /// | ||
| 21 | /// This trait should be implemented on a configuration type for a given peripheral, and | ||
| 22 | /// provide the methods that will be called by the higher level operations like | ||
| 23 | /// `embassy_mcxa::clocks::enable_and_reset()`. | ||
| 24 | pub trait SPConfHelper { | ||
| 25 | /// This method is called AFTER a given MRCC peripheral has been enabled (e.g. un-gated), | ||
| 26 | /// but BEFORE the peripheral reset line is reset. | ||
| 27 | /// | ||
| 28 | /// This function should check that any relevant upstream clocks are enabled, are in a | ||
| 29 | /// reasonable power state, and that the requested configuration can be made. If any of | ||
| 30 | /// these checks fail, an `Err(ClockError)` should be returned, likely `ClockError::BadConfig`. | ||
| 31 | /// | ||
| 32 | /// This function SHOULD NOT make any changes to the system clock configuration, even | ||
| 33 | /// unsafely, as this should remain static for the duration of the program. | ||
| 34 | /// | ||
| 35 | /// This function WILL be called in a critical section, care should be taken not to delay | ||
| 36 | /// for an unreasonable amount of time. | ||
| 37 | /// | ||
| 38 | /// On success, this function MUST return an `Ok(freq)`, where `freq` is the frequency | ||
| 39 | /// fed into the peripheral, taking into account the selected source clock, as well as | ||
| 40 | /// any pre-divisors. | ||
| 41 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError>; | ||
| 42 | } | ||
| 43 | |||
| 44 | // config types | ||
| 45 | |||
| 46 | /// This type represents a divider in the range 1..=16. | ||
| 47 | /// | ||
| 48 | /// At a hardware level, this is an 8-bit register from 0..=15, | ||
| 49 | /// which adds one. | ||
| 50 | /// | ||
| 51 | /// While the *clock* domain seems to use 8-bit dividers, the *peripheral* domain | ||
| 52 | /// seems to use 4 bit dividers! | ||
| 53 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 54 | pub struct Div4(pub(super) u8); | ||
| 55 | |||
| 56 | impl Div4 { | ||
| 57 | /// Divide by one, or no division | ||
| 58 | pub const fn no_div() -> Self { | ||
| 59 | Self(0) | ||
| 60 | } | ||
| 61 | |||
| 62 | /// Store a "raw" divisor value that will divide the source by | ||
| 63 | /// `(n + 1)`, e.g. `Div4::from_raw(0)` will divide the source | ||
| 64 | /// by 1, and `Div4::from_raw(15)` will divide the source by | ||
| 65 | /// 16. | ||
| 66 | pub const fn from_raw(n: u8) -> Option<Self> { | ||
| 67 | if n > 0b1111 { | ||
| 68 | None | ||
| 69 | } else { | ||
| 70 | Some(Self(n)) | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | /// Store a specific divisor value that will divide the source | ||
| 75 | /// by `n`. e.g. `Div4::from_divisor(1)` will divide the source | ||
| 76 | /// by 1, and `Div4::from_divisor(16)` will divide the source | ||
| 77 | /// by 16. | ||
| 78 | /// | ||
| 79 | /// Will return `None` if `n` is not in the range `1..=16`. | ||
| 80 | /// Consider [`Self::from_raw`] for an infallible version. | ||
| 81 | pub const fn from_divisor(n: u8) -> Option<Self> { | ||
| 82 | let Some(n) = n.checked_sub(1) else { | ||
| 83 | return None; | ||
| 84 | }; | ||
| 85 | if n > 0b1111 { | ||
| 86 | return None; | ||
| 87 | } | ||
| 88 | Some(Self(n)) | ||
| 89 | } | ||
| 90 | |||
| 91 | /// Convert into "raw" bits form | ||
| 92 | #[inline(always)] | ||
| 93 | pub const fn into_bits(self) -> u8 { | ||
| 94 | self.0 | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Convert into "divisor" form, as a u32 for convenient frequency math | ||
| 98 | #[inline(always)] | ||
| 99 | pub const fn into_divisor(self) -> u32 { | ||
| 100 | self.0 as u32 + 1 | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | /// A basic type that always returns an error when `post_enable_config` is called. | ||
| 105 | /// | ||
| 106 | /// Should only be used as a placeholder. | ||
| 107 | pub struct UnimplementedConfig; | ||
| 108 | |||
| 109 | impl SPConfHelper for UnimplementedConfig { | ||
| 110 | fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 111 | Err(ClockError::UnimplementedConfig) | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /// A basic type that always returns `Ok(0)` when `post_enable_config` is called. | ||
| 116 | /// | ||
| 117 | /// This should only be used for peripherals that are "ambiently" clocked, like `PORTn` | ||
| 118 | /// peripherals, which have no selectable/configurable source clock. | ||
| 119 | pub struct NoConfig; | ||
| 120 | impl SPConfHelper for NoConfig { | ||
| 121 | fn post_enable_config(&self, _clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 122 | Ok(0) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | // | ||
| 127 | // LPUart | ||
| 128 | // | ||
| 129 | |||
| 130 | /// Selectable clocks for Lpuart peripherals | ||
| 131 | #[derive(Debug, Clone, Copy)] | ||
| 132 | pub enum LpuartClockSel { | ||
| 133 | /// FRO12M/FRO_LF/SIRC clock source, passed through divider | ||
| 134 | /// "fro_lf_div" | ||
| 135 | FroLfDiv, | ||
| 136 | /// FRO180M/FRO_HF/FIRC clock source, passed through divider | ||
| 137 | /// "fro_hf_div" | ||
| 138 | FroHfDiv, | ||
| 139 | /// SOSC/XTAL/EXTAL clock source | ||
| 140 | ClkIn, | ||
| 141 | /// FRO16K/clk_16k source | ||
| 142 | Clk16K, | ||
| 143 | /// clk_1m/FRO_LF divided by 12 | ||
| 144 | Clk1M, | ||
| 145 | /// Output of PLL1, passed through clock divider, | ||
| 146 | /// "pll1_clk_div", maybe "pll1_lf_div"? | ||
| 147 | Pll1ClkDiv, | ||
| 148 | /// Disabled | ||
| 149 | None, | ||
| 150 | } | ||
| 151 | |||
| 152 | /// Which instance of the Lpuart is this? | ||
| 153 | /// | ||
| 154 | /// Should not be directly selectable by end-users. | ||
| 155 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 156 | pub enum LpuartInstance { | ||
| 157 | /// Instance 0 | ||
| 158 | Lpuart0, | ||
| 159 | /// Instance 1 | ||
| 160 | Lpuart1, | ||
| 161 | /// Instance 2 | ||
| 162 | Lpuart2, | ||
| 163 | /// Instance 3 | ||
| 164 | Lpuart3, | ||
| 165 | /// Instance 4 | ||
| 166 | Lpuart4, | ||
| 167 | /// Instance 5 | ||
| 168 | Lpuart5, | ||
| 169 | } | ||
| 170 | |||
| 171 | /// Top level configuration for `Lpuart` instances. | ||
| 172 | pub struct LpuartConfig { | ||
| 173 | /// Power state required for this peripheral | ||
| 174 | pub power: PoweredClock, | ||
| 175 | /// Clock source | ||
| 176 | pub source: LpuartClockSel, | ||
| 177 | /// Clock divisor | ||
| 178 | pub div: Div4, | ||
| 179 | /// Which instance is this? | ||
| 180 | // NOTE: should not be user settable | ||
| 181 | pub(crate) instance: LpuartInstance, | ||
| 182 | } | ||
| 183 | |||
| 184 | impl SPConfHelper for LpuartConfig { | ||
| 185 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 186 | // check that source is suitable | ||
| 187 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | ||
| 188 | use mcxa_pac::mrcc0::mrcc_lpuart0_clksel::Mux; | ||
| 189 | |||
| 190 | let (clkdiv, clksel) = match self.instance { | ||
| 191 | LpuartInstance::Lpuart0 => (mrcc0.mrcc_lpuart0_clkdiv(), mrcc0.mrcc_lpuart0_clksel()), | ||
| 192 | LpuartInstance::Lpuart1 => (mrcc0.mrcc_lpuart1_clkdiv(), mrcc0.mrcc_lpuart1_clksel()), | ||
| 193 | LpuartInstance::Lpuart2 => (mrcc0.mrcc_lpuart2_clkdiv(), mrcc0.mrcc_lpuart2_clksel()), | ||
| 194 | LpuartInstance::Lpuart3 => (mrcc0.mrcc_lpuart3_clkdiv(), mrcc0.mrcc_lpuart3_clksel()), | ||
| 195 | LpuartInstance::Lpuart4 => (mrcc0.mrcc_lpuart4_clkdiv(), mrcc0.mrcc_lpuart4_clksel()), | ||
| 196 | LpuartInstance::Lpuart5 => (mrcc0.mrcc_lpuart5_clkdiv(), mrcc0.mrcc_lpuart5_clksel()), | ||
| 197 | }; | ||
| 198 | |||
| 199 | let (freq, variant) = match self.source { | ||
| 200 | LpuartClockSel::FroLfDiv => { | ||
| 201 | let freq = clocks.ensure_fro_lf_div_active(&self.power)?; | ||
| 202 | (freq, Mux::ClkrootFunc0) | ||
| 203 | } | ||
| 204 | LpuartClockSel::FroHfDiv => { | ||
| 205 | let freq = clocks.ensure_fro_hf_div_active(&self.power)?; | ||
| 206 | (freq, Mux::ClkrootFunc2) | ||
| 207 | } | ||
| 208 | LpuartClockSel::ClkIn => { | ||
| 209 | let freq = clocks.ensure_clk_in_active(&self.power)?; | ||
| 210 | (freq, Mux::ClkrootFunc3) | ||
| 211 | } | ||
| 212 | LpuartClockSel::Clk16K => { | ||
| 213 | let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; | ||
| 214 | (freq, Mux::ClkrootFunc4) | ||
| 215 | } | ||
| 216 | LpuartClockSel::Clk1M => { | ||
| 217 | let freq = clocks.ensure_clk_1m_active(&self.power)?; | ||
| 218 | (freq, Mux::ClkrootFunc5) | ||
| 219 | } | ||
| 220 | LpuartClockSel::Pll1ClkDiv => { | ||
| 221 | let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; | ||
| 222 | (freq, Mux::ClkrootFunc6) | ||
| 223 | } | ||
| 224 | LpuartClockSel::None => unsafe { | ||
| 225 | // no ClkrootFunc7, just write manually for now | ||
| 226 | clksel.write(|w| w.bits(0b111)); | ||
| 227 | clkdiv.modify(|_r, w| { | ||
| 228 | w.reset().on(); | ||
| 229 | w.halt().on(); | ||
| 230 | w | ||
| 231 | }); | ||
| 232 | return Ok(0); | ||
| 233 | }, | ||
| 234 | }; | ||
| 235 | |||
| 236 | // set clksel | ||
| 237 | clksel.modify(|_r, w| w.mux().variant(variant)); | ||
| 238 | |||
| 239 | // Set up clkdiv | ||
| 240 | clkdiv.modify(|_r, w| { | ||
| 241 | w.halt().on(); | ||
| 242 | w.reset().on(); | ||
| 243 | w | ||
| 244 | }); | ||
| 245 | clkdiv.modify(|_r, w| { | ||
| 246 | w.halt().off(); | ||
| 247 | w.reset().off(); | ||
| 248 | unsafe { w.div().bits(self.div.into_bits()) }; | ||
| 249 | w | ||
| 250 | }); | ||
| 251 | |||
| 252 | while clkdiv.read().unstab().is_on() {} | ||
| 253 | |||
| 254 | Ok(freq / self.div.into_divisor()) | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | // | ||
| 259 | // OSTimer | ||
| 260 | // | ||
| 261 | |||
| 262 | /// Selectable clocks for the OSTimer peripheral | ||
| 263 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 264 | pub enum OstimerClockSel { | ||
| 265 | /// 16k clock, sourced from FRO16K (Vdd Core) | ||
| 266 | Clk16kVddCore, | ||
| 267 | /// 1 MHz Clock sourced from FRO12M | ||
| 268 | Clk1M, | ||
| 269 | /// Disabled | ||
| 270 | None, | ||
| 271 | } | ||
| 272 | |||
| 273 | /// Top level configuration for the `OSTimer` peripheral | ||
| 274 | pub struct OsTimerConfig { | ||
| 275 | /// Power state required for this peripheral | ||
| 276 | pub power: PoweredClock, | ||
| 277 | /// Selected clock source for this peripheral | ||
| 278 | pub source: OstimerClockSel, | ||
| 279 | } | ||
| 280 | |||
| 281 | impl SPConfHelper for OsTimerConfig { | ||
| 282 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 283 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | ||
| 284 | Ok(match self.source { | ||
| 285 | OstimerClockSel::Clk16kVddCore => { | ||
| 286 | let freq = clocks.ensure_clk_16k_vdd_core_active(&self.power)?; | ||
| 287 | mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_16k()); | ||
| 288 | freq | ||
| 289 | } | ||
| 290 | OstimerClockSel::Clk1M => { | ||
| 291 | let freq = clocks.ensure_clk_1m_active(&self.power)?; | ||
| 292 | mrcc0.mrcc_ostimer0_clksel().write(|w| w.mux().clkroot_1m()); | ||
| 293 | freq | ||
| 294 | } | ||
| 295 | OstimerClockSel::None => { | ||
| 296 | mrcc0.mrcc_ostimer0_clksel().write(|w| unsafe { w.mux().bits(0b11) }); | ||
| 297 | 0 | ||
| 298 | } | ||
| 299 | }) | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | // | ||
| 304 | // Adc | ||
| 305 | // | ||
| 306 | |||
| 307 | /// Selectable clocks for the ADC peripheral | ||
| 308 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 309 | pub enum AdcClockSel { | ||
| 310 | /// Divided `fro_lf`/`clk_12m`/FRO12M source | ||
| 311 | FroLfDiv, | ||
| 312 | /// Gated `fro_hf`/`FRO180M` source | ||
| 313 | FroHf, | ||
| 314 | /// External Clock Source | ||
| 315 | ClkIn, | ||
| 316 | /// 1MHz clock sourced by a divided `fro_lf`/`clk_12m` | ||
| 317 | Clk1M, | ||
| 318 | /// Internal PLL output, with configurable divisor | ||
| 319 | Pll1ClkDiv, | ||
| 320 | /// No clock/disabled | ||
| 321 | None, | ||
| 322 | } | ||
| 323 | |||
| 324 | /// Top level configuration for the ADC peripheral | ||
| 325 | pub struct AdcConfig { | ||
| 326 | /// Power state required for this peripheral | ||
| 327 | pub power: PoweredClock, | ||
| 328 | /// Selected clock-source for this peripheral | ||
| 329 | pub source: AdcClockSel, | ||
| 330 | /// Pre-divisor, applied to the upstream clock output | ||
| 331 | pub div: Div4, | ||
| 332 | } | ||
| 333 | |||
| 334 | impl SPConfHelper for AdcConfig { | ||
| 335 | fn post_enable_config(&self, clocks: &Clocks) -> Result<u32, ClockError> { | ||
| 336 | use mcxa_pac::mrcc0::mrcc_adc_clksel::Mux; | ||
| 337 | let mrcc0 = unsafe { pac::Mrcc0::steal() }; | ||
| 338 | let (freq, variant) = match self.source { | ||
| 339 | AdcClockSel::FroLfDiv => { | ||
| 340 | let freq = clocks.ensure_fro_lf_div_active(&self.power)?; | ||
| 341 | (freq, Mux::ClkrootFunc0) | ||
| 342 | } | ||
| 343 | AdcClockSel::FroHf => { | ||
| 344 | let freq = clocks.ensure_fro_hf_active(&self.power)?; | ||
| 345 | (freq, Mux::ClkrootFunc1) | ||
| 346 | } | ||
| 347 | AdcClockSel::ClkIn => { | ||
| 348 | let freq = clocks.ensure_clk_in_active(&self.power)?; | ||
| 349 | (freq, Mux::ClkrootFunc3) | ||
| 350 | } | ||
| 351 | AdcClockSel::Clk1M => { | ||
| 352 | let freq = clocks.ensure_clk_1m_active(&self.power)?; | ||
| 353 | (freq, Mux::ClkrootFunc5) | ||
| 354 | } | ||
| 355 | AdcClockSel::Pll1ClkDiv => { | ||
| 356 | let freq = clocks.ensure_pll1_clk_div_active(&self.power)?; | ||
| 357 | (freq, Mux::ClkrootFunc6) | ||
| 358 | } | ||
| 359 | AdcClockSel::None => { | ||
| 360 | mrcc0.mrcc_adc_clksel().write(|w| unsafe { | ||
| 361 | // no ClkrootFunc7, just write manually for now | ||
| 362 | w.mux().bits(0b111) | ||
| 363 | }); | ||
| 364 | mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { | ||
| 365 | w.reset().on(); | ||
| 366 | w.halt().on(); | ||
| 367 | w | ||
| 368 | }); | ||
| 369 | return Ok(0); | ||
| 370 | } | ||
| 371 | }; | ||
| 372 | |||
| 373 | // set clksel | ||
| 374 | mrcc0.mrcc_adc_clksel().modify(|_r, w| w.mux().variant(variant)); | ||
| 375 | |||
| 376 | // Set up clkdiv | ||
| 377 | mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { | ||
| 378 | w.halt().on(); | ||
| 379 | w.reset().on(); | ||
| 380 | w | ||
| 381 | }); | ||
| 382 | mrcc0.mrcc_adc_clkdiv().modify(|_r, w| { | ||
| 383 | w.halt().off(); | ||
| 384 | w.reset().off(); | ||
| 385 | unsafe { w.div().bits(self.div.into_bits()) }; | ||
| 386 | w | ||
| 387 | }); | ||
| 388 | |||
| 389 | while mrcc0.mrcc_adc_clkdiv().read().unstab().is_on() {} | ||
| 390 | |||
| 391 | Ok(freq / self.div.into_divisor()) | ||
| 392 | } | ||
| 393 | } | ||
diff --git a/src/config.rs b/src/config.rs index 93aed5a99..0939c11f1 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | // HAL configuration (minimal), mirroring embassy-imxrt style | 1 | // HAL configuration (minimal), mirroring embassy-imxrt style |
| 2 | 2 | ||
| 3 | use crate::clocks::config::ClocksConfig; | ||
| 3 | use crate::interrupt::Priority; | 4 | use crate::interrupt::Priority; |
| 4 | 5 | ||
| 5 | #[non_exhaustive] | 6 | #[non_exhaustive] |
| @@ -7,6 +8,7 @@ pub struct Config { | |||
| 7 | pub time_interrupt_priority: Priority, | 8 | pub time_interrupt_priority: Priority, |
| 8 | pub rtc_interrupt_priority: Priority, | 9 | pub rtc_interrupt_priority: Priority, |
| 9 | pub adc_interrupt_priority: Priority, | 10 | pub adc_interrupt_priority: Priority, |
| 11 | pub clock_cfg: ClocksConfig, | ||
| 10 | } | 12 | } |
| 11 | 13 | ||
| 12 | impl Default for Config { | 14 | impl Default for Config { |
| @@ -15,6 +17,7 @@ impl Default for Config { | |||
| 15 | time_interrupt_priority: Priority::from(0), | 17 | time_interrupt_priority: Priority::from(0), |
| 16 | rtc_interrupt_priority: Priority::from(0), | 18 | rtc_interrupt_priority: Priority::from(0), |
| 17 | adc_interrupt_priority: Priority::from(0), | 19 | adc_interrupt_priority: Priority::from(0), |
| 20 | clock_cfg: ClocksConfig::default(), | ||
| 18 | } | 21 | } |
| 19 | } | 22 | } |
| 20 | } | 23 | } |
diff --git a/src/lib.rs b/src/lib.rs index 4e5ac0109..86c0dc45b 100644 --- a/src/lib.rs +++ b/src/lib.rs | |||
| @@ -6,7 +6,6 @@ | |||
| 6 | pub mod clocks; // still provide clock helpers | 6 | pub mod clocks; // still provide clock helpers |
| 7 | pub mod gpio; | 7 | pub mod gpio; |
| 8 | pub mod pins; // pin mux helpers | 8 | pub mod pins; // pin mux helpers |
| 9 | pub mod reset; // reset control helpers | ||
| 10 | 9 | ||
| 11 | pub mod adc; | 10 | pub mod adc; |
| 12 | pub mod config; | 11 | pub mod config; |
| @@ -14,9 +13,8 @@ pub mod interrupt; | |||
| 14 | pub mod lpuart; | 13 | pub mod lpuart; |
| 15 | pub mod ostimer; | 14 | pub mod ostimer; |
| 16 | pub mod rtc; | 15 | pub mod rtc; |
| 17 | pub mod uart; | ||
| 18 | 16 | ||
| 19 | embassy_hal_internal::peripherals!(LPUART2, OSTIMER0, GPIO, RTC0, ADC1,); | 17 | embassy_hal_internal::peripherals!(PORT1, PORT2, PORT3, LPUART2, OSTIMER0, GPIO, PIO2_2, PIO2_3, GPIO3, RTC0, ADC1,); |
| 20 | 18 | ||
| 21 | /// Get access to the PAC Peripherals for low-level register access. | 19 | /// Get access to the PAC Peripherals for low-level register access. |
| 22 | /// This is a lazy-initialized singleton that can be called after init(). | 20 | /// This is a lazy-initialized singleton that can be called after init(). |
| @@ -46,11 +44,9 @@ pub use mcxa_pac as pac; | |||
| 46 | pub(crate) use mcxa_pac as pac; | 44 | pub(crate) use mcxa_pac as pac; |
| 47 | pub use ostimer::Ostimer0 as Ostimer0Token; | 45 | pub use ostimer::Ostimer0 as Ostimer0Token; |
| 48 | pub use rtc::Rtc0 as Rtc0Token; | 46 | pub use rtc::Rtc0 as Rtc0Token; |
| 49 | pub use uart::Lpuart2 as Uart2Token; | ||
| 50 | 47 | ||
| 51 | /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. | 48 | /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. |
| 52 | /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). | 49 | /// Also applies configurable NVIC priority for the OSTIMER OS_EVENT interrupt (no enabling). |
| 53 | #[allow(unused_variables)] | ||
| 54 | pub fn init(cfg: crate::config::Config) -> Peripherals { | 50 | pub fn init(cfg: crate::config::Config) -> Peripherals { |
| 55 | let peripherals = Peripherals::take(); | 51 | let peripherals = Peripherals::take(); |
| 56 | // Apply user-configured priority early; enabling is left to examples/apps | 52 | // Apply user-configured priority early; enabling is left to examples/apps |
| @@ -59,6 +55,10 @@ pub fn init(cfg: crate::config::Config) -> Peripherals { | |||
| 59 | crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); | 55 | crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); |
| 60 | // Apply user-configured priority early; enabling is left to examples/apps | 56 | // Apply user-configured priority early; enabling is left to examples/apps |
| 61 | crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); | 57 | crate::interrupt::ADC1.set_priority(cfg.adc_interrupt_priority); |
| 58 | |||
| 59 | // Configure clocks | ||
| 60 | crate::clocks::init(cfg.clock_cfg).unwrap(); | ||
| 61 | |||
| 62 | peripherals | 62 | peripherals |
| 63 | } | 63 | } |
| 64 | 64 | ||
diff --git a/src/lpuart/buffered.rs b/src/lpuart/buffered.rs index a617c10a5..7b575c086 100644 --- a/src/lpuart/buffered.rs +++ b/src/lpuart/buffered.rs | |||
| @@ -212,10 +212,19 @@ impl<'a> BufferedLpuart<'a> { | |||
| 212 | config.enable_tx = true; | 212 | config.enable_tx = true; |
| 213 | config.enable_rx = true; | 213 | config.enable_rx = true; |
| 214 | 214 | ||
| 215 | // Enable clocks | ||
| 216 | let conf = LpuartConfig { | ||
| 217 | power: config.power, | ||
| 218 | source: config.source, | ||
| 219 | div: config.div, | ||
| 220 | instance: T::CLOCK_INSTANCE, | ||
| 221 | }; | ||
| 222 | let clock_freq = unsafe { enable_and_reset::<T>(&conf).map_err(Error::ClockSetup)? }; | ||
| 223 | |||
| 215 | // Perform standard initialization | 224 | // Perform standard initialization |
| 216 | perform_software_reset(regs); | 225 | perform_software_reset(regs); |
| 217 | disable_transceiver(regs); | 226 | disable_transceiver(regs); |
| 218 | configure_baudrate(regs, config.baudrate_bps, config.clock)?; | 227 | configure_baudrate(regs, config.baudrate_bps, clock_freq)?; |
| 219 | configure_frame_format(regs, &config); | 228 | configure_frame_format(regs, &config); |
| 220 | configure_control_settings(regs, &config); | 229 | configure_control_settings(regs, &config); |
| 221 | configure_fifo(regs, &config); | 230 | configure_fifo(regs, &config); |
diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs index 35b531421..b3d7c4885 100644 --- a/src/lpuart/mod.rs +++ b/src/lpuart/mod.rs | |||
| @@ -3,6 +3,8 @@ use core::marker::PhantomData; | |||
| 3 | use embassy_hal_internal::{Peri, PeripheralType}; | 3 | use embassy_hal_internal::{Peri, PeripheralType}; |
| 4 | use paste::paste; | 4 | use paste::paste; |
| 5 | 5 | ||
| 6 | use crate::clocks::periph_helpers::{Div4, LpuartClockSel, LpuartConfig}; | ||
| 7 | use crate::clocks::{enable_and_reset, ClockError, Gate, PoweredClock}; | ||
| 6 | use crate::pac::lpuart0::baud::Sbns as StopBits; | 8 | use crate::pac::lpuart0::baud::Sbns as StopBits; |
| 7 | use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; | 9 | use crate::pac::lpuart0::ctrl::{Idlecfg as IdleConfig, Ilt as IdleType, Pt as Parity, M as DataBits}; |
| 8 | use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; | 10 | use crate::pac::lpuart0::modir::{Txctsc as TxCtsConfig, Txctssrc as TxCtsSource}; |
| @@ -18,71 +20,6 @@ pub mod buffered; | |||
| 18 | // Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API | 20 | // Stub implementation for LIB (Peripherals), GPIO, DMA and CLOCK until stable API |
| 19 | // Pin and Clock initialization is currently done at the examples level. | 21 | // Pin and Clock initialization is currently done at the examples level. |
| 20 | 22 | ||
| 21 | // --- START LIB --- | ||
| 22 | |||
| 23 | // Use our own instance of Peripherals, until we align `lib.rs` with the EMBASSY-IMXRT approach | ||
| 24 | // Inlined peripherals_definition! to bypass the `Peripherals::take_with_cs()` check | ||
| 25 | // SHOULD NOT BE USED IN THE FINAL VERSION | ||
| 26 | pub mod lib { | ||
| 27 | // embassy_hal_internal::peripherals!(LPUART2, PIO2_2, PIO2_3) | ||
| 28 | |||
| 29 | embassy_hal_internal::peripherals_definition!(LPUART2, PIO2_2, PIO2_3,); | ||
| 30 | #[doc = r" Struct containing all the peripheral singletons."] | ||
| 31 | #[doc = r""] | ||
| 32 | #[doc = r" To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]."] | ||
| 33 | #[allow(non_snake_case)] | ||
| 34 | pub struct Peripherals { | ||
| 35 | #[doc = concat!(stringify!(LPUART2)," peripheral")] | ||
| 36 | pub LPUART2: embassy_hal_internal::Peri<'static, peripherals::LPUART2>, | ||
| 37 | #[doc = concat!(stringify!(PIO2_2)," peripheral")] | ||
| 38 | pub PIO2_2: embassy_hal_internal::Peri<'static, peripherals::PIO2_2>, | ||
| 39 | #[doc = concat!(stringify!(PIO2_3)," peripheral")] | ||
| 40 | pub PIO2_3: embassy_hal_internal::Peri<'static, peripherals::PIO2_3>, | ||
| 41 | } | ||
| 42 | impl Peripherals { | ||
| 43 | #[doc = r"Returns all the peripherals *once*"] | ||
| 44 | #[inline] | ||
| 45 | pub(crate) fn take() -> Self { | ||
| 46 | critical_section::with(Self::take_with_cs) | ||
| 47 | } | ||
| 48 | #[doc = r"Returns all the peripherals *once*"] | ||
| 49 | #[inline] | ||
| 50 | pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { | ||
| 51 | #[no_mangle] | ||
| 52 | static mut _EMBASSY_DEVICE_PERIPHERALS2: bool = false; // ALIGN: Temporary fix to use stub Peripherals | ||
| 53 | unsafe { | ||
| 54 | if _EMBASSY_DEVICE_PERIPHERALS2 { | ||
| 55 | panic!("init called more than once!") | ||
| 56 | } | ||
| 57 | _EMBASSY_DEVICE_PERIPHERALS2 = true; | ||
| 58 | Self::steal() | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | impl Peripherals { | ||
| 63 | #[doc = r" Unsafely create an instance of this peripheral out of thin air."] | ||
| 64 | #[doc = r""] | ||
| 65 | #[doc = r" # Safety"] | ||
| 66 | #[doc = r""] | ||
| 67 | #[doc = r" You must ensure that you're only using one instance of this type at a time."] | ||
| 68 | #[inline] | ||
| 69 | pub unsafe fn steal() -> Self { | ||
| 70 | Self { | ||
| 71 | LPUART2: peripherals::LPUART2::steal(), | ||
| 72 | PIO2_2: peripherals::PIO2_2::steal(), | ||
| 73 | PIO2_3: peripherals::PIO2_3::steal(), | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Initialize HAL | ||
| 79 | pub fn init() -> Peripherals { | ||
| 80 | Peripherals::take() | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | // --- END LIB --- | ||
| 85 | |||
| 86 | // --- START GPIO --- | 23 | // --- START GPIO --- |
| 87 | 24 | ||
| 88 | mod gpio { | 25 | mod gpio { |
| @@ -101,13 +38,13 @@ mod gpio { | |||
| 101 | macro_rules! impl_gpio_pin { | 38 | macro_rules! impl_gpio_pin { |
| 102 | ($($pin:ident),*) => { | 39 | ($($pin:ident),*) => { |
| 103 | $( | 40 | $( |
| 104 | impl SealedPin for super::lib::peripherals::$pin {} | 41 | impl SealedPin for crate::peripherals::$pin {} |
| 105 | 42 | ||
| 106 | impl GpioPin for super::lib::peripherals::$pin {} | 43 | impl GpioPin for crate::peripherals::$pin {} |
| 107 | 44 | ||
| 108 | impl From<super::lib::peripherals::$pin> for AnyPin { | 45 | impl From<crate::peripherals::$pin> for AnyPin { |
| 109 | // TODO: AJM: any reason we aren't using $pin? | 46 | // TODO: AJM: any reason we aren't using $pin? |
| 110 | fn from(_val: super::lib::peripherals::$pin) -> Self { | 47 | fn from(_val: crate::peripherals::$pin) -> Self { |
| 111 | AnyPin | 48 | AnyPin |
| 112 | } | 49 | } |
| 113 | } | 50 | } |
| @@ -143,18 +80,6 @@ use dma::Channel; | |||
| 143 | 80 | ||
| 144 | // --- END DMA --- | 81 | // --- END DMA --- |
| 145 | 82 | ||
| 146 | // --- START CLOCK --- | ||
| 147 | mod clock { | ||
| 148 | #[derive(Debug, Clone, Copy)] | ||
| 149 | pub enum Clock { | ||
| 150 | FroLf, // Low-Frequency Free-Running Oscillator | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | use clock::Clock; | ||
| 155 | |||
| 156 | // --- END CLOCK --- | ||
| 157 | |||
| 158 | // ============================================================================ | 83 | // ============================================================================ |
| 159 | // MISC | 84 | // MISC |
| 160 | // ============================================================================ | 85 | // ============================================================================ |
| @@ -182,7 +107,8 @@ pub struct Info { | |||
| 182 | 107 | ||
| 183 | /// Trait for LPUART peripheral instances | 108 | /// Trait for LPUART peripheral instances |
| 184 | #[allow(private_bounds)] | 109 | #[allow(private_bounds)] |
| 185 | pub trait Instance: SealedInstance + PeripheralType + 'static + Send { | 110 | pub trait Instance: SealedInstance + PeripheralType + 'static + Send + Gate<MrccPeriphConfig = LpuartConfig> { |
| 111 | const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance; | ||
| 186 | type Interrupt: interrupt::typelevel::Interrupt; | 112 | type Interrupt: interrupt::typelevel::Interrupt; |
| 187 | } | 113 | } |
| 188 | 114 | ||
| @@ -190,7 +116,7 @@ macro_rules! impl_instance { | |||
| 190 | ($($n:expr),*) => { | 116 | ($($n:expr),*) => { |
| 191 | $( | 117 | $( |
| 192 | paste!{ | 118 | paste!{ |
| 193 | impl SealedInstance for lib::peripherals::[<LPUART $n>] { | 119 | impl SealedInstance for crate::peripherals::[<LPUART $n>] { |
| 194 | fn info() -> Info { | 120 | fn info() -> Info { |
| 195 | Info { | 121 | Info { |
| 196 | regs: unsafe { &*pac::[<Lpuart $n>]::ptr() }, | 122 | regs: unsafe { &*pac::[<Lpuart $n>]::ptr() }, |
| @@ -208,7 +134,9 @@ macro_rules! impl_instance { | |||
| 208 | } | 134 | } |
| 209 | } | 135 | } |
| 210 | 136 | ||
| 211 | impl Instance for lib::peripherals::[<LPUART $n>] { | 137 | impl Instance for crate::peripherals::[<LPUART $n>] { |
| 138 | const CLOCK_INSTANCE: crate::clocks::periph_helpers::LpuartInstance | ||
| 139 | = crate::clocks::periph_helpers::LpuartInstance::[<Lpuart $n>]; | ||
| 212 | type Interrupt = crate::interrupt::typelevel::[<LPUART $n>]; | 140 | type Interrupt = crate::interrupt::typelevel::[<LPUART $n>]; |
| 213 | } | 141 | } |
| 214 | } | 142 | } |
| @@ -236,8 +164,7 @@ pub fn disable_transceiver(regs: Regs) { | |||
| 236 | } | 164 | } |
| 237 | 165 | ||
| 238 | /// Calculate and configure baudrate settings | 166 | /// Calculate and configure baudrate settings |
| 239 | pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock: Clock) -> Result<()> { | 167 | pub fn configure_baudrate(regs: Regs, baudrate_bps: u32, clock_freq: u32) -> Result<()> { |
| 240 | let clock_freq = get_fc_freq(clock)?; | ||
| 241 | let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; | 168 | let (osr, sbr) = calculate_baudrate(baudrate_bps, clock_freq)?; |
| 242 | 169 | ||
| 243 | // Configure BAUD register | 170 | // Configure BAUD register |
| @@ -408,16 +335,6 @@ pub fn calculate_baudrate(baudrate: u32, src_clock_hz: u32) -> Result<(u8, u16)> | |||
| 408 | Ok((osr, sbr)) | 335 | Ok((osr, sbr)) |
| 409 | } | 336 | } |
| 410 | 337 | ||
| 411 | pub fn get_fc_freq(clock: Clock) -> Result<u32> { | ||
| 412 | // This is a placeholder - actual implementation would query the clock system | ||
| 413 | // In real implementation, this would get the LPUART clock frequency | ||
| 414 | match clock { | ||
| 415 | Clock::FroLf => Ok(12_000_000), // Low frequency oscillator | ||
| 416 | #[allow(unreachable_patterns)] | ||
| 417 | _ => Err(Error::InvalidArgument), | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | /// Wait for all transmit operations to complete | 338 | /// Wait for all transmit operations to complete |
| 422 | pub fn wait_for_tx_complete(regs: Regs) { | 339 | pub fn wait_for_tx_complete(regs: Regs) { |
| 423 | // Wait for TX FIFO to empty | 340 | // Wait for TX FIFO to empty |
| @@ -504,7 +421,7 @@ macro_rules! impl_pin_trait { | |||
| 504 | ($fcn:ident, $mode:ident, $($pin:ident, $alt:ident),*) => { | 421 | ($fcn:ident, $mode:ident, $($pin:ident, $alt:ident),*) => { |
| 505 | paste! { | 422 | paste! { |
| 506 | $( | 423 | $( |
| 507 | impl [<$mode:camel Pin>]<lib::peripherals::$fcn> for lib::peripherals::$pin { | 424 | impl [<$mode:camel Pin>]<crate::peripherals::$fcn> for crate::peripherals::$pin { |
| 508 | fn [<as_ $mode>](&self) { | 425 | fn [<as_ $mode>](&self) { |
| 509 | let _alt = gpio::Alt::$alt; | 426 | let _alt = gpio::Alt::$alt; |
| 510 | // todo!("Configure pin for LPUART function") | 427 | // todo!("Configure pin for LPUART function") |
| @@ -553,6 +470,8 @@ pub enum Error { | |||
| 553 | TxFifoFull, | 470 | TxFifoFull, |
| 554 | /// TX Busy | 471 | /// TX Busy |
| 555 | TxBusy, | 472 | TxBusy, |
| 473 | /// Clock Error | ||
| 474 | ClockSetup(ClockError), | ||
| 556 | } | 475 | } |
| 557 | 476 | ||
| 558 | /// A specialized Result type for LPUART operations | 477 | /// A specialized Result type for LPUART operations |
| @@ -565,10 +484,14 @@ pub type Result<T> = core::result::Result<T, Error>; | |||
| 565 | /// Lpuart config | 484 | /// Lpuart config |
| 566 | #[derive(Debug, Clone, Copy)] | 485 | #[derive(Debug, Clone, Copy)] |
| 567 | pub struct Config { | 486 | pub struct Config { |
| 487 | /// Power state required for this peripheral | ||
| 488 | pub power: PoweredClock, | ||
| 489 | /// Clock source | ||
| 490 | pub source: LpuartClockSel, | ||
| 491 | /// Clock divisor | ||
| 492 | pub div: Div4, | ||
| 568 | /// Baud rate in bits per second | 493 | /// Baud rate in bits per second |
| 569 | pub baudrate_bps: u32, | 494 | pub baudrate_bps: u32, |
| 570 | /// Clock | ||
| 571 | pub clock: Clock, | ||
| 572 | /// Parity configuration | 495 | /// Parity configuration |
| 573 | pub parity_mode: Option<Parity>, | 496 | pub parity_mode: Option<Parity>, |
| 574 | /// Number of data bits | 497 | /// Number of data bits |
| @@ -605,7 +528,6 @@ impl Default for Config { | |||
| 605 | fn default() -> Self { | 528 | fn default() -> Self { |
| 606 | Self { | 529 | Self { |
| 607 | baudrate_bps: 115_200u32, | 530 | baudrate_bps: 115_200u32, |
| 608 | clock: Clock::FroLf, | ||
| 609 | parity_mode: None, | 531 | parity_mode: None, |
| 610 | data_bits_count: DataBits::Data8, | 532 | data_bits_count: DataBits::Data8, |
| 611 | msb_firs: MsbFirst::LsbFirst, | 533 | msb_firs: MsbFirst::LsbFirst, |
| @@ -621,6 +543,9 @@ impl Default for Config { | |||
| 621 | enable_tx: false, | 543 | enable_tx: false, |
| 622 | enable_rx: false, | 544 | enable_rx: false, |
| 623 | swap_txd_rxd: false, | 545 | swap_txd_rxd: false, |
| 546 | power: PoweredClock::NormalEnabledDeepSleepDisabled, | ||
| 547 | source: LpuartClockSel::FroLfDiv, | ||
| 548 | div: Div4::no_div(), | ||
| 624 | } | 549 | } |
| 625 | } | 550 | } |
| 626 | } | 551 | } |
| @@ -706,10 +631,19 @@ impl<'a, M: Mode> Lpuart<'a, M> { | |||
| 706 | ) -> Result<()> { | 631 | ) -> Result<()> { |
| 707 | let regs = T::info().regs; | 632 | let regs = T::info().regs; |
| 708 | 633 | ||
| 634 | // Enable clocks | ||
| 635 | let conf = LpuartConfig { | ||
| 636 | power: config.power, | ||
| 637 | source: config.source, | ||
| 638 | div: config.div, | ||
| 639 | instance: T::CLOCK_INSTANCE, | ||
| 640 | }; | ||
| 641 | let clock_freq = unsafe { enable_and_reset::<T>(&conf).map_err(Error::ClockSetup)? }; | ||
| 642 | |||
| 709 | // Perform initialization sequence | 643 | // Perform initialization sequence |
| 710 | perform_software_reset(regs); | 644 | perform_software_reset(regs); |
| 711 | disable_transceiver(regs); | 645 | disable_transceiver(regs); |
| 712 | configure_baudrate(regs, config.baudrate_bps, config.clock)?; | 646 | configure_baudrate(regs, config.baudrate_bps, clock_freq)?; |
| 713 | configure_frame_format(regs, &config); | 647 | configure_frame_format(regs, &config); |
| 714 | configure_control_settings(regs, &config); | 648 | configure_control_settings(regs, &config); |
| 715 | configure_fifo(regs, &config); | 649 | configure_fifo(regs, &config); |
| @@ -838,6 +772,10 @@ impl<'a> LpuartTx<'a, Blocking> { | |||
| 838 | Ok(()) | 772 | Ok(()) |
| 839 | } | 773 | } |
| 840 | 774 | ||
| 775 | pub fn write_str_blocking(&mut self, buf: &str) { | ||
| 776 | let _ = self.blocking_write(buf.as_bytes()); | ||
| 777 | } | ||
| 778 | |||
| 841 | /// Write data to LPUART TX without blocking. | 779 | /// Write data to LPUART TX without blocking. |
| 842 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { | 780 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { |
| 843 | for x in buf { | 781 | for x in buf { |
| @@ -967,6 +905,22 @@ impl<'a> Lpuart<'a, Blocking> { | |||
| 967 | self.tx.blocking_write(buf) | 905 | self.tx.blocking_write(buf) |
| 968 | } | 906 | } |
| 969 | 907 | ||
| 908 | pub fn write_byte(&mut self, byte: u8) { | ||
| 909 | _ = self.tx.write_byte(byte); | ||
| 910 | } | ||
| 911 | |||
| 912 | pub fn read_byte_blocking(&mut self) -> u8 { | ||
| 913 | loop { | ||
| 914 | if let Ok(b) = self.rx.read_byte() { | ||
| 915 | return b; | ||
| 916 | } | ||
| 917 | } | ||
| 918 | } | ||
| 919 | |||
| 920 | pub fn write_str_blocking(&mut self, buf: &str) { | ||
| 921 | self.tx.write_str_blocking(buf); | ||
| 922 | } | ||
| 923 | |||
| 970 | /// Write data to LPUART TX without blocking | 924 | /// Write data to LPUART TX without blocking |
| 971 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { | 925 | pub fn write(&mut self, buf: &[u8]) -> Result<()> { |
| 972 | self.tx.write(buf) | 926 | self.tx.write(buf) |
diff --git a/src/ostimer.rs b/src/ostimer.rs index 8bc68389a..cd5451b53 100644 --- a/src/ostimer.rs +++ b/src/ostimer.rs | |||
| @@ -29,8 +29,13 @@ | |||
| 29 | 29 | ||
| 30 | use core::sync::atomic::{AtomicBool, Ordering}; | 30 | use core::sync::atomic::{AtomicBool, Ordering}; |
| 31 | 31 | ||
| 32 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 33 | |||
| 34 | use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; | ||
| 35 | use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; | ||
| 32 | use crate::interrupt::InterruptExt; | 36 | use crate::interrupt::InterruptExt; |
| 33 | use crate::pac; | 37 | use crate::pac; |
| 38 | use crate::peripherals::OSTIMER0; | ||
| 34 | 39 | ||
| 35 | // PAC defines the shared RegisterBlock under `ostimer0`. | 40 | // PAC defines the shared RegisterBlock under `ostimer0`. |
| 36 | type Regs = pac::ostimer0::RegisterBlock; | 41 | type Regs = pac::ostimer0::RegisterBlock; |
| @@ -197,16 +202,16 @@ impl<'d> Alarm<'d> { | |||
| 197 | pub struct Config { | 202 | pub struct Config { |
| 198 | /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. | 203 | /// Initialize MATCH registers to their max values and mask/clear the interrupt flag. |
| 199 | pub init_match_max: bool, | 204 | pub init_match_max: bool, |
| 200 | /// OSTIMER clock frequency in Hz (must match the actual hardware clock) | 205 | pub power: PoweredClock, |
| 201 | pub clock_frequency_hz: u64, | 206 | pub source: OstimerClockSel, |
| 202 | } | 207 | } |
| 203 | 208 | ||
| 204 | impl Default for Config { | 209 | impl Default for Config { |
| 205 | fn default() -> Self { | 210 | fn default() -> Self { |
| 206 | Self { | 211 | Self { |
| 207 | init_match_max: true, | 212 | init_match_max: true, |
| 208 | // Default to 1MHz - user should override this with actual frequency | 213 | power: PoweredClock::NormalEnabledDeepSleepDisabled, |
| 209 | clock_frequency_hz: 1_000_000, | 214 | source: OstimerClockSel::Clk1M, |
| 210 | } | 215 | } |
| 211 | } | 216 | } |
| 212 | } | 217 | } |
| @@ -222,8 +227,16 @@ impl<'d, I: Instance> Ostimer<'d, I> { | |||
| 222 | /// Construct OSTIMER handle. | 227 | /// Construct OSTIMER handle. |
| 223 | /// Requires clocks for the instance to be enabled by the board before calling. | 228 | /// Requires clocks for the instance to be enabled by the board before calling. |
| 224 | /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. | 229 | /// Does not enable NVIC or INTENA; use time_driver::init() for async operation. |
| 225 | pub fn new(_inst: impl Instance, cfg: Config, _p: &'d crate::pac::Peripherals) -> Self { | 230 | pub fn new(_inst: Peri<'d, I>, cfg: Config) -> Self { |
| 226 | assert!(cfg.clock_frequency_hz > 0, "OSTIMER frequency must be greater than 0"); | 231 | let clock_freq = unsafe { |
| 232 | enable_and_reset::<I>(&OsTimerConfig { | ||
| 233 | power: cfg.power, | ||
| 234 | source: cfg.source, | ||
| 235 | }) | ||
| 236 | .expect("Enabling OsTimer clock should not fail") | ||
| 237 | }; | ||
| 238 | |||
| 239 | assert!(clock_freq > 0, "OSTIMER frequency must be greater than 0"); | ||
| 227 | 240 | ||
| 228 | if cfg.init_match_max { | 241 | if cfg.init_match_max { |
| 229 | let r: &Regs = unsafe { &*I::ptr() }; | 242 | let r: &Regs = unsafe { &*I::ptr() }; |
| @@ -233,7 +246,7 @@ impl<'d, I: Instance> Ostimer<'d, I> { | |||
| 233 | 246 | ||
| 234 | Self { | 247 | Self { |
| 235 | _inst: core::marker::PhantomData, | 248 | _inst: core::marker::PhantomData, |
| 236 | clock_frequency_hz: cfg.clock_frequency_hz, | 249 | clock_frequency_hz: clock_freq as u64, |
| 237 | _phantom: core::marker::PhantomData, | 250 | _phantom: core::marker::PhantomData, |
| 238 | } | 251 | } |
| 239 | } | 252 | } |
| @@ -260,7 +273,7 @@ impl<'d, I: Instance> Ostimer<'d, I> { | |||
| 260 | /// # Safety | 273 | /// # Safety |
| 261 | /// This operation will reset the entire OSTIMER peripheral. Any active alarms | 274 | /// This operation will reset the entire OSTIMER peripheral. Any active alarms |
| 262 | /// or time_driver operations will be disrupted. Use with caution. | 275 | /// or time_driver operations will be disrupted. Use with caution. |
| 263 | pub fn reset(&self, peripherals: &crate::pac::Peripherals) { | 276 | pub fn reset(&self, _peripherals: &crate::pac::Peripherals) { |
| 264 | critical_section::with(|_| { | 277 | critical_section::with(|_| { |
| 265 | let r: &Regs = unsafe { &*I::ptr() }; | 278 | let r: &Regs = unsafe { &*I::ptr() }; |
| 266 | 279 | ||
| @@ -270,19 +283,17 @@ impl<'d, I: Instance> Ostimer<'d, I> { | |||
| 270 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); | 283 | .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); |
| 271 | 284 | ||
| 272 | unsafe { | 285 | unsafe { |
| 273 | crate::reset::assert::<crate::reset::line::Ostimer0>(peripherals); | 286 | assert_reset::<OSTIMER0>(); |
| 274 | } | ||
| 275 | 287 | ||
| 276 | for _ in 0..RESET_STABILIZE_SPINS { | 288 | for _ in 0..RESET_STABILIZE_SPINS { |
| 277 | cortex_m::asm::nop(); | 289 | cortex_m::asm::nop(); |
| 278 | } | 290 | } |
| 279 | 291 | ||
| 280 | unsafe { | 292 | release_reset::<OSTIMER0>(); |
| 281 | crate::reset::release::<crate::reset::line::Ostimer0>(peripherals); | ||
| 282 | } | ||
| 283 | 293 | ||
| 284 | while !<crate::reset::line::Ostimer0 as crate::reset::ResetLine>::is_released(&peripherals.mrcc0) { | 294 | while !is_reset_released::<OSTIMER0>() { |
| 285 | cortex_m::asm::nop(); | 295 | cortex_m::asm::nop(); |
| 296 | } | ||
| 286 | } | 297 | } |
| 287 | 298 | ||
| 288 | for _ in 0..RESET_STABILIZE_SPINS { | 299 | for _ in 0..RESET_STABILIZE_SPINS { |
| @@ -469,14 +480,13 @@ fn now_ticks_read() -> u64 { | |||
| 469 | // Read high then low to minimize incoherent snapshots | 480 | // Read high then low to minimize incoherent snapshots |
| 470 | let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); | 481 | let hi = (r.evtimerh().read().evtimer_count_value().bits() as u64) & (EVTIMER_HI_MASK as u64); |
| 471 | let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; | 482 | let lo = r.evtimerl().read().evtimer_count_value().bits() as u64; |
| 472 | |||
| 473 | // Combine and convert from Gray code to binary | 483 | // Combine and convert from Gray code to binary |
| 474 | let gray = lo | (hi << EVTIMER_HI_SHIFT); | 484 | let gray = lo | (hi << EVTIMER_HI_SHIFT); |
| 475 | gray_to_bin(gray) | 485 | gray_to_bin(gray) |
| 476 | } | 486 | } |
| 477 | 487 | ||
| 478 | // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance | 488 | // Instance trait like other drivers, providing a PAC pointer for this OSTIMER instance |
| 479 | pub trait Instance { | 489 | pub trait Instance: Gate<MrccPeriphConfig = OsTimerConfig> + PeripheralType { |
| 480 | fn ptr() -> *const Regs; | 490 | fn ptr() -> *const Regs; |
| 481 | } | 491 | } |
| 482 | 492 | ||
| @@ -491,12 +501,12 @@ impl Instance for crate::peripherals::OSTIMER0 { | |||
| 491 | } | 501 | } |
| 492 | 502 | ||
| 493 | // Also implement Instance for the Peri wrapper type | 503 | // Also implement Instance for the Peri wrapper type |
| 494 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { | 504 | // impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { |
| 495 | #[inline(always)] | 505 | // #[inline(always)] |
| 496 | fn ptr() -> *const Regs { | 506 | // fn ptr() -> *const Regs { |
| 497 | pac::Ostimer0::ptr() | 507 | // pac::Ostimer0::ptr() |
| 498 | } | 508 | // } |
| 499 | } | 509 | // } |
| 500 | 510 | ||
| 501 | #[inline(always)] | 511 | #[inline(always)] |
| 502 | fn bin_to_gray(x: u64) -> u64 { | 512 | fn bin_to_gray(x: u64) -> u64 { |
| @@ -524,7 +534,10 @@ pub mod time_driver { | |||
| 524 | bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, | 534 | bin_to_gray, now_ticks_read, Regs, ALARM_ACTIVE, ALARM_CALLBACK, ALARM_FLAG, ALARM_TARGET_TIME, |
| 525 | EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, | 535 | EVTIMER_HI_MASK, EVTIMER_HI_SHIFT, LOW_32_BIT_MASK, |
| 526 | }; | 536 | }; |
| 537 | use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; | ||
| 538 | use crate::clocks::{enable_and_reset, PoweredClock}; | ||
| 527 | use crate::pac; | 539 | use crate::pac; |
| 540 | use crate::peripherals::OSTIMER0; | ||
| 528 | pub struct Driver; | 541 | pub struct Driver; |
| 529 | static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); | 542 | static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); |
| 530 | 543 | ||
| @@ -611,6 +624,14 @@ pub mod time_driver { | |||
| 611 | /// Note: The frequency parameter is currently accepted for API compatibility. | 624 | /// Note: The frequency parameter is currently accepted for API compatibility. |
| 612 | /// The embassy_time_driver macro handles driver registration automatically. | 625 | /// The embassy_time_driver macro handles driver registration automatically. |
| 613 | pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { | 626 | pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { |
| 627 | let _clock_freq = unsafe { | ||
| 628 | enable_and_reset::<OSTIMER0>(&OsTimerConfig { | ||
| 629 | power: PoweredClock::AlwaysEnabled, | ||
| 630 | source: OstimerClockSel::Clk1M, | ||
| 631 | }) | ||
| 632 | .expect("Enabling OsTimer clock should not fail") | ||
| 633 | }; | ||
| 634 | |||
| 614 | // Mask/clear at peripheral and set default MATCH | 635 | // Mask/clear at peripheral and set default MATCH |
| 615 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; | 636 | let r: &Regs = unsafe { &*pac::Ostimer0::ptr() }; |
| 616 | super::prime_match_registers(r); | 637 | super::prime_match_registers(r); |
diff --git a/src/reset.rs b/src/reset.rs deleted file mode 100644 index 1c131d1cc..000000000 --- a/src/reset.rs +++ /dev/null | |||
| @@ -1,112 +0,0 @@ | |||
| 1 | //! Reset control helpers built on PAC field writers. | ||
| 2 | use crate::pac; | ||
| 3 | |||
| 4 | /// Trait describing a reset line that can be asserted/deasserted. | ||
| 5 | pub trait ResetLine { | ||
| 6 | /// Drive the peripheral out of reset. | ||
| 7 | unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 8 | |||
| 9 | /// Drive the peripheral into reset. | ||
| 10 | unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock); | ||
| 11 | |||
| 12 | /// Check whether the peripheral is currently released. | ||
| 13 | fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool; | ||
| 14 | } | ||
| 15 | |||
| 16 | /// Release a reset line for the given peripheral set. | ||
| 17 | #[inline] | ||
| 18 | pub unsafe fn release<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 19 | R::release(&peripherals.mrcc0); | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Assert a reset line for the given peripheral set. | ||
| 23 | #[inline] | ||
| 24 | pub unsafe fn assert<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 25 | R::assert(&peripherals.mrcc0); | ||
| 26 | } | ||
| 27 | |||
| 28 | /// Pulse a reset line (assert then release) with a short delay. | ||
| 29 | #[inline] | ||
| 30 | pub unsafe fn pulse<R: ResetLine>(peripherals: &pac::Peripherals) { | ||
| 31 | let mrcc = &peripherals.mrcc0; | ||
| 32 | R::assert(mrcc); | ||
| 33 | cortex_m::asm::nop(); | ||
| 34 | cortex_m::asm::nop(); | ||
| 35 | R::release(mrcc); | ||
| 36 | } | ||
| 37 | |||
| 38 | macro_rules! impl_reset_line { | ||
| 39 | ($name:ident, $reg:ident, $field:ident) => { | ||
| 40 | pub struct $name; | ||
| 41 | |||
| 42 | impl ResetLine for $name { | ||
| 43 | #[inline] | ||
| 44 | unsafe fn release(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 45 | mrcc.$reg().modify(|_, w| w.$field().enabled()); | ||
| 46 | } | ||
| 47 | |||
| 48 | #[inline] | ||
| 49 | unsafe fn assert(mrcc: &pac::mrcc0::RegisterBlock) { | ||
| 50 | mrcc.$reg().modify(|_, w| w.$field().disabled()); | ||
| 51 | } | ||
| 52 | |||
| 53 | #[inline] | ||
| 54 | fn is_released(mrcc: &pac::mrcc0::RegisterBlock) -> bool { | ||
| 55 | mrcc.$reg().read().$field().is_enabled() | ||
| 56 | } | ||
| 57 | } | ||
| 58 | }; | ||
| 59 | } | ||
| 60 | |||
| 61 | pub mod line { | ||
| 62 | use super::*; | ||
| 63 | |||
| 64 | impl_reset_line!(Port2, mrcc_glb_rst1, port2); | ||
| 65 | impl_reset_line!(Port3, mrcc_glb_rst1, port3); | ||
| 66 | impl_reset_line!(Gpio3, mrcc_glb_rst2, gpio3); | ||
| 67 | impl_reset_line!(Lpuart2, mrcc_glb_rst0, lpuart2); | ||
| 68 | impl_reset_line!(Ostimer0, mrcc_glb_rst1, ostimer0); | ||
| 69 | impl_reset_line!(Port1, mrcc_glb_rst1, port1); | ||
| 70 | impl_reset_line!(Adc1, mrcc_glb_rst1, adc1); | ||
| 71 | } | ||
| 72 | |||
| 73 | #[inline] | ||
| 74 | pub unsafe fn release_reset_port2(peripherals: &pac::Peripherals) { | ||
| 75 | release::<line::Port2>(peripherals); | ||
| 76 | } | ||
| 77 | |||
| 78 | #[inline] | ||
| 79 | pub unsafe fn release_reset_port3(peripherals: &pac::Peripherals) { | ||
| 80 | release::<line::Port3>(peripherals); | ||
| 81 | } | ||
| 82 | |||
| 83 | #[inline] | ||
| 84 | pub unsafe fn release_reset_gpio3(peripherals: &pac::Peripherals) { | ||
| 85 | release::<line::Gpio3>(peripherals); | ||
| 86 | } | ||
| 87 | |||
| 88 | #[inline] | ||
| 89 | pub unsafe fn release_reset_lpuart2(peripherals: &pac::Peripherals) { | ||
| 90 | release::<line::Lpuart2>(peripherals); | ||
| 91 | } | ||
| 92 | |||
| 93 | #[inline] | ||
| 94 | pub unsafe fn release_reset_ostimer0(peripherals: &pac::Peripherals) { | ||
| 95 | release::<line::Ostimer0>(peripherals); | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Convenience shim retained for existing call sites. | ||
| 99 | #[inline] | ||
| 100 | pub unsafe fn reset_ostimer0(peripherals: &pac::Peripherals) { | ||
| 101 | pulse::<line::Ostimer0>(peripherals); | ||
| 102 | } | ||
| 103 | |||
| 104 | #[inline] | ||
| 105 | pub unsafe fn release_reset_port1(peripherals: &pac::Peripherals) { | ||
| 106 | release::<line::Port1>(peripherals); | ||
| 107 | } | ||
| 108 | |||
| 109 | #[inline] | ||
| 110 | pub unsafe fn release_reset_adc1(peripherals: &pac::Peripherals) { | ||
| 111 | release::<line::Adc1>(peripherals); | ||
| 112 | } | ||
diff --git a/src/rtc.rs b/src/rtc.rs index facb9cf8c..b750a97ea 100644 --- a/src/rtc.rs +++ b/src/rtc.rs | |||
| @@ -1,6 +1,9 @@ | |||
| 1 | //! RTC DateTime driver. | 1 | //! RTC DateTime driver. |
| 2 | use core::sync::atomic::{AtomicBool, Ordering}; | 2 | use core::sync::atomic::{AtomicBool, Ordering}; |
| 3 | 3 | ||
| 4 | use embassy_hal_internal::{Peri, PeripheralType}; | ||
| 5 | |||
| 6 | use crate::clocks::with_clocks; | ||
| 4 | use crate::pac; | 7 | use crate::pac; |
| 5 | use crate::pac::rtc0::cr::Um; | 8 | use crate::pac::rtc0::cr::Um; |
| 6 | 9 | ||
| @@ -9,7 +12,7 @@ type Regs = pac::rtc0::RegisterBlock; | |||
| 9 | static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); | 12 | static ALARM_TRIGGERED: AtomicBool = AtomicBool::new(false); |
| 10 | 13 | ||
| 11 | // Token-based instance pattern like embassy-imxrt | 14 | // Token-based instance pattern like embassy-imxrt |
| 12 | pub trait Instance { | 15 | pub trait Instance: PeripheralType { |
| 13 | fn ptr() -> *const Regs; | 16 | fn ptr() -> *const Regs; |
| 14 | } | 17 | } |
| 15 | 18 | ||
| @@ -22,14 +25,6 @@ impl Instance for crate::peripherals::RTC0 { | |||
| 22 | } | 25 | } |
| 23 | } | 26 | } |
| 24 | 27 | ||
| 25 | // Also implement Instance for the Peri wrapper type | ||
| 26 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::RTC0> { | ||
| 27 | #[inline(always)] | ||
| 28 | fn ptr() -> *const Regs { | ||
| 29 | pac::Rtc0::ptr() | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | const DAYS_IN_A_YEAR: u32 = 365; | 28 | const DAYS_IN_A_YEAR: u32 = 365; |
| 34 | const SECONDS_IN_A_DAY: u32 = 86400; | 29 | const SECONDS_IN_A_DAY: u32 = 86400; |
| 35 | const SECONDS_IN_A_HOUR: u32 = 3600; | 30 | const SECONDS_IN_A_HOUR: u32 = 3600; |
| @@ -157,15 +152,24 @@ pub fn get_default_config() -> RtcConfig { | |||
| 157 | } | 152 | } |
| 158 | } | 153 | } |
| 159 | /// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) | 154 | /// Minimal RTC handle for a specific instance I (store the zero-sized token like embassy) |
| 160 | pub struct Rtc<I: Instance> { | 155 | pub struct Rtc<'a, I: Instance> { |
| 161 | _inst: core::marker::PhantomData<I>, | 156 | _inst: core::marker::PhantomData<&'a mut I>, |
| 162 | } | 157 | } |
| 163 | 158 | ||
| 164 | impl<I: Instance> Rtc<I> { | 159 | impl<'a, I: Instance> Rtc<'a, I> { |
| 165 | /// initialize RTC | 160 | /// initialize RTC |
| 166 | pub fn new(_inst: impl Instance, config: RtcConfig) -> Self { | 161 | pub fn new(_inst: Peri<'a, I>, config: RtcConfig) -> Self { |
| 167 | let rtc = unsafe { &*I::ptr() }; | 162 | let rtc = unsafe { &*I::ptr() }; |
| 168 | 163 | ||
| 164 | // The RTC is NOT gated by the MRCC, but we DO need to make sure the 16k clock | ||
| 165 | // on the vsys domain is active | ||
| 166 | let clocks = with_clocks(|c| c.clk_16k_vsys.clone()); | ||
| 167 | match clocks { | ||
| 168 | None => panic!("Clocks have not been initialized"), | ||
| 169 | Some(None) => panic!("Clocks initialized, but clk_16k_vsys not active"), | ||
| 170 | Some(Some(_)) => {} | ||
| 171 | } | ||
| 172 | |||
| 169 | /* RTC reset */ | 173 | /* RTC reset */ |
| 170 | rtc.cr().modify(|_, w| w.swr().set_bit()); | 174 | rtc.cr().modify(|_, w| w.swr().set_bit()); |
| 171 | rtc.cr().modify(|_, w| w.swr().clear_bit()); | 175 | rtc.cr().modify(|_, w| w.swr().clear_bit()); |
diff --git a/src/uart.rs b/src/uart.rs deleted file mode 100644 index 3705959d3..000000000 --- a/src/uart.rs +++ /dev/null | |||
| @@ -1,316 +0,0 @@ | |||
| 1 | //! Minimal polling UART2 bring-up replicating MCUXpresso hello_world ordering. | ||
| 2 | //! WARNING: This is a narrow implementation only for debug console (115200 8N1). | ||
| 3 | |||
| 4 | // TODO(AJM): As of 2025-11-13, we need to do a pass to ensure safety docs | ||
| 5 | // are complete prior to release. | ||
| 6 | #![allow(clippy::missing_safety_doc)] | ||
| 7 | |||
| 8 | use core::cell::RefCell; | ||
| 9 | |||
| 10 | use cortex_m::interrupt::Mutex; | ||
| 11 | use embassy_sync::signal::Signal; | ||
| 12 | |||
| 13 | use crate::pac; | ||
| 14 | |||
| 15 | // svd2rust defines the shared LPUART RegisterBlock under lpuart0; all instances reuse it. | ||
| 16 | type Regs = pac::lpuart0::RegisterBlock; | ||
| 17 | |||
| 18 | // Token-based instance pattern like embassy-imxrt | ||
| 19 | pub trait Instance { | ||
| 20 | fn ptr() -> *const Regs; | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Token for LPUART2 provided by embassy-hal-internal peripherals macro. | ||
| 24 | pub type Lpuart2 = crate::peripherals::LPUART2; | ||
| 25 | impl Instance for crate::peripherals::LPUART2 { | ||
| 26 | #[inline(always)] | ||
| 27 | fn ptr() -> *const Regs { | ||
| 28 | pac::Lpuart2::ptr() | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | // Also implement Instance for the Peri wrapper type | ||
| 33 | impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::LPUART2> { | ||
| 34 | #[inline(always)] | ||
| 35 | fn ptr() -> *const Regs { | ||
| 36 | pac::Lpuart2::ptr() | ||
| 37 | } | ||
| 38 | } | ||
| 39 | |||
| 40 | /// UART configuration (explicit src_hz; no hardcoded frequencies) | ||
| 41 | #[derive(Copy, Clone)] | ||
| 42 | pub struct Config { | ||
| 43 | pub src_hz: u32, | ||
| 44 | pub baud: u32, | ||
| 45 | pub parity: Parity, | ||
| 46 | pub stop_bits: StopBits, | ||
| 47 | } | ||
| 48 | |||
| 49 | #[derive(Copy, Clone)] | ||
| 50 | pub enum Parity { | ||
| 51 | None, | ||
| 52 | Even, | ||
| 53 | Odd, | ||
| 54 | } | ||
| 55 | #[derive(Copy, Clone)] | ||
| 56 | pub enum StopBits { | ||
| 57 | One, | ||
| 58 | Two, | ||
| 59 | } | ||
| 60 | |||
| 61 | impl Config { | ||
| 62 | pub fn new(src_hz: u32) -> Self { | ||
| 63 | Self { | ||
| 64 | src_hz, | ||
| 65 | baud: 115_200, | ||
| 66 | parity: Parity::None, | ||
| 67 | stop_bits: StopBits::One, | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Compute a valid (OSR, SBR) tuple for given source clock and baud. | ||
| 73 | /// Uses a functional fold approach to find the best OSR/SBR combination | ||
| 74 | /// with minimal baud rate error. | ||
| 75 | fn compute_osr_sbr(src_hz: u32, baud: u32) -> (u8, u16) { | ||
| 76 | let (best_osr, best_sbr, _best_err) = (8u32..=32).fold( | ||
| 77 | (16u8, 4u16, u32::MAX), // (best_osr, best_sbr, best_err) | ||
| 78 | |(best_osr, best_sbr, best_err), osr| { | ||
| 79 | let denom = baud.saturating_mul(osr); | ||
| 80 | if denom == 0 { | ||
| 81 | return (best_osr, best_sbr, best_err); | ||
| 82 | } | ||
| 83 | |||
| 84 | let sbr = (src_hz + denom / 2) / denom; // round | ||
| 85 | if sbr == 0 || sbr > 0x1FFF { | ||
| 86 | return (best_osr, best_sbr, best_err); | ||
| 87 | } | ||
| 88 | |||
| 89 | let actual = src_hz / (osr * sbr); | ||
| 90 | let err = actual.abs_diff(baud); | ||
| 91 | |||
| 92 | // Update best if this is better, or same error but higher OSR | ||
| 93 | if err < best_err || (err == best_err && osr as u8 > best_osr) { | ||
| 94 | (osr as u8, sbr as u16, err) | ||
| 95 | } else { | ||
| 96 | (best_osr, best_sbr, best_err) | ||
| 97 | } | ||
| 98 | }, | ||
| 99 | ); | ||
| 100 | (best_osr, best_sbr) | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Minimal UART handle for a specific instance I (store the zero-sized token like embassy) | ||
| 104 | pub struct Uart<I: Instance> { | ||
| 105 | _inst: core::marker::PhantomData<I>, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl<I: Instance> Uart<I> { | ||
| 109 | /// Create and initialize LPUART (reset + config). Clocks and pins must be prepared by the caller. | ||
| 110 | pub fn new(_inst: impl Instance, cfg: Config) -> Self { | ||
| 111 | let l = unsafe { &*I::ptr() }; | ||
| 112 | // 1) software reset pulse | ||
| 113 | l.global().write(|w| w.rst().reset()); | ||
| 114 | cortex_m::asm::delay(3); // Short delay for reset to take effect | ||
| 115 | l.global().write(|w| w.rst().no_effect()); | ||
| 116 | cortex_m::asm::delay(10); // Allow peripheral to stabilize after reset | ||
| 117 | // 2) BAUD | ||
| 118 | let (osr, sbr) = compute_osr_sbr(cfg.src_hz, cfg.baud); | ||
| 119 | l.baud().modify(|_, w| { | ||
| 120 | let w = match cfg.stop_bits { | ||
| 121 | StopBits::One => w.sbns().one(), | ||
| 122 | StopBits::Two => w.sbns().two(), | ||
| 123 | }; | ||
| 124 | // OSR field encodes (osr-1); use raw bits to avoid a long match on all variants | ||
| 125 | let raw_osr = osr.saturating_sub(1); | ||
| 126 | unsafe { w.osr().bits(raw_osr).sbr().bits(sbr) } | ||
| 127 | }); | ||
| 128 | // 3) CTRL baseline and parity | ||
| 129 | l.ctrl().write(|w| { | ||
| 130 | let w = w.ilt().from_stop().idlecfg().idle_2(); | ||
| 131 | let w = match cfg.parity { | ||
| 132 | Parity::None => w.pe().disabled(), | ||
| 133 | Parity::Even => w.pe().enabled().pt().even(), | ||
| 134 | Parity::Odd => w.pe().enabled().pt().odd(), | ||
| 135 | }; | ||
| 136 | w.re().enabled().te().enabled().rie().disabled() | ||
| 137 | }); | ||
| 138 | // 4) FIFOs and WATER: keep it simple for polling; disable FIFOs and set RX watermark to 0 | ||
| 139 | l.fifo().modify(|_, w| { | ||
| 140 | w.txfe() | ||
| 141 | .disabled() | ||
| 142 | .rxfe() | ||
| 143 | .disabled() | ||
| 144 | .txflush() | ||
| 145 | .txfifo_rst() | ||
| 146 | .rxflush() | ||
| 147 | .rxfifo_rst() | ||
| 148 | }); | ||
| 149 | l.water() | ||
| 150 | .modify(|_, w| unsafe { w.txwater().bits(0).rxwater().bits(0) }); | ||
| 151 | Self { | ||
| 152 | _inst: core::marker::PhantomData, | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | /// Enable RX interrupts. The caller must ensure an appropriate IRQ handler is installed. | ||
| 157 | pub unsafe fn enable_rx_interrupts(&self) { | ||
| 158 | let l = &*I::ptr(); | ||
| 159 | l.ctrl().modify(|_, w| w.rie().enabled()); | ||
| 160 | } | ||
| 161 | |||
| 162 | #[inline(never)] | ||
| 163 | pub fn write_byte(&self, b: u8) { | ||
| 164 | let l = unsafe { &*I::ptr() }; | ||
| 165 | // Timeout after ~10ms at 12MHz (assuming 115200 baud, should be plenty) | ||
| 166 | const DATA_OFFSET: usize = 0x1C; // DATA register offset inside LPUART block | ||
| 167 | let data_ptr = unsafe { (I::ptr() as *mut u8).add(DATA_OFFSET) }; | ||
| 168 | for _ in 0..120000 { | ||
| 169 | if l.water().read().txcount().bits() == 0 { | ||
| 170 | unsafe { core::ptr::write_volatile(data_ptr, b) }; | ||
| 171 | return; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | // If timeout, skip the write to avoid hanging | ||
| 175 | } | ||
| 176 | |||
| 177 | #[inline(never)] | ||
| 178 | pub fn write_str_blocking(&self, s: &str) { | ||
| 179 | for &b in s.as_bytes() { | ||
| 180 | if b == b'\n' { | ||
| 181 | self.write_byte(b'\r'); | ||
| 182 | } | ||
| 183 | self.write_byte(b); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | pub fn read_byte_blocking(&self) -> u8 { | ||
| 187 | let l = unsafe { &*I::ptr() }; | ||
| 188 | while !l.stat().read().rdrf().is_rxdata() {} | ||
| 189 | (l.data().read().bits() & 0xFF) as u8 | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | // Simple ring buffer for UART RX data | ||
| 194 | const RX_BUFFER_SIZE: usize = 256; | ||
| 195 | pub struct RingBuffer { | ||
| 196 | buffer: [u8; RX_BUFFER_SIZE], | ||
| 197 | read_idx: usize, | ||
| 198 | write_idx: usize, | ||
| 199 | count: usize, | ||
| 200 | } | ||
| 201 | |||
| 202 | impl Default for RingBuffer { | ||
| 203 | fn default() -> Self { | ||
| 204 | Self::new() | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | impl RingBuffer { | ||
| 209 | pub const fn new() -> Self { | ||
| 210 | Self { | ||
| 211 | buffer: [0; RX_BUFFER_SIZE], | ||
| 212 | read_idx: 0, | ||
| 213 | write_idx: 0, | ||
| 214 | count: 0, | ||
| 215 | } | ||
| 216 | } | ||
| 217 | |||
| 218 | pub fn push(&mut self, data: u8) -> bool { | ||
| 219 | if self.count >= RX_BUFFER_SIZE { | ||
| 220 | return false; // Buffer full | ||
| 221 | } | ||
| 222 | self.buffer[self.write_idx] = data; | ||
| 223 | self.write_idx = (self.write_idx + 1) % RX_BUFFER_SIZE; | ||
| 224 | self.count += 1; | ||
| 225 | true | ||
| 226 | } | ||
| 227 | |||
| 228 | pub fn pop(&mut self) -> Option<u8> { | ||
| 229 | if self.count == 0 { | ||
| 230 | return None; | ||
| 231 | } | ||
| 232 | let data = self.buffer[self.read_idx]; | ||
| 233 | self.read_idx = (self.read_idx + 1) % RX_BUFFER_SIZE; | ||
| 234 | self.count -= 1; | ||
| 235 | Some(data) | ||
| 236 | } | ||
| 237 | |||
| 238 | pub fn is_empty(&self) -> bool { | ||
| 239 | self.count == 0 | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn len(&self) -> usize { | ||
| 243 | self.count | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | // Global RX buffer shared between interrupt handler and UART instance | ||
| 248 | static RX_BUFFER: Mutex<RefCell<RingBuffer>> = Mutex::new(RefCell::new(RingBuffer::new())); | ||
| 249 | static RX_SIGNAL: Signal<embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex, ()> = Signal::new(); | ||
| 250 | |||
| 251 | // Debug counter for interrupt handler calls | ||
| 252 | static mut INTERRUPT_COUNT: u32 = 0; | ||
| 253 | |||
| 254 | impl<I: Instance> Uart<I> { | ||
| 255 | /// Read a byte asynchronously using interrupts | ||
| 256 | pub async fn read_byte_async(&self) -> u8 { | ||
| 257 | loop { | ||
| 258 | // Check if we have data in the buffer | ||
| 259 | let byte = cortex_m::interrupt::free(|cs| { | ||
| 260 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 261 | buffer.pop() | ||
| 262 | }); | ||
| 263 | |||
| 264 | if let Some(byte) = byte { | ||
| 265 | return byte; | ||
| 266 | } | ||
| 267 | |||
| 268 | // Wait for the interrupt signal | ||
| 269 | RX_SIGNAL.wait().await; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | /// Check if there's data available in the RX buffer | ||
| 274 | pub fn rx_data_available(&self) -> bool { | ||
| 275 | cortex_m::interrupt::free(|cs| { | ||
| 276 | let buffer = RX_BUFFER.borrow(cs).borrow(); | ||
| 277 | !buffer.is_empty() | ||
| 278 | }) | ||
| 279 | } | ||
| 280 | |||
| 281 | /// Try to read a byte from RX buffer (non-blocking) | ||
| 282 | pub fn try_read_byte(&self) -> Option<u8> { | ||
| 283 | cortex_m::interrupt::free(|cs| { | ||
| 284 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 285 | buffer.pop() | ||
| 286 | }) | ||
| 287 | } | ||
| 288 | } | ||
| 289 | |||
| 290 | /// Type-level handler for LPUART2 interrupts, compatible with bind_interrupts!. | ||
| 291 | pub struct UartInterruptHandler; | ||
| 292 | |||
| 293 | impl crate::interrupt::typelevel::Handler<crate::interrupt::typelevel::LPUART2> for UartInterruptHandler { | ||
| 294 | unsafe fn on_interrupt() { | ||
| 295 | INTERRUPT_COUNT += 1; | ||
| 296 | |||
| 297 | let lpuart = &*pac::Lpuart2::ptr(); | ||
| 298 | |||
| 299 | // Check if we have RX data | ||
| 300 | if lpuart.stat().read().rdrf().is_rxdata() { | ||
| 301 | // Read the data byte | ||
| 302 | let data = (lpuart.data().read().bits() & 0xFF) as u8; | ||
| 303 | |||
| 304 | // Store in ring buffer | ||
| 305 | cortex_m::interrupt::free(|cs| { | ||
| 306 | let mut buffer = RX_BUFFER.borrow(cs).borrow_mut(); | ||
| 307 | if buffer.push(data) { | ||
| 308 | // Data added successfully, signal waiting tasks | ||
| 309 | RX_SIGNAL.signal(()); | ||
| 310 | } | ||
| 311 | }); | ||
| 312 | } | ||
| 313 | // Always clear any error flags that might cause spurious interrupts | ||
| 314 | let _ = lpuart.stat().read(); | ||
| 315 | } | ||
| 316 | } | ||
