diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index b36af4795..ccc9210df 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -463,6 +463,15 @@ fn main() { (("lpuart", "RTS"), quote!(crate::usart::RtsPin)), (("lpuart", "CK"), quote!(crate::usart::CkPin)), (("lpuart", "DE"), quote!(crate::usart::DePin)), + (("sai", "SCK_A"), quote!(crate::sai::SckAPin)), + (("sai", "SCK_B"), quote!(crate::sai::SckBPin)), + (("sai", "FS_A"), quote!(crate::sai::FsAPin)), + (("sai", "FS_B"), quote!(crate::sai::FsBPin)), + (("sai", "SD_A"), quote!(crate::sai::SdAPin)), + (("sai", "SD_B"), quote!(crate::sai::SdBPin)), + (("sai", "MCLK_A"), quote!(crate::sai::MclkAPin)), + (("sai", "MCLK_B"), quote!(crate::sai::MclkBPin)), + (("sai", "WS"), quote!(crate::sai::WsPin)), (("spi", "SCK"), quote!(crate::spi::SckPin)), (("spi", "MOSI"), quote!(crate::spi::MosiPin)), (("spi", "MISO"), quote!(crate::spi::MisoPin)), @@ -750,6 +759,8 @@ fn main() { (("usart", "TX"), quote!(crate::usart::TxDma)), (("lpuart", "RX"), quote!(crate::usart::RxDma)), (("lpuart", "TX"), quote!(crate::usart::TxDma)), + (("sai", "A"), quote!(crate::sai::DmaA)), + (("sai", "B"), quote!(crate::sai::DmaB)), (("spi", "RX"), quote!(crate::spi::RxDma)), (("spi", "TX"), quote!(crate::spi::TxDma)), (("i2c", "RX"), quote!(crate::i2c::RxDma)), diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 1e184f9d7..2718c96da 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -55,6 +55,8 @@ pub mod qspi; pub mod rng; #[cfg(all(rtc, not(rtc_v1)))] pub mod rtc; +#[cfg(sai)] +pub mod sai; #[cfg(sdmmc)] pub mod sdmmc; #[cfg(spi)] diff --git a/embassy-stm32/src/rcc/f4.rs b/embassy-stm32/src/rcc/f4.rs index 5914c926a..ebf78d0e2 100644 --- a/embassy-stm32/src/rcc/f4.rs +++ b/embassy-stm32/src/rcc/f4.rs @@ -32,6 +32,9 @@ pub struct Config { #[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] pub plli2s: Option, + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + pub pllsai: Option, + pub pll48: bool, pub rtc: Option, pub lsi: bool, @@ -50,10 +53,9 @@ fn setup_i2s_pll(_vco_in: u32, _plli2s: Option) -> Option { } #[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] -fn setup_i2s_pll(vco_in: u32, plli2s: Option) -> Option { +fn calculate_sai_i2s_pll_values(vco_in: u32, max_div: u32, target: Option) -> Option<(u32, u32, u32)> { let min_div = 2; - let max_div = 7; - let target = match plli2s { + let target = match target { Some(target) => target, None => return None, }; @@ -77,15 +79,48 @@ fn setup_i2s_pll(vco_in: u32, plli2s: Option) -> Option { }) .min_by_key(|(_, _, _, error)| *error)?; + Some((n, outdiv, output)) +} + +#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] +fn setup_i2s_pll(vco_in: u32, plli2s: Option) -> Option { + let (n, outdiv, output) = calculate_sai_i2s_pll_values(vco_in, 7, plli2s)?; + RCC.plli2scfgr().modify(|w| { w.set_plli2sn(n as u16); w.set_plli2sr(outdiv as u8); + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + w.set_plli2sq(outdiv as u8); //set sai divider same as i2s }); Some(output) } -fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, plli2s: Option, pll48clk: bool) -> PllResults { +#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479)))] +fn setup_sai_pll(_vco_in: u32, _pllsai: Option) -> Option { + None +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] +fn setup_sai_pll(vco_in: u32, pllsai: Option) -> Option { + let (n, outdiv, output) = calculate_sai_i2s_pll_values(vco_in, 15, pllsai)?; + + RCC.pllsaicfgr().modify(|w| { + w.set_pllsain(n as u16); + w.set_pllsaiq(outdiv as u8); + }); + + Some(output) +} + +fn setup_pll( + pllsrcclk: u32, + use_hse: bool, + pllsysclk: Option, + plli2s: Option, + pllsai: Option, + pll48clk: bool, +) -> PllResults { use crate::pac::rcc::vals::{Pllp, Pllsrc}; let sysclk = pllsysclk.unwrap_or(pllsrcclk); @@ -97,6 +132,7 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, plli2s: Opti pllsysclk: None, pll48clk: None, plli2sclk: None, + pllsaiclk: None, }; } // Input divisor from PLL source clock, must result to frequency in @@ -147,6 +183,7 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, plli2s: Opti w.set_pllp(Pllp::from_bits(pllp as u8)); w.set_pllq(pllq as u8); w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)); + w.set_pllr(0); }); let real_pllsysclk = vco_in * plln / sysclk_div; @@ -156,6 +193,7 @@ fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, plli2s: Opti pllsysclk: Some(real_pllsysclk), pll48clk: if pll48clk { Some(real_pll48clk) } else { None }, plli2sclk: setup_i2s_pll(vco_in, plli2s), + pllsaiclk: setup_sai_pll(vco_in, pllsai), } } @@ -343,6 +381,10 @@ pub(crate) unsafe fn init(config: Config) { config.plli2s.map(|i2s| i2s.0), #[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))] None, + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + config.pllsai.map(|sai| sai.0), + #[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479)))] + None, config.pll48, ); @@ -440,6 +482,12 @@ pub(crate) unsafe fn init(config: Config) { while !RCC.cr().read().plli2srdy() {} } + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + if plls.pllsaiclk.is_some() { + RCC.cr().modify(|w| w.set_pllsaion(true)); + while !RCC.cr().read().pllsairdy() {} + } + RCC.cfgr().modify(|w| { w.set_ppre2(Ppre::from_bits(ppre2_bits)); w.set_ppre1(Ppre::from_bits(ppre1_bits)); @@ -490,7 +538,7 @@ pub(crate) unsafe fn init(config: Config) { plli2s: plls.plli2sclk.map(Hertz), #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] - pllsai: None, + pllsai: plls.pllsaiclk.map(Hertz), rtc: rtc, rtc_hse: None, @@ -503,6 +551,8 @@ struct PllResults { pll48clk: Option, #[allow(dead_code)] plli2sclk: Option, + #[allow(dead_code)] + pllsaiclk: Option, } mod max { diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs new file mode 100644 index 000000000..4ffa6e9ce --- /dev/null +++ b/embassy-stm32/src/sai/mod.rs @@ -0,0 +1,894 @@ +#![macro_use] + +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::gpio::sealed::{AFType, Pin as _}; +use crate::gpio::AnyPin; +use crate::pac::sai::{vals, Sai as Regs}; +use crate::rcc::RccPeripheral; +use crate::{peripherals, Peripheral}; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + NotATransmitter, + NotAReceiver, + OverrunError, +} + +impl From for Error { + fn from(_: ringbuffer::OverrunError) -> Self { + Self::OverrunError + } +} + +#[derive(Copy, Clone)] +pub enum SyncBlock { + None, + Sai1BlockA, + Sai1BlockB, + Sai2BlockA, + Sai2BlockB, +} + +#[derive(Copy, Clone)] +pub enum SyncIn { + None, + ChannelZero, + ChannelOne, +} + +#[derive(Copy, Clone)] +pub enum Mode { + Master, + Slave, +} + +#[derive(Copy, Clone)] +enum TxRx { + Transmiter, + Receiver, +} + +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 { + Mode::Master => vals::Mode::MASTERTX, + Mode::Slave => vals::Mode::SLAVETX, + }, + TxRx::Receiver => match self { + Mode::Master => vals::Mode::MASTERRX, + Mode::Slave => vals::Mode::SLAVERX, + }, + } + } +} + +#[derive(Copy, Clone)] +pub enum SlotSize { + DataSize, + /// 16 bit data length on 16 bit wide channel + Channel16, + /// 16 bit data length on 32 bit wide channel + Channel32, +} + +impl SlotSize { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn slotsz(&self) -> vals::Slotsz { + match self { + SlotSize::DataSize => vals::Slotsz::DATASIZE, + SlotSize::Channel16 => vals::Slotsz::BIT16, + SlotSize::Channel32 => vals::Slotsz::BIT32, + } + } +} + +#[derive(Copy, Clone)] +pub enum DataSize { + Data8, + Data10, + Data16, + Data20, + Data24, + Data32, +} + +impl DataSize { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn ds(&self) -> vals::Ds { + match self { + DataSize::Data8 => vals::Ds::BIT8, + DataSize::Data10 => vals::Ds::BIT10, + DataSize::Data16 => vals::Ds::BIT16, + DataSize::Data20 => vals::Ds::BIT20, + DataSize::Data24 => vals::Ds::BIT24, + DataSize::Data32 => vals::Ds::BIT32, + } + } +} + +#[derive(Copy, Clone)] +pub enum FifoThreshold { + Empty, + Quarter, + Half, + ThreeQuarters, + Full, +} + +impl FifoThreshold { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn fth(&self) -> vals::Fth { + match self { + FifoThreshold::Empty => vals::Fth::EMPTY, + FifoThreshold::Quarter => vals::Fth::QUARTER1, + FifoThreshold::Half => vals::Fth::QUARTER2, + FifoThreshold::ThreeQuarters => vals::Fth::QUARTER3, + FifoThreshold::Full => vals::Fth::FULL, + } + } +} + +#[derive(Copy, Clone)] +pub enum FifoLevel { + Empty, + FirstQuarter, + SecondQuarter, + ThirdQuarter, + FourthQuarter, + Full, +} + +#[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] +impl From for FifoLevel { + fn from(flvl: vals::Flvl) -> Self { + match flvl { + vals::Flvl::EMPTY => FifoLevel::Empty, + vals::Flvl::QUARTER1 => FifoLevel::FirstQuarter, + vals::Flvl::QUARTER2 => FifoLevel::SecondQuarter, + vals::Flvl::QUARTER3 => FifoLevel::ThirdQuarter, + vals::Flvl::QUARTER4 => FifoLevel::FourthQuarter, + vals::Flvl::FULL => FifoLevel::Full, + _ => FifoLevel::Empty, + } + } +} + +#[derive(Copy, Clone)] +pub enum MuteDetection { + NoMute, + Mute, +} + +#[derive(Copy, Clone)] +pub enum MuteValue { + Zero, + LastValue, +} + +impl MuteValue { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn muteval(&self) -> vals::Muteval { + match self { + MuteValue::Zero => vals::Muteval::SENDZERO, + MuteValue::LastValue => vals::Muteval::SENDLAST, + } + } +} + +#[derive(Copy, Clone)] +pub enum OverUnderStatus { + NoError, + OverUnderRunDetected, +} + +#[derive(Copy, Clone)] +pub enum Protocol { + Free, + Spdif, + Ac97, +} + +impl Protocol { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn prtcfg(&self) -> vals::Prtcfg { + match self { + Protocol::Free => vals::Prtcfg::FREE, + Protocol::Spdif => vals::Prtcfg::SPDIF, + Protocol::Ac97 => vals::Prtcfg::AC97, + } + } +} + +#[derive(Copy, Clone)] +pub enum SyncEnable { + Asynchronous, + /// 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() + External, +} + +impl SyncEnable { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn syncen(&self) -> vals::Syncen { + match self { + SyncEnable::Asynchronous => vals::Syncen::ASYNCHRONOUS, + SyncEnable::Internal => vals::Syncen::INTERNAL, + SyncEnable::External => vals::Syncen::EXTERNAL, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum StereoMono { + Stereo, + Mono, +} + +impl StereoMono { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn mono(&self) -> vals::Mono { + match self { + StereoMono::Stereo => vals::Mono::STEREO, + StereoMono::Mono => vals::Mono::MONO, + } + } +} + +#[derive(Copy, Clone)] +pub enum BitOrder { + LsbFirst, + MsbFirst, +} + +impl BitOrder { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn lsbfirst(&self) -> vals::Lsbfirst { + match self { + BitOrder::LsbFirst => vals::Lsbfirst::LSBFIRST, + BitOrder::MsbFirst => vals::Lsbfirst::MSBFIRST, + } + } +} + +#[derive(Copy, Clone)] +pub enum FrameSyncOffset { + /// This is used in modes other than standard I2S phillips mode + OnFirstBit, + /// This is used in standard I2S phillips mode + BeforeFirstBit, +} + +impl FrameSyncOffset { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn fsoff(&self) -> vals::Fsoff { + match self { + FrameSyncOffset::OnFirstBit => vals::Fsoff::ONFIRST, + FrameSyncOffset::BeforeFirstBit => vals::Fsoff::BEFOREFIRST, + } + } +} + +#[derive(Copy, Clone)] +pub enum FrameSyncPolarity { + ActiveLow, + ActiveHigh, +} + +impl FrameSyncPolarity { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn fspol(&self) -> vals::Fspol { + match self { + FrameSyncPolarity::ActiveLow => vals::Fspol::FALLINGEDGE, + FrameSyncPolarity::ActiveHigh => vals::Fspol::RISINGEDGE, + } + } +} + +#[derive(Copy, Clone)] +pub enum FrameSyncDefinition { + StartOfFrame, + ChannelIdentification, +} + +impl FrameSyncDefinition { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn fsdef(&self) -> bool { + match self { + FrameSyncDefinition::StartOfFrame => false, + FrameSyncDefinition::ChannelIdentification => true, + } + } +} + +#[derive(Copy, Clone)] +pub enum ClockStrobe { + Falling, + Rising, +} + +impl ClockStrobe { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn ckstr(&self) -> vals::Ckstr { + match self { + ClockStrobe::Falling => vals::Ckstr::FALLINGEDGE, + ClockStrobe::Rising => vals::Ckstr::RISINGEDGE, + } + } +} + +#[derive(Copy, Clone)] +pub enum ComplementFormat { + OnesComplement, + TwosComplement, +} + +impl ComplementFormat { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn cpl(&self) -> vals::Cpl { + match self { + ComplementFormat::OnesComplement => vals::Cpl::ONESCOMPLEMENT, + ComplementFormat::TwosComplement => vals::Cpl::TWOSCOMPLEMENT, + } + } +} + +#[derive(Copy, Clone)] +pub enum Companding { + None, + MuLaw, + ALaw, +} + +impl Companding { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn comp(&self) -> vals::Comp { + match self { + Companding::None => vals::Comp::NOCOMPANDING, + Companding::MuLaw => vals::Comp::MULAW, + Companding::ALaw => vals::Comp::ALAW, + } + } +} + +#[derive(Copy, Clone)] +pub enum OutputDrive { + OnStart, + Immediately, +} + +impl OutputDrive { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn outdriv(&self) -> vals::Outdriv { + match self { + OutputDrive::OnStart => vals::Outdriv::ONSTART, + OutputDrive::Immediately => vals::Outdriv::IMMEDIATELY, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +pub enum MasterClockDivider { + MasterClockDisabled, + Div1, + Div2, + Div4, + Div6, + Div8, + Div10, + Div12, + Div14, + Div16, + Div18, + Div20, + Div22, + Div24, + Div26, + Div28, + Div30, +} + +impl MasterClockDivider { + #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + pub const fn mckdiv(&self) -> u8 { + match self { + MasterClockDivider::MasterClockDisabled => 0, + MasterClockDivider::Div1 => 0, + MasterClockDivider::Div2 => 1, + MasterClockDivider::Div4 => 2, + MasterClockDivider::Div6 => 3, + MasterClockDivider::Div8 => 4, + MasterClockDivider::Div10 => 5, + MasterClockDivider::Div12 => 6, + MasterClockDivider::Div14 => 7, + MasterClockDivider::Div16 => 8, + MasterClockDivider::Div18 => 9, + MasterClockDivider::Div20 => 10, + MasterClockDivider::Div22 => 11, + MasterClockDivider::Div24 => 12, + MasterClockDivider::Div26 => 13, + MasterClockDivider::Div28 => 14, + MasterClockDivider::Div30 => 15, + } + } +} + +/// [`SAI`] configuration. +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + pub mode: Mode, + pub sync_enable: SyncEnable, + pub is_sync_output: bool, + pub protocol: Protocol, + pub slot_size: SlotSize, + pub slot_count: word::U4, + pub slot_enable: u16, + pub first_bit_offset: word::U5, + pub data_size: DataSize, + pub stereo_mono: StereoMono, + pub bit_order: BitOrder, + pub frame_sync_offset: FrameSyncOffset, + pub frame_sync_polarity: FrameSyncPolarity, + pub frame_sync_active_level_length: word::U7, + pub frame_sync_definition: FrameSyncDefinition, + pub frame_length: u8, + pub clock_strobe: ClockStrobe, + pub output_drive: OutputDrive, + pub master_clock_divider: MasterClockDivider, + pub is_high_impedenane_on_inactive_slot: bool, + pub fifo_threshold: FifoThreshold, + pub companding: Companding, + pub complement_format: ComplementFormat, + pub mute_value: MuteValue, + pub mute_detection_counter: word::U5, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: Mode::Master, + is_sync_output: false, + sync_enable: SyncEnable::Asynchronous, + protocol: Protocol::Free, + slot_size: SlotSize::DataSize, + slot_count: word::U4(2), + first_bit_offset: word::U5(0), + slot_enable: 0b11, + data_size: DataSize::Data16, + stereo_mono: StereoMono::Stereo, + bit_order: BitOrder::LsbFirst, + frame_sync_offset: FrameSyncOffset::BeforeFirstBit, + frame_sync_polarity: FrameSyncPolarity::ActiveLow, + frame_sync_active_level_length: word::U7(16), + frame_sync_definition: FrameSyncDefinition::ChannelIdentification, + frame_length: 32, + master_clock_divider: MasterClockDivider::MasterClockDisabled, + clock_strobe: ClockStrobe::Rising, + output_drive: OutputDrive::Immediately, + is_high_impedenane_on_inactive_slot: false, + fifo_threshold: FifoThreshold::ThreeQuarters, + companding: Companding::None, + complement_format: ComplementFormat::TwosComplement, + mute_value: MuteValue::Zero, + mute_detection_counter: word::U5(4), + } + } +} + +impl Config { + pub fn new_i2s() -> Self { + return Default::default(); + } + + pub fn new_msb_first() -> Self { + Self { + bit_order: BitOrder::MsbFirst, + frame_sync_offset: FrameSyncOffset::OnFirstBit, + ..Default::default() + } + } +} + +#[derive(Copy, Clone)] +pub enum SubBlock { + A = 0, + B = 1, +} + +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>), +} + +#[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] +fn wdr(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>, + fs: Option>, + sck: Option>, + mclk: Option>, + ring_buffer: RingBuffer<'d, C, W>, + sub_block: SubBlock, +} + +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), + } + } + + pub fn new_asynchronous_transmitter_with_mclk_a( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + sd: impl Peripheral

> + 'd, + fs: impl Peripheral

> + 'd, + mclk: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + mut config: Config, + ) -> Self + where + C: Channel + DmaA, + { + into_ref!(mclk); + + mclk.set_as_af(mclk.af_num(), AFType::OutputPushPull); + 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) + } + + pub fn new_asynchronous_transmitter_a( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + sd: impl Peripheral

> + 'd, + fs: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + config: Config, + ) -> Self + where + C: Channel + DmaA, + { + into_ref!(peri, dma, sck, sd, fs); + + let (sd_af_type, ck_af_type) = Self::get_transmitter_af_types(config.mode); + sd.set_as_af(sd.af_num(), sd_af_type); + sd.set_speed(crate::gpio::Speed::VeryHigh); + + sck.set_as_af(sck.af_num(), ck_af_type); + sck.set_speed(crate::gpio::Speed::VeryHigh); + 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; + + Self::new_inner( + peri, + sub_block, + Some(sck.map_into()), + 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) + }), + config, + ) + } + + pub fn new_asynchronous_transmitter_with_mclk_b( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + sd: impl Peripheral

> + 'd, + fs: impl Peripheral

> + 'd, + mclk: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + mut config: Config, + ) -> Self + where + C: Channel + DmaB, + { + into_ref!(mclk); + + mclk.set_as_af(mclk.af_num(), AFType::OutputPushPull); + 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) + } + + pub fn new_asynchronous_transmitter_b( + peri: impl Peripheral

+ 'd, + sck: impl Peripheral

> + 'd, + sd: impl Peripheral

> + 'd, + fs: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + dma_buf: &'d mut [W], + config: Config, + ) -> Self + where + C: Channel + DmaB, + { + into_ref!(dma, peri, sck, sd, fs); + + let (sd_af_type, ck_af_type) = Self::get_transmitter_af_types(config.mode); + + sd.set_as_af(sd.af_num(), sd_af_type); + sd.set_speed(crate::gpio::Speed::VeryHigh); + + sck.set_as_af(sck.af_num(), ck_af_type); + sck.set_speed(crate::gpio::Speed::VeryHigh); + 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; + + Self::new_inner( + peri, + sub_block, + Some(sck.map_into()), + 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) + }), + config, + ) + } + + pub fn start(self: &mut Self) { + match self.ring_buffer { + RingBuffer::Writable(ref mut rb) => { + rb.start(); + } + RingBuffer::Readable(ref mut rb) => { + rb.start(); + } + } + } + + fn is_transmitter(ring_buffer: &RingBuffer) -> bool { + match ring_buffer { + RingBuffer::Writable(_) => true, + _ => false, + } + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + sub_block: SubBlock, + sck: Option>, + mclk: Option>, + sd: Option>, + fs: Option>, + ring_buffer: RingBuffer<'d, C, W>, + config: Config, + ) -> Self { + T::enable(); + T::reset(); + + #[cfg(any(sai_v4))] + { + // Not totally clear from the datasheet if this is right + // This is only used if using SyncEnable::External + let value: u8 = if T::REGS.as_ptr() == stm32_metapac::SAI1.as_ptr() { + 1 //this is SAI1, so sync with SAI2 + } else { + 0 //this is SAI2, so sync with SAI1 + }; + T::REGS.gcr().modify(|w| { + w.set_syncin(value); + }); + + if config.is_sync_output { + let syncout: u8 = match sub_block { + SubBlock::A => 0b01, + SubBlock::B => 0b10, + }; + T::REGS.gcr().modify(|w| { + w.set_syncout(syncout); + }); + } + } + + #[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_mode(config.mode.mode(if Self::is_transmitter(&ring_buffer) { + TxRx::Transmiter + } else { + TxRx::Receiver + })); + w.set_prtcfg(config.protocol.prtcfg()); + w.set_ds(config.data_size.ds()); + w.set_lsbfirst(config.bit_order.lsbfirst()); + w.set_ckstr(config.clock_strobe.ckstr()); + w.set_syncen(config.sync_enable.syncen()); + w.set_mono(config.stereo_mono.mono()); + w.set_outdriv(config.output_drive.outdriv()); + w.set_mckdiv(config.master_clock_divider.mckdiv()); + w.set_nodiv( + if config.master_clock_divider == MasterClockDivider::MasterClockDisabled { + vals::Nodiv::NODIV + } else { + vals::Nodiv::MASTERCLOCK + }, + ); + w.set_dmaen(true); + }); + + ch.cr2().modify(|w| { + w.set_fth(config.fifo_threshold.fth()); + w.set_comp(config.companding.comp()); + w.set_cpl(config.complement_format.cpl()); + w.set_muteval(config.mute_value.muteval()); + w.set_mutecnt(config.mute_detection_counter.0 as u8); + w.set_tris(config.is_high_impedenane_on_inactive_slot); + }); + + ch.frcr().modify(|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_frl(config.frame_length - 1); + }); + + ch.slotr().modify(|w| { + w.set_nbslot(config.slot_count.0 as u8 - 1); + w.set_slotsz(config.slot_size.slotsz()); + w.set_fboff(config.first_bit_offset.0 as u8); + w.set_sloten(vals::Sloten(config.slot_enable as u16)); + }); + + ch.cr1().modify(|w| w.set_saien(true)); + } + + Self { + _peri: peri.into_ref(), + sub_block, + sck, + mclk, + sd, + fs, + ring_buffer, + } + } + + pub fn flush(&mut self) { + let ch = T::REGS.ch(self.sub_block as usize); + ch.cr1().modify(|w| w.set_saien(false)); + #[cfg(any(sai_v1, sai_v2))] + { + ch.cr2().modify(|w| w.set_fflush(vals::Fflush::FLUSH)); + } + #[cfg(any(sai_v3, sai_v4))] + { + ch.cr2().modify(|w| w.set_fflush(true)); + } + ch.cr1().modify(|w| w.set_saien(true)); + } + + pub fn set_mute(&mut self, value: bool) { + let ch = T::REGS.ch(self.sub_block as usize); + ch.cr2().modify(|w| w.set_mute(value)); + } + + /// Reconfigures it with the supplied config. + pub fn reconfigure(&mut self, _config: Config) {} + + pub fn get_current_config(&self) -> Config { + Config::default() + } + + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + match &mut self.ring_buffer { + RingBuffer::Writable(buffer) => { + buffer.write_exact(data).await?; + Ok(()) + } + _ => return Err(Error::NotATransmitter), + } + } + + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + match &mut self.ring_buffer { + RingBuffer::Readable(buffer) => { + buffer.read_exact(data).await?; + Ok(()) + } + _ => Err(Error::NotAReceiver), + } + } +} + +impl<'d, T: Instance, C: Channel, W: word::Word> Drop for Sai<'d, T, C, W> { + fn drop(&mut self) { + let ch = T::REGS.ch(self.sub_block as usize); + ch.cr1().modify(|w| w.set_saien(false)); + self.fs.as_ref().map(|x| x.set_as_disconnected()); + self.sd.as_ref().map(|x| x.set_as_disconnected()); + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.mclk.as_ref().map(|x| x.set_as_disconnected()); + } +} + +pub(crate) mod sealed { + use super::*; + + pub trait Instance { + const REGS: Regs; + } +} + +pub trait Word: word::Word {} + +pub trait Instance: Peripheral

+ sealed::Instance + RccPeripheral {} +pin_trait!(SckAPin, Instance); +pin_trait!(SckBPin, Instance); +pin_trait!(FsAPin, Instance); +pin_trait!(FsBPin, Instance); +pin_trait!(SdAPin, Instance); +pin_trait!(SdBPin, Instance); +pin_trait!(MclkAPin, Instance); +pin_trait!(MclkBPin, Instance); + +dma_trait!(DmaA, Instance); +dma_trait!(DmaB, Instance); + +foreach_peripheral!( + (sai, $inst:ident) => { + impl sealed::Instance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +impl<'d, T: Instance, C: Channel, W: word::Word> SetConfig for Sai<'d, T, C, W> { + type Config = Config; + fn set_config(&mut self, config: &Self::Config) { + self.reconfigure(*config); + } +}