diff options
| author | Christian Perez Llamas <[email protected]> | 2022-11-09 19:14:43 +0100 |
|---|---|---|
| committer | Christian Perez Llamas <[email protected]> | 2022-11-09 19:19:01 +0100 |
| commit | cecd77938c694ff2bad2a259ff64f2f468dcb04a (patch) | |
| tree | ad4cca7a642cc0a8a2fd2858538560d0a2ba55b1 /embassy-nrf/src/i2s.rs | |
| parent | 059610a8de49ff2d38311f343d3d1a6f8d90a720 (diff) | |
Draft: Initial support for I2S with a working example.
Co-authored-by: @brainstorm <[email protected]>
Diffstat (limited to 'embassy-nrf/src/i2s.rs')
| -rw-r--r-- | embassy-nrf/src/i2s.rs | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs new file mode 100644 index 000000000..0199ac615 --- /dev/null +++ b/embassy-nrf/src/i2s.rs | |||
| @@ -0,0 +1,403 @@ | |||
| 1 | #![macro_use] | ||
| 2 | |||
| 3 | //! I2S | ||
| 4 | |||
| 5 | use core::future::poll_fn; | ||
| 6 | use core::sync::atomic::{compiler_fence, Ordering}; | ||
| 7 | use core::task::Poll; | ||
| 8 | |||
| 9 | use embassy_hal_common::drop::OnDrop; | ||
| 10 | use embassy_hal_common::{into_ref, PeripheralRef}; | ||
| 11 | use pac::i2s::config::mcken; | ||
| 12 | |||
| 13 | use crate::{pac, Peripheral}; | ||
| 14 | use crate::interrupt::{Interrupt, InterruptExt}; | ||
| 15 | use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; | ||
| 16 | use crate::gpio::sealed::Pin as _; | ||
| 17 | |||
| 18 | // TODO: Define those in lib.rs somewhere else | ||
| 19 | // | ||
| 20 | // I2S EasyDMA MAXCNT bit length = 14 | ||
| 21 | const MAX_DMA_MAXCNT: u32 = 1 << 14; | ||
| 22 | |||
| 23 | // Limits for Easy DMA - it can only read from data ram | ||
| 24 | pub const SRAM_LOWER: usize = 0x2000_0000; | ||
| 25 | pub const SRAM_UPPER: usize = 0x3000_0000; | ||
| 26 | |||
| 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 29 | #[non_exhaustive] | ||
| 30 | pub enum Error { | ||
| 31 | BufferTooLong, | ||
| 32 | BufferZeroLength, | ||
| 33 | DMABufferNotInDataMemory, | ||
| 34 | BufferMisaligned, | ||
| 35 | // TODO: add other error variants. | ||
| 36 | } | ||
| 37 | |||
| 38 | #[derive(Clone)] | ||
| 39 | #[non_exhaustive] | ||
| 40 | pub struct Config { | ||
| 41 | pub ratio: Ratio, | ||
| 42 | pub sample_width: SampleWidth, | ||
| 43 | pub align: Align, | ||
| 44 | pub format: Format, | ||
| 45 | pub channels: Channels, | ||
| 46 | } | ||
| 47 | |||
| 48 | impl Default for Config { | ||
| 49 | fn default() -> Self { | ||
| 50 | Self { | ||
| 51 | ratio: Ratio::_32x, | ||
| 52 | sample_width: SampleWidth::_16bit, | ||
| 53 | align: Align::Left, | ||
| 54 | format: Format::I2S, | ||
| 55 | channels: Channels::Stereo, | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | /// MCK / LRCK ratio. | ||
| 61 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 62 | pub enum Ratio { | ||
| 63 | _32x, | ||
| 64 | _48x, | ||
| 65 | _64x, | ||
| 66 | _96x, | ||
| 67 | _128x, | ||
| 68 | _192x, | ||
| 69 | _256x, | ||
| 70 | _384x, | ||
| 71 | _512x, | ||
| 72 | } | ||
| 73 | |||
| 74 | impl From<Ratio> for u8 { | ||
| 75 | fn from(variant: Ratio) -> Self { | ||
| 76 | variant as _ | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 81 | pub enum SampleWidth { | ||
| 82 | _8bit, | ||
| 83 | _16bit, | ||
| 84 | _24bit, | ||
| 85 | } | ||
| 86 | |||
| 87 | impl From<SampleWidth> for u8 { | ||
| 88 | fn from(variant: SampleWidth) -> Self { | ||
| 89 | variant as _ | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Alignment of sample within a frame. | ||
| 94 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 95 | pub enum Align { | ||
| 96 | Left, | ||
| 97 | Right, | ||
| 98 | } | ||
| 99 | |||
| 100 | impl From<Align> for bool { | ||
| 101 | fn from(variant: Align) -> Self { | ||
| 102 | match variant { | ||
| 103 | Align::Left => false, | ||
| 104 | Align::Right => true, | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | /// Frame format. | ||
| 110 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 111 | pub enum Format { | ||
| 112 | I2S, | ||
| 113 | Aligned, | ||
| 114 | } | ||
| 115 | |||
| 116 | impl From<Format> for bool { | ||
| 117 | fn from(variant: Format) -> Self { | ||
| 118 | match variant { | ||
| 119 | Format::I2S => false, | ||
| 120 | Format::Aligned => true, | ||
| 121 | } | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | /// Enable channels. | ||
| 126 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 127 | pub enum Channels { | ||
| 128 | Stereo, | ||
| 129 | Left, | ||
| 130 | Right, | ||
| 131 | } | ||
| 132 | |||
| 133 | impl From<Channels> for u8 { | ||
| 134 | fn from(variant: Channels) -> Self { | ||
| 135 | variant as _ | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | /// I2S Mode | ||
| 140 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 141 | pub enum Mode { | ||
| 142 | Controller, | ||
| 143 | Peripheral, | ||
| 144 | } | ||
| 145 | |||
| 146 | // /// Master clock generator frequency. | ||
| 147 | // #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 148 | // pub enum MckFreq { | ||
| 149 | // _32MDiv8 = 0x20000000, | ||
| 150 | // _32MDiv10 = 0x18000000, | ||
| 151 | // _32MDiv11 = 0x16000000, | ||
| 152 | // _32MDiv15 = 0x11000000, | ||
| 153 | // _32MDiv16 = 0x10000000, | ||
| 154 | // _32MDiv21 = 0x0C000000, | ||
| 155 | // _32MDiv23 = 0x0B000000, | ||
| 156 | // _32MDiv30 = 0x08800000, | ||
| 157 | // _32MDiv31 = 0x08400000, | ||
| 158 | // _32MDiv32 = 0x08000000, | ||
| 159 | // _32MDiv42 = 0x06000000, | ||
| 160 | // _32MDiv63 = 0x04100000, | ||
| 161 | // _32MDiv125 = 0x020C0000, | ||
| 162 | // } | ||
| 163 | |||
| 164 | |||
| 165 | /// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. | ||
| 166 | /// | ||
| 167 | /// For more details about EasyDMA, consult the module documentation. | ||
| 168 | pub struct I2s<'d, T: Instance> { | ||
| 169 | output: I2sOutput<'d, T>, | ||
| 170 | input: I2sInput<'d, T>, | ||
| 171 | } | ||
| 172 | |||
| 173 | /// Transmitter interface to the UARTE peripheral obtained | ||
| 174 | /// via [Uarte]::split. | ||
| 175 | pub struct I2sOutput<'d, T: Instance> { | ||
| 176 | _p: PeripheralRef<'d, T>, | ||
| 177 | } | ||
| 178 | |||
| 179 | /// Receiver interface to the UARTE peripheral obtained | ||
| 180 | /// via [Uarte]::split. | ||
| 181 | pub struct I2sInput<'d, T: Instance> { | ||
| 182 | _p: PeripheralRef<'d, T>, | ||
| 183 | } | ||
| 184 | |||
| 185 | impl<'d, T: Instance> I2s<'d, T> { | ||
| 186 | /// Create a new I2S | ||
| 187 | pub fn new( | ||
| 188 | i2s: impl Peripheral<P = T> + 'd, | ||
| 189 | // irq: impl Peripheral<P = T::Interrupt> + 'd, | ||
| 190 | mck: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 191 | sck: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 192 | lrck: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 193 | sdin: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 194 | sdout: impl Peripheral<P = impl GpioPin> + 'd, | ||
| 195 | config: Config, | ||
| 196 | ) -> Self { | ||
| 197 | into_ref!(mck, sck, lrck, sdin, sdout); | ||
| 198 | Self::new_inner( | ||
| 199 | i2s, | ||
| 200 | // irq, | ||
| 201 | mck.map_into(), sck.map_into(), lrck.map_into(), sdin.map_into(), sdout.map_into(), config) | ||
| 202 | } | ||
| 203 | |||
| 204 | fn new_inner( | ||
| 205 | i2s: impl Peripheral<P = T> + 'd, | ||
| 206 | // irq: impl Peripheral<P = T::Interrupt> + 'd, | ||
| 207 | mck: PeripheralRef<'d, AnyPin>, | ||
| 208 | sck: PeripheralRef<'d, AnyPin>, | ||
| 209 | lrck: PeripheralRef<'d, AnyPin>, | ||
| 210 | sdin: PeripheralRef<'d, AnyPin>, | ||
| 211 | sdout: PeripheralRef<'d, AnyPin>, | ||
| 212 | config: Config, | ||
| 213 | ) -> Self { | ||
| 214 | into_ref!( | ||
| 215 | i2s, | ||
| 216 | // irq, | ||
| 217 | mck, sck, lrck, sdin, sdout); | ||
| 218 | |||
| 219 | let r = T::regs(); | ||
| 220 | |||
| 221 | // TODO get configuration rather than hardcoding ratio, swidth, align, format, channels | ||
| 222 | |||
| 223 | r.config.mcken.write(|w| w.mcken().enabled()); | ||
| 224 | r.config.mckfreq.write(|w| w.mckfreq()._32mdiv16()); | ||
| 225 | r.config.ratio.write(|w| w.ratio()._192x()); | ||
| 226 | r.config.mode.write(|w| w.mode().master()); | ||
| 227 | r.config.swidth.write(|w| w.swidth()._16bit()); | ||
| 228 | r.config.align.write(|w| w.align().left()); | ||
| 229 | r.config.format.write(|w| w.format().i2s()); | ||
| 230 | r.config.channels.write(|w| w.channels().stereo()); | ||
| 231 | |||
| 232 | r.psel.mck.write(|w| { | ||
| 233 | unsafe { w.bits(mck.psel_bits()) }; | ||
| 234 | w.connect().connected() | ||
| 235 | }); | ||
| 236 | |||
| 237 | r.psel.sck.write(|w| { | ||
| 238 | unsafe { w.bits(sck.psel_bits()) }; | ||
| 239 | w.connect().connected() | ||
| 240 | }); | ||
| 241 | |||
| 242 | r.psel.lrck.write(|w| { | ||
| 243 | unsafe { w.bits(lrck.psel_bits()) }; | ||
| 244 | w.connect().connected() | ||
| 245 | }); | ||
| 246 | |||
| 247 | r.psel.sdin.write(|w| { | ||
| 248 | unsafe { w.bits(sdin.psel_bits()) }; | ||
| 249 | w.connect().connected() | ||
| 250 | }); | ||
| 251 | |||
| 252 | r.psel.sdout.write(|w| { | ||
| 253 | unsafe { w.bits(sdout.psel_bits()) }; | ||
| 254 | w.connect().connected() | ||
| 255 | }); | ||
| 256 | |||
| 257 | r.enable.write(|w| w.enable().enabled()); | ||
| 258 | |||
| 259 | Self { | ||
| 260 | output: I2sOutput { | ||
| 261 | _p: unsafe { i2s.clone_unchecked() }, | ||
| 262 | }, | ||
| 263 | input: I2sInput { _p: i2s }, | ||
| 264 | } | ||
| 265 | } | ||
| 266 | |||
| 267 | /// Enables the I2S module. | ||
| 268 | #[inline(always)] | ||
| 269 | pub fn enable(&self) -> &Self { | ||
| 270 | let r = T::regs(); | ||
| 271 | r.enable.write(|w| w.enable().enabled()); | ||
| 272 | self | ||
| 273 | } | ||
| 274 | |||
| 275 | /// Disables the I2S module. | ||
| 276 | #[inline(always)] | ||
| 277 | pub fn disable(&self) -> &Self { | ||
| 278 | let r = T::regs(); | ||
| 279 | r.enable.write(|w| w.enable().disabled()); | ||
| 280 | self | ||
| 281 | } | ||
| 282 | |||
| 283 | /// Starts I2S transfer. | ||
| 284 | #[inline(always)] | ||
| 285 | pub fn start(&self) -> &Self { | ||
| 286 | let r = T::regs(); | ||
| 287 | self.enable(); | ||
| 288 | r.tasks_start.write(|w| unsafe { w.bits(1) }); | ||
| 289 | self | ||
| 290 | } | ||
| 291 | |||
| 292 | /// Stops the I2S transfer and waits until it has stopped. | ||
| 293 | #[inline(always)] | ||
| 294 | pub fn stop(&self) -> &Self { | ||
| 295 | todo!() | ||
| 296 | } | ||
| 297 | |||
| 298 | /// Enables/disables I2S transmission (TX). | ||
| 299 | #[inline(always)] | ||
| 300 | pub fn set_tx_enabled(&self, enabled: bool) -> &Self { | ||
| 301 | let r = T::regs(); | ||
| 302 | r.config.txen.write(|w| w.txen().bit(enabled)); | ||
| 303 | self | ||
| 304 | } | ||
| 305 | |||
| 306 | /// Enables/disables I2S reception (RX). | ||
| 307 | #[inline(always)] | ||
| 308 | pub fn set_rx_enabled(&self, enabled: bool) -> &Self { | ||
| 309 | let r = T::regs(); | ||
| 310 | r.config.rxen.write(|w| w.rxen().bit(enabled)); | ||
| 311 | self | ||
| 312 | } | ||
| 313 | |||
| 314 | /// Transmits the given `tx_buffer`. | ||
| 315 | /// Buffer address must be 4 byte aligned and located in RAM. | ||
| 316 | /// Returns a value that represents the in-progress DMA transfer. | ||
| 317 | // TODO Define a better interface for the input buffer | ||
| 318 | #[allow(unused_mut)] | ||
| 319 | pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { | ||
| 320 | self.output.tx(ptr, len).await | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | impl<'d, T: Instance> I2sOutput<'d, T> { | ||
| 325 | /// Transmits the given `tx_buffer`. | ||
| 326 | /// Buffer address must be 4 byte aligned and located in RAM. | ||
| 327 | /// Returns a value that represents the in-progress DMA transfer. | ||
| 328 | // TODO Define a better interface for the input buffer | ||
| 329 | pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> { | ||
| 330 | if ptr as u32 % 4 != 0 { | ||
| 331 | return Err(Error::BufferMisaligned); | ||
| 332 | } | ||
| 333 | let maxcnt = (len / (core::mem::size_of::<u32>() / core::mem::size_of::<u8>())) as u32; | ||
| 334 | if maxcnt > MAX_DMA_MAXCNT { | ||
| 335 | return Err(Error::BufferTooLong); | ||
| 336 | } | ||
| 337 | if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER { | ||
| 338 | return Err(Error::DMABufferNotInDataMemory); | ||
| 339 | } | ||
| 340 | |||
| 341 | let r = T::regs(); | ||
| 342 | let _s = T::state(); | ||
| 343 | |||
| 344 | // TODO we can not progress until the last buffer written in TXD.PTR | ||
| 345 | // has started the transmission. | ||
| 346 | // We can use some sync primitive from `embassy-sync`. | ||
| 347 | |||
| 348 | r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); | ||
| 349 | r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); | ||
| 350 | |||
| 351 | Ok(()) | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | pub(crate) mod sealed { | ||
| 356 | use core::sync::atomic::AtomicU8; | ||
| 357 | |||
| 358 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 359 | |||
| 360 | use super::*; | ||
| 361 | |||
| 362 | pub struct State { | ||
| 363 | pub input_waker: AtomicWaker, | ||
| 364 | pub output_waker: AtomicWaker, | ||
| 365 | pub buffers_refcount: AtomicU8, | ||
| 366 | } | ||
| 367 | impl State { | ||
| 368 | pub const fn new() -> Self { | ||
| 369 | Self { | ||
| 370 | input_waker: AtomicWaker::new(), | ||
| 371 | output_waker: AtomicWaker::new(), | ||
| 372 | buffers_refcount: AtomicU8::new(0), | ||
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | pub trait Instance { | ||
| 378 | fn regs() -> &'static pac::i2s::RegisterBlock; | ||
| 379 | fn state() -> &'static State; | ||
| 380 | } | ||
| 381 | } | ||
| 382 | |||
| 383 | pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { | ||
| 384 | type Interrupt: Interrupt; | ||
| 385 | } | ||
| 386 | |||
| 387 | macro_rules! impl_i2s { | ||
| 388 | ($type:ident, $pac_type:ident, $irq:ident) => { | ||
| 389 | impl crate::i2s::sealed::Instance for peripherals::$type { | ||
| 390 | fn regs() -> &'static pac::i2s::RegisterBlock { | ||
| 391 | unsafe { &*pac::$pac_type::ptr() } | ||
| 392 | } | ||
| 393 | fn state() -> &'static crate::i2s::sealed::State { | ||
| 394 | static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new(); | ||
| 395 | &STATE | ||
| 396 | } | ||
| 397 | } | ||
| 398 | impl crate::i2s::Instance for peripherals::$type { | ||
| 399 | type Interrupt = crate::interrupt::$irq; | ||
| 400 | } | ||
| 401 | }; | ||
| 402 | } | ||
| 403 | |||
