diff options
| author | i509VCB <[email protected]> | 2025-06-23 23:15:09 -0500 |
|---|---|---|
| committer | i509VCB <[email protected]> | 2025-07-06 17:40:10 -0500 |
| commit | e57dffafa5723931dd529afe8e22cba0c9ea09f0 (patch) | |
| tree | ecfad1783ad50f72520d770c6c5fb114d8981932 | |
| parent | 8b65f9cf0f4095080297bf5c3e09334296da8076 (diff) | |
mspm0: add dma driver
| -rw-r--r-- | embassy-mspm0/Cargo.toml | 4 | ||||
| -rw-r--r-- | embassy-mspm0/build.rs | 21 | ||||
| -rw-r--r-- | embassy-mspm0/src/dma.rs | 626 | ||||
| -rw-r--r-- | embassy-mspm0/src/gpio.rs | 18 | ||||
| -rw-r--r-- | embassy-mspm0/src/lib.rs | 115 | ||||
| -rw-r--r-- | examples/mspm0g3507/.cargo/config.toml | 2 | ||||
| -rw-r--r-- | tests/mspm0/Cargo.toml | 1 | ||||
| -rw-r--r-- | tests/mspm0/build.rs | 3 | ||||
| -rw-r--r-- | tests/mspm0/memory_g3519.x | 6 | ||||
| -rw-r--r-- | tests/mspm0/src/bin/dma.rs | 503 | ||||
| -rw-r--r-- | tests/mspm0/src/bin/uart.rs | 5 |
11 files changed, 1279 insertions, 25 deletions
diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml index 6f767a3c0..550b037c1 100644 --- a/embassy-mspm0/Cargo.toml +++ b/embassy-mspm0/Cargo.toml | |||
| @@ -46,14 +46,14 @@ cortex-m = "0.7.6" | |||
| 46 | critical-section = "1.2.0" | 46 | critical-section = "1.2.0" |
| 47 | 47 | ||
| 48 | # mspm0-metapac = { version = "" } | 48 | # mspm0-metapac = { version = "" } |
| 49 | mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-26a6f681eda4ef120e8cb614a1631727c848590f" } | 49 | mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-235158ac2865d8aac3a1eceb2d62026eb12bf38f" } |
| 50 | 50 | ||
| 51 | [build-dependencies] | 51 | [build-dependencies] |
| 52 | proc-macro2 = "1.0.94" | 52 | proc-macro2 = "1.0.94" |
| 53 | quote = "1.0.40" | 53 | quote = "1.0.40" |
| 54 | 54 | ||
| 55 | # mspm0-metapac = { version = "", default-features = false, features = ["metadata"] } | 55 | # mspm0-metapac = { version = "", default-features = false, features = ["metadata"] } |
| 56 | mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-26a6f681eda4ef120e8cb614a1631727c848590f", default-features = false, features = ["metadata"] } | 56 | mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-235158ac2865d8aac3a1eceb2d62026eb12bf38f", default-features = false, features = ["metadata"] } |
| 57 | 57 | ||
| 58 | [features] | 58 | [features] |
| 59 | default = ["rt"] | 59 | default = ["rt"] |
diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs index 6cd62895b..b9ba3aecf 100644 --- a/embassy-mspm0/build.rs +++ b/embassy-mspm0/build.rs | |||
| @@ -67,6 +67,7 @@ fn generate_code() { | |||
| 67 | g.extend(generate_peripheral_instances()); | 67 | g.extend(generate_peripheral_instances()); |
| 68 | g.extend(generate_pin_trait_impls()); | 68 | g.extend(generate_pin_trait_impls()); |
| 69 | g.extend(generate_groups()); | 69 | g.extend(generate_groups()); |
| 70 | g.extend(generate_dma_channel_count()); | ||
| 70 | 71 | ||
| 71 | let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | 72 | let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); |
| 72 | let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); | 73 | let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); |
| @@ -209,6 +210,12 @@ fn generate_groups() -> TokenStream { | |||
| 209 | } | 210 | } |
| 210 | } | 211 | } |
| 211 | 212 | ||
| 213 | fn generate_dma_channel_count() -> TokenStream { | ||
| 214 | let count = METADATA.dma_channels.len(); | ||
| 215 | |||
| 216 | quote! { pub const DMA_CHANNELS: usize = #count; } | ||
| 217 | } | ||
| 218 | |||
| 212 | #[derive(Debug, Clone)] | 219 | #[derive(Debug, Clone)] |
| 213 | struct Singleton { | 220 | struct Singleton { |
| 214 | name: String, | 221 | name: String, |
| @@ -543,8 +550,6 @@ fn generate_peripheral_instances() -> TokenStream { | |||
| 543 | for peripheral in METADATA.peripherals { | 550 | for peripheral in METADATA.peripherals { |
| 544 | let peri = format_ident!("{}", peripheral.name); | 551 | let peri = format_ident!("{}", peripheral.name); |
| 545 | 552 | ||
| 546 | // Will be filled in when uart implementation is finished | ||
| 547 | let _ = peri; | ||
| 548 | let tokens = match peripheral.kind { | 553 | let tokens = match peripheral.kind { |
| 549 | "uart" => Some(quote! { impl_uart_instance!(#peri); }), | 554 | "uart" => Some(quote! { impl_uart_instance!(#peri); }), |
| 550 | _ => None, | 555 | _ => None, |
| @@ -555,6 +560,18 @@ fn generate_peripheral_instances() -> TokenStream { | |||
| 555 | } | 560 | } |
| 556 | } | 561 | } |
| 557 | 562 | ||
| 563 | // DMA channels | ||
| 564 | for dma_channel in METADATA.dma_channels.iter() { | ||
| 565 | let peri = format_ident!("DMA_CH{}", dma_channel.number); | ||
| 566 | let num = dma_channel.number; | ||
| 567 | |||
| 568 | if dma_channel.full { | ||
| 569 | impls.push(quote! { impl_full_dma_channel!(#peri, #num); }); | ||
| 570 | } else { | ||
| 571 | impls.push(quote! { impl_dma_channel!(#peri, #num); }); | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 558 | quote! { | 575 | quote! { |
| 559 | #(#impls)* | 576 | #(#impls)* |
| 560 | } | 577 | } |
diff --git a/embassy-mspm0/src/dma.rs b/embassy-mspm0/src/dma.rs new file mode 100644 index 000000000..66b79709c --- /dev/null +++ b/embassy-mspm0/src/dma.rs | |||
| @@ -0,0 +1,626 @@ | |||
| 1 | //! Direct Memory Access (DMA) | ||
| 2 | |||
| 3 | #![macro_use] | ||
| 4 | |||
| 5 | use core::future::Future; | ||
| 6 | use core::mem; | ||
| 7 | use core::pin::Pin; | ||
| 8 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 9 | use core::task::{Context, Poll}; | ||
| 10 | |||
| 11 | use critical_section::CriticalSection; | ||
| 12 | use embassy_hal_internal::interrupt::InterruptExt; | ||
| 13 | use embassy_hal_internal::{impl_peripheral, PeripheralType}; | ||
| 14 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 15 | use mspm0_metapac::common::{Reg, RW}; | ||
| 16 | use mspm0_metapac::dma::regs; | ||
| 17 | use mspm0_metapac::dma::vals::{self, Autoen, Em, Incr, Preirq, Wdth}; | ||
| 18 | |||
| 19 | use crate::{interrupt, pac, Peri}; | ||
| 20 | |||
| 21 | /// The burst size of a DMA transfer. | ||
| 22 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 23 | pub enum BurstSize { | ||
| 24 | /// The whole block transfer is completed in one transfer without interruption. | ||
| 25 | Complete, | ||
| 26 | |||
| 27 | /// The burst size is 8, after 9 transfers the block transfer is interrupted and the priority | ||
| 28 | /// is reevaluated. | ||
| 29 | _8, | ||
| 30 | |||
| 31 | /// The burst size is 16, after 17 transfers the block transfer is interrupted and the priority | ||
| 32 | /// is reevaluated. | ||
| 33 | _16, | ||
| 34 | |||
| 35 | /// The burst size is 32, after 32 transfers the block transfer is interrupted and the priority | ||
| 36 | /// is reevaluated. | ||
| 37 | _32, | ||
| 38 | } | ||
| 39 | |||
| 40 | /// DMA channel. | ||
| 41 | #[allow(private_bounds)] | ||
| 42 | pub trait Channel: Into<AnyChannel> + PeripheralType {} | ||
| 43 | |||
| 44 | /// Full DMA channel. | ||
| 45 | #[allow(private_bounds)] | ||
| 46 | pub trait FullChannel: Channel + Into<AnyFullChannel> {} | ||
| 47 | |||
| 48 | /// Type-erased DMA channel. | ||
| 49 | pub struct AnyChannel { | ||
| 50 | pub(crate) id: u8, | ||
| 51 | } | ||
| 52 | impl_peripheral!(AnyChannel); | ||
| 53 | |||
| 54 | impl SealedChannel for AnyChannel { | ||
| 55 | fn id(&self) -> u8 { | ||
| 56 | self.id | ||
| 57 | } | ||
| 58 | } | ||
| 59 | impl Channel for AnyChannel {} | ||
| 60 | |||
| 61 | /// Type-erased full DMA channel. | ||
| 62 | pub struct AnyFullChannel { | ||
| 63 | pub(crate) id: u8, | ||
| 64 | } | ||
| 65 | impl_peripheral!(AnyFullChannel); | ||
| 66 | |||
| 67 | impl SealedChannel for AnyFullChannel { | ||
| 68 | fn id(&self) -> u8 { | ||
| 69 | self.id | ||
| 70 | } | ||
| 71 | } | ||
| 72 | impl Channel for AnyFullChannel {} | ||
| 73 | impl FullChannel for AnyFullChannel {} | ||
| 74 | |||
| 75 | impl From<AnyFullChannel> for AnyChannel { | ||
| 76 | fn from(value: AnyFullChannel) -> Self { | ||
| 77 | Self { id: value.id } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | #[allow(private_bounds)] | ||
| 82 | pub trait Word: SealedWord { | ||
| 83 | /// Size in bytes for the width. | ||
| 84 | fn size() -> isize; | ||
| 85 | } | ||
| 86 | |||
| 87 | impl SealedWord for u8 { | ||
| 88 | fn width() -> vals::Wdth { | ||
| 89 | vals::Wdth::BYTE | ||
| 90 | } | ||
| 91 | } | ||
| 92 | impl Word for u8 { | ||
| 93 | fn size() -> isize { | ||
| 94 | 1 | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | impl SealedWord for u16 { | ||
| 99 | fn width() -> vals::Wdth { | ||
| 100 | vals::Wdth::HALF | ||
| 101 | } | ||
| 102 | } | ||
| 103 | impl Word for u16 { | ||
| 104 | fn size() -> isize { | ||
| 105 | 2 | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | impl SealedWord for u32 { | ||
| 110 | fn width() -> vals::Wdth { | ||
| 111 | vals::Wdth::WORD | ||
| 112 | } | ||
| 113 | } | ||
| 114 | impl Word for u32 { | ||
| 115 | fn size() -> isize { | ||
| 116 | 4 | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | impl SealedWord for u64 { | ||
| 121 | fn width() -> vals::Wdth { | ||
| 122 | vals::Wdth::LONG | ||
| 123 | } | ||
| 124 | } | ||
| 125 | impl Word for u64 { | ||
| 126 | fn size() -> isize { | ||
| 127 | 8 | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | // TODO: u128 (LONGLONG) support. G350x does support it, but other parts do not such as C110x. More metadata is | ||
| 132 | // needed to properly enable this. | ||
| 133 | // impl SealedWord for u128 { | ||
| 134 | // fn width() -> vals::Wdth { | ||
| 135 | // vals::Wdth::LONGLONG | ||
| 136 | // } | ||
| 137 | // } | ||
| 138 | // impl Word for u128 { | ||
| 139 | // fn size() -> isize { | ||
| 140 | // 16 | ||
| 141 | // } | ||
| 142 | // } | ||
| 143 | |||
| 144 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
| 145 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 146 | pub enum Error { | ||
| 147 | /// The DMA transfer is too large. | ||
| 148 | /// | ||
| 149 | /// The hardware limits the DMA to 16384 transfers per channel at a time. This means that transferring | ||
| 150 | /// 16384 `u8` and 16384 `u64` are equivalent, since the DMA must copy 16384 values. | ||
| 151 | TooManyTransfers, | ||
| 152 | } | ||
| 153 | |||
| 154 | /// DMA transfer mode for basic channels. | ||
| 155 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
| 156 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 157 | pub enum TransferMode { | ||
| 158 | /// Each DMA trigger will transfer a single value. | ||
| 159 | Single, | ||
| 160 | |||
| 161 | /// Each DMA trigger will transfer the complete block with one trigger. | ||
| 162 | Block, | ||
| 163 | } | ||
| 164 | |||
| 165 | /// DMA transfer options. | ||
| 166 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
| 167 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 168 | #[non_exhaustive] | ||
| 169 | pub struct TransferOptions { | ||
| 170 | /// DMA transfer mode. | ||
| 171 | pub mode: TransferMode, | ||
| 172 | // TODO: Read and write stride. | ||
| 173 | } | ||
| 174 | |||
| 175 | impl Default for TransferOptions { | ||
| 176 | fn default() -> Self { | ||
| 177 | Self { | ||
| 178 | mode: TransferMode::Single, | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | /// DMA transfer. | ||
| 184 | #[must_use = "futures do nothing unless you `.await` or poll them"] | ||
| 185 | pub struct Transfer<'a> { | ||
| 186 | channel: Peri<'a, AnyChannel>, | ||
| 187 | } | ||
| 188 | |||
| 189 | impl<'a> Transfer<'a> { | ||
| 190 | /// Software trigger source. | ||
| 191 | /// | ||
| 192 | /// Using this trigger source means that a transfer will start immediately rather than waiting for | ||
| 193 | /// a hardware event. This can be useful if you want to do a DMA accelerated memcpy. | ||
| 194 | pub const SOFTWARE_TRIGGER: u8 = 0; | ||
| 195 | |||
| 196 | /// Create a new read DMA transfer. | ||
| 197 | pub unsafe fn new_read<SW: Word, DW: Word>( | ||
| 198 | channel: Peri<'a, impl Channel>, | ||
| 199 | trigger_source: u8, | ||
| 200 | src: *mut SW, | ||
| 201 | dst: &'a mut [DW], | ||
| 202 | options: TransferOptions, | ||
| 203 | ) -> Result<Self, Error> { | ||
| 204 | Self::new_read_raw(channel, trigger_source, src, dst, options) | ||
| 205 | } | ||
| 206 | |||
| 207 | /// Create a new read DMA transfer, using raw pointers. | ||
| 208 | pub unsafe fn new_read_raw<SW: Word, DW: Word>( | ||
| 209 | channel: Peri<'a, impl Channel>, | ||
| 210 | trigger_source: u8, | ||
| 211 | src: *mut SW, | ||
| 212 | dst: *mut [DW], | ||
| 213 | options: TransferOptions, | ||
| 214 | ) -> Result<Self, Error> { | ||
| 215 | verify_transfer::<DW>(dst)?; | ||
| 216 | |||
| 217 | let channel = channel.into(); | ||
| 218 | channel.configure( | ||
| 219 | trigger_source, | ||
| 220 | src.cast(), | ||
| 221 | SW::width(), | ||
| 222 | dst.cast(), | ||
| 223 | DW::width(), | ||
| 224 | dst.len() as u16, | ||
| 225 | false, | ||
| 226 | true, | ||
| 227 | options, | ||
| 228 | ); | ||
| 229 | channel.start(); | ||
| 230 | |||
| 231 | Ok(Self { channel }) | ||
| 232 | } | ||
| 233 | |||
| 234 | /// Create a new write DMA transfer. | ||
| 235 | pub unsafe fn new_write<SW: Word, DW: Word>( | ||
| 236 | channel: Peri<'a, impl Channel>, | ||
| 237 | trigger_source: u8, | ||
| 238 | src: &'a [SW], | ||
| 239 | dst: *mut DW, | ||
| 240 | options: TransferOptions, | ||
| 241 | ) -> Result<Self, Error> { | ||
| 242 | Self::new_write_raw(channel, trigger_source, src, dst, options) | ||
| 243 | } | ||
| 244 | |||
| 245 | /// Create a new write DMA transfer, using raw pointers. | ||
| 246 | pub unsafe fn new_write_raw<SW: Word, DW: Word>( | ||
| 247 | channel: Peri<'a, impl Channel>, | ||
| 248 | trigger_source: u8, | ||
| 249 | src: *const [SW], | ||
| 250 | dst: *mut DW, | ||
| 251 | options: TransferOptions, | ||
| 252 | ) -> Result<Self, Error> { | ||
| 253 | verify_transfer::<SW>(src)?; | ||
| 254 | |||
| 255 | let channel = channel.into(); | ||
| 256 | channel.configure( | ||
| 257 | trigger_source, | ||
| 258 | src.cast(), | ||
| 259 | SW::width(), | ||
| 260 | dst.cast(), | ||
| 261 | DW::width(), | ||
| 262 | src.len() as u16, | ||
| 263 | true, | ||
| 264 | false, | ||
| 265 | options, | ||
| 266 | ); | ||
| 267 | channel.start(); | ||
| 268 | |||
| 269 | Ok(Self { channel }) | ||
| 270 | } | ||
| 271 | |||
| 272 | // TODO: Copy between slices. | ||
| 273 | |||
| 274 | /// Request the transfer to resume. | ||
| 275 | pub fn resume(&mut self) { | ||
| 276 | self.channel.resume(); | ||
| 277 | } | ||
| 278 | |||
| 279 | /// Request the transfer to pause, keeping the existing configuration for this channel. | ||
| 280 | /// To restart the transfer, call [`start`](Self::start) again. | ||
| 281 | /// | ||
| 282 | /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. | ||
| 283 | pub fn request_pause(&mut self) { | ||
| 284 | self.channel.request_pause(); | ||
| 285 | } | ||
| 286 | |||
| 287 | /// Return whether this transfer is still running. | ||
| 288 | /// | ||
| 289 | /// If this returns [`false`], it can be because either the transfer finished, or | ||
| 290 | /// it was requested to stop early with [`request_stop`]. | ||
| 291 | pub fn is_running(&mut self) -> bool { | ||
| 292 | self.channel.is_running() | ||
| 293 | } | ||
| 294 | |||
| 295 | /// Blocking wait until the transfer finishes. | ||
| 296 | pub fn blocking_wait(mut self) { | ||
| 297 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 298 | compiler_fence(Ordering::SeqCst); | ||
| 299 | |||
| 300 | while self.is_running() {} | ||
| 301 | |||
| 302 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 303 | compiler_fence(Ordering::SeqCst); | ||
| 304 | |||
| 305 | // Prevent drop from being called since we ran to completion (drop will try to pause). | ||
| 306 | mem::forget(self); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | impl<'a> Unpin for Transfer<'a> {} | ||
| 311 | impl<'a> Future for Transfer<'a> { | ||
| 312 | type Output = (); | ||
| 313 | |||
| 314 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||
| 315 | let state: &ChannelState = &STATE[self.channel.id as usize]; | ||
| 316 | |||
| 317 | state.waker.register(cx.waker()); | ||
| 318 | |||
| 319 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 320 | compiler_fence(Ordering::SeqCst); | ||
| 321 | |||
| 322 | if self.channel.is_running() { | ||
| 323 | Poll::Pending | ||
| 324 | } else { | ||
| 325 | Poll::Ready(()) | ||
| 326 | } | ||
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | impl<'a> Drop for Transfer<'a> { | ||
| 331 | fn drop(&mut self) { | ||
| 332 | self.channel.request_pause(); | ||
| 333 | while self.is_running() {} | ||
| 334 | |||
| 335 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 336 | compiler_fence(Ordering::SeqCst); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | // impl details | ||
| 341 | |||
| 342 | fn verify_transfer<W: Word>(ptr: *const [W]) -> Result<(), Error> { | ||
| 343 | if ptr.len() > (u16::MAX as usize) { | ||
| 344 | return Err(Error::TooManyTransfers); | ||
| 345 | } | ||
| 346 | |||
| 347 | // TODO: Stride checks | ||
| 348 | |||
| 349 | Ok(()) | ||
| 350 | } | ||
| 351 | |||
| 352 | fn convert_burst_size(value: BurstSize) -> vals::Burstsz { | ||
| 353 | match value { | ||
| 354 | BurstSize::Complete => vals::Burstsz::INFINITI, | ||
| 355 | BurstSize::_8 => vals::Burstsz::BURST_8, | ||
| 356 | BurstSize::_16 => vals::Burstsz::BURST_16, | ||
| 357 | BurstSize::_32 => vals::Burstsz::BURST_32, | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | fn convert_mode(mode: TransferMode) -> vals::Tm { | ||
| 362 | match mode { | ||
| 363 | TransferMode::Single => vals::Tm::SINGLE, | ||
| 364 | TransferMode::Block => vals::Tm::BLOCK, | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | const CHANNEL_COUNT: usize = crate::_generated::DMA_CHANNELS; | ||
| 369 | static STATE: [ChannelState; CHANNEL_COUNT] = [const { ChannelState::new() }; CHANNEL_COUNT]; | ||
| 370 | |||
| 371 | struct ChannelState { | ||
| 372 | waker: AtomicWaker, | ||
| 373 | } | ||
| 374 | |||
| 375 | impl ChannelState { | ||
| 376 | const fn new() -> Self { | ||
| 377 | Self { | ||
| 378 | waker: AtomicWaker::new(), | ||
| 379 | } | ||
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | /// SAFETY: Must only be called once. | ||
| 384 | /// | ||
| 385 | /// Changing the burst size mid transfer may have some odd behavior. | ||
| 386 | pub(crate) unsafe fn init(_cs: CriticalSection, burst_size: BurstSize, round_robin: bool) { | ||
| 387 | pac::DMA.prio().modify(|prio| { | ||
| 388 | prio.set_burstsz(convert_burst_size(burst_size)); | ||
| 389 | prio.set_roundrobin(round_robin); | ||
| 390 | }); | ||
| 391 | pac::DMA.int_event(0).imask().modify(|w| { | ||
| 392 | w.set_dataerr(true); | ||
| 393 | w.set_addrerr(true); | ||
| 394 | }); | ||
| 395 | |||
| 396 | interrupt::DMA.enable(); | ||
| 397 | } | ||
| 398 | |||
| 399 | pub(crate) trait SealedWord { | ||
| 400 | fn width() -> vals::Wdth; | ||
| 401 | } | ||
| 402 | |||
| 403 | pub(crate) trait SealedChannel { | ||
| 404 | fn id(&self) -> u8; | ||
| 405 | |||
| 406 | #[inline] | ||
| 407 | fn tctl(&self) -> Reg<regs::Tctl, RW> { | ||
| 408 | pac::DMA.trig(self.id() as usize).tctl() | ||
| 409 | } | ||
| 410 | |||
| 411 | #[inline] | ||
| 412 | fn ctl(&self) -> Reg<regs::Ctl, RW> { | ||
| 413 | pac::DMA.chan(self.id() as usize).ctl() | ||
| 414 | } | ||
| 415 | |||
| 416 | #[inline] | ||
| 417 | fn sa(&self) -> Reg<u32, RW> { | ||
| 418 | pac::DMA.chan(self.id() as usize).sa() | ||
| 419 | } | ||
| 420 | |||
| 421 | #[inline] | ||
| 422 | fn da(&self) -> Reg<u32, RW> { | ||
| 423 | pac::DMA.chan(self.id() as usize).da() | ||
| 424 | } | ||
| 425 | |||
| 426 | #[inline] | ||
| 427 | fn sz(&self) -> Reg<regs::Sz, RW> { | ||
| 428 | pac::DMA.chan(self.id() as usize).sz() | ||
| 429 | } | ||
| 430 | |||
| 431 | #[inline] | ||
| 432 | fn mask_interrupt(&self, enable: bool) { | ||
| 433 | // Enabling interrupts is an RMW operation. | ||
| 434 | critical_section::with(|_cs| { | ||
| 435 | pac::DMA.int_event(0).imask().modify(|w| { | ||
| 436 | w.set_ch(self.id() as usize, enable); | ||
| 437 | }); | ||
| 438 | }) | ||
| 439 | } | ||
| 440 | |||
| 441 | /// # Safety | ||
| 442 | /// | ||
| 443 | /// - `src` must be valid for the lifetime of the transfer. | ||
| 444 | /// - `dst` must be valid for the lifetime of the transfer. | ||
| 445 | unsafe fn configure( | ||
| 446 | &self, | ||
| 447 | trigger_sel: u8, | ||
| 448 | src: *const u32, | ||
| 449 | src_wdth: Wdth, | ||
| 450 | dst: *const u32, | ||
| 451 | dst_wdth: Wdth, | ||
| 452 | transfer_count: u16, | ||
| 453 | increment_src: bool, | ||
| 454 | increment_dst: bool, | ||
| 455 | options: TransferOptions, | ||
| 456 | ) { | ||
| 457 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 458 | compiler_fence(Ordering::SeqCst); | ||
| 459 | |||
| 460 | self.ctl().modify(|w| { | ||
| 461 | // SLAU 5.2.5: | ||
| 462 | // "The DMATSEL bits should be modified only when the DMACTLx.DMAEN bit is | ||
| 463 | // 0; otherwise, unpredictable DMA triggers can occur." | ||
| 464 | // | ||
| 465 | // We also want to stop any transfers before setup. | ||
| 466 | w.set_en(false); | ||
| 467 | w.set_req(false); | ||
| 468 | |||
| 469 | // Not every part supports auto enable, so force its value to 0. | ||
| 470 | w.set_autoen(Autoen::NONE); | ||
| 471 | w.set_preirq(Preirq::PREIRQ_DISABLE); | ||
| 472 | w.set_srcwdth(src_wdth); | ||
| 473 | w.set_dstwdth(dst_wdth); | ||
| 474 | w.set_srcincr(if increment_src { | ||
| 475 | Incr::INCREMENT | ||
| 476 | } else { | ||
| 477 | Incr::UNCHANGED | ||
| 478 | }); | ||
| 479 | w.set_dstincr(if increment_dst { | ||
| 480 | Incr::INCREMENT | ||
| 481 | } else { | ||
| 482 | Incr::UNCHANGED | ||
| 483 | }); | ||
| 484 | |||
| 485 | w.set_em(Em::NORMAL); | ||
| 486 | // Single and block will clear the enable bit when the transfers finish. | ||
| 487 | w.set_tm(convert_mode(options.mode)); | ||
| 488 | }); | ||
| 489 | |||
| 490 | self.tctl().write(|w| { | ||
| 491 | w.set_tsel(trigger_sel); | ||
| 492 | // Basic channels do not implement cross triggering. | ||
| 493 | w.set_tint(vals::Tint::EXTERNAL); | ||
| 494 | }); | ||
| 495 | |||
| 496 | self.sz().write(|w| { | ||
| 497 | w.set_size(transfer_count); | ||
| 498 | }); | ||
| 499 | |||
| 500 | self.sa().write_value(src as u32); | ||
| 501 | self.da().write_value(dst as u32); | ||
| 502 | |||
| 503 | // Enable the channel. | ||
| 504 | self.ctl().modify(|w| { | ||
| 505 | // FIXME: Why did putting set_req later fix some transfers | ||
| 506 | w.set_en(true); | ||
| 507 | w.set_req(true); | ||
| 508 | }); | ||
| 509 | } | ||
| 510 | |||
| 511 | fn start(&self) { | ||
| 512 | self.mask_interrupt(true); | ||
| 513 | |||
| 514 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 515 | compiler_fence(Ordering::SeqCst); | ||
| 516 | |||
| 517 | // Request the DMA transfer to start. | ||
| 518 | self.ctl().modify(|w| { | ||
| 519 | w.set_req(true); | ||
| 520 | }); | ||
| 521 | } | ||
| 522 | |||
| 523 | fn resume(&self) { | ||
| 524 | self.mask_interrupt(true); | ||
| 525 | |||
| 526 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 527 | compiler_fence(Ordering::SeqCst); | ||
| 528 | |||
| 529 | self.ctl().modify(|w| { | ||
| 530 | // w.set_en(true); | ||
| 531 | w.set_req(true); | ||
| 532 | }); | ||
| 533 | } | ||
| 534 | |||
| 535 | fn request_pause(&self) { | ||
| 536 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 537 | compiler_fence(Ordering::SeqCst); | ||
| 538 | |||
| 539 | // Stop the transfer. | ||
| 540 | // | ||
| 541 | // SLAU846 5.2.6: | ||
| 542 | // "A DMA block transfer in progress can be stopped by clearing the DMAEN bit" | ||
| 543 | self.ctl().modify(|w| { | ||
| 544 | // w.set_en(false); | ||
| 545 | w.set_req(false); | ||
| 546 | }); | ||
| 547 | } | ||
| 548 | |||
| 549 | fn is_running(&self) -> bool { | ||
| 550 | // "Subsequent reads and writes cannot be moved ahead of preceding reads." | ||
| 551 | compiler_fence(Ordering::SeqCst); | ||
| 552 | |||
| 553 | let ctl = self.ctl().read(); | ||
| 554 | |||
| 555 | // Is the transfer requested? | ||
| 556 | ctl.req() | ||
| 557 | // Is the channel enabled? | ||
| 558 | && ctl.en() | ||
| 559 | } | ||
| 560 | } | ||
| 561 | |||
| 562 | macro_rules! impl_dma_channel { | ||
| 563 | ($instance: ident, $num: expr) => { | ||
| 564 | impl crate::dma::SealedChannel for crate::peripherals::$instance { | ||
| 565 | fn id(&self) -> u8 { | ||
| 566 | $num | ||
| 567 | } | ||
| 568 | } | ||
| 569 | |||
| 570 | impl From<crate::peripherals::$instance> for crate::dma::AnyChannel { | ||
| 571 | fn from(value: crate::peripherals::$instance) -> Self { | ||
| 572 | use crate::dma::SealedChannel; | ||
| 573 | |||
| 574 | Self { id: value.id() } | ||
| 575 | } | ||
| 576 | } | ||
| 577 | |||
| 578 | impl crate::dma::Channel for crate::peripherals::$instance {} | ||
| 579 | }; | ||
| 580 | } | ||
| 581 | |||
| 582 | // C1104 has no full DMA channels. | ||
| 583 | #[allow(unused_macros)] | ||
| 584 | macro_rules! impl_full_dma_channel { | ||
| 585 | ($instance: ident, $num: expr) => { | ||
| 586 | impl_dma_channel!($instance, $num); | ||
| 587 | |||
| 588 | impl From<crate::peripherals::$instance> for crate::dma::AnyFullChannel { | ||
| 589 | fn from(value: crate::peripherals::$instance) -> Self { | ||
| 590 | use crate::dma::SealedChannel; | ||
| 591 | |||
| 592 | Self { id: value.id() } | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | impl crate::dma::FullChannel for crate::peripherals::$instance {} | ||
| 597 | }; | ||
| 598 | } | ||
| 599 | |||
| 600 | #[cfg(feature = "rt")] | ||
| 601 | #[interrupt] | ||
| 602 | fn DMA() { | ||
| 603 | use crate::BitIter; | ||
| 604 | |||
| 605 | let events = pac::DMA.int_event(0); | ||
| 606 | let mis = events.mis().read(); | ||
| 607 | |||
| 608 | // TODO: Handle DATAERR and ADDRERR? However we do not know which channel causes an error. | ||
| 609 | if mis.dataerr() { | ||
| 610 | panic!("DMA data error"); | ||
| 611 | } else if mis.addrerr() { | ||
| 612 | panic!("DMA address error") | ||
| 613 | } | ||
| 614 | |||
| 615 | // Ignore preirq interrupts (values greater than 16). | ||
| 616 | for i in BitIter(mis.0 & 0x0000_FFFF) { | ||
| 617 | if let Some(state) = STATE.get(i as usize) { | ||
| 618 | state.waker.wake(); | ||
| 619 | |||
| 620 | // Notify the future that the counter size hit zero | ||
| 621 | events.imask().modify(|w| { | ||
| 622 | w.set_ch(i as usize, false); | ||
| 623 | }); | ||
| 624 | } | ||
| 625 | } | ||
| 626 | } | ||
diff --git a/embassy-mspm0/src/gpio.rs b/embassy-mspm0/src/gpio.rs index 738d51928..e6380e819 100644 --- a/embassy-mspm0/src/gpio.rs +++ b/embassy-mspm0/src/gpio.rs | |||
| @@ -1090,7 +1090,9 @@ pub(crate) fn init(gpio: gpio::Gpio) { | |||
| 1090 | 1090 | ||
| 1091 | #[cfg(feature = "rt")] | 1091 | #[cfg(feature = "rt")] |
| 1092 | fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { | 1092 | fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { |
| 1093 | use crate::BitIter; | ||
| 1093 | // Only consider pins which have interrupts unmasked. | 1094 | // Only consider pins which have interrupts unmasked. |
| 1095 | |||
| 1094 | let bits = gpio.cpu_int().mis().read().0; | 1096 | let bits = gpio.cpu_int().mis().read().0; |
| 1095 | 1097 | ||
| 1096 | for i in BitIter(bits) { | 1098 | for i in BitIter(bits) { |
| @@ -1103,22 +1105,6 @@ fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { | |||
| 1103 | } | 1105 | } |
| 1104 | } | 1106 | } |
| 1105 | 1107 | ||
| 1106 | struct BitIter(u32); | ||
| 1107 | |||
| 1108 | impl Iterator for BitIter { | ||
| 1109 | type Item = u32; | ||
| 1110 | |||
| 1111 | fn next(&mut self) -> Option<Self::Item> { | ||
| 1112 | match self.0.trailing_zeros() { | ||
| 1113 | 32 => None, | ||
| 1114 | b => { | ||
| 1115 | self.0 &= !(1 << b); | ||
| 1116 | Some(b) | ||
| 1117 | } | ||
| 1118 | } | ||
| 1119 | } | ||
| 1120 | } | ||
| 1121 | |||
| 1122 | // C110x and L110x have a dedicated interrupts just for GPIOA. | 1108 | // C110x and L110x have a dedicated interrupts just for GPIOA. |
| 1123 | // | 1109 | // |
| 1124 | // These chips do not have a GROUP1 interrupt. | 1110 | // These chips do not have a GROUP1 interrupt. |
diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs index 7ff60e946..bb8d91403 100644 --- a/embassy-mspm0/src/lib.rs +++ b/embassy-mspm0/src/lib.rs | |||
| @@ -13,6 +13,7 @@ pub(crate) mod fmt; | |||
| 13 | // This must be declared early as well for | 13 | // This must be declared early as well for |
| 14 | mod macros; | 14 | mod macros; |
| 15 | 15 | ||
| 16 | pub mod dma; | ||
| 16 | pub mod gpio; | 17 | pub mod gpio; |
| 17 | pub mod timer; | 18 | pub mod timer; |
| 18 | pub mod uart; | 19 | pub mod uart; |
| @@ -59,22 +60,106 @@ pub(crate) use mspm0_metapac as pac; | |||
| 59 | 60 | ||
| 60 | pub use crate::_generated::interrupt; | 61 | pub use crate::_generated::interrupt; |
| 61 | 62 | ||
| 63 | /// Macro to bind interrupts to handlers. | ||
| 64 | /// | ||
| 65 | /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) | ||
| 66 | /// and implements the right [`Binding`]s for it. You can pass this struct to drivers to | ||
| 67 | /// prove at compile-time that the right interrupts have been bound. | ||
| 68 | /// | ||
| 69 | /// Example of how to bind one interrupt: | ||
| 70 | /// | ||
| 71 | /// ```rust,ignore | ||
| 72 | /// use embassy_nrf::{bind_interrupts, spim, peripherals}; | ||
| 73 | /// | ||
| 74 | /// bind_interrupts!( | ||
| 75 | /// /// Binds the SPIM3 interrupt. | ||
| 76 | /// struct Irqs { | ||
| 77 | /// SPIM3 => spim::InterruptHandler<peripherals::SPI3>; | ||
| 78 | /// } | ||
| 79 | /// ); | ||
| 80 | /// ``` | ||
| 81 | /// | ||
| 82 | /// Example of how to bind multiple interrupts in a single macro invocation: | ||
| 83 | /// | ||
| 84 | /// ```rust,ignore | ||
| 85 | /// use embassy_nrf::{bind_interrupts, spim, twim, peripherals}; | ||
| 86 | /// | ||
| 87 | /// bind_interrupts!(struct Irqs { | ||
| 88 | /// SPIM3 => spim::InterruptHandler<peripherals::SPI3>; | ||
| 89 | /// TWISPI0 => twim::InterruptHandler<peripherals::TWISPI0>; | ||
| 90 | /// }); | ||
| 91 | /// ``` | ||
| 92 | |||
| 93 | // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. | ||
| 94 | #[macro_export] | ||
| 95 | macro_rules! bind_interrupts { | ||
| 96 | ($(#[$attr:meta])* $vis:vis struct $name:ident { | ||
| 97 | $( | ||
| 98 | $(#[cfg($cond_irq:meta)])? | ||
| 99 | $irq:ident => $( | ||
| 100 | $(#[cfg($cond_handler:meta)])? | ||
| 101 | $handler:ty | ||
| 102 | ),*; | ||
| 103 | )* | ||
| 104 | }) => { | ||
| 105 | #[derive(Copy, Clone)] | ||
| 106 | $(#[$attr])* | ||
| 107 | $vis struct $name; | ||
| 108 | |||
| 109 | $( | ||
| 110 | #[allow(non_snake_case)] | ||
| 111 | #[no_mangle] | ||
| 112 | $(#[cfg($cond_irq)])? | ||
| 113 | unsafe extern "C" fn $irq() { | ||
| 114 | $( | ||
| 115 | $(#[cfg($cond_handler)])? | ||
| 116 | <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); | ||
| 117 | |||
| 118 | )* | ||
| 119 | } | ||
| 120 | |||
| 121 | $(#[cfg($cond_irq)])? | ||
| 122 | $crate::bind_interrupts!(@inner | ||
| 123 | $( | ||
| 124 | $(#[cfg($cond_handler)])? | ||
| 125 | unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} | ||
| 126 | )* | ||
| 127 | ); | ||
| 128 | )* | ||
| 129 | }; | ||
| 130 | (@inner $($t:tt)*) => { | ||
| 131 | $($t)* | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 62 | /// `embassy-mspm0` global configuration. | 135 | /// `embassy-mspm0` global configuration. |
| 63 | #[non_exhaustive] | 136 | #[non_exhaustive] |
| 64 | #[derive(Clone, Copy)] | 137 | #[derive(Clone, Copy)] |
| 65 | pub struct Config { | 138 | pub struct Config { |
| 66 | // TODO | 139 | // TODO: OSC configuration. |
| 140 | /// The size of DMA block transfer burst. | ||
| 141 | /// | ||
| 142 | /// If this is set to a value | ||
| 143 | pub dma_burst_size: dma::BurstSize, | ||
| 144 | |||
| 145 | /// Whether the DMA channels are used in a fixed priority or a round robin fashion. | ||
| 146 | /// | ||
| 147 | /// If [`false`], the DMA priorities are fixed. | ||
| 148 | /// | ||
| 149 | /// If [`true`], after a channel finishes a transfer it becomes the lowest priority. | ||
| 150 | pub dma_round_robin: bool, | ||
| 67 | } | 151 | } |
| 68 | 152 | ||
| 69 | impl Default for Config { | 153 | impl Default for Config { |
| 70 | fn default() -> Self { | 154 | fn default() -> Self { |
| 71 | Self { | 155 | Self { |
| 72 | // TODO | 156 | dma_burst_size: dma::BurstSize::Complete, |
| 157 | dma_round_robin: false, | ||
| 73 | } | 158 | } |
| 74 | } | 159 | } |
| 75 | } | 160 | } |
| 76 | 161 | ||
| 77 | pub fn init(_config: Config) -> Peripherals { | 162 | pub fn init(config: Config) -> Peripherals { |
| 78 | critical_section::with(|cs| { | 163 | critical_section::with(|cs| { |
| 79 | let peripherals = Peripherals::take_with_cs(cs); | 164 | let peripherals = Peripherals::take_with_cs(cs); |
| 80 | 165 | ||
| @@ -112,9 +197,33 @@ pub fn init(_config: Config) -> Peripherals { | |||
| 112 | crate::interrupt::typelevel::GPIOA::enable(); | 197 | crate::interrupt::typelevel::GPIOA::enable(); |
| 113 | } | 198 | } |
| 114 | 199 | ||
| 200 | // SAFETY: Peripherals::take_with_cs will only be run once or panic. | ||
| 201 | unsafe { dma::init(cs, config.dma_burst_size, config.dma_round_robin) }; | ||
| 202 | |||
| 115 | #[cfg(feature = "_time-driver")] | 203 | #[cfg(feature = "_time-driver")] |
| 116 | time_driver::init(cs); | 204 | time_driver::init(cs); |
| 117 | 205 | ||
| 118 | peripherals | 206 | peripherals |
| 119 | }) | 207 | }) |
| 120 | } | 208 | } |
| 209 | |||
| 210 | pub(crate) mod sealed { | ||
| 211 | #[allow(dead_code)] | ||
| 212 | pub trait Sealed {} | ||
| 213 | } | ||
| 214 | |||
| 215 | struct BitIter(u32); | ||
| 216 | |||
| 217 | impl Iterator for BitIter { | ||
| 218 | type Item = u32; | ||
| 219 | |||
| 220 | fn next(&mut self) -> Option<Self::Item> { | ||
| 221 | match self.0.trailing_zeros() { | ||
| 222 | 32 => None, | ||
| 223 | b => { | ||
| 224 | self.0 &= !(1 << b); | ||
| 225 | Some(b) | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | } | ||
diff --git a/examples/mspm0g3507/.cargo/config.toml b/examples/mspm0g3507/.cargo/config.toml index 34c720cdd..e711afaf2 100644 --- a/examples/mspm0g3507/.cargo/config.toml +++ b/examples/mspm0g3507/.cargo/config.toml | |||
| @@ -6,4 +6,4 @@ runner = "probe-rs run --chip MSPM0G3507 --protocol=swd" | |||
| 6 | target = "thumbv6m-none-eabi" | 6 | target = "thumbv6m-none-eabi" |
| 7 | 7 | ||
| 8 | [env] | 8 | [env] |
| 9 | DEFMT_LOG = "debug" | 9 | DEFMT_LOG = "trace" |
diff --git a/tests/mspm0/Cargo.toml b/tests/mspm0/Cargo.toml index 386536bee..1c6f7d1cd 100644 --- a/tests/mspm0/Cargo.toml +++ b/tests/mspm0/Cargo.toml | |||
| @@ -6,6 +6,7 @@ license = "MIT OR Apache-2.0" | |||
| 6 | 6 | ||
| 7 | [features] | 7 | [features] |
| 8 | mspm0g3507 = [ "embassy-mspm0/mspm0g3507pm" ] | 8 | mspm0g3507 = [ "embassy-mspm0/mspm0g3507pm" ] |
| 9 | mspm0g3519 = [ "embassy-mspm0/mspm0g3519pz" ] | ||
| 9 | 10 | ||
| 10 | [dependencies] | 11 | [dependencies] |
| 11 | teleprobe-meta = "1.1" | 12 | teleprobe-meta = "1.1" |
diff --git a/tests/mspm0/build.rs b/tests/mspm0/build.rs index 0b58fb9e9..43a9ac04f 100644 --- a/tests/mspm0/build.rs +++ b/tests/mspm0/build.rs | |||
| @@ -8,6 +8,9 @@ fn main() -> Result<(), Box<dyn Error>> { | |||
| 8 | #[cfg(feature = "mspm0g3507")] | 8 | #[cfg(feature = "mspm0g3507")] |
| 9 | let memory_x = include_bytes!("memory_g3507.x"); | 9 | let memory_x = include_bytes!("memory_g3507.x"); |
| 10 | 10 | ||
| 11 | #[cfg(feature = "mspm0g3519")] | ||
| 12 | let memory_x = include_bytes!("memory_g3519.x"); | ||
| 13 | |||
| 11 | fs::write(out.join("memory.x"), memory_x).unwrap(); | 14 | fs::write(out.join("memory.x"), memory_x).unwrap(); |
| 12 | 15 | ||
| 13 | println!("cargo:rustc-link-search={}", out.display()); | 16 | println!("cargo:rustc-link-search={}", out.display()); |
diff --git a/tests/mspm0/memory_g3519.x b/tests/mspm0/memory_g3519.x new file mode 100644 index 000000000..d62f10360 --- /dev/null +++ b/tests/mspm0/memory_g3519.x | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 128K | ||
| 4 | /* Select non-parity range of SRAM due to SRAM_ERR_01 errata in SLAZ758 */ | ||
| 5 | RAM : ORIGIN = 0x20200000, LENGTH = 64K | ||
| 6 | } | ||
diff --git a/tests/mspm0/src/bin/dma.rs b/tests/mspm0/src/bin/dma.rs new file mode 100644 index 000000000..6fd973a18 --- /dev/null +++ b/tests/mspm0/src/bin/dma.rs | |||
| @@ -0,0 +1,503 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | #[cfg(feature = "mspm0g3507")] | ||
| 5 | teleprobe_meta::target!(b"lp-mspm0g3507"); | ||
| 6 | |||
| 7 | #[cfg(feature = "mspm0g3519")] | ||
| 8 | teleprobe_meta::target!(b"lp-mspm0g3519"); | ||
| 9 | |||
| 10 | use core::slice; | ||
| 11 | |||
| 12 | use defmt::{assert, assert_eq, *}; | ||
| 13 | use embassy_executor::Spawner; | ||
| 14 | use embassy_mspm0::dma::{Channel, Transfer, TransferMode, TransferOptions, Word}; | ||
| 15 | use embassy_mspm0::Peri; | ||
| 16 | use {defmt_rtt as _, panic_probe as _}; | ||
| 17 | |||
| 18 | #[embassy_executor::main] | ||
| 19 | async fn main(_spawner: Spawner) { | ||
| 20 | let mut p = embassy_mspm0::init(Default::default()); | ||
| 21 | info!("Hello World!"); | ||
| 22 | |||
| 23 | { | ||
| 24 | info!("Single u8 read (blocking)"); | ||
| 25 | single_read(p.DMA_CH0.reborrow(), 0x41_u8); | ||
| 26 | |||
| 27 | info!("Single u16 read (blocking)"); | ||
| 28 | single_read(p.DMA_CH0.reborrow(), 0xFF41_u16); | ||
| 29 | |||
| 30 | info!("Single u32 read (blocking)"); | ||
| 31 | single_read(p.DMA_CH0.reborrow(), 0xFFEE_FF41_u32); | ||
| 32 | |||
| 33 | info!("Single u64 read (blocking)"); | ||
| 34 | single_read(p.DMA_CH0.reborrow(), 0x0011_2233_FFEE_FF41_u64); | ||
| 35 | } | ||
| 36 | |||
| 37 | // Widening transfers | ||
| 38 | { | ||
| 39 | info!("Single u8 read to u16"); | ||
| 40 | widening_single_read::<u8, u16>(p.DMA_CH0.reborrow(), 0x41); | ||
| 41 | |||
| 42 | info!("Single u8 read to u32"); | ||
| 43 | widening_single_read::<u8, u32>(p.DMA_CH0.reborrow(), 0x43); | ||
| 44 | |||
| 45 | info!("Single u8 read to u64"); | ||
| 46 | widening_single_read::<u8, u64>(p.DMA_CH0.reborrow(), 0x47); | ||
| 47 | |||
| 48 | info!("Single u16 read to u32"); | ||
| 49 | widening_single_read::<u16, u32>(p.DMA_CH0.reborrow(), 0xAE43); | ||
| 50 | |||
| 51 | info!("Single u16 read to u64"); | ||
| 52 | widening_single_read::<u16, u64>(p.DMA_CH0.reborrow(), 0xAF47); | ||
| 53 | |||
| 54 | info!("Single u32 read to u64"); | ||
| 55 | widening_single_read::<u32, u64>(p.DMA_CH0.reborrow(), 0xDEAD_AF47); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Narrowing transfers. | ||
| 59 | { | ||
| 60 | info!("Single u16 read to u8"); | ||
| 61 | narrowing_single_read::<u16, u8>(p.DMA_CH0.reborrow(), 0x4142); | ||
| 62 | |||
| 63 | info!("Single u32 read to u8"); | ||
| 64 | narrowing_single_read::<u32, u8>(p.DMA_CH0.reborrow(), 0x4142_2414); | ||
| 65 | |||
| 66 | info!("Single u64 read to u8"); | ||
| 67 | narrowing_single_read::<u64, u8>(p.DMA_CH0.reborrow(), 0x4142_2414_5153_7776); | ||
| 68 | |||
| 69 | info!("Single u32 read to u16"); | ||
| 70 | narrowing_single_read::<u32, u16>(p.DMA_CH0.reborrow(), 0x4142_2414); | ||
| 71 | |||
| 72 | info!("Single u64 read to u16"); | ||
| 73 | narrowing_single_read::<u64, u16>(p.DMA_CH0.reborrow(), 0x4142_2414_5153_7776); | ||
| 74 | |||
| 75 | info!("Single u64 read to u32"); | ||
| 76 | narrowing_single_read::<u64, u32>(p.DMA_CH0.reborrow(), 0x4142_2414_5153_7776); | ||
| 77 | } | ||
| 78 | |||
| 79 | { | ||
| 80 | info!("Single u8 read (async)"); | ||
| 81 | async_single_read(p.DMA_CH0.reborrow(), 0x42_u8).await; | ||
| 82 | |||
| 83 | info!("Single u16 read (async)"); | ||
| 84 | async_single_read(p.DMA_CH0.reborrow(), 0xAE42_u16).await; | ||
| 85 | |||
| 86 | info!("Single u32 read (async)"); | ||
| 87 | async_single_read(p.DMA_CH0.reborrow(), 0xFE44_1500_u32).await; | ||
| 88 | |||
| 89 | info!("Single u64 read (async)"); | ||
| 90 | async_single_read(p.DMA_CH0.reborrow(), 0x8F7F_6F5F_4F3F_2F1F_u64).await; | ||
| 91 | } | ||
| 92 | |||
| 93 | { | ||
| 94 | info!("Multiple u8 reads (blocking)"); | ||
| 95 | block_read::<_, 16>(p.DMA_CH0.reborrow(), 0x98_u8); | ||
| 96 | |||
| 97 | info!("Multiple u16 reads (blocking)"); | ||
| 98 | block_read::<_, 2>(p.DMA_CH0.reborrow(), 0x9801_u16); | ||
| 99 | |||
| 100 | info!("Multiple u32 reads (blocking)"); | ||
| 101 | block_read::<_, 4>(p.DMA_CH0.reborrow(), 0x9821_9801_u32); | ||
| 102 | |||
| 103 | info!("Multiple u64 reads (blocking)"); | ||
| 104 | block_read::<_, 4>(p.DMA_CH0.reborrow(), 0xABCD_EF01_2345_6789_u64); | ||
| 105 | } | ||
| 106 | |||
| 107 | { | ||
| 108 | info!("Multiple u8 reads (async)"); | ||
| 109 | async_block_read::<_, 8>(p.DMA_CH0.reborrow(), 0x86_u8).await; | ||
| 110 | |||
| 111 | info!("Multiple u16 reads (async)"); | ||
| 112 | async_block_read::<_, 6>(p.DMA_CH0.reborrow(), 0x7777_u16).await; | ||
| 113 | |||
| 114 | info!("Multiple u32 reads (async)"); | ||
| 115 | async_block_read::<_, 3>(p.DMA_CH0.reborrow(), 0xA5A5_A5A5_u32).await; | ||
| 116 | |||
| 117 | info!("Multiple u64 reads (async)"); | ||
| 118 | async_block_read::<_, 14>(p.DMA_CH0.reborrow(), 0x5A5A_5A5A_A5A5_A5A5_u64).await; | ||
| 119 | } | ||
| 120 | |||
| 121 | // Intentionally skip testing multiple reads in single transfer mode. | ||
| 122 | // | ||
| 123 | // If the destination length is greater than 1 and single transfer mode is used then two transfers | ||
| 124 | // are performed in a trigger. Similarly with any other length of destination above 2, only 2 transfers | ||
| 125 | // are performed. Issuing another trigger (resume) results in no further progress. More than likely | ||
| 126 | // the test does not work due to some combination of a hardware bug and the datasheet being unclear | ||
| 127 | // regarding what ends a software trigger. | ||
| 128 | // | ||
| 129 | // However this case works fine with a hardware trigger (such as the ADC hardware trigger). | ||
| 130 | |||
| 131 | { | ||
| 132 | info!("Single u8 write (blocking)"); | ||
| 133 | single_write(p.DMA_CH0.reborrow(), 0x41_u8); | ||
| 134 | |||
| 135 | info!("Single u16 write (blocking)"); | ||
| 136 | single_write(p.DMA_CH0.reborrow(), 0x4142_u16); | ||
| 137 | |||
| 138 | info!("Single u32 write (blocking)"); | ||
| 139 | single_write(p.DMA_CH0.reborrow(), 0x4142_4344_u32); | ||
| 140 | |||
| 141 | info!("Single u64 write (blocking)"); | ||
| 142 | single_write(p.DMA_CH0.reborrow(), 0x4142_4344_4546_4748_u64); | ||
| 143 | } | ||
| 144 | |||
| 145 | { | ||
| 146 | info!("Single u8 write (async)"); | ||
| 147 | async_single_write(p.DMA_CH0.reborrow(), 0xAA_u8).await; | ||
| 148 | |||
| 149 | info!("Single u16 write (async)"); | ||
| 150 | async_single_write(p.DMA_CH0.reborrow(), 0xBBBB_u16).await; | ||
| 151 | |||
| 152 | info!("Single u32 write (async)"); | ||
| 153 | async_single_write(p.DMA_CH0.reborrow(), 0xCCCC_CCCC_u32).await; | ||
| 154 | |||
| 155 | info!("Single u64 write (async)"); | ||
| 156 | async_single_write(p.DMA_CH0.reborrow(), 0xDDDD_DDDD_DDDD_DDDD_u64).await; | ||
| 157 | } | ||
| 158 | |||
| 159 | { | ||
| 160 | info!("Multiple u8 writes (blocking)"); | ||
| 161 | block_write(p.DMA_CH0.reborrow(), &[0xFF_u8, 0x7F, 0x3F, 0x1F]); | ||
| 162 | |||
| 163 | info!("Multiple u16 writes (blocking)"); | ||
| 164 | block_write(p.DMA_CH0.reborrow(), &[0xFFFF_u16, 0xFF7F, 0xFF3F, 0xFF1F]); | ||
| 165 | |||
| 166 | info!("Multiple u32 writes (blocking)"); | ||
| 167 | block_write( | ||
| 168 | p.DMA_CH0.reborrow(), | ||
| 169 | &[0xFF00_00FF_u32, 0xFF00_007F, 0x0000_FF3F, 0xFF1F_0000], | ||
| 170 | ); | ||
| 171 | |||
| 172 | info!("Multiple u64 writes (blocking)"); | ||
| 173 | block_write( | ||
| 174 | p.DMA_CH0.reborrow(), | ||
| 175 | &[ | ||
| 176 | 0xFF00_0000_0000_00FF_u64, | ||
| 177 | 0x0000_FF00_007F_0000, | ||
| 178 | 0x0000_FF3F_0000_0000, | ||
| 179 | 0xFF1F_0000_1111_837A, | ||
| 180 | ], | ||
| 181 | ); | ||
| 182 | } | ||
| 183 | |||
| 184 | { | ||
| 185 | info!("Multiple u8 writes (async)"); | ||
| 186 | async_block_write(p.DMA_CH0.reborrow(), &[0u8, 1, 2, 3]).await; | ||
| 187 | |||
| 188 | info!("Multiple u16 writes (async)"); | ||
| 189 | async_block_write(p.DMA_CH0.reborrow(), &[0x9801u16, 0x9802, 0x9803, 0x9800, 0x9000]).await; | ||
| 190 | |||
| 191 | info!("Multiple u32 writes (async)"); | ||
| 192 | async_block_write(p.DMA_CH0.reborrow(), &[0x9801_ABCDu32, 0xFFAC_9802, 0xDEAD_9803]).await; | ||
| 193 | |||
| 194 | info!("Multiple u64 writes (async)"); | ||
| 195 | async_block_write( | ||
| 196 | p.DMA_CH0.reborrow(), | ||
| 197 | &[ | ||
| 198 | 0xA55A_1111_3333_5555_u64, | ||
| 199 | 0x1111_A55A_3333_5555, | ||
| 200 | 0x5555_A55A_3333_1111, | ||
| 201 | 0x01234_5678_89AB_CDEF, | ||
| 202 | ], | ||
| 203 | ) | ||
| 204 | .await; | ||
| 205 | } | ||
| 206 | |||
| 207 | // TODO: Mixed byte and word transfers. | ||
| 208 | |||
| 209 | info!("Test OK"); | ||
| 210 | cortex_m::asm::bkpt(); | ||
| 211 | } | ||
| 212 | |||
| 213 | fn single_read<W: Word + Copy + Default + Eq + defmt::Format>(mut channel: Peri<'_, impl Channel>, mut src: W) { | ||
| 214 | let options = TransferOptions::default(); | ||
| 215 | let mut dst = W::default(); | ||
| 216 | |||
| 217 | // SAFETY: src and dst outlive the transfer. | ||
| 218 | let transfer = unsafe { | ||
| 219 | unwrap!(Transfer::new_read( | ||
| 220 | channel.reborrow(), | ||
| 221 | Transfer::SOFTWARE_TRIGGER, | ||
| 222 | &mut src, | ||
| 223 | slice::from_mut(&mut dst), | ||
| 224 | options, | ||
| 225 | )) | ||
| 226 | }; | ||
| 227 | transfer.blocking_wait(); | ||
| 228 | |||
| 229 | assert_eq!(src, dst); | ||
| 230 | } | ||
| 231 | |||
| 232 | async fn async_single_read<W: Word + Copy + Default + Eq + defmt::Format>( | ||
| 233 | mut channel: Peri<'_, impl Channel>, | ||
| 234 | mut src: W, | ||
| 235 | ) { | ||
| 236 | let options = TransferOptions::default(); | ||
| 237 | let mut dst = W::default(); | ||
| 238 | |||
| 239 | // SAFETY: src and dst outlive the transfer. | ||
| 240 | let transfer = unsafe { | ||
| 241 | unwrap!(Transfer::new_read( | ||
| 242 | channel.reborrow(), | ||
| 243 | Transfer::SOFTWARE_TRIGGER, | ||
| 244 | &mut src, | ||
| 245 | slice::from_mut(&mut dst), | ||
| 246 | options, | ||
| 247 | )) | ||
| 248 | }; | ||
| 249 | transfer.await; | ||
| 250 | |||
| 251 | assert_eq!(src, dst); | ||
| 252 | } | ||
| 253 | |||
| 254 | fn block_read<W: Word + Copy + Default + Eq + defmt::Format, const N: usize>( | ||
| 255 | mut channel: Peri<'_, impl Channel>, | ||
| 256 | mut src: W, | ||
| 257 | ) { | ||
| 258 | let mut options = TransferOptions::default(); | ||
| 259 | // Complete the entire transfer. | ||
| 260 | options.mode = TransferMode::Block; | ||
| 261 | |||
| 262 | let mut dst = [W::default(); N]; | ||
| 263 | |||
| 264 | // SAFETY: src and dst outlive the transfer. | ||
| 265 | let transfer = unsafe { | ||
| 266 | unwrap!(Transfer::new_read( | ||
| 267 | channel.reborrow(), | ||
| 268 | Transfer::SOFTWARE_TRIGGER, | ||
| 269 | &mut src, | ||
| 270 | &mut dst[..], | ||
| 271 | options, | ||
| 272 | )) | ||
| 273 | }; | ||
| 274 | transfer.blocking_wait(); | ||
| 275 | |||
| 276 | assert_eq!(dst, [src; N]); | ||
| 277 | } | ||
| 278 | |||
| 279 | async fn async_block_read<W: Word + Copy + Default + Eq + defmt::Format, const N: usize>( | ||
| 280 | mut channel: Peri<'_, impl Channel>, | ||
| 281 | mut src: W, | ||
| 282 | ) { | ||
| 283 | let mut options = TransferOptions::default(); | ||
| 284 | // Complete the entire transfer. | ||
| 285 | options.mode = TransferMode::Block; | ||
| 286 | |||
| 287 | let mut dst = [W::default(); N]; | ||
| 288 | |||
| 289 | // SAFETY: src and dst outlive the transfer. | ||
| 290 | let transfer = unsafe { | ||
| 291 | unwrap!(Transfer::new_read( | ||
| 292 | channel.reborrow(), | ||
| 293 | Transfer::SOFTWARE_TRIGGER, | ||
| 294 | &mut src, | ||
| 295 | &mut dst[..], | ||
| 296 | options, | ||
| 297 | )) | ||
| 298 | }; | ||
| 299 | transfer.await; | ||
| 300 | |||
| 301 | assert_eq!(dst, [src; N]); | ||
| 302 | } | ||
| 303 | |||
| 304 | fn single_write<W: Word + Default + Eq + defmt::Format>(mut channel: Peri<'_, impl Channel>, src: W) { | ||
| 305 | let options = TransferOptions::default(); | ||
| 306 | let mut dst = W::default(); | ||
| 307 | |||
| 308 | // SAFETY: src and dst outlive the transfer. | ||
| 309 | let transfer = unsafe { | ||
| 310 | unwrap!(Transfer::new_write( | ||
| 311 | channel.reborrow(), | ||
| 312 | Transfer::SOFTWARE_TRIGGER, | ||
| 313 | slice::from_ref(&src), | ||
| 314 | &mut dst, | ||
| 315 | options, | ||
| 316 | )) | ||
| 317 | }; | ||
| 318 | transfer.blocking_wait(); | ||
| 319 | |||
| 320 | assert_eq!(src, dst); | ||
| 321 | } | ||
| 322 | |||
| 323 | async fn async_single_write<W: Word + Default + Eq + defmt::Format>(mut channel: Peri<'_, impl Channel>, src: W) { | ||
| 324 | let options = TransferOptions::default(); | ||
| 325 | let mut dst = W::default(); | ||
| 326 | |||
| 327 | // SAFETY: src and dst outlive the transfer. | ||
| 328 | let transfer = unsafe { | ||
| 329 | unwrap!(Transfer::new_write( | ||
| 330 | channel.reborrow(), | ||
| 331 | Transfer::SOFTWARE_TRIGGER, | ||
| 332 | slice::from_ref(&src), | ||
| 333 | &mut dst, | ||
| 334 | options, | ||
| 335 | )) | ||
| 336 | }; | ||
| 337 | transfer.await; | ||
| 338 | |||
| 339 | assert_eq!(src, dst); | ||
| 340 | } | ||
| 341 | |||
| 342 | fn block_write<W: Word + Default + Eq + defmt::Format>(mut channel: Peri<'_, impl Channel>, src: &[W]) { | ||
| 343 | let mut options = TransferOptions::default(); | ||
| 344 | // Complete the entire transfer. | ||
| 345 | options.mode = TransferMode::Block; | ||
| 346 | |||
| 347 | let mut dst = W::default(); | ||
| 348 | |||
| 349 | // Starting from 1 because a zero length transfer does nothing. | ||
| 350 | for i in 1..src.len() { | ||
| 351 | info!("-> {} write(s)", i); | ||
| 352 | |||
| 353 | // SAFETY: src and dst outlive the transfer. | ||
| 354 | let transfer = unsafe { | ||
| 355 | unwrap!(Transfer::new_write( | ||
| 356 | channel.reborrow(), | ||
| 357 | Transfer::SOFTWARE_TRIGGER, | ||
| 358 | &src[..i], | ||
| 359 | &mut dst, | ||
| 360 | options, | ||
| 361 | )) | ||
| 362 | }; | ||
| 363 | transfer.blocking_wait(); | ||
| 364 | |||
| 365 | // The result will be the last value written. | ||
| 366 | assert_eq!(dst, src[i - 1]); | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | async fn async_block_write<W: Word + Default + Eq + defmt::Format>(mut channel: Peri<'_, impl Channel>, src: &[W]) { | ||
| 371 | let mut options = TransferOptions::default(); | ||
| 372 | // Complete the entire transfer. | ||
| 373 | options.mode = TransferMode::Block; | ||
| 374 | |||
| 375 | let mut dst = W::default(); | ||
| 376 | |||
| 377 | // Starting from 1 because a zero length transfer does nothing. | ||
| 378 | for i in 1..src.len() { | ||
| 379 | info!("-> {} write(s)", i); | ||
| 380 | // SAFETY: src and dst outlive the transfer. | ||
| 381 | let transfer = unsafe { | ||
| 382 | unwrap!(Transfer::new_write( | ||
| 383 | channel.reborrow(), | ||
| 384 | Transfer::SOFTWARE_TRIGGER, | ||
| 385 | &src[..i], | ||
| 386 | &mut dst, | ||
| 387 | options, | ||
| 388 | )) | ||
| 389 | }; | ||
| 390 | transfer.await; | ||
| 391 | |||
| 392 | // The result will be the last value written. | ||
| 393 | assert_eq!(dst, src[i - 1]); | ||
| 394 | } | ||
| 395 | } | ||
| 396 | |||
| 397 | /// [`single_read`], but testing when the destination is wider than the source. | ||
| 398 | /// | ||
| 399 | /// The MSPM0 DMA states that the upper bytes when the destination is longer than the source are zeroed. | ||
| 400 | /// This matches the behavior in Rust for all unsigned integer types. | ||
| 401 | fn widening_single_read<SW, DW>(mut channel: Peri<'_, impl Channel>, mut src: SW) | ||
| 402 | where | ||
| 403 | SW: Word + Copy + Default + Eq + defmt::Format, | ||
| 404 | DW: Word + Copy + Default + Eq + defmt::Format + From<SW>, | ||
| 405 | { | ||
| 406 | assert!( | ||
| 407 | DW::size() > SW::size(), | ||
| 408 | "This test only works when the destination is larger than the source" | ||
| 409 | ); | ||
| 410 | |||
| 411 | let options = TransferOptions::default(); | ||
| 412 | let mut dst = DW::default(); | ||
| 413 | |||
| 414 | // SAFETY: src and dst outlive the transfer. | ||
| 415 | let transfer = unsafe { | ||
| 416 | unwrap!(Transfer::new_read( | ||
| 417 | channel.reborrow(), | ||
| 418 | Transfer::SOFTWARE_TRIGGER, | ||
| 419 | &mut src, | ||
| 420 | slice::from_mut(&mut dst), | ||
| 421 | options, | ||
| 422 | )) | ||
| 423 | }; | ||
| 424 | transfer.blocking_wait(); | ||
| 425 | |||
| 426 | assert_eq!(DW::from(src), dst); | ||
| 427 | } | ||
| 428 | |||
| 429 | /// [`single_read`], but testing when the destination is narrower than the source. | ||
| 430 | /// | ||
| 431 | /// The MSPM0 DMA states that the upper bytes when the source is longer than the destination are dropped. | ||
| 432 | /// This matches the behavior in Rust for all unsigned integer types. | ||
| 433 | fn narrowing_single_read<SW, DW>(mut channel: Peri<'_, impl Channel>, mut src: SW) | ||
| 434 | where | ||
| 435 | SW: Word + Copy + Default + Eq + defmt::Format + From<DW>, | ||
| 436 | DW: Word + Copy + Default + Eq + defmt::Format + Narrow<SW>, | ||
| 437 | { | ||
| 438 | assert!( | ||
| 439 | SW::size() > DW::size(), | ||
| 440 | "This test only works when the source is larger than the destination" | ||
| 441 | ); | ||
| 442 | |||
| 443 | let options = TransferOptions::default(); | ||
| 444 | let mut dst = DW::default(); | ||
| 445 | |||
| 446 | // SAFETY: src and dst outlive the transfer. | ||
| 447 | let transfer = unsafe { | ||
| 448 | unwrap!(Transfer::new_read( | ||
| 449 | channel.reborrow(), | ||
| 450 | Transfer::SOFTWARE_TRIGGER, | ||
| 451 | &mut src, | ||
| 452 | slice::from_mut(&mut dst), | ||
| 453 | options, | ||
| 454 | )) | ||
| 455 | }; | ||
| 456 | transfer.blocking_wait(); | ||
| 457 | |||
| 458 | // The expected value is the source value masked by the maximum destination value. | ||
| 459 | // This is effectively `src as DW as SW` to drop the upper byte(s). | ||
| 460 | let expect = SW::from(DW::narrow(src)); | ||
| 461 | assert_eq!(expect, dst.into()); | ||
| 462 | } | ||
| 463 | |||
| 464 | /// A pseudo `as` trait to allow downcasting integer types (TryFrom could fail). | ||
| 465 | trait Narrow<T> { | ||
| 466 | fn narrow(value: T) -> Self; | ||
| 467 | } | ||
| 468 | |||
| 469 | impl Narrow<u16> for u8 { | ||
| 470 | fn narrow(value: u16) -> Self { | ||
| 471 | value as u8 | ||
| 472 | } | ||
| 473 | } | ||
| 474 | |||
| 475 | impl Narrow<u32> for u8 { | ||
| 476 | fn narrow(value: u32) -> Self { | ||
| 477 | value as u8 | ||
| 478 | } | ||
| 479 | } | ||
| 480 | |||
| 481 | impl Narrow<u64> for u8 { | ||
| 482 | fn narrow(value: u64) -> Self { | ||
| 483 | value as u8 | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | impl Narrow<u32> for u16 { | ||
| 488 | fn narrow(value: u32) -> Self { | ||
| 489 | value as u16 | ||
| 490 | } | ||
| 491 | } | ||
| 492 | |||
| 493 | impl Narrow<u64> for u16 { | ||
| 494 | fn narrow(value: u64) -> Self { | ||
| 495 | value as u16 | ||
| 496 | } | ||
| 497 | } | ||
| 498 | |||
| 499 | impl Narrow<u64> for u32 { | ||
| 500 | fn narrow(value: u64) -> Self { | ||
| 501 | value as u32 | ||
| 502 | } | ||
| 503 | } | ||
diff --git a/tests/mspm0/src/bin/uart.rs b/tests/mspm0/src/bin/uart.rs index 458129d44..916ce0d4b 100644 --- a/tests/mspm0/src/bin/uart.rs +++ b/tests/mspm0/src/bin/uart.rs | |||
| @@ -4,6 +4,9 @@ | |||
| 4 | #[cfg(feature = "mspm0g3507")] | 4 | #[cfg(feature = "mspm0g3507")] |
| 5 | teleprobe_meta::target!(b"lp-mspm0g3507"); | 5 | teleprobe_meta::target!(b"lp-mspm0g3507"); |
| 6 | 6 | ||
| 7 | #[cfg(feature = "mspm0g3519")] | ||
| 8 | teleprobe_meta::target!(b"lp-mspm0g3519"); | ||
| 9 | |||
| 7 | use defmt::{assert_eq, unwrap, *}; | 10 | use defmt::{assert_eq, unwrap, *}; |
| 8 | use embassy_executor::Spawner; | 11 | use embassy_executor::Spawner; |
| 9 | use embassy_mspm0::mode::Blocking; | 12 | use embassy_mspm0::mode::Blocking; |
| @@ -23,7 +26,7 @@ async fn main(_spawner: Spawner) { | |||
| 23 | 26 | ||
| 24 | // TODO: Allow creating a looped-back UART (so pins are not needed). | 27 | // TODO: Allow creating a looped-back UART (so pins are not needed). |
| 25 | // Do not select default UART since the virtual COM port is attached to UART0. | 28 | // Do not select default UART since the virtual COM port is attached to UART0. |
| 26 | #[cfg(feature = "mspm0g3507")] | 29 | #[cfg(any(feature = "mspm0g3507", feature = "mspm0g3519"))] |
| 27 | let (mut tx, mut rx, mut uart) = (p.PA8, p.PA9, p.UART1); | 30 | let (mut tx, mut rx, mut uart) = (p.PA8, p.PA9, p.UART1); |
| 28 | 31 | ||
| 29 | const MFCLK_BUAD_RATES: &[u32] = &[1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]; | 32 | const MFCLK_BUAD_RATES: &[u32] = &[1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]; |
