Skip to content

Commit

Permalink
Adds a SAI driver to the tree
Browse files Browse the repository at this point in the history
A SAI driver lets a SaiTx and SaiRx be optionally constructed given pins.

A Sai[Tx/Rx] lets channels be constructed be constructed given a data pin. This is
a trait impl such that only available channels may actually be
constructed. In the case of SAI1 that means 4 channels. In the case of
other SAI instances only 1 channel.

In the end only each level manipulates certain registers to setup
channels appropriately so I believe safety can be kept.

Read/Write functionality can then be built on arrays of tx/rx channels
on top of private traits and impls per channel perhaps.

The end goal is pretty simple though, provide an interface for streaming
audio frames (sets of channel samples) in or out of a SAI data channel.
  • Loading branch information
SpinFast committed Nov 13, 2023
1 parent 33e6656 commit 3712b83
Show file tree
Hide file tree
Showing 2 changed files with 317 additions and 1 deletion.
315 changes: 315 additions & 0 deletions src/common/sai.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
//! Synchronous Audio Interface.
//!
//! [`Sai`] provides a pair of synchronous audio word streams containaing stereo data.
//!
//! This driver also exposes the peripheral's lower-level, hardware-dependent audio stream
//! configuration and FIFO pair.
//!
//! Each Sai instance has at minimum a tx and rx data line. Each data line supports up to 32 audio
//! words per frame. Audio words are 8 to 32 bits. Frames can be used to send multichannel audio
//! data over a single serial stream such as stereo audio.
//!
//! Each data line comes with its own 32x32 FIFO allowing for a full frame to be sent and/or received
//! without software interaction.
//!
//! The configuration of the SAI is encoded in configuration structure that can be used with a singular
//! configure method.

use crate::ccm;
use crate::iomuxc::{consts, sai};
use crate::ral;

pub enum SaiByteOrder {
LSB = 0,
MSB = 1,
}

pub enum SaiClockPolarity {
ActiveHigh = 0,
ActiveLow = 1,
}

impl SaiClockPolarity {
const SampleOnRising: SaiClockPolarity = SaiClockPolarity::ActiveLow;
const SampleOnFalling: SaiClockPolarity = SaiClockPolarity::ActiveHigh;
}

pub struct SaiFrameSync {
sync_width: u8,
sync_early: bool,
polarity: SaiClockPolarity,
}

pub enum SaiMclkSource {
Sysclk = 0,
Select1 = 1,
Select2 = 2,
Select3 = 3,
}

pub enum SaiBclkSource {
Bus = 0,
Opt1 = 1,
Opt2 = 2,
Opt3 = 3,
}

impl SaiBclkSource {
const MclkDiv: SaiBclkSource = SaiBclkSource::Opt1;
}

pub struct SaiBitClock {
src_swap: bool,
input_delay: bool,
polarity: SaiClockPolarity,
source: SaiBclkSource,
}

pub struct SaiSerialData {
byte_order: SaiByteOrder,
word_length: u8,
frame_length: u8,
}

pub enum SaiMasterSlave {
Master = 0,
Slave = 1,
BclkMasterFrameSyncSlave = 2,
BclkSlaveFrameSyncMaster = 3,
}

pub enum SaiFrameSyncMode {
Async = 0,
Sync = 1,
SyncWithOtherTx = 2,
SyncwithOtherRx = 3,
}

pub struct SaiConfig {
serial_data: SaiSerialData,
frame_sync: SaiFrameSync,
bit_clock: SaiBitClock,
master_slave: SaiMasterSlave,
sync_mode: SaiFrameSyncMode,
start_channel: u8,
end_channel: u8,
channel_mask: u8,
channels: u8,
}

impl SaiConfig {
fn i2s(bit_width: u8, channel_mask: u8) -> Self {
SaiConfig {
serial_data: SaiSerialData {
byte_order: SaiByteOrder::MSB,
word_length: bit_width,
frame_length: 2,
},
frame_sync: SaiFrameSync {
sync_width: bit_width,
sync_early: true,
polarity: SaiClockPolarity::ActiveLow,
},
bit_clock: SaiBitClock {
src_swap: false,
input_delay: false,
polarity: SaiClockPolarity::SampleOnRising,
source: SaiBclkSource::MclkDiv,
},
master_slave: SaiMasterSlave::Master,
sync_mode: SaiFrameSyncMode::Async,
start_channel: 0,
end_channel: 0,
channel_mask,
channels: 0,
}
}
}

pub struct TxPins<TxSync, TxBclk> {
/// Frame sync pin
pub sync: TxSync,
/// Bit clock pin
pub bclk: TxBclk,
}

pub struct RxPins<RxSync, RxBclk> {
/// Frame sync pin
pub sync: RxSync,
/// Bit clock pin
pub bclk: RxBclk,
}

/// A SAI peripheral instance
pub struct Sai<const N: u8> {
pub(super) sai: ral::sai::Instance<N>,
}

// An instance of a SAI transmitter
pub struct SaiTx<P, const N: u8> {
pub(super) sai: ral::sai::Instance<N>,
pins: P,
}

/// An instance of a SAI transmitter channel
///
/// NOTE: A SAI channel is representative of the physical data pin and associated FIFO
/// not a time division channel for packing multi-channel audio in each frame
pub struct SaiTxChannel<P, const N: u8, const C: u8> {
sai: ral::sai::Instance<N>,
tx_data: P,
}

/// An instance of a SAI receiver
pub struct SaiRx<P, const N: u8> {
pub(super) sai: ral::sai::Instance<N>,
pins: P,
}

/// An instance of a SAI receiver channel
///
/// NOTE: A SAI channel is representative of the physical data pin and associated FIFO
/// not a time division channel for packing multi-channel audio in each frame.
pub struct SaiRxChannel<P, const N: u8, const C: u8> {
sai: ral::sai::Instance<N>,
rx_data: P,
}

/// Trait a SaiRx implements for each channel available for taking
pub trait TakeRxChannel<P: sai::RxDataSignal, const N: u8, const C: u8> {
fn take_channel(&mut self, rx_data: P) -> Result<SaiRxChannel<P, N, C>, SaiError>;
}

// Trait a SaiTx implements for each channel available for taking
pub trait TakeTxChannel<P: sai::TxDataSignal, const N: u8, const C: u8> {
fn take_channel(&mut self, tx_data: P) -> Result<SaiTxChannel<P, N, C>, SaiError>;
}

/// Possible errors when interfacing the SAI.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SaiError {
/// The transaction frame size is incorrect.
///
/// The frame size, in bits, must be between 8 bits and
/// 4096 bits.
FrameSize,
/// Caller provided no data.
NoData,
/// Channel already taken
ChannelTaken,
}

fn reset_tx(regs: &ral::sai::RegisterBlock) {
ral::write_reg!(ral::sai, regs, TCSR, SR: 1, FR: 1);
ral::modify_reg!(ral::sai, regs, TCSR, SR: 0);
ral::write_reg!(ral::sai, regs, TCR2, 0);
ral::write_reg!(ral::sai, regs, TCR3, 0);
ral::write_reg!(ral::sai, regs, TCR4, 0);
ral::write_reg!(ral::sai, regs, TCR5, 0);
ral::write_reg!(ral::sai, regs, TMR, 0);
}

fn reset_rx(regs: &ral::sai::RegisterBlock) {
ral::write_reg!(ral::sai, regs, RCSR, SR: 1, FR: 1);
ral::modify_reg!(ral::sai, regs, RCSR, SR: 0);
ral::write_reg!(ral::sai, regs, RCR2, 0);
ral::write_reg!(ral::sai, regs, RCR3, 0);
ral::write_reg!(ral::sai, regs, RCR4, 0);
ral::write_reg!(ral::sai, regs, RCR5, 0);
ral::write_reg!(ral::sai, regs, RMR, 0);
}

impl<const N: u8> Sai<N> {
/// The peripheral instance.
pub const N: u8 = N;

/// Initialize the SAI instance by resetting everything
pub fn init(mut sai: ral::sai::Instance<N>, sample_rate: u32, cfg: &SaiConfig) -> Self {
reset_tx(&mut sai);
reset_rx(&mut sai);
Sai { sai }
}

/// Take the SAI transmit handle given a set of TxPins
pub fn take_tx<TxSync, TxBclk, P>(&mut self, pins: P) -> Result<SaiTx<P, N>, SaiError> {
//TODO check if Tx already taken
Ok(SaiTx {
sai: unsafe { ral::sai::Instance::new(&*self.sai) },
pins,
})
}

/// Take the SAI receive handle given a set of RxPins
pub fn take_rx<RxSync: sai::Signal, RxBclk: sai::Signal, P>(
&mut self,
pins: P,
) -> Result<SaiRx<P, N>, SaiError> {
//TODO check if Rx already taken
Ok(SaiRx {
sai: unsafe { ral::sai::Instance::new(&*self.sai) },
pins,
})
}
}

//TODO automate the Take[Tx/Rx]Channel impls with a macro across the various SAI instances available on the part
impl<P> TakeTxChannel<P, 1, 1> for Sai<1>
where
P: sai::TxDataSignal,
{
fn take_channel(&mut self, tx_data: P) -> Result<SaiTxChannel<P, 1, 1>, SaiError> {
//TODO check channel mask and update it if needed
Ok(SaiTxChannel {
sai: unsafe { ral::sai::Instance::new(&*self.sai) },
tx_data,
})
}
}

//TODO automate the Take[Tx/Rx]Channel impls with a macro across the various SAI instances available on the part
impl<P> TakeRxChannel<P, 1, 1> for Sai<1>
where
P: sai::RxDataSignal,
{
fn take_channel(&mut self, rx_data: P) -> Result<SaiRxChannel<P, 1, 1>, SaiError> {
//TODO check channel mask and update it if needed
Ok(SaiRxChannel {
sai: unsafe { ral::sai::Instance::new(&*self.sai) },
rx_data,
})
}
}

/// Trait to write a single machine word of audio data, potentially packed, to a channel
trait AudioWriteWord {
fn write_word(&mut self, word: u32);
}

/// Trait to read a single machine word of audio data, potentially packed, from a channel
trait AudioReadWord {
fn read_word(&mut self) -> u32;
}

impl<P, const N: u8, const C: u8> AudioWriteWord for SaiTxChannel<P, N, C> {
#[inline]
fn write_word(&mut self, word: u32) {
ral::write_reg!(ral::sai, self.sai, TDR[C as usize], word);
}
}

impl<P, const N: u8, const C: u8> AudioReadWord for SaiRxChannel<P, N, C> {
#[inline]
fn read_word(&mut self) -> u32 {
ral::read_reg!(ral::sai, self.sai, RDR[C as usize])
}
}

/// Trait for writing a full frame of unpacked audio data
trait AudioWriteFrame<T, const L: usize> {
fn write_frame(&mut self, buf: &[T; L]);
}

/// Trait for reading a full frame of unpacked audio data
trait AudioReadFrame<T, const L: usize> {
fn read_frame(&mut self, buf: &mut [T; L]);
}
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,14 @@ mod common {
pub mod lpspi;
pub mod lpuart;
pub mod pit;
pub mod sai;
pub mod snvs;
pub mod timer;
}

// These common drivers have no associated chip APIs, so
// export them directly.
pub use common::{flexpwm, gpio, gpt, lpi2c, lpspi, lpuart, pit, snvs, timer};
pub use common::{flexpwm, gpio, gpt, lpi2c, lpspi, lpuart, pit, sai, snvs, timer};

/// Clock control module.
///
Expand Down

0 comments on commit 3712b83

Please sign in to comment.