aboutsummaryrefslogtreecommitdiff
path: root/embassy-nrf/src/i2s.rs
diff options
context:
space:
mode:
authorChristian Perez Llamas <[email protected]>2022-11-09 19:14:43 +0100
committerChristian Perez Llamas <[email protected]>2022-11-09 19:19:01 +0100
commitcecd77938c694ff2bad2a259ff64f2f468dcb04a (patch)
treead4cca7a642cc0a8a2fd2858538560d0a2ba55b1 /embassy-nrf/src/i2s.rs
parent059610a8de49ff2d38311f343d3d1a6f8d90a720 (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.rs403
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
5use core::future::poll_fn;
6use core::sync::atomic::{compiler_fence, Ordering};
7use core::task::Poll;
8
9use embassy_hal_common::drop::OnDrop;
10use embassy_hal_common::{into_ref, PeripheralRef};
11use pac::i2s::config::mcken;
12
13use crate::{pac, Peripheral};
14use crate::interrupt::{Interrupt, InterruptExt};
15use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
16use crate::gpio::sealed::Pin as _;
17
18// TODO: Define those in lib.rs somewhere else
19//
20// I2S EasyDMA MAXCNT bit length = 14
21const MAX_DMA_MAXCNT: u32 = 1 << 14;
22
23// Limits for Easy DMA - it can only read from data ram
24pub const SRAM_LOWER: usize = 0x2000_0000;
25pub 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]
30pub enum Error {
31 BufferTooLong,
32 BufferZeroLength,
33 DMABufferNotInDataMemory,
34 BufferMisaligned,
35 // TODO: add other error variants.
36}
37
38#[derive(Clone)]
39#[non_exhaustive]
40pub 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
48impl 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)]
62pub enum Ratio {
63 _32x,
64 _48x,
65 _64x,
66 _96x,
67 _128x,
68 _192x,
69 _256x,
70 _384x,
71 _512x,
72}
73
74impl From<Ratio> for u8 {
75 fn from(variant: Ratio) -> Self {
76 variant as _
77 }
78}
79
80#[derive(Debug, Eq, PartialEq, Clone, Copy)]
81pub enum SampleWidth {
82 _8bit,
83 _16bit,
84 _24bit,
85}
86
87impl 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)]
95pub enum Align {
96 Left,
97 Right,
98}
99
100impl 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)]
111pub enum Format {
112 I2S,
113 Aligned,
114}
115
116impl 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)]
127pub enum Channels {
128 Stereo,
129 Left,
130 Right,
131}
132
133impl 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)]
141pub 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.
168pub 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.
175pub struct I2sOutput<'d, T: Instance> {
176 _p: PeripheralRef<'d, T>,
177}
178
179/// Receiver interface to the UARTE peripheral obtained
180/// via [Uarte]::split.
181pub struct I2sInput<'d, T: Instance> {
182 _p: PeripheralRef<'d, T>,
183}
184
185impl<'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
324impl<'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
355pub(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
383pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
384 type Interrupt: Interrupt;
385}
386
387macro_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