diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index 4ffa6e9c..a89c132c 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -4,7 +4,7 @@ use embassy_embedded_hal::SetConfig; use embassy_hal_internal::{into_ref, PeripheralRef}; pub use crate::dma::word; -use crate::dma::{ringbuffer, Channel, ReadableRingBuffer, TransferOptions, WritableRingBuffer}; +use crate::dma::{ringbuffer, Channel, ReadableRingBuffer, Request, TransferOptions, WritableRingBuffer}; use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::AnyPin; use crate::pac::sai::{vals, Sai as Regs}; @@ -48,8 +48,8 @@ pub enum Mode { } #[derive(Copy, Clone)] -enum TxRx { - Transmiter, +pub enum TxRx { + Transmitter, Receiver, } @@ -57,7 +57,7 @@ impl Mode { #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] const fn mode(&self, tx_rx: TxRx) -> vals::Mode { match tx_rx { - TxRx::Transmiter => match self { + TxRx::Transmitter => match self { Mode::Master => vals::Mode::MASTERTX, Mode::Slave => vals::Mode::SLAVETX, }, @@ -212,6 +212,7 @@ pub enum SyncEnable { /// Syncs with the other A/B sub-block within the SAI unit Internal, /// Syncs with a sub-block in the other SAI unit - use set_sync_output() and set_sync_input() + #[cfg(any(sai_v4))] External, } @@ -221,6 +222,7 @@ impl SyncEnable { match self { SyncEnable::Asynchronous => vals::Syncen::ASYNCHRONOUS, SyncEnable::Internal => vals::Syncen::INTERNAL, + #[cfg(any(sai_v4))] SyncEnable::External => vals::Syncen::EXTERNAL, } } @@ -425,6 +427,7 @@ impl MasterClockDivider { #[derive(Copy, Clone)] pub struct Config { pub mode: Mode, + pub tx_rx: TxRx, pub sync_enable: SyncEnable, pub is_sync_output: bool, pub protocol: Protocol, @@ -455,6 +458,7 @@ impl Default for Config { fn default() -> Self { Self { mode: Mode::Master, + tx_rx: TxRx::Transmitter, is_sync_output: false, sync_enable: SyncEnable::Asynchronous, protocol: Protocol::Free, @@ -505,7 +509,6 @@ pub enum SubBlock { enum RingBuffer<'d, C: Channel, W: word::Word> { Writable(WritableRingBuffer<'d, C, W>), - #[allow(dead_code)] // remove this after implementing new_* functions for receiver Readable(ReadableRingBuffer<'d, C, W>), } @@ -515,6 +518,12 @@ fn wdr(w: crate::pac::sai::Sai, sub_block: SubBlock) -> *mut W { ch.dr().as_ptr() as _ } +#[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] +fn rdr(w: crate::pac::sai::Sai, sub_block: SubBlock) -> *mut W { + let ch = w.ch(sub_block as usize); + ch.dr().as_ptr() as _ +} + pub struct Sai<'d, T: Instance, C: Channel, W: word::Word> { _peri: PeripheralRef<'d, T>, sd: Option>, @@ -526,14 +535,45 @@ pub struct Sai<'d, T: Instance, C: Channel, W: word::Word> { } impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { - fn get_transmitter_af_types(mode: Mode) -> (AFType, AFType) { - match mode { - Mode::Master => (AFType::OutputPushPull, AFType::OutputPushPull), - Mode::Slave => (AFType::OutputPushPull, AFType::Input), + // return the type for (sd, sck) + fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AFType, AFType) { + ( + //sd is defined by tx/rx mode + match tx_rx { + TxRx::Transmitter => AFType::OutputPushPull, + TxRx::Receiver => AFType::Input, + }, + //clocks (mclk, sck and fs) are defined by master/slave + match mode { + Mode::Master => AFType::OutputPushPull, + Mode::Slave => AFType::Input, + }, + ) + } + + fn get_ring_buffer( + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + request: Request, + sub_block: SubBlock, + tx_rx: TxRx, + ) -> RingBuffer<'d, C, W> { + let opts = TransferOptions { + half_transfer_ir: true, + //the new_write() and new_read() always use circular mode + ..Default::default() + }; + match tx_rx { + TxRx::Transmitter => RingBuffer::Writable(unsafe { + WritableRingBuffer::new_write(dma, request, wdr(T::REGS, sub_block), dma_buf, opts) + }), + TxRx::Receiver => RingBuffer::Readable(unsafe { + ReadableRingBuffer::new_read(dma, request, rdr(T::REGS, sub_block), dma_buf, opts) + }), } } - pub fn new_asynchronous_transmitter_with_mclk_a( + pub fn new_asynchronous_block_a_with_mclk( peri: impl Peripheral

+ 'd, sck: impl Peripheral

> + 'd, sd: impl Peripheral

> + 'd, @@ -548,17 +588,19 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { { into_ref!(mclk); - mclk.set_as_af(mclk.af_num(), AFType::OutputPushPull); + let (_sd_af_type, ck_af_type) = Self::get_af_types(config.mode, config.tx_rx); + + mclk.set_as_af(mclk.af_num(), ck_af_type); mclk.set_speed(crate::gpio::Speed::VeryHigh); if config.master_clock_divider == MasterClockDivider::MasterClockDisabled { config.master_clock_divider = MasterClockDivider::Div1; } - Self::new_asynchronous_transmitter_a(peri, sck, sd, fs, dma, dma_buf, config) + Self::new_asynchronous_block_a(peri, sck, sd, fs, dma, dma_buf, config) } - pub fn new_asynchronous_transmitter_a( + pub fn new_asynchronous_block_a( peri: impl Peripheral

+ 'd, sck: impl Peripheral

> + 'd, sd: impl Peripheral

> + 'd, @@ -572,7 +614,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { { into_ref!(peri, dma, sck, sd, fs); - let (sd_af_type, ck_af_type) = Self::get_transmitter_af_types(config.mode); + let (sd_af_type, ck_af_type) = Self::get_af_types(config.mode, config.tx_rx); sd.set_as_af(sd.af_num(), sd_af_type); sd.set_speed(crate::gpio::Speed::VeryHigh); @@ -581,14 +623,8 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { fs.set_as_af(fs.af_num(), ck_af_type); fs.set_speed(crate::gpio::Speed::VeryHigh); - let request = dma.request(); - let opts = TransferOptions { - half_transfer_ir: true, - circular: true, - ..Default::default() - }; - let sub_block = SubBlock::A; + let request = dma.request(); Self::new_inner( peri, @@ -597,14 +633,12 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { None, Some(sd.map_into()), Some(fs.map_into()), - RingBuffer::Writable(unsafe { - WritableRingBuffer::new_write(dma, request, wdr(T::REGS, sub_block), dma_buf, opts) - }), + Self::get_ring_buffer(dma, dma_buf, request, sub_block, config.tx_rx), config, ) } - pub fn new_asynchronous_transmitter_with_mclk_b( + pub fn new_asynchronous_block_b_with_mclk( peri: impl Peripheral

+ 'd, sck: impl Peripheral

> + 'd, sd: impl Peripheral

> + 'd, @@ -619,17 +653,19 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { { into_ref!(mclk); - mclk.set_as_af(mclk.af_num(), AFType::OutputPushPull); + let (_sd_af_type, ck_af_type) = Self::get_af_types(config.mode, config.tx_rx); + + mclk.set_as_af(mclk.af_num(), ck_af_type); mclk.set_speed(crate::gpio::Speed::VeryHigh); if config.master_clock_divider == MasterClockDivider::MasterClockDisabled { config.master_clock_divider = MasterClockDivider::Div1; } - Self::new_asynchronous_transmitter_b(peri, sck, sd, fs, dma, dma_buf, config) + Self::new_asynchronous_block_b(peri, sck, sd, fs, dma, dma_buf, config) } - pub fn new_asynchronous_transmitter_b( + pub fn new_asynchronous_block_b( peri: impl Peripheral

+ 'd, sck: impl Peripheral

> + 'd, sd: impl Peripheral

> + 'd, @@ -643,7 +679,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { { into_ref!(dma, peri, sck, sd, fs); - let (sd_af_type, ck_af_type) = Self::get_transmitter_af_types(config.mode); + let (sd_af_type, ck_af_type) = Self::get_af_types(config.mode, config.tx_rx); sd.set_as_af(sd.af_num(), sd_af_type); sd.set_speed(crate::gpio::Speed::VeryHigh); @@ -653,13 +689,8 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { fs.set_as_af(fs.af_num(), ck_af_type); fs.set_speed(crate::gpio::Speed::VeryHigh); - let request = dma.request(); - let opts = TransferOptions { - half_transfer_ir: true, - ..Default::default() - }; - let sub_block = SubBlock::B; + let request = dma.request(); Self::new_inner( peri, @@ -668,9 +699,92 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { None, Some(sd.map_into()), Some(fs.map_into()), - RingBuffer::Writable(unsafe { - WritableRingBuffer::new_write(dma, request, wdr(T::REGS, sub_block), dma_buf, opts) - }), + Self::get_ring_buffer(dma, dma_buf, request, sub_block, config.tx_rx), + config, + ) + } + + fn update_synchronous_config(config: &mut Config) { + config.mode = Mode::Slave; + config.is_sync_output = false; + + #[cfg(any(sai_v1, sai_v2, sai_v3))] + { + config.sync_enable = SyncEnable::Internal; + } + + #[cfg(any(sai_v4))] + { + //this must either be Internal or External + //The asynchronous sub-block on the same SAI needs to enable is_sync_output + assert!(config.sync_enable != SyncEnable::Asynchronous); + } + } + + pub fn new_synchronous_block_a( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + mut config: Config, + ) -> Self + where + C: Channel + DmaA, + { + Self::update_synchronous_config(&mut config); + + into_ref!(dma, peri, sd); + + let (sd_af_type, _ck_af_type) = Self::get_af_types(config.mode, config.tx_rx); + + sd.set_as_af(sd.af_num(), sd_af_type); + sd.set_speed(crate::gpio::Speed::VeryHigh); + + let sub_block = SubBlock::A; + let request = dma.request(); + + Self::new_inner( + peri, + sub_block, + None, + None, + Some(sd.map_into()), + None, + Self::get_ring_buffer(dma, dma_buf, request, sub_block, config.tx_rx), + config, + ) + } + + pub fn new_synchronous_block_b( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + mut config: Config, + ) -> Self + where + C: Channel + DmaB, + { + Self::update_synchronous_config(&mut config); + + into_ref!(dma, peri, sd); + + let (sd_af_type, _ck_af_type) = Self::get_af_types(config.mode, config.tx_rx); + + sd.set_as_af(sd.af_num(), sd_af_type); + sd.set_speed(crate::gpio::Speed::VeryHigh); + + let sub_block = SubBlock::B; + let request = dma.request(); + + Self::new_inner( + peri, + sub_block, + None, + None, + Some(sd.map_into()), + None, + Self::get_ring_buffer(dma, dma_buf, request, sub_block, config.tx_rx), config, ) } @@ -704,12 +818,21 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { config: Config, ) -> Self { T::enable(); - T::reset(); + + // can't reset here because the other sub-block might be in use + + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + { + let ch = T::REGS.ch(sub_block as usize); + ch.cr1().modify(|w| w.set_saien(false)); + } #[cfg(any(sai_v4))] { // Not totally clear from the datasheet if this is right - // This is only used if using SyncEnable::External + // This is only used if using SyncEnable::External on the other SAI unit + // Syncing from SAIX subblock A to subblock B does not require this + // Only syncing from SAI1 subblock A/B to SAI2 subblock A/B let value: u8 = if T::REGS.as_ptr() == stm32_metapac::SAI1.as_ptr() { 1 //this is SAI1, so sync with SAI2 } else { @@ -735,7 +858,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { let ch = T::REGS.ch(sub_block as usize); ch.cr1().modify(|w| { w.set_mode(config.mode.mode(if Self::is_transmitter(&ring_buffer) { - TxRx::Transmiter + TxRx::Transmitter } else { TxRx::Receiver })); @@ -770,7 +893,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { w.set_fsoff(config.frame_sync_offset.fsoff()); w.set_fspol(config.frame_sync_polarity.fspol()); w.set_fsdef(config.frame_sync_definition.fsdef()); - w.set_fsall(config.frame_sync_active_level_length.0 as u8); + w.set_fsall(config.frame_sync_active_level_length.0 as u8 - 1); w.set_frl(config.frame_length - 1); }); @@ -782,6 +905,10 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { }); ch.cr1().modify(|w| w.set_saien(true)); + + if ch.cr1().read().saien() == false { + panic!("SAI failed to enable. Check that config is valid (frame length, slot count, etc)"); + } } Self { @@ -795,6 +922,11 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { } } + pub fn reset() { + T::enable(); + T::reset(); + } + pub fn flush(&mut self) { let ch = T::REGS.ch(self.sub_block as usize); ch.cr1().modify(|w| w.set_saien(false));