//! Serial audio interface (SAI) support, for digital audio input and output. Used for I2S, PCM/DSP, TDM,
//! AC'97 etc. See L443 Reference Manual, section 41, or H743 RM, section 51.

use core::ops::Deref;

#[cfg(any(feature = "f3", feature = "l4"))]
use crate::dma::DmaInput;
#[cfg(not(any(feature = "f4", feature = "l552")))]
use crate::dma::{self, ChannelCfg, Dma, DmaChannel};
#[cfg(feature = "g0")]
use crate::pac::dma as dma_p;
#[cfg(any(
    feature = "f3",
    feature = "l4",
    feature = "l5",
    feature = "g4",
    feature = "h7",
    feature = "wb"
))]
use crate::pac::dma1 as dma_p;
#[cfg(feature = "g4")]
use crate::pac::sai;
#[cfg(not(any(feature = "g4", feature = "h7")))]
use crate::pac::sai1 as sai;
#[cfg(feature = "h7")]
use crate::pac::sai4 as sai;
use crate::{clocks::Clocks, pac::RCC, util::RccPeriph};

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select Master or Slave mode. Sets xCR1 register, MODE field.
/// The audio subblock can be a transmitter or receiver, in master or slave mode. The master
/// mode means the SCK_x bit clock and the frame synchronization signal are generated from
/// the SAI, whereas in slave mode, they come from another external or internal master. There
/// is a particular case for which the FS signal direction is not directly linked to the master or
/// slave mode definition. In AC’97 protocol, it will be an SAI output even if the SAI (link
/// controller) is set-up to consume the SCK clock (and so to be in Slave mode).
pub enum SaiMode {
    /// The SAI delivers the timing signals to the external connected device:
    /// The bit clock and the frame synchronization are output on pin SCK_x and FS_x,
    /// respectively. Both SCK_x, FS_x and MCLK_x are configured as outputs.
    /// If needed, the SAI can also generate a master clock on MCLK_x pin.
    MasterTransmitter = 0b00,
    MasterReceiver = 0b01,
    /// The SAI expects to receive timing signals from an external device.
    /// If the SAI subblock is configured in asynchronous mode, then SCK_x and FS_x pins
    /// are configured as inputs.
    /// If the SAI subblock is configured to operate synchronously with another SAI interface or
    /// with the second audio subblock, the corresponding SCK_x and FS_x pins are left free
    /// to be used as general purpose I/Os.
    SlaveTransmitter = 0b10,
    SlaveReceiver = 0b11,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select Stereo or Mono mode. Sets xCR1 register, MONO field.
pub enum Mono {
    Stereo = 0,
    Mono = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select if signals generated by SAI change on SCK rising, or falling edge. Sets xCR1 register,
/// CKSTR field.
pub enum ClockStrobe {
    /// Signals generated by the SAI change on SCK rising edge, while signals received by the SAI are
    /// sampled on the SCK falling edge.
    TransmitRisingEdge = 0,
    /// Signals generated by the SAI change on SCK falling edge, while signals received by the SAI are
    /// sampled on the SCK rising edge.
    TransmitFallingEdge = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Frame synchronization offset.
/// Depending on the audio protocol targeted in the application, the Frame synchronization
/// signal can be asserted when transmitting the last bit or the first bit of the audio frame (this is
/// the case in I2S standard protocol and in MSB-justified protocol, respectively).
/// Sets FRCR register, FSOFF field.
pub enum FsOffset {
    /// FS is asserted on the first bit of the slot 0.
    FirstBit = 0,
    /// FS is asserted one bit before the first bit of the slot 0
    BeforeFirstBit = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// This bit is set and cleared by software. It is used to configure the level of the start of frame on the FS
/// signal. It is meaningless and is not used in AC’97 or SPDIF audio block configuration.
/// This bit must be configured when the audio block is disabled.
/// Sets FRCR register, FSPOL field.
pub enum FsPolarity {
    /// FS is active low (falling edge)
    ActiveLow = 0,
    /// FS is active high (rising edge)
    ActiveHigh = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Set Where the signal is in the frame. Sets FRCR register, FSDEF field.
pub enum FsSignal {
    /// Start of frame, like for instance the PCM/DSP, TDM, AC’97, audio protocols.
    /// Sets FRCR register, FSDEF field.
    Frame = 0,
    /// Start of frame and channel side identification within the audio frame like for the I2S,
    /// the MSB or LSB-justified protocols.
    FrameAndChannel = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Set which bit is transmitted first: Least significant, or Most significant. You may have to
/// choose the one used by your SAI device. Sets xCR1 register, LSBFIRST field.
pub enum FirstBit {
    /// Data are transferred with MSB first
    MsbFirst = 0,
    /// Data are transferred with LSB first
    LsbFirst = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select the number of connected PDM mics. It is possible to select
/// between 2,4,6 or 8 microphones. For example, if the application is using 3 microphones, the
/// user has to select 4.
/// Sets PDMCR register, MICNBR field.
pub enum NumPdmMics {
    /// Configuration with 2 microphones
    N2 = 0b00,
    /// Configuration with 4 microphones
    N4 = 0b01,
    /// Configuration with 6 microphones
    N6 = 0b10,
    /// Configuration with 8 microphones
    N7 = 0b11,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// FIFO threshold. Affects xCR2 reg, FTH field. Affects when SAI interrupts, and
/// DMA transfers occur.
pub enum FifoThresh {
    /// FIFO empty (transmitter and receiver modes)
    Empty = 0b000,
    /// FIFO ≤ ¼ but not empty (transmitter mode), FIFO < ¼ but not empty (receiver mode)
    T1_4 = 0b001,
    /// ¼ < FIFO ≤ ½ (transmitter mode), ¼ ≤ FIFO < ½ (receiver mode)
    T1_2 = 0b010,
    /// ½ < FIFO ≤ ¾ (transmitter mode), ½ ≤ FIFO < ¾ (receiver mode)
    T3_4 = 0b011,
    /// ¾ < FIFO but not full (transmitter mode), ¾ ≤ FIFO but not full (receiver mode)
    T3_4B = 0b100,
    /// FIFO full (transmitter and receiver modes)
    Full = 0b101,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Oversampling ratio for master clock. You may have to
/// choose the one used by your SAI device. Sets xCR1 register, OSR field.
pub enum OversamplingRatio {
    /// Master clock frequency = F_FS x 256
    FMul256 = 0,
    /// Master clock frequency = F_FS x 512
    FMul512 = 1,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Specify wheather sub-clocks A and B are synchronized. Sets xCR1 register, SYNCEN field.
pub enum SyncMode {
    /// Audio sub-block in asynchronous mode
    Async = 0b00,
    /// Audio sub-block is synchronous with the other internal audio sub-block. In this case, the audio
    /// sub-block must be configured in slave mode
    Sync = 0b01,
    /// Audio subblock is synchronous with an external SAI embedded peripheral. In this case the audio
    /// subblock should be configured in Slave mode.
    SyncExternal = 0b10, // todo: May only be valid for some MCUs, eg ones with multiple SAI devices.
}

#[cfg(not(feature = "l4"))]
#[derive(Clone, Copy)]
#[repr(u8)]
/// Synchronization outputs. Sets xGCR register, SYNCOUT field. Must be set when SAI is diabled.
/// Not block specific.
pub enum SyncOut {
    /// No synchronization output signals. SYNCOUT[1:0] should be configured as “No synchronization
    /// output signals” when audio block is configured as SPDIF
    NoSync = 0b00,
    /// Block A used for further synchronization for others SAI
    BlockA = 0b01,
    ///  Block B used for further synchronization for others SAI
    BlockB = 0b10,
}

#[cfg(not(feature = "l4"))]
#[derive(Clone, Copy)]
#[repr(u8)]
/// Synchronization inputs. Sets xGCR register, SYNCIn field. Must be set when SAI is diabled.
/// Not block specific. Syncs this SAI peripheral with the one specified here, if `sync_mode` is
/// configured as `SyncExternal`. The block synchronized with is controlled by that SAI's `sync_out`
/// setting.
pub enum SyncIn {
    /// Sync with SAI1 as master.
    Sai1 = 0,
    /// Sync with SAI2 as master.
    Sai2 = 1,
    /// Sync with SAI3 as master.
    Sai3 = 2,
    /// Sync with SAI4 as master.
    Sai4 = 3,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select the audio protocol to use. xCR1 register, PRTCFG field.
pub enum Protocol {
    /// Free protocol. Free protocol allows to use the powerful configuration of the audio block to
    /// address a specific audio protocol (such as I2S, LSB/MSB justified, TDM, PCM/DSP...) by setting
    /// most of the configuration register bits as well as frame configuration register.
    Free = 0b00,
    /// SPDIF protocol
    Spdif = 0b01,
    /// AC'97 protocol
    Ac97 = 0b10,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select the data size to use. xCR1 register, DS field.
pub enum DataSize {
    /// 8 bits
    S8 = 0b010,
    /// 10 bits
    S10 = 0b011,
    /// 16 bits
    S16 = 0b100,
    /// 20 bits
    S20 = 0b101,
    /// 24 bits
    S24 = 0b110,
    /// 32 bits
    S32 = 0b111,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select the slot size to use. xSLOTR register, SLOTSZ field.
pub enum SlotSize {
    /// The slot size is equivalent to the data size (specified in DS[3:0] in the SAI_xCR1 register)
    DataSize = 0b00,
    /// 16 bits
    S16 = 0b01,
    /// 32 bits
    S32 = 0b10,
}

#[derive(Clone, Copy)]
#[repr(u8)]
/// Select wheather the master clock is generated. xDR1 register, NOMCK field on H7.
/// on other variants such as WB, affects the MCKEN and NODIV fields (?).
pub enum MasterClock {
    // These bit values are for NOMCK, ie on H7. We use inverse logic when setting the bits
    // on other variants.
    /// (H7): Master clock generator is enabled
    Used = 0,
    /// (H7):  Master clock generator is disabled. The clock divider controlled by MCKDIV can still be used to
    /// generate the bit clock.
    NotUsed = 1,
}

#[derive(Clone, Copy)]
/// The type of SAI interrupt to configure. Reference Section 41.5 of the L4 RM.
/// Enabled in xIM register, yIE fields. See H743 RM, section 51.5: SAI interrupts.
pub enum SaiInterrupt {
    /// FIFO request interrupt enable. When this bit is set, an interrupt is generated if the FREQ bit in the SAI_xSR register is set.
    /// Since the audio block defaults to operate as a transmitter after reset, the MODE bit must be
    /// configured before setting FREQIE to avoid a parasitic interrupt in receiver mode
    Freq,
    /// When the audio block is configured as receiver, an overrun condition may appear if data are
    /// received in an audio frame when the FIFO is full and not able to store the received data. In
    /// this case, the received data are lost, the flag OVRUDR in the SAI_xSR register is set and an
    /// interrupt is generated if OVRUDRIE bit is set in the SAI_xIM register.
    ///
    /// An underrun may occur when the audio block in the SAI is a transmitter and the FIFO is
    /// empty when data need to be transmitted. If an underrun is detected, the slot number for
    /// which the event occurs is stored and MUTE value (00) is sent until the FIFO is ready to
    /// transmit the data corresponding to the slot for which the underrun was detected (refer to
    /// Figure 664). This avoids desynchronization between the memory pointer and the slot in the
    /// audio frame.
    Ovrudr,
    /// The AFSDET flag is used only in slave mode. It is never asserted in master mode. It
    /// indicates that a frame synchronization (FS) has been detected earlier than expected since
    /// the frame length, the frame polarity, the frame offset are defined and known.
    AfsDet,
    /// The LFSDET flag in the SAI_xSR register can be set only when the SAI audio block
    /// operates as a slave. The frame length, the frame polarity and the frame offset configuration
    /// are known in register SAI_xFRcr().
    LfsDet,
    /// The CNRDY flag in the SAI_xSR register is relevant only if the SAI audio block is configured
    /// to operate in AC’97 mode (PRTCFG[1:0] = 10 in the SAI_xCR1 register). If CNRDYIE bit is
    /// set in the SAI_xIM register, an interrupt is generated when the CNRDY flag is set.
    /// CNRDY is asserted when the Codec is not ready to communicate during the reception of
    /// the TAG 0 (slot0) of the AC’97 audio frame.
    CnRdy,
    /// Mute detection
    MuteDet,
    /// When the audio block operates as a master (MODE[1] = 0) and NOMCK bit is equal to 0,
    /// the WCKCFG flag is set as soon as the SAI is enabled if the following conditions are met:
    /// • (FRL+1) is not a power of 2, and
    /// • (FRL+1) is not between 8 and 256.
    /// MODE, NOMCK, and SAIEN bits belong to SAI_xCR1 register and FRL to SAI_xFRCR
    /// register.
    WckCfg,
}

#[derive(Clone, Copy)]
pub enum SaiChannel {
    A,
    B,
}

/// Configuration for the SAI peripheral. Mainly affects the ACR and BCR registers.
/// Used for either channel. For details, see documentation of individual structs and fields.
/// You may be forced into certain settings based on the device used.
#[derive(Clone)]
pub struct SaiConfig {
    pub mode: SaiMode,
    /// Select protocols between Free, Ac'97, and SPDIF. Defaults to Free.
    pub protocol: Protocol,
    /// Select mono or stereo modes. Default to mono.
    pub mono: Mono,
    /// An audio subblock can be configured to operate synchronously with the second audio
    /// subblock in the same SAI. In this case, the bit clock and the frame synchronization signals
    /// are shared to reduce the number of external pins used for the communication. Default to async.
    pub sync: SyncMode,
    /// Used for synchronization with other SAI blocks and peripherals. Set this using the A config.
    /// Controls which block is the master synchronization signal for other SAI peripherals.
    #[cfg(not(feature = "l4"))]
    pub sync_out: SyncOut,
    /// Used for synchronization with other SAI blocks and peripherals. Set this using the A config.
    /// Configurd to sync with SAI1, if syncmode is external.
    #[cfg(not(feature = "l4"))]
    pub sync_in: SyncIn,
    /// Clock strobing edge. Defaults to Signals generated by the SAI change on SCK rising edge, while signals received by the SAI are
    /// sampled on the SCK falling edge
    pub clock_strobe: ClockStrobe,
    /// Size of the data in the slot. Defaults to 24-bit.
    pub datasize: DataSize,
    /// Size of the slot; can be 16 or 32 bits. Defaults to the same or larger for the data size.
    pub slotsize: SlotSize,
    /// Select wheather the master clock out is enabled, eg for syncing external devices. Defaults
    /// to disabled.
    pub master_clock: MasterClock,
    pub first_bit: FirstBit,
    pub oversampling_ratio: OversamplingRatio,
    /// Define the audio frame length expressed in number
    /// of SCK clock cycles: the number of bits in the frame is equal to FRL[7:0] + 1.
    /// The minimum number of bits to transfer in an audio frame must be equal to 8, otherwise the audio
    /// block will behaves in an unexpected way. This is the case when the data size is 8 bits and only one
    /// slot 0 is defined in NBSLOT[4:0] of SAI_xSLOTR register (NBSLOT[3:0] = 0000).
    /// In master mode, if the master clock (available on MCLK_x pin) is used, the frame length should be
    /// aligned with a number equal to a power of 2, ranging from 8 to 256. When the master clock is not
    /// used (NOMCK = 1), it is recommended to program the frame length to an value ranging from 8 to
    /// 256.
    pub frame_length: u16, // u16 to allow the value of 256.
    pub fs_offset: FsOffset,
    /// Active high, or active low polarity. Defaults to active high.
    pub fs_polarity: FsPolarity,
    /// Start of frame. Default to frame and channel.
    pub fs_signal: FsSignal,
    /// Number of slots. Defaults to 2.
    pub num_slots: u8,
    /// The FIFO threshold configures when the FREQ interrupt is generated based on how full
    /// the FIFO is.
    pub fifo_thresh: FifoThresh,
    /// These bits are set and cleared by software.
    /// The value set in this bitfield defines the position of the first data transfer bit in the slot. It represents
    /// an offset value. In transmission mode, the bits outside the data field are forced to 0. In reception
    /// mode, the extra received bits are discarded.
    /// These bits must be set when the audio block is disabled.
    /// They are ignored in AC’97 or SPDIF mode.
    pub first_bit_offset: u8,
    /// Enable Pulse Density Modulation (PDM) functionality, eg for digital microphones.
    /// See the relevant ST Application note: AN5027
    pub pdm_mode: bool,
    /// The number of connected PDM mics, if applicable. Defualts to 2.
    pub num_pdm_mics: NumPdmMics,
    /// Which PDM CK line to enable. Must be 1-4. Defaults to 1. (CK1 in User manuals)
    pub pdm_clock_used: u8,
    /// Master clock divider. Divides the kernel clock input. Defaults to 0, for no division.
    pub mckdiv: u8,
}

impl Default for SaiConfig {
    fn default() -> Self {
        Self {
            mode: SaiMode::MasterTransmitter,
            protocol: Protocol::Free,
            mono: Mono::Stereo,
            sync: SyncMode::Async,
            #[cfg(not(feature = "l4"))]
            sync_out: SyncOut::NoSync,
            #[cfg(not(feature = "l4"))]
            sync_in: SyncIn::Sai1,
            clock_strobe: ClockStrobe::TransmitRisingEdge,
            datasize: DataSize::S24,
            slotsize: SlotSize::DataSize,
            master_clock: MasterClock::NotUsed,
            first_bit: FirstBit::MsbFirst,
            oversampling_ratio: OversamplingRatio::FMul256,
            frame_length: 64,
            // fs_level_len: 32, // For now, we always use frame_length / 2, for 50% duty cycle.
            fs_offset: FsOffset::FirstBit,
            fs_polarity: FsPolarity::ActiveHigh,
            fs_signal: FsSignal::FrameAndChannel, // Use FrameAndChannel for I2S.
            num_slots: 2,
            first_bit_offset: 0,
            fifo_thresh: FifoThresh::T1_4,
            pdm_mode: false,
            num_pdm_mics: NumPdmMics::N2,
            pdm_clock_used: 1,
            mckdiv: 0,
        }
    }
}

// todo: Populate these presets
impl SaiConfig {
    /// Default configuration for I2S.
    pub fn i2s_preset() -> Self {
        Self {
            // Use our default of 2 slots, and a frame length of 64 bits, to allow for up
            // to 32 bits per slot.
            // We also use our default fifo thresh of 1/4 of the total size of 8 words,
            // ie 1 word per channel
            // Note that we include some settings here that are present in default,
            // for explicitness. (ie required by I2S, but perhaps arbitrary in default)
            first_bit: FirstBit::MsbFirst,
            // H743 RM: Frame schronization offset: Depending on the audio protocol targeted in the
            // application, the Frame synchronization signal can be asserted when transmitting
            // the last bit or the first bit of the audio frame (this is the case in I2S standard
            // protocol and in MSB-justified protocol, respectively)
            fs_offset: FsOffset::BeforeFirstBit,
            // RM: this bit has to be set for I2S or MSB/LSB-justified protocols.
            fs_signal: FsSignal::FrameAndChannel,
            protocol: Protocol::Free,
            datasize: DataSize::S24,
            frame_length: 64,
            num_slots: 2,
            // From the INMP441 datasheet: The default data format is I²S (two’s complement), MSB-first.
            // In this format, the MSB of each word is delayed by one SCK cycle from
            // the start of each half-frame
            // Note that this is already handled by by `fs_offset`.
            first_bit_offset: 0,
            fifo_thresh: FifoThresh::T1_4,
            pdm_mode: false,

            ..Default::default()
        }
    }

    /// Default configuration for TDM. Configures an I2S-style delay of 1 between FS and
    /// data start. Configures the FS signal to be a pulse indicating frame start. Sets
    /// window length based on data size and number of slots.
    pub fn tdm_preset(num_slots: u8, slotsize: SlotSize) -> Self {
        // let frame_length = match datasize {
        //     DataSize::S8 => 8 * num_slots as u16,
        //     DataSize::S10 => 10 * num_slots as u16,
        //     DataSize::S16 => 16 * num_slots as u16,
        //     DataSize::S20 => 20 * num_slots as u16,
        //     DataSize::S24 => 24 * num_slots as u16,
        //     DataSize::S32 => 32 * num_slots as u16,
        // };

        let frame_length = match slotsize {
            SlotSize::S16 => 16 * num_slots as u16,
            SlotSize::S32 => 32 * num_slots as u16,
            SlotSize::DataSize => panic!(),
        };

        Self {
            first_bit: FirstBit::MsbFirst,
            fs_offset: FsOffset::BeforeFirstBit,
            fs_signal: FsSignal::Frame,
            protocol: Protocol::Free,
            slotsize,
            frame_length,
            num_slots,
            first_bit_offset: 0,
            // todo: Smartly set up fifo thresh based on number of slots?
            fifo_thresh: FifoThresh::Full,
            pdm_mode: false,

            ..Default::default()
        }
    }

    /// Default configuration for PDM microphones. See H743 RM, Table 422. TDM settings.
    /// See table 423 for how to configure Frame Length, and number of slots.
    /// This default configures for 48kHz sample rate, assuming 3.072Mhz SAI clock,
    /// and 1 slots of 16 bits per frame. If using something else, see Table 423, and
    /// modify as required.
    pub fn pdm_mic_preset(num_mics: NumPdmMics, clock_used: u8) -> Self {
        Self {
            // These first settings (up to `pdm_mode1) are taken directly from Table 422.
            //Mode must be MASTER receiver
            mode: SaiMode::MasterReceiver,

            // Free protocol for TDM
            protocol: Protocol::Free,
            // Signal transitions occur on the rising edge of the SCK_A bit clock. Signals
            // are stable on the falling edge of the bit clock.
            clock_strobe: ClockStrobe::TransmitRisingEdge,
            mono: Mono::Stereo,
            // Note: FSALL is set to 0, to set Pulse width is one bit clock cycle.
            // We handle that directly in `new()`.
            // FS signal is a start of frame
            fs_signal: FsSignal::Frame,
            // FS is active High
            fs_polarity: FsPolarity::ActiveHigh,
            // FS is asserted on the first bit of slot 0
            fs_offset: FsOffset::FirstBit,
            // No offset on slot
            first_bit_offset: 0,
            master_clock: MasterClock::NotUsed,

            // These next 3 settings may depend on master clock speed, sample rate, and number
            // of mics. See table 423.
            // This is currently set for 2 mics, 1 slot of 16-bits per frame.
            // 3.072Mhz SAI freq.
            // RM: FRL = (16 x (MICNBR + 1)) - 1
            frame_length: (16 * (num_mics as u8 as u16 + 1)) - 1,
            // todo: Make these more flexible instead of hard-coded
            datasize: DataSize::S16,
            num_slots: 1,
            fifo_thresh: FifoThresh::Empty,

            pdm_mode: true,
            num_pdm_mics: num_mics,
            pdm_clock_used: clock_used,
            ..Default::default()
        }
    }

    /// Default configuration for AC'97
    pub fn ac97_preset() -> Self {
        Self {
            protocol: Protocol::Ac97,
            // Start of frame, like for instance the PCM/DSP, TDM, AC’97 audio protocols,
            fs_signal: FsSignal::Frame,
            // Note that AC97 uses 13 slots, but with the AC97 protocol set, the slots setting is
            // ignored.
            ..Default::default()
        }
    }

    /// Default configuration for SPDIF
    pub fn spdif_preset() -> Self {
        Self {
            protocol: Protocol::Spdif,
            ..Default::default()
        }
    }
}

/// Represents the Serial Audio Interface (SAI) peripheral, used for digital audio
/// input and output.
pub struct Sai<R> {
    pub regs: R,
    config_a: SaiConfig,
    config_b: SaiConfig,
}

impl<R> Sai<R>
where
    R: Deref<Target = sai::RegisterBlock> + RccPeriph,
{
    /// Initialize a SAI peripheral, including  enabling and resetting
    /// its RCC peripheral clock.
    pub fn new(regs: R, config_a: SaiConfig, config_b: SaiConfig, _clocks: &Clocks) -> Self {
        let rcc = unsafe { &(*RCC::ptr()) };
        R::en_reset(rcc);

        // todo: Do we always want to configure and enable both A and B?

        // Set the master clock divider.

        // See H7 RM, Table 421.

        // mckdiv = SAI clock / (sampling freq * 256) ?? (512 for oversampling?)

        // with NOMCK = 1: (No master clock)
        // F_SCK = F_sai_ker_ck / MCKDIV
        // F_FS = F_sai_ker_ck / ((FRL + 1) * MCKDIV)

        // 6-bit fields.
        assert!(config_a.mckdiv <= 0b111111);
        assert!(config_b.mckdiv <= 0b111111);

        // For info on modes, reference H743 RM, section 51.4.3: "Configuring and
        // Enabling SAI modes".
        regs.cha().cr1().modify(|_, w| unsafe {
            w.mode().bits(config_a.mode as u8);
            w.prtcfg().bits(config_a.protocol as u8);
            w.mono().bit(config_a.mono as u8 != 0);
            w.syncen().bits(config_a.sync as u8);
            w.ckstr().bit(config_a.clock_strobe as u8 != 0);
            // The NOMCK bit of the SAI_xCR1 register is used to define whether the master clock is
            // generated or not.
            // Inversed polarity on non-H7 based on how we have `MasterClock` enabled.
            #[cfg(not(any(feature = "h7", feature = "l4", feature = "l5")))]
            w.mcken().bit(config_a.master_clock as u8 == 0);
            #[cfg(feature = "h7")]
            // Due to an H743v PAC error, xCR bit 19 is called NODIV (Which is how it is on other platforms).
            // This is actually the NOMCK bit.
            // todo: NODIV may need to be set by the user and presets - not hard-set like this!
            w.nodiv().bit(config_a.master_clock as u8 != 0);
            // The audio frame can target different data sizes by configuring bit DS[2:0] in the SAI_xCR1
            // register. The data sizes may be 8, 10, 16, 20, 24 or 32 bits. During the transfer, either the
            // MSB or the LSB of the data are sent first, depending on the configuration of bit LSBFIRST in
            // the SAI_xCR1 register.
            w.ds().bits(config_a.datasize as u8);
            #[cfg(not(feature = "l4"))]
            w.osr().bit(config_a.oversampling_ratio as u8 != 0);
            // This bit is set and cleared by software. It must be configured when the audio block is disabled. This
            // bit has no meaning in AC’97 audio protocol since AC’97 data are always transferred with the MSB
            // first. This bit has no meaning in SPDIF audio protocol since in SPDIF data are always transferred
            // with LSB first
            w.lsbfirst().bit(config_a.first_bit as u8 != 0);
            w.mckdiv().bits(config_a.mckdiv)
        });
        // todo: MCKEN vice NOMCK?? Make sure your enum reflects how you handle it.

        // RM: External synchronization
        // The audio subblocks can also be configured to operate synchronously with another SAI.
        // This can be done as follow:
        // 1. The SAI, which is configured as the source from which the other SAI is synchronized,
        // has to define which of its audio subblock is supposed to provide the FS and SCK
        // signals to other SAI. This is done by programming SYNCOUT[1:0] bits.
        // 2. The SAI which shall receive the synchronization signals has to select which SAI will
        // provide the synchronization by setting the proper value on SYNCIN[1:0] bits. For each
        // of the two SAI audio subblocks, the user must then specify if it operates synchronously
        // with the other SAI via the SYNCEN bit.
        // Note: SYNCIN[1:0] and SYNCOUT[1:0] bits are located into the SAI_GCR register, and SYNCEN
        // bits into SAI_xCR1 register.
        //
        // If both audio subblocks in a given SAI need to be synchronized with another SAI, it is
        // possible to choose one of the following configurations:
        // • Configure each audio block to be synchronous with another SAI block through the
        // SYNCEN[1:0] bits.
        // • Configure one audio block to be synchronous with another SAI through the
        // SYNCEN[1:0] bits. The other audio block is then configured as synchronous with the
        // second SAI audio block through SYNCEN[1:0] bits.

        // We use config A's settings here, and ignore config B. These must be set with SAI disabled.
        #[cfg(not(any(feature = "l4", feature = "wb", feature = "g4")))]
        regs.gcr().modify(|_, w| unsafe {
            w.syncout().bits(config_a.sync_out as u8);
            w.syncin().bits(config_a.sync_in as u8)
        });

        regs.chb().cr1().modify(|_, w| unsafe {
            w.mode().bits(config_b.mode as u8);
            w.prtcfg().bits(config_b.protocol as u8);
            w.mono().bit(config_b.mono as u8 != 0);
            w.syncen().bits(config_b.sync as u8);
            w.ckstr().bit(config_b.clock_strobe as u8 != 0);
            #[cfg(not(any(feature = "h7", feature = "l4", feature = "l5")))]
            w.mcken().bit(config_b.master_clock as u8 == 0);
            #[cfg(feature = "h7")]
            w.nodiv().bit(config_b.master_clock as u8 != 0);
            w.ds().bits(config_b.datasize as u8);
            #[cfg(not(feature = "l4"))]
            w.osr().bit(config_b.oversampling_ratio as u8 != 0);
            w.lsbfirst().bit(config_b.first_bit as u8 != 0);
            w.mckdiv().bits(config_a.mckdiv)
        });

        // todo: Add this to config and don't hard-set.
        regs.cha().cr2().modify(|_, w| unsafe {
            w.comp().bits(0);
            w.cpl().clear_bit();
            #[cfg(feature = "wb")]
            w.mutecnt().bits(0); // rec only
            #[cfg(not(feature = "wb"))]
            w.muteval().clear_bit(); // xmitter only
            w.mute().clear_bit(); // xmitter only
            w.tris().clear_bit(); // xmitter only
            // The FIFO pointers can be reinitialized when the SAI is disabled by setting bit FFLUSH in the
            // SAI_xCR2 register. If FFLUSH is set when the SAI is enabled the data present in the FIFO
            // will be lost automatically.
            w.fflush().bit(true);
            // FIFO threshold
            w.fth().bits(config_a.fifo_thresh as u8)
        });

        regs.chb().cr2().modify(|_, w| unsafe {
            w.comp().bits(0);
            w.cpl().clear_bit();
            #[cfg(feature = "wb")]
            w.mutecnt().bits(0); // rec only
            #[cfg(not(feature = "wb"))]
            w.muteval().clear_bit(); // xmitter only
            w.mute().clear_bit(); // xmitter only
            w.tris().clear_bit(); // xmitter only
            w.fflush().bit(true);
            w.fth().bits(config_b.fifo_thresh as u8)
        });

        // The FS signal can have a different meaning depending on the FS function. FSDEF bit in the
        // SAI_xFRCR register selects which meaning it will have:
        // • 0: start of frame, like for instance the PCM/DSP, TDM, AC’97, audio protocols,
        // • 1: start of frame and channel side identification within the audio frame like for the I2S,
        // the MSB or LSB-justified protocols.
        // When the FS signal is considered as a start of frame and channel side identification within
        // the frame, the number of declared slots must be considered to be half the number for the left
        // channel and half the number for the right channel. If the number of bit clock cycles on half
        // audio frame is greater than the number of slots dedicated to a channel side, and TRIS = 0, 0
        // is sent for transmission for the remaining bit clock cycles in the SAI_xCR2 register.
        // Otherwise if TRIS = 1, the SD line is released to HI-Z. In reception mode, the remaining bit
        // clock cycles are not considered until the channel side changes.

        if config_a.frame_length < 8
            || config_b.frame_length < 8
            || config_a.frame_length > 256
            || config_b.frame_length > 256
        {
            panic!("Frame length must be bewteen 8 and 256")
        }

        let fsall_bits_a = if let FsSignal::Frame = config_a.fs_signal {
            0
        } else {
            // Hard-set a 50% duty cycle. Don't think this is a safe assumption? Send in an issue
            // or PR.
            (config_a.frame_length / 2) as u8 - 1
        };

        let fsall_bits_b = if let FsSignal::Frame = config_a.fs_signal {
            0
        } else {
            // Hard-set a 50% duty cycle. Don't think this is a safe assumption? Send in an issue
            // or PR.
            (config_a.frame_length / 2) as u8 - 1
        };

        // The audio frame length can be configured to up to 256 bit clock cycles, by setting
        // FRL[7:0] field in the SAI_xFRCR register.
        regs.cha().frcr().modify(|_, w| unsafe {
            w.fsoff().bit(config_a.fs_offset as u8 != 0);
            w.fspol().bit(config_a.fs_polarity as u8 != 0);
            w.fsdef().bit(config_a.fs_signal as u8 != 0);
            w.fsall().bits(fsall_bits_a);
            w.frl().bits((config_a.frame_length - 1) as u8)
        });

        regs.chb().frcr().modify(|_, w| unsafe {
            w.fsoff().bit(config_a.fs_offset as u8 != 0);
            w.fspol().bit(config_b.fs_polarity as u8 != 0);
            w.fsdef().bit(config_b.fs_signal as u8 != 0);
            w.fsall().bits(fsall_bits_b);
            w.frl().bits((config_b.frame_length - 1) as u8)
        });

        assert!(config_a.first_bit_offset <= 0b11111);
        assert!(config_b.first_bit_offset <= 0b11111);

        // Each SLOTEN bit corresponds to a slot position from 0 to 15 (maximum 16 slots).
        // So, to enable the first 2 slots, we set 0b11. The code below calculates this.
        let slot_en_bits = 2_u16.pow(config_a.num_slots as u32) - 1;

        regs.cha().slotr().modify(|_, w| unsafe {
            w.sloten().bits(slot_en_bits);
            // The slot is the basic element in the audio frame. The number of slots in the audio frame is
            // equal to NBSLOT[3:0] + 1.
            w.nbslot().bits(config_a.num_slots - 1);
            // The slot size must be higher or equal to the data size. If this condition is not respected, the behavior
            // of the SAI will be undetermined.
            w.slotsz().bits(config_a.slotsize as u8);
            w.fboff().bits(config_a.first_bit_offset)
        });

        let slot_en_bits = 2_u16.pow(config_b.num_slots as u32) - 1;
        regs.chb().slotr().modify(|_, w| unsafe {
            w.sloten().bits(slot_en_bits);
            w.nbslot().bits(config_b.num_slots - 1);
            w.slotsz().bits(config_b.slotsize as u8);
            w.fboff().bits(config_b.first_bit_offset)
        });

        // The PDM function is intended to be used in conjunction with SAI_A subblock configured in
        // TDM master mode. It cannot be used with SAI_B subblock. The PDM interface uses the
        // timing signals provided by the TDM interface of SAI_A and adapts them to generate a
        // bitstream clock (SAI_CK[m]).

        // AN: The PDM function is intended to be used in conjunction with SAI_A sub-block, configured in
        // Time Division Multiplexing (TDM) master mode. It cannot be used with SAI_B sub-block

        // Enabling the PDM interface (H743 RM, section 51.4.10)
        // To enable the PDM interface, follow the sequence below:
        // 1. Configure SAI_A in TDM master mode (see Table 422).
        // (Above. Although we don't check this)
        // 2. Configure the PDM interface as follows:
        #[cfg(not(feature = "l4"))]
        if config_a.pdm_mode {
            assert!(config_a.pdm_clock_used <= 4 && config_a.pdm_clock_used >= 1);

            regs.pdmcr().modify(|_, w| unsafe {
                // a) Define the number of digital microphones via MICNBR.
                w.micnbr().bits(config_a.num_pdm_mics as u8);
                // b) Enable the bitstream clock needed in the application by setting the corresponding
                // bits on CKEN to 1.
                w.cken1().bit(config_a.pdm_clock_used == 1);
                w.cken2().bit(config_a.pdm_clock_used == 2);
                #[cfg(not(feature = "l5"))]
                w.cken3().bit(config_a.pdm_clock_used == 3);
                #[cfg(not(feature = "l5"))]
                w.cken4().bit(config_a.pdm_clock_used == 4);
                // 3. Enable the PDM interface, via PDMEN bit.
                w.pdmen().bit(true)
            });
        }

        // 4. Enable the SAI_A.
        // (Handled with the `enable()` function called by the user.)
        // Note: Once the PDM interface and SAI_A are enabled, the first 2 TDMA frames received on
        // SAI_ADR are invalid and shall be dropped.

        // Note that most register fields set in this initialization function must be done with
        // SAIEN disabled.

        Self {
            regs,
            config_a,
            config_b,
        }
    }

    /// Enable an audio subblock (channel).
    pub fn enable(&mut self, channel: SaiChannel) {
        // Each of the audio blocks in the SAI are enabled by SAIEN bit in the SAI_xCR1 register. As
        // soon as this bit is active, the transmitter or the receiver is sensitive to the activity on the
        // clock line, data line and synchronization line in slave mode.
        // In master TX mode, enabling the audio block immediately generates the bit clock for the
        // external slaves even if there is no data in the FIFO, However FS signal generation is
        // conditioned by the presence of data in the FIFO. After the FIFO receives the first data to
        // transmit, this data is output to external slaves. If there is no data to transmit in the FIFO, 0
        // values are then sent in the audio frame with an underrun flag generation.
        // In slave mode, the audio frame starts when the audio block is enabled and when a start of
        // frame is detected.
        // In Slave TX mode, no underrun event is possible on the first frame after the audio block is
        // enabled, because the mandatory operating sequence in this case is:
        // 1. Write into the SAI_xDR (by software or by DMA).
        // 2. Wait until the FIFO threshold (FLH) flag is different from 0b000 (FIFO empty).
        // 3. Enable the audio block in slave transmitter mode.

        match channel {
            SaiChannel::A => {
                // todo: Do we want to flush?
                self.regs.cha().cr2().modify(|_, w| w.fflush().bit(true));
                self.regs.cha().cr1().modify(|_, w| w.saien().bit(true));

                // Note: This read check only fires the WCKCFG bit if Master out is enabled.

                if self.regs.cha().sr().read().wckcfg().bit_is_set() {
                    panic!("Wrong clock configuration. Clock configuration does not respect the rule concerning
the frame length specification defined in Section 51.4.6: Frame synchronization (configuration of
FRL[7:0] bit in the SAI_xFRCR register)
This bit is used only when the audio block operates in master mode (MODE[1] = 0) and NOMCK = 0.
It can generate an interrupt if WCKCFGIE bit is set in SAI_xIM register");
                }
            }
            SaiChannel::B => {
                self.regs.chb().cr2().modify(|_, w| w.fflush().bit(true));
                self.regs.chb().cr1().modify(|_, w| w.saien().bit(true));

                if self.regs.chb().sr().read().wckcfg().bit_is_set() {
                    panic!("Wrong clock configuration. Clock configuration does not respect the rule concerning the frame length specification defined in
Section 51.4.6: Frame synchronization (configuration of FRL[7:0] bit in the SAI_xFRCR register)
This bit is used only when the audio block operates in master mode (MODE[1] = 0) and NOMCK = 0.
It can generate an interrupt if WCKCFGIE bit is set in SAI_xIM register");
                }
            }
        };
    }

    /// Disable an audio subblock (channel). See H743 RM, section 51.4.15.
    /// The SAI audio block can be disabled at any moment by clearing SAIEN bit in the SAI_xCR1
    /// register. All the already started frames are automatically completed before the SAI is stops
    /// working. SAIEN bit remains High until the SAI is completely switched-off at the end of the
    /// current audio frame transfer.
    /// If an audio block in the SAI operates synchronously with the other one, the one which is the
    /// master must be disabled first.
    pub fn disable(&mut self, channel: SaiChannel) {
        match channel {
            SaiChannel::A => self.regs.cha().cr1().modify(|_, w| w.saien().clear_bit()),
            SaiChannel::B => self.regs.chb().cr1().modify(|_, w| w.saien().clear_bit()),
        };
    }

    /// Read a word of data.
    pub fn read(&self, channel: SaiChannel) -> i32 {
        match channel {
            SaiChannel::A => self.regs.cha().dr().read().bits() as i32,
            SaiChannel::B => self.regs.chb().dr().read().bits() as i32,
        }
    }

    // /// Read 2 words of data from a channel: Left and Right channel, in that order.
    // pub fn read(&self, channel: SaiChannel) -> (u32, u32) {
    //     // // todo TEMP TS!
    //     let reading = self.regs.cha().dr().read().bits();
    //     return (reading, reading);
    //
    //     match channel {
    //         SaiChannel::A => (
    //             self.regs.cha().dr().read().bits(),
    //             self.regs.cha().dr().read().bits(),
    //         ),
    //         SaiChannel::B => (
    //             self.regs.chb().dr().read().bits(),
    //             self.regs.chb().dr().read().bits(),
    //         ),
    //     }
    // }

    /// Send 2 words of data to a single channel: Left and right channel, in that order.
    /// A write to the SR register loads the FIFO provided the FIFO is not full.
    pub fn write(&mut self, channel: SaiChannel, left_word: i32, right_word: i32) {
        match channel {
            SaiChannel::A => self
                .regs
                .cha()
                .dr()
                .write(|w| unsafe { w.bits(left_word as u32).bits(right_word as u32) }),
            SaiChannel::B => self
                .regs
                .chb()
                .dr()
                .write(|w| unsafe { w.bits(left_word as u32).bits(right_word as u32) }),
        };

        // todo: Why 2 words?
        // todo: Check FIFO level?

        // The FIFO is 8 words long. A write consists of 2 words, in stereo mode.
        // Therefore you need to wait for 3/4s to ensure 2 words are available for writing.
        // match audio_ch.sr().read().flvl().variant() {
        //     Val(sr::FLVL_A::FULL) => Err(nb::Error::WouldBlock),
        //     Val(sr::FLVL_A::QUARTER4) => Err(nb::Error::WouldBlock),
        //     _ => {
        //         unsafe {
        //             audio_ch.dr().write(|w| w.bits(left_word).bits(right_word));
        //         }
        //         Ok(())
        //     }
        // }
    }

    /// Send data over SAI with DMA. H743 RM, section 51.4.16: SAI DMA Interface.
    /// To free the CPU and to optimize bus bandwidth, each SAI audio block has an independent
    /// DMA interface to read/write from/to the SAI_xDR register (to access the internal FIFO).
    /// There is one DMA channel per audio subblock supporting basic DMA request/acknowledge
    /// protocol.
    /// Before configuring the SAI block, the SAI DMA channel must be disabled.
    #[cfg(not(any(feature = "f4", feature = "l552")))]
    pub unsafe fn write_dma<D>(
        &mut self,
        buf: &[i32], // todo size?
        sai_channel: SaiChannel,
        dma_channel: DmaChannel,
        channel_cfg: ChannelCfg,
        dma: &mut Dma<D>,
    ) where
        D: Deref<Target = dma_p::RegisterBlock>,
    {
        let (ptr, len) = (buf.as_ptr(), buf.len());

        // todo: DMA2 support.

        // L44 RM, Table 41. "DMA1 requests for each channel"
        #[cfg(any(feature = "f3", feature = "l4"))]
        let dma_channel = match sai_channel {
            SaiChannel::A => DmaInput::Sai1A.dma1_channel(),
            SaiChannel::B => DmaInput::Sai1B.dma1_channel(),
        };

        #[cfg(feature = "l4")]
        match sai_channel {
            SaiChannel::A => dma.channel_select(DmaInput::Sai1A),
            SaiChannel::B => dma.channel_select(DmaInput::Sai1B),
        };

        // To configure the audio subblock for DMA transfer, set DMAEN bit in the SAI_xCR1 register.
        // The DMA request is managed directly by the FIFO controller depending on the FIFO
        // threshold level (for more details refer to Section 51.4.9: Internal FIFOs). DMA transfer
        // direction is linked to the SAI audio subblock configuration:
        // • If the audio block operates as a transmitter, the audio block FIFO controller outputs a
        // DMA request to load the FIFO with data written in the SAI_xDR register.
        // • If the audio block is operates as a receiver, the DMA request is related to read
        // operations from the SAI_xDR register.
        match sai_channel {
            SaiChannel::A => self.regs.cha().cr1().modify(|_, w| w.dmaen().bit(true)),
            SaiChannel::B => self.regs.chb().cr1().modify(|_, w| w.dmaen().bit(true)),
        };

        // Follow the sequence below to configure the SAI interface in DMA mode:
        // 1. Configure SAI and FIFO threshold levels to specify when the DMA request will be
        // launched.
        // (Set in `new`).
        // 2. Configure SAI DMA channel. (handled by `dma.cfg_channel`)
        // 3. Enable the DMA. (handled by `dma.cfg_channel`)

        let periph_addr = match sai_channel {
            SaiChannel::A => &self.regs.cha().dr() as *const _ as u32,
            SaiChannel::B => &self.regs.chb().dr() as *const _ as u32,
        };

        let len = len as u32;

        let cfg_datasize = match sai_channel {
            SaiChannel::A => self.config_a.datasize,
            SaiChannel::B => self.config_b.datasize,
        };

        let datasize = match cfg_datasize {
            DataSize::S8 => dma::DataSize::S8,
            DataSize::S10 => dma::DataSize::S16,
            DataSize::S16 => dma::DataSize::S16,
            _ => dma::DataSize::S32,
        };

        dma.cfg_channel(
            dma_channel,
            periph_addr,
            ptr as u32,
            len,
            dma::Direction::ReadFromMem,
            datasize,
            datasize,
            channel_cfg,
        );

        // 4. Enable the SAI interface. (handled by `Sai::enable() in user code`.)
    }

    /// Read data from SAI with DMA. H743 RM, section 51.4.16: SAI DMA Interface.
    /// To free the CPU and to optimize bus bandwidth, each SAI audio block has an independent
    /// DMA interface to read/write from/to the SAI_xDR register (to access the internal FIFO).
    /// There is one DMA channel per audio subblock supporting basic DMA request/acknowledge
    /// protocol.
    #[cfg(not(any(feature = "f4", feature = "l552")))]
    pub unsafe fn read_dma<D>(
        &mut self,
        buf: &mut [i32], // todo size?
        sai_channel: SaiChannel,
        dma_channel: DmaChannel,
        channel_cfg: ChannelCfg,
        dma: &mut Dma<D>,
    ) where
        D: Deref<Target = dma_p::RegisterBlock>,
    {
        let (ptr, len) = (buf.as_mut_ptr(), buf.len());

        // See commends on `write_dma`.

        // L44 RM, Table 41. "DMA1 requests for each channel
        // todo: DMA2 support.

        #[cfg(any(feature = "f3", feature = "l4"))]
        let dma_channel = match sai_channel {
            SaiChannel::A => DmaInput::Sai1A.dma1_channel(),
            SaiChannel::B => DmaInput::Sai1B.dma1_channel(),
        };

        #[cfg(feature = "l4")]
        match sai_channel {
            SaiChannel::A => dma.channel_select(DmaInput::Sai1A),
            SaiChannel::B => dma.channel_select(DmaInput::Sai1B),
        };

        match sai_channel {
            SaiChannel::A => self.regs.cha().cr1().modify(|_, w| w.dmaen().bit(true)),
            SaiChannel::B => self.regs.chb().cr1().modify(|_, w| w.dmaen().bit(true)),
        };

        let periph_addr = match sai_channel {
            SaiChannel::A => &self.regs.cha().dr() as *const _ as u32,
            SaiChannel::B => &self.regs.chb().dr() as *const _ as u32,
        };

        let num_data = len as u32;

        let cfg_datasize = match sai_channel {
            SaiChannel::A => self.config_a.datasize,
            SaiChannel::B => self.config_b.datasize,
        };

        let datasize = match cfg_datasize {
            DataSize::S8 => dma::DataSize::S8,
            DataSize::S10 => dma::DataSize::S16,
            DataSize::S16 => dma::DataSize::S16,
            _ => dma::DataSize::S32,
        };

        dma.cfg_channel(
            dma_channel,
            periph_addr,
            ptr as u32,
            num_data,
            dma::Direction::ReadFromPeriph,
            datasize,
            datasize,
            channel_cfg,
        );

        // 4. Enable the SAI interface. (handled by `Sai::enable() in user code`.)
    }

    /// Enable a specific type of interrupt. See L4 RM, Table 220: "SAI interrupt sources".
    pub fn enable_interrupt(&mut self, interrupt_type: SaiInterrupt, channel: SaiChannel) {
        // Disable the UART to allow writing the `add` and `addm7` bits
        // L4 RM: Follow the sequence below to enable an interrupt:
        // 1. Disable SAI interrupt.
        // 2. Configure SAI.
        // 3. Configure SAI interrupt source.
        // 4. Enable SAI.

        //todo: Does that mean we need to disable and re-enable SAI here?

        match channel {
            SaiChannel::A => {
                self.regs.cha().im().modify(|_, w| match interrupt_type {
                    SaiInterrupt::Freq => w.freqie().bit(true),
                    SaiInterrupt::Ovrudr => w.ovrudrie().bit(true),
                    SaiInterrupt::AfsDet => w.afsdetie().bit(true),
                    SaiInterrupt::LfsDet => w.lfsdetie().bit(true),
                    SaiInterrupt::CnRdy => w.cnrdyie().bit(true),
                    SaiInterrupt::MuteDet => w.mutedetie().bit(true),
                    SaiInterrupt::WckCfg => w.wckcfgie().bit(true),
                });
            }
            SaiChannel::B => {
                self.regs.chb().im().modify(|_, w| match interrupt_type {
                    SaiInterrupt::Freq => w.freqie().bit(true),
                    SaiInterrupt::Ovrudr => w.ovrudrie().bit(true),
                    SaiInterrupt::AfsDet => w.afsdetie().bit(true),
                    SaiInterrupt::LfsDet => w.lfsdetie().bit(true),
                    SaiInterrupt::CnRdy => w.cnrdyie().bit(true),
                    SaiInterrupt::MuteDet => w.mutedetie().bit(true),
                    SaiInterrupt::WckCfg => w.wckcfgie().bit(true),
                });
            }
        }
    }

    /// Clears the interrupt pending flag for a specific type of interrupt.
    pub fn clear_interrupt(&mut self, interrupt_type: SaiInterrupt, channel: SaiChannel) {
        match channel {
            SaiChannel::A => {
                self.regs.cha().clrfr().write(|w| match interrupt_type {
                    // This Interrupt (FREQ bit in SAI_xSR register) is
                    // cleared by hardware when the FIFO becomes empty (FLVL[2:0] bits in SAI_xSR is equal
                    // to 0b000) i.e no data are stored in FIFO.
                    SaiInterrupt::Freq => w.cmutedet().bit(true), // There is no Freq flag.
                    SaiInterrupt::Ovrudr => w.covrudr().bit(true),
                    SaiInterrupt::AfsDet => w.cafsdet().bit(true),
                    SaiInterrupt::LfsDet => w.clfsdet().bit(true),
                    SaiInterrupt::CnRdy => w.ccnrdy().bit(true),
                    SaiInterrupt::MuteDet => w.cmutedet().bit(true),
                    SaiInterrupt::WckCfg => w.cwckcfg().bit(true),
                });
            }
            SaiChannel::B => {
                self.regs.chb().clrfr().write(|w| match interrupt_type {
                    SaiInterrupt::Freq => w.cmutedet().bit(true),
                    SaiInterrupt::Ovrudr => w.covrudr().bit(true),
                    SaiInterrupt::AfsDet => w.cafsdet().bit(true),
                    SaiInterrupt::LfsDet => w.clfsdet().bit(true),
                    SaiInterrupt::CnRdy => w.ccnrdy().bit(true),
                    SaiInterrupt::MuteDet => w.cmutedet().bit(true),
                    SaiInterrupt::WckCfg => w.cwckcfg().bit(true),
                });
            }
        }
    }

    /// Print the (raw) contents of the (A and B) status registers.
    pub fn read_status(&self) -> (u32, u32) {
        unsafe {
            (
                self.regs.cha().sr().read().bits(),
                self.regs.chb().sr().read().bits(),
            )
        }
    }
}
