embassy/embassy-nrf/src/i2s.rs

404 lines
10 KiB
Rust
Raw Normal View History

#![macro_use]
//! I2S
use core::future::poll_fn;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::Poll;
use embassy_hal_common::drop::OnDrop;
use embassy_hal_common::{into_ref, PeripheralRef};
use pac::i2s::config::mcken;
use crate::{pac, Peripheral};
use crate::interrupt::{Interrupt, InterruptExt};
use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
use crate::gpio::sealed::Pin as _;
// TODO: Define those in lib.rs somewhere else
//
// I2S EasyDMA MAXCNT bit length = 14
const MAX_DMA_MAXCNT: u32 = 1 << 14;
// Limits for Easy DMA - it can only read from data ram
pub const SRAM_LOWER: usize = 0x2000_0000;
pub const SRAM_UPPER: usize = 0x3000_0000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
BufferTooLong,
BufferZeroLength,
DMABufferNotInDataMemory,
BufferMisaligned,
// TODO: add other error variants.
}
#[derive(Clone)]
#[non_exhaustive]
pub struct Config {
pub ratio: Ratio,
pub sample_width: SampleWidth,
pub align: Align,
pub format: Format,
pub channels: Channels,
}
impl Default for Config {
fn default() -> Self {
Self {
ratio: Ratio::_32x,
sample_width: SampleWidth::_16bit,
align: Align::Left,
format: Format::I2S,
channels: Channels::Stereo,
}
}
}
/// MCK / LRCK ratio.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Ratio {
_32x,
_48x,
_64x,
_96x,
_128x,
_192x,
_256x,
_384x,
_512x,
}
impl From<Ratio> for u8 {
fn from(variant: Ratio) -> Self {
variant as _
}
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum SampleWidth {
_8bit,
_16bit,
_24bit,
}
impl From<SampleWidth> for u8 {
fn from(variant: SampleWidth) -> Self {
variant as _
}
}
/// Alignment of sample within a frame.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Align {
Left,
Right,
}
impl From<Align> for bool {
fn from(variant: Align) -> Self {
match variant {
Align::Left => false,
Align::Right => true,
}
}
}
/// Frame format.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Format {
I2S,
Aligned,
}
impl From<Format> for bool {
fn from(variant: Format) -> Self {
match variant {
Format::I2S => false,
Format::Aligned => true,
}
}
}
/// Enable channels.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Channels {
Stereo,
Left,
Right,
}
impl From<Channels> for u8 {
fn from(variant: Channels) -> Self {
variant as _
}
}
/// I2S Mode
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Mode {
Controller,
Peripheral,
}
// /// Master clock generator frequency.
// #[derive(Debug, Eq, PartialEq, Clone, Copy)]
// pub enum MckFreq {
// _32MDiv8 = 0x20000000,
// _32MDiv10 = 0x18000000,
// _32MDiv11 = 0x16000000,
// _32MDiv15 = 0x11000000,
// _32MDiv16 = 0x10000000,
// _32MDiv21 = 0x0C000000,
// _32MDiv23 = 0x0B000000,
// _32MDiv30 = 0x08800000,
// _32MDiv31 = 0x08400000,
// _32MDiv32 = 0x08000000,
// _32MDiv42 = 0x06000000,
// _32MDiv63 = 0x04100000,
// _32MDiv125 = 0x020C0000,
// }
/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload.
///
/// For more details about EasyDMA, consult the module documentation.
pub struct I2s<'d, T: Instance> {
output: I2sOutput<'d, T>,
input: I2sInput<'d, T>,
}
/// Transmitter interface to the UARTE peripheral obtained
/// via [Uarte]::split.
pub struct I2sOutput<'d, T: Instance> {
_p: PeripheralRef<'d, T>,
}
/// Receiver interface to the UARTE peripheral obtained
/// via [Uarte]::split.
pub struct I2sInput<'d, T: Instance> {
_p: PeripheralRef<'d, T>,
}
impl<'d, T: Instance> I2s<'d, T> {
/// Create a new I2S
pub fn new(
i2s: impl Peripheral<P = T> + 'd,
// irq: impl Peripheral<P = T::Interrupt> + 'd,
mck: impl Peripheral<P = impl GpioPin> + 'd,
sck: impl Peripheral<P = impl GpioPin> + 'd,
lrck: impl Peripheral<P = impl GpioPin> + 'd,
sdin: impl Peripheral<P = impl GpioPin> + 'd,
sdout: impl Peripheral<P = impl GpioPin> + 'd,
config: Config,
) -> Self {
into_ref!(mck, sck, lrck, sdin, sdout);
Self::new_inner(
i2s,
// irq,
mck.map_into(), sck.map_into(), lrck.map_into(), sdin.map_into(), sdout.map_into(), config)
}
fn new_inner(
i2s: impl Peripheral<P = T> + 'd,
// irq: impl Peripheral<P = T::Interrupt> + 'd,
mck: PeripheralRef<'d, AnyPin>,
sck: PeripheralRef<'d, AnyPin>,
lrck: PeripheralRef<'d, AnyPin>,
sdin: PeripheralRef<'d, AnyPin>,
sdout: PeripheralRef<'d, AnyPin>,
config: Config,
) -> Self {
into_ref!(
i2s,
// irq,
mck, sck, lrck, sdin, sdout);
let r = T::regs();
// TODO get configuration rather than hardcoding ratio, swidth, align, format, channels
r.config.mcken.write(|w| w.mcken().enabled());
r.config.mckfreq.write(|w| w.mckfreq()._32mdiv16());
r.config.ratio.write(|w| w.ratio()._192x());
r.config.mode.write(|w| w.mode().master());
r.config.swidth.write(|w| w.swidth()._16bit());
r.config.align.write(|w| w.align().left());
r.config.format.write(|w| w.format().i2s());
r.config.channels.write(|w| w.channels().stereo());
r.psel.mck.write(|w| {
unsafe { w.bits(mck.psel_bits()) };
w.connect().connected()
});
r.psel.sck.write(|w| {
unsafe { w.bits(sck.psel_bits()) };
w.connect().connected()
});
r.psel.lrck.write(|w| {
unsafe { w.bits(lrck.psel_bits()) };
w.connect().connected()
});
r.psel.sdin.write(|w| {
unsafe { w.bits(sdin.psel_bits()) };
w.connect().connected()
});
r.psel.sdout.write(|w| {
unsafe { w.bits(sdout.psel_bits()) };
w.connect().connected()
});
r.enable.write(|w| w.enable().enabled());
Self {
output: I2sOutput {
_p: unsafe { i2s.clone_unchecked() },
},
input: I2sInput { _p: i2s },
}
}
/// Enables the I2S module.
#[inline(always)]
pub fn enable(&self) -> &Self {
let r = T::regs();
r.enable.write(|w| w.enable().enabled());
self
}
/// Disables the I2S module.
#[inline(always)]
pub fn disable(&self) -> &Self {
let r = T::regs();
r.enable.write(|w| w.enable().disabled());
self
}
/// Starts I2S transfer.
#[inline(always)]
pub fn start(&self) -> &Self {
let r = T::regs();
self.enable();
r.tasks_start.write(|w| unsafe { w.bits(1) });
self
}
/// Stops the I2S transfer and waits until it has stopped.
#[inline(always)]
pub fn stop(&self) -> &Self {
todo!()
}
/// Enables/disables I2S transmission (TX).
#[inline(always)]
pub fn set_tx_enabled(&self, enabled: bool) -> &Self {
let r = T::regs();
r.config.txen.write(|w| w.txen().bit(enabled));
self
}
/// Enables/disables I2S reception (RX).
#[inline(always)]
pub fn set_rx_enabled(&self, enabled: bool) -> &Self {
let r = T::regs();
r.config.rxen.write(|w| w.rxen().bit(enabled));
self
}
/// Transmits the given `tx_buffer`.
/// Buffer address must be 4 byte aligned and located in RAM.
/// Returns a value that represents the in-progress DMA transfer.
// TODO Define a better interface for the input buffer
#[allow(unused_mut)]
pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> {
self.output.tx(ptr, len).await
}
}
impl<'d, T: Instance> I2sOutput<'d, T> {
/// Transmits the given `tx_buffer`.
/// Buffer address must be 4 byte aligned and located in RAM.
/// Returns a value that represents the in-progress DMA transfer.
// TODO Define a better interface for the input buffer
pub async fn tx(&mut self, ptr: *const u8, len: usize) -> Result<(), Error> {
if ptr as u32 % 4 != 0 {
return Err(Error::BufferMisaligned);
}
let maxcnt = (len / (core::mem::size_of::<u32>() / core::mem::size_of::<u8>())) as u32;
if maxcnt > MAX_DMA_MAXCNT {
return Err(Error::BufferTooLong);
}
if (ptr as usize) < SRAM_LOWER || (ptr as usize) > SRAM_UPPER {
return Err(Error::DMABufferNotInDataMemory);
}
let r = T::regs();
let _s = T::state();
// TODO we can not progress until the last buffer written in TXD.PTR
// has started the transmission.
// We can use some sync primitive from `embassy-sync`.
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) });
r.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) });
Ok(())
}
}
pub(crate) mod sealed {
use core::sync::atomic::AtomicU8;
use embassy_sync::waitqueue::AtomicWaker;
use super::*;
pub struct State {
pub input_waker: AtomicWaker,
pub output_waker: AtomicWaker,
pub buffers_refcount: AtomicU8,
}
impl State {
pub const fn new() -> Self {
Self {
input_waker: AtomicWaker::new(),
output_waker: AtomicWaker::new(),
buffers_refcount: AtomicU8::new(0),
}
}
}
pub trait Instance {
fn regs() -> &'static pac::i2s::RegisterBlock;
fn state() -> &'static State;
}
}
pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send {
type Interrupt: Interrupt;
}
macro_rules! impl_i2s {
($type:ident, $pac_type:ident, $irq:ident) => {
impl crate::i2s::sealed::Instance for peripherals::$type {
fn regs() -> &'static pac::i2s::RegisterBlock {
unsafe { &*pac::$pac_type::ptr() }
}
fn state() -> &'static crate::i2s::sealed::State {
static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new();
&STATE
}
}
impl crate::i2s::Instance for peripherals::$type {
type Interrupt = crate::interrupt::$irq;
}
};
}