diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs new file mode 100644 index 00000000..1fcee226 --- /dev/null +++ b/embassy-stm32/src/sai/mod.rs @@ -0,0 +1,888 @@ +#![macro_use] + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::dma::{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::time::Hertz; +use crate::{peripherals, Peripheral}; + +pub use crate::dma::word; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + NotATransmitter, + NotAReceiver, +} + +#[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>), + 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, + } + } + + 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)); + } + + 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); + } +}