rtt-f030/bsp/frdm-k64f/device/MK64F12/fsl_sai.c

1193 lines
39 KiB
C

/*
* Copyright (c) 2016, Freescale Semiconductor, Inc.
* Copyright 2016-2017 NXP
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* o Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "fsl_sai.h"
/*******************************************************************************
* Definitations
******************************************************************************/
enum _sai_transfer_state
{
kSAI_Busy = 0x0U, /*!< SAI is busy */
kSAI_Idle, /*!< Transfer is done. */
kSAI_Error /*!< Transfer error occured. */
};
/*! @brief Typedef for sai tx interrupt handler. */
typedef void (*sai_tx_isr_t)(I2S_Type *base, sai_handle_t *saiHandle);
/*! @brief Typedef for sai rx interrupt handler. */
typedef void (*sai_rx_isr_t)(I2S_Type *base, sai_handle_t *saiHandle);
/*******************************************************************************
* Prototypes
******************************************************************************/
#if defined(FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) && (FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER)
/*!
* @brief Set the master clock divider.
*
* This API will compute the master clock divider according to master clock frequency and master
* clock source clock source frequency.
*
* @param base SAI base pointer.
* @param mclk_Hz Mater clock frequency in Hz.
* @param mclkSrcClock_Hz Master clock source frequency in Hz.
*/
static void SAI_SetMasterClockDivider(I2S_Type *base, uint32_t mclk_Hz, uint32_t mclkSrcClock_Hz);
#endif /* FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER */
/*!
* @brief Get the instance number for SAI.
*
* @param base SAI base pointer.
*/
uint32_t SAI_GetInstance(I2S_Type *base);
/*!
* @brief sends a piece of data in non-blocking way.
*
* @param base SAI base pointer
* @param channel Data channel used.
* @param bitWidth How many bits in a audio word, usually 8/16/24/32 bits.
* @param buffer Pointer to the data to be written.
* @param size Bytes to be written.
*/
static void SAI_WriteNonBlocking(I2S_Type *base, uint32_t channel, uint32_t bitWidth, uint8_t *buffer, uint32_t size);
/*!
* @brief Receive a piece of data in non-blocking way.
*
* @param base SAI base pointer
* @param channel Data channel used.
* @param bitWidth How many bits in a audio word, usually 8/16/24/32 bits.
* @param buffer Pointer to the data to be read.
* @param size Bytes to be read.
*/
static void SAI_ReadNonBlocking(I2S_Type *base, uint32_t channel, uint32_t bitWidth, uint8_t *buffer, uint32_t size);
/*******************************************************************************
* Variables
******************************************************************************/
/* Base pointer array */
static I2S_Type *const s_saiBases[] = I2S_BASE_PTRS;
/*!@brief SAI handle pointer */
sai_handle_t *s_saiHandle[ARRAY_SIZE(s_saiBases)][2];
/* IRQ number array */
static const IRQn_Type s_saiTxIRQ[] = I2S_TX_IRQS;
static const IRQn_Type s_saiRxIRQ[] = I2S_RX_IRQS;
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* Clock name array */
static const clock_ip_name_t s_saiClock[] = SAI_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
/*! @brief Pointer to tx IRQ handler for each instance. */
static sai_tx_isr_t s_saiTxIsr;
/*! @brief Pointer to tx IRQ handler for each instance. */
static sai_rx_isr_t s_saiRxIsr;
/*******************************************************************************
* Code
******************************************************************************/
#if defined(FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) && (FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER)
static void SAI_SetMasterClockDivider(I2S_Type *base, uint32_t mclk_Hz, uint32_t mclkSrcClock_Hz)
{
uint32_t freq = mclkSrcClock_Hz;
uint16_t fract, divide;
uint32_t remaind = 0;
uint32_t current_remainder = 0xFFFFFFFFU;
uint16_t current_fract = 0;
uint16_t current_divide = 0;
uint32_t mul_freq = 0;
uint32_t max_fract = 256;
/*In order to prevent overflow */
freq /= 100;
mclk_Hz /= 100;
/* Compute the max fract number */
max_fract = mclk_Hz * 4096 / freq + 1;
if (max_fract > 256)
{
max_fract = 256;
}
/* Looking for the closet frequency */
for (fract = 1; fract < max_fract; fract++)
{
mul_freq = freq * fract;
remaind = mul_freq % mclk_Hz;
divide = mul_freq / mclk_Hz;
/* Find the exactly frequency */
if (remaind == 0)
{
current_fract = fract;
current_divide = mul_freq / mclk_Hz;
break;
}
/* Closer to next one, set the closest to next data */
if (remaind > mclk_Hz / 2)
{
remaind = mclk_Hz - remaind;
divide += 1;
}
/* Update the closest div and fract */
if (remaind < current_remainder)
{
current_fract = fract;
current_divide = divide;
current_remainder = remaind;
}
}
/* Fill the computed fract and divider to registers */
base->MDR = I2S_MDR_DIVIDE(current_divide - 1) | I2S_MDR_FRACT(current_fract - 1);
/* Waiting for the divider updated */
while (base->MCR & I2S_MCR_DUF_MASK)
{
}
}
#endif /* FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER */
uint32_t SAI_GetInstance(I2S_Type *base)
{
uint32_t instance;
/* Find the instance index from base address mappings. */
for (instance = 0; instance < ARRAY_SIZE(s_saiBases); instance++)
{
if (s_saiBases[instance] == base)
{
break;
}
}
assert(instance < ARRAY_SIZE(s_saiBases));
return instance;
}
static void SAI_WriteNonBlocking(I2S_Type *base, uint32_t channel, uint32_t bitWidth, uint8_t *buffer, uint32_t size)
{
uint32_t i = 0;
uint8_t j = 0;
uint8_t bytesPerWord = bitWidth / 8U;
uint32_t data = 0;
uint32_t temp = 0;
for (i = 0; i < size / bytesPerWord; i++)
{
for (j = 0; j < bytesPerWord; j++)
{
temp = (uint32_t)(*buffer);
data |= (temp << (8U * j));
buffer++;
}
base->TDR[channel] = data;
data = 0;
}
}
static void SAI_ReadNonBlocking(I2S_Type *base, uint32_t channel, uint32_t bitWidth, uint8_t *buffer, uint32_t size)
{
uint32_t i = 0;
uint8_t j = 0;
uint8_t bytesPerWord = bitWidth / 8U;
uint32_t data = 0;
for (i = 0; i < size / bytesPerWord; i++)
{
data = base->RDR[channel];
for (j = 0; j < bytesPerWord; j++)
{
*buffer = (data >> (8U * j)) & 0xFF;
buffer++;
}
}
}
void SAI_TxInit(I2S_Type *base, const sai_config_t *config)
{
uint32_t val = 0;
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* Enable the SAI clock */
CLOCK_EnableClock(s_saiClock[SAI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
#if defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)
/* Master clock source setting */
val = (base->MCR & ~I2S_MCR_MICS_MASK);
base->MCR = (val | I2S_MCR_MICS(config->mclkSource));
/* Configure Master clock output enable */
val = (base->MCR & ~I2S_MCR_MOE_MASK);
base->MCR = (val | I2S_MCR_MOE(config->mclkOutputEnable));
#endif /* FSL_FEATURE_SAI_HAS_MCR */
/* Configure audio protocol */
switch (config->protocol)
{
case kSAI_BusLeftJustified:
base->TCR2 |= I2S_TCR2_BCP_MASK;
base->TCR3 &= ~I2S_TCR3_WDFL_MASK;
base->TCR4 = I2S_TCR4_MF(1U) | I2S_TCR4_SYWD(31U) | I2S_TCR4_FSE(0U) | I2S_TCR4_FSP(0U) | I2S_TCR4_FRSZ(1U);
break;
case kSAI_BusRightJustified:
base->TCR2 |= I2S_TCR2_BCP_MASK;
base->TCR3 &= ~I2S_TCR3_WDFL_MASK;
base->TCR4 = I2S_TCR4_MF(1U) | I2S_TCR4_SYWD(31U) | I2S_TCR4_FSE(0U) | I2S_TCR4_FSP(0U) | I2S_TCR4_FRSZ(1U);
break;
case kSAI_BusI2S:
base->TCR2 |= I2S_TCR2_BCP_MASK;
base->TCR3 &= ~I2S_TCR3_WDFL_MASK;
base->TCR4 = I2S_TCR4_MF(1U) | I2S_TCR4_SYWD(31U) | I2S_TCR4_FSE(1U) | I2S_TCR4_FSP(1U) | I2S_TCR4_FRSZ(1U);
break;
case kSAI_BusPCMA:
base->TCR2 &= ~I2S_TCR2_BCP_MASK;
base->TCR3 &= ~I2S_TCR3_WDFL_MASK;
base->TCR4 = I2S_TCR4_MF(1U) | I2S_TCR4_SYWD(0U) | I2S_TCR4_FSE(1U) | I2S_TCR4_FSP(0U) | I2S_TCR4_FRSZ(1U);
break;
case kSAI_BusPCMB:
base->TCR2 &= ~I2S_TCR2_BCP_MASK;
base->TCR3 &= ~I2S_TCR3_WDFL_MASK;
base->TCR4 = I2S_TCR4_MF(1U) | I2S_TCR4_SYWD(0U) | I2S_TCR4_FSE(0U) | I2S_TCR4_FSP(0U) | I2S_TCR4_FRSZ(1U);
break;
default:
break;
}
/* Set master or slave */
if (config->masterSlave == kSAI_Master)
{
base->TCR2 |= I2S_TCR2_BCD_MASK;
base->TCR4 |= I2S_TCR4_FSD_MASK;
/* Bit clock source setting */
val = base->TCR2 & (~I2S_TCR2_MSEL_MASK);
base->TCR2 = (val | I2S_TCR2_MSEL(config->bclkSource));
}
else
{
base->TCR2 &= ~I2S_TCR2_BCD_MASK;
base->TCR4 &= ~I2S_TCR4_FSD_MASK;
}
/* Set Sync mode */
switch (config->syncMode)
{
case kSAI_ModeAsync:
val = base->TCR2;
val &= ~I2S_TCR2_SYNC_MASK;
base->TCR2 = (val | I2S_TCR2_SYNC(0U));
break;
case kSAI_ModeSync:
val = base->TCR2;
val &= ~I2S_TCR2_SYNC_MASK;
base->TCR2 = (val | I2S_TCR2_SYNC(1U));
/* If sync with Rx, should set Rx to async mode */
val = base->RCR2;
val &= ~I2S_RCR2_SYNC_MASK;
base->RCR2 = (val | I2S_RCR2_SYNC(0U));
break;
case kSAI_ModeSyncWithOtherTx:
val = base->TCR2;
val &= ~I2S_TCR2_SYNC_MASK;
base->TCR2 = (val | I2S_TCR2_SYNC(2U));
break;
case kSAI_ModeSyncWithOtherRx:
val = base->TCR2;
val &= ~I2S_TCR2_SYNC_MASK;
base->TCR2 = (val | I2S_TCR2_SYNC(3U));
break;
default:
break;
}
}
void SAI_RxInit(I2S_Type *base, const sai_config_t *config)
{
uint32_t val = 0;
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* Enable SAI clock first. */
CLOCK_EnableClock(s_saiClock[SAI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
#if defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)
/* Master clock source setting */
val = (base->MCR & ~I2S_MCR_MICS_MASK);
base->MCR = (val | I2S_MCR_MICS(config->mclkSource));
/* Configure Master clock output enable */
val = (base->MCR & ~I2S_MCR_MOE_MASK);
base->MCR = (val | I2S_MCR_MOE(config->mclkOutputEnable));
#endif /* FSL_FEATURE_SAI_HAS_MCR */
/* Configure audio protocol */
switch (config->protocol)
{
case kSAI_BusLeftJustified:
base->RCR2 |= I2S_RCR2_BCP_MASK;
base->RCR3 &= ~I2S_RCR3_WDFL_MASK;
base->RCR4 = I2S_RCR4_MF(1U) | I2S_RCR4_SYWD(31U) | I2S_RCR4_FSE(0U) | I2S_RCR4_FSP(0U) | I2S_RCR4_FRSZ(1U);
break;
case kSAI_BusRightJustified:
base->RCR2 |= I2S_RCR2_BCP_MASK;
base->RCR3 &= ~I2S_RCR3_WDFL_MASK;
base->RCR4 = I2S_RCR4_MF(1U) | I2S_RCR4_SYWD(31U) | I2S_RCR4_FSE(0U) | I2S_RCR4_FSP(0U) | I2S_RCR4_FRSZ(1U);
break;
case kSAI_BusI2S:
base->RCR2 |= I2S_RCR2_BCP_MASK;
base->RCR3 &= ~I2S_RCR3_WDFL_MASK;
base->RCR4 = I2S_RCR4_MF(1U) | I2S_RCR4_SYWD(31U) | I2S_RCR4_FSE(1U) | I2S_RCR4_FSP(1U) | I2S_RCR4_FRSZ(1U);
break;
case kSAI_BusPCMA:
base->RCR2 &= ~I2S_RCR2_BCP_MASK;
base->RCR3 &= ~I2S_RCR3_WDFL_MASK;
base->RCR4 = I2S_RCR4_MF(1U) | I2S_RCR4_SYWD(0U) | I2S_RCR4_FSE(1U) | I2S_RCR4_FSP(0U) | I2S_RCR4_FRSZ(1U);
break;
case kSAI_BusPCMB:
base->RCR2 &= ~I2S_RCR2_BCP_MASK;
base->RCR3 &= ~I2S_RCR3_WDFL_MASK;
base->RCR4 = I2S_RCR4_MF(1U) | I2S_RCR4_SYWD(0U) | I2S_RCR4_FSE(0U) | I2S_RCR4_FSP(0U) | I2S_RCR4_FRSZ(1U);
break;
default:
break;
}
/* Set master or slave */
if (config->masterSlave == kSAI_Master)
{
base->RCR2 |= I2S_RCR2_BCD_MASK;
base->RCR4 |= I2S_RCR4_FSD_MASK;
/* Bit clock source setting */
val = base->RCR2 & (~I2S_RCR2_MSEL_MASK);
base->RCR2 = (val | I2S_RCR2_MSEL(config->bclkSource));
}
else
{
base->RCR2 &= ~I2S_RCR2_BCD_MASK;
base->RCR4 &= ~I2S_RCR4_FSD_MASK;
}
/* Set Sync mode */
switch (config->syncMode)
{
case kSAI_ModeAsync:
val = base->RCR2;
val &= ~I2S_RCR2_SYNC_MASK;
base->RCR2 = (val | I2S_RCR2_SYNC(0U));
break;
case kSAI_ModeSync:
val = base->RCR2;
val &= ~I2S_RCR2_SYNC_MASK;
base->RCR2 = (val | I2S_RCR2_SYNC(1U));
/* If sync with Tx, should set Tx to async mode */
val = base->TCR2;
val &= ~I2S_TCR2_SYNC_MASK;
base->TCR2 = (val | I2S_TCR2_SYNC(0U));
break;
case kSAI_ModeSyncWithOtherTx:
val = base->RCR2;
val &= ~I2S_RCR2_SYNC_MASK;
base->RCR2 = (val | I2S_RCR2_SYNC(2U));
break;
case kSAI_ModeSyncWithOtherRx:
val = base->RCR2;
val &= ~I2S_RCR2_SYNC_MASK;
base->RCR2 = (val | I2S_RCR2_SYNC(3U));
break;
default:
break;
}
}
void SAI_Deinit(I2S_Type *base)
{
SAI_TxEnable(base, false);
SAI_RxEnable(base, false);
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
CLOCK_DisableClock(s_saiClock[SAI_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}
void SAI_TxGetDefaultConfig(sai_config_t *config)
{
config->bclkSource = kSAI_BclkSourceMclkDiv;
config->masterSlave = kSAI_Master;
config->mclkSource = kSAI_MclkSourceSysclk;
config->protocol = kSAI_BusLeftJustified;
config->syncMode = kSAI_ModeAsync;
#if defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)
config->mclkOutputEnable = true;
#endif /* FSL_FEATURE_SAI_HAS_MCR */
}
void SAI_RxGetDefaultConfig(sai_config_t *config)
{
config->bclkSource = kSAI_BclkSourceMclkDiv;
config->masterSlave = kSAI_Master;
config->mclkSource = kSAI_MclkSourceSysclk;
config->protocol = kSAI_BusLeftJustified;
config->syncMode = kSAI_ModeSync;
#if defined(FSL_FEATURE_SAI_HAS_MCR) && (FSL_FEATURE_SAI_HAS_MCR)
config->mclkOutputEnable = true;
#endif /* FSL_FEATURE_SAI_HAS_MCR */
}
void SAI_TxReset(I2S_Type *base)
{
/* Set the software reset and FIFO reset to clear internal state */
base->TCSR = I2S_TCSR_SR_MASK | I2S_TCSR_FR_MASK;
/* Clear software reset bit, this should be done by software */
base->TCSR &= ~I2S_TCSR_SR_MASK;
/* Reset all Tx register values */
base->TCR2 = 0;
base->TCR3 = 0;
base->TCR4 = 0;
base->TCR5 = 0;
base->TMR = 0;
}
void SAI_RxReset(I2S_Type *base)
{
/* Set the software reset and FIFO reset to clear internal state */
base->RCSR = I2S_RCSR_SR_MASK | I2S_RCSR_FR_MASK;
/* Clear software reset bit, this should be done by software */
base->RCSR &= ~I2S_RCSR_SR_MASK;
/* Reset all Rx register values */
base->RCR2 = 0;
base->RCR3 = 0;
base->RCR4 = 0;
base->RCR5 = 0;
base->RMR = 0;
}
void SAI_TxEnable(I2S_Type *base, bool enable)
{
if (enable)
{
/* If clock is sync with Rx, should enable RE bit. */
if (((base->TCR2 & I2S_TCR2_SYNC_MASK) >> I2S_TCR2_SYNC_SHIFT) == 0x1U)
{
base->RCSR = ((base->RCSR & 0xFFE3FFFFU) | I2S_RCSR_RE_MASK);
}
base->TCSR = ((base->TCSR & 0xFFE3FFFFU) | I2S_TCSR_TE_MASK);
}
else
{
/* Should not close RE even sync with Rx */
base->TCSR = ((base->TCSR & 0xFFE3FFFFU) & (~I2S_TCSR_TE_MASK));
}
}
void SAI_RxEnable(I2S_Type *base, bool enable)
{
if (enable)
{
/* If clock is sync with Tx, should enable TE bit. */
if (((base->RCR2 & I2S_RCR2_SYNC_MASK) >> I2S_RCR2_SYNC_SHIFT) == 0x1U)
{
base->TCSR = ((base->TCSR & 0xFFE3FFFFU) | I2S_TCSR_TE_MASK);
}
base->RCSR = ((base->RCSR & 0xFFE3FFFFU) | I2S_RCSR_RE_MASK);
}
else
{
base->RCSR = ((base->RCSR & 0xFFE3FFFFU) & (~I2S_RCSR_RE_MASK));
}
}
void SAI_TxSetFormat(I2S_Type *base,
sai_transfer_format_t *format,
uint32_t mclkSourceClockHz,
uint32_t bclkSourceClockHz)
{
uint32_t bclk = format->sampleRate_Hz * 32U * 2U;
/* Compute the mclk */
#if defined(FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) && (FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER)
/* Check if master clock divider enabled, then set master clock divider */
if (base->MCR & I2S_MCR_MOE_MASK)
{
SAI_SetMasterClockDivider(base, format->masterClockHz, mclkSourceClockHz);
}
#endif /* FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER */
/* Set bclk if needed */
if (base->TCR2 & I2S_TCR2_BCD_MASK)
{
base->TCR2 &= ~I2S_TCR2_DIV_MASK;
base->TCR2 |= I2S_TCR2_DIV((bclkSourceClockHz / bclk) / 2U - 1U);
}
/* Set bitWidth */
if (format->protocol == kSAI_BusRightJustified)
{
base->TCR5 = I2S_TCR5_WNW(31U) | I2S_TCR5_W0W(31U) | I2S_TCR5_FBT(31U);
}
else
{
base->TCR5 = I2S_TCR5_WNW(31U) | I2S_TCR5_W0W(31U) | I2S_TCR5_FBT(format->bitWidth - 1);
}
/* Set mono or stereo */
base->TMR = (uint32_t)format->stereo;
/* Set data channel */
base->TCR3 &= ~I2S_TCR3_TCE_MASK;
base->TCR3 |= I2S_TCR3_TCE(1U << format->channel);
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
/* Set watermark */
base->TCR1 = format->watermark;
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
}
void SAI_RxSetFormat(I2S_Type *base,
sai_transfer_format_t *format,
uint32_t mclkSourceClockHz,
uint32_t bclkSourceClockHz)
{
uint32_t bclk = format->sampleRate_Hz * 32U * 2U;
/* Compute the mclk */
#if defined(FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER) && (FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER)
/* Check if master clock divider enabled */
if (base->MCR & I2S_MCR_MOE_MASK)
{
SAI_SetMasterClockDivider(base, format->masterClockHz, mclkSourceClockHz);
}
#endif /* FSL_FEATURE_SAI_HAS_MCLKDIV_REGISTER */
/* Set bclk if needed */
if (base->RCR2 & I2S_RCR2_BCD_MASK)
{
base->RCR2 &= ~I2S_RCR2_DIV_MASK;
base->RCR2 |= I2S_RCR2_DIV((bclkSourceClockHz / bclk) / 2U - 1U);
}
/* Set bitWidth */
if (format->protocol == kSAI_BusRightJustified)
{
base->RCR5 = I2S_RCR5_WNW(31U) | I2S_RCR5_W0W(31U) | I2S_RCR5_FBT(31U);
}
else
{
base->RCR5 = I2S_RCR5_WNW(31U) | I2S_RCR5_W0W(31U) | I2S_RCR5_FBT(format->bitWidth - 1);
}
/* Set mono or stereo */
base->RMR = (uint32_t)format->stereo;
/* Set data channel */
base->RCR3 &= ~I2S_RCR3_RCE_MASK;
base->RCR3 |= I2S_RCR3_RCE(1U << format->channel);
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
/* Set watermark */
base->RCR1 = format->watermark;
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
}
void SAI_WriteBlocking(I2S_Type *base, uint32_t channel, uint32_t bitWidth, uint8_t *buffer, uint32_t size)
{
uint32_t i = 0;
uint8_t bytesPerWord = bitWidth / 8U;
while (i < size)
{
/* Wait until it can write data */
while (!(base->TCSR & I2S_TCSR_FWF_MASK))
{
}
SAI_WriteNonBlocking(base, channel, bitWidth, buffer, bytesPerWord);
buffer += bytesPerWord;
i += bytesPerWord;
}
/* Wait until the last data is sent */
while (!(base->TCSR & I2S_TCSR_FWF_MASK))
{
}
}
void SAI_ReadBlocking(I2S_Type *base, uint32_t channel, uint32_t bitWidth, uint8_t *buffer, uint32_t size)
{
uint32_t i = 0;
uint8_t bytesPerWord = bitWidth / 8U;
while (i < size)
{
/* Wait until data is received */
while (!(base->RCSR & I2S_RCSR_FWF_MASK))
{
}
SAI_ReadNonBlocking(base, channel, bitWidth, buffer, bytesPerWord);
buffer += bytesPerWord;
i += bytesPerWord;
}
}
void SAI_TransferTxCreateHandle(I2S_Type *base, sai_handle_t *handle, sai_transfer_callback_t callback, void *userData)
{
assert(handle);
/* Zero the handle */
memset(handle, 0, sizeof(*handle));
s_saiHandle[SAI_GetInstance(base)][0] = handle;
handle->callback = callback;
handle->userData = userData;
/* Set the isr pointer */
s_saiTxIsr = SAI_TransferTxHandleIRQ;
/* Enable Tx irq */
EnableIRQ(s_saiTxIRQ[SAI_GetInstance(base)]);
}
void SAI_TransferRxCreateHandle(I2S_Type *base, sai_handle_t *handle, sai_transfer_callback_t callback, void *userData)
{
assert(handle);
/* Zero the handle */
memset(handle, 0, sizeof(*handle));
s_saiHandle[SAI_GetInstance(base)][1] = handle;
handle->callback = callback;
handle->userData = userData;
/* Set the isr pointer */
s_saiRxIsr = SAI_TransferRxHandleIRQ;
/* Enable Rx irq */
EnableIRQ(s_saiRxIRQ[SAI_GetInstance(base)]);
}
status_t SAI_TransferTxSetFormat(I2S_Type *base,
sai_handle_t *handle,
sai_transfer_format_t *format,
uint32_t mclkSourceClockHz,
uint32_t bclkSourceClockHz)
{
assert(handle);
if ((mclkSourceClockHz < format->sampleRate_Hz) || (bclkSourceClockHz < format->sampleRate_Hz))
{
return kStatus_InvalidArgument;
}
/* Copy format to handle */
handle->bitWidth = format->bitWidth;
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
handle->watermark = format->watermark;
#endif
handle->channel = format->channel;
SAI_TxSetFormat(base, format, mclkSourceClockHz, bclkSourceClockHz);
return kStatus_Success;
}
status_t SAI_TransferRxSetFormat(I2S_Type *base,
sai_handle_t *handle,
sai_transfer_format_t *format,
uint32_t mclkSourceClockHz,
uint32_t bclkSourceClockHz)
{
assert(handle);
if ((mclkSourceClockHz < format->sampleRate_Hz) || (bclkSourceClockHz < format->sampleRate_Hz))
{
return kStatus_InvalidArgument;
}
/* Copy format to handle */
handle->bitWidth = format->bitWidth;
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
handle->watermark = format->watermark;
#endif
handle->channel = format->channel;
SAI_RxSetFormat(base, format, mclkSourceClockHz, bclkSourceClockHz);
return kStatus_Success;
}
status_t SAI_TransferSendNonBlocking(I2S_Type *base, sai_handle_t *handle, sai_transfer_t *xfer)
{
assert(handle);
/* Check if the queue is full */
if (handle->saiQueue[handle->queueUser].data)
{
return kStatus_SAI_QueueFull;
}
/* Add into queue */
handle->transferSize[handle->queueUser] = xfer->dataSize;
handle->saiQueue[handle->queueUser].data = xfer->data;
handle->saiQueue[handle->queueUser].dataSize = xfer->dataSize;
handle->queueUser = (handle->queueUser + 1) % SAI_XFER_QUEUE_SIZE;
/* Set the state to busy */
handle->state = kSAI_Busy;
/* Enable interrupt */
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
/* Use FIFO request interrupt and fifo error*/
SAI_TxEnableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFORequestInterruptEnable);
#else
SAI_TxEnableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFOWarningInterruptEnable);
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
/* Enable Tx transfer */
SAI_TxEnable(base, true);
return kStatus_Success;
}
status_t SAI_TransferReceiveNonBlocking(I2S_Type *base, sai_handle_t *handle, sai_transfer_t *xfer)
{
assert(handle);
/* Check if the queue is full */
if (handle->saiQueue[handle->queueUser].data)
{
return kStatus_SAI_QueueFull;
}
/* Add into queue */
handle->transferSize[handle->queueUser] = xfer->dataSize;
handle->saiQueue[handle->queueUser].data = xfer->data;
handle->saiQueue[handle->queueUser].dataSize = xfer->dataSize;
handle->queueUser = (handle->queueUser + 1) % SAI_XFER_QUEUE_SIZE;
/* Set state to busy */
handle->state = kSAI_Busy;
/* Enable interrupt */
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
/* Use FIFO request interrupt and fifo error*/
SAI_RxEnableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFORequestInterruptEnable);
#else
SAI_RxEnableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFOWarningInterruptEnable);
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
/* Enable Rx transfer */
SAI_RxEnable(base, true);
return kStatus_Success;
}
status_t SAI_TransferGetSendCount(I2S_Type *base, sai_handle_t *handle, size_t *count)
{
assert(handle);
status_t status = kStatus_Success;
if (handle->state != kSAI_Busy)
{
status = kStatus_NoTransferInProgress;
}
else
{
*count = (handle->transferSize[handle->queueDriver] - handle->saiQueue[handle->queueDriver].dataSize);
}
return status;
}
status_t SAI_TransferGetReceiveCount(I2S_Type *base, sai_handle_t *handle, size_t *count)
{
assert(handle);
status_t status = kStatus_Success;
if (handle->state != kSAI_Busy)
{
status = kStatus_NoTransferInProgress;
}
else
{
*count = (handle->transferSize[handle->queueDriver] - handle->saiQueue[handle->queueDriver].dataSize);
}
return status;
}
void SAI_TransferAbortSend(I2S_Type *base, sai_handle_t *handle)
{
assert(handle);
/* Stop Tx transfer and disable interrupt */
SAI_TxEnable(base, false);
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
/* Use FIFO request interrupt and fifo error */
SAI_TxDisableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFORequestInterruptEnable);
#else
SAI_TxDisableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFOWarningInterruptEnable);
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
handle->state = kSAI_Idle;
/* Clear the queue */
memset(handle->saiQueue, 0, sizeof(sai_transfer_t) * SAI_XFER_QUEUE_SIZE);
handle->queueDriver = 0;
handle->queueUser = 0;
}
void SAI_TransferAbortReceive(I2S_Type *base, sai_handle_t *handle)
{
assert(handle);
/* Stop Tx transfer and disable interrupt */
SAI_RxEnable(base, false);
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
/* Use FIFO request interrupt and fifo error */
SAI_RxDisableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFORequestInterruptEnable);
#else
SAI_RxDisableInterrupts(base, kSAI_FIFOErrorInterruptEnable | kSAI_FIFOWarningInterruptEnable);
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
handle->state = kSAI_Idle;
/* Clear the queue */
memset(handle->saiQueue, 0, sizeof(sai_transfer_t) * SAI_XFER_QUEUE_SIZE);
handle->queueDriver = 0;
handle->queueUser = 0;
}
void SAI_TransferTxHandleIRQ(I2S_Type *base, sai_handle_t *handle)
{
assert(handle);
uint8_t *buffer = handle->saiQueue[handle->queueDriver].data;
uint8_t dataSize = handle->bitWidth / 8U;
/* Handle Error */
if (base->TCSR & I2S_TCSR_FEF_MASK)
{
/* Clear FIFO error flag to continue transfer */
SAI_TxClearStatusFlags(base, kSAI_FIFOErrorFlag);
/* Call the callback */
if (handle->callback)
{
(handle->callback)(base, handle, kStatus_SAI_TxError, handle->userData);
}
}
/* Handle transfer */
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if (base->TCSR & I2S_TCSR_FRF_MASK)
{
/* Judge if the data need to transmit is less than space */
uint8_t size = MIN((handle->saiQueue[handle->queueDriver].dataSize),
(size_t)((FSL_FEATURE_SAI_FIFO_COUNT - handle->watermark) * dataSize));
/* Copy the data from sai buffer to FIFO */
SAI_WriteNonBlocking(base, handle->channel, handle->bitWidth, buffer, size);
/* Update the internal counter */
handle->saiQueue[handle->queueDriver].dataSize -= size;
handle->saiQueue[handle->queueDriver].data += size;
}
#else
if (base->TCSR & I2S_TCSR_FWF_MASK)
{
uint8_t size = MIN((handle->saiQueue[handle->queueDriver].dataSize), dataSize);
SAI_WriteNonBlocking(base, handle->channel, handle->bitWidth, buffer, size);
/* Update internal counter */
handle->saiQueue[handle->queueDriver].dataSize -= size;
handle->saiQueue[handle->queueDriver].data += size;
}
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
/* If finished a blcok, call the callback function */
if (handle->saiQueue[handle->queueDriver].dataSize == 0U)
{
memset(&handle->saiQueue[handle->queueDriver], 0, sizeof(sai_transfer_t));
handle->queueDriver = (handle->queueDriver + 1) % SAI_XFER_QUEUE_SIZE;
if (handle->callback)
{
(handle->callback)(base, handle, kStatus_SAI_TxIdle, handle->userData);
}
}
/* If all data finished, just stop the transfer */
if (handle->saiQueue[handle->queueDriver].data == NULL)
{
SAI_TransferAbortSend(base, handle);
}
}
void SAI_TransferRxHandleIRQ(I2S_Type *base, sai_handle_t *handle)
{
assert(handle);
uint8_t *buffer = handle->saiQueue[handle->queueDriver].data;
uint8_t dataSize = handle->bitWidth / 8U;
/* Handle Error */
if (base->RCSR & I2S_RCSR_FEF_MASK)
{
/* Clear FIFO error flag to continue transfer */
SAI_RxClearStatusFlags(base, kSAI_FIFOErrorFlag);
/* Call the callback */
if (handle->callback)
{
(handle->callback)(base, handle, kStatus_SAI_RxError, handle->userData);
}
}
/* Handle transfer */
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if (base->RCSR & I2S_RCSR_FRF_MASK)
{
/* Judge if the data need to transmit is less than space */
uint8_t size = MIN((handle->saiQueue[handle->queueDriver].dataSize), (handle->watermark * dataSize));
/* Copy the data from sai buffer to FIFO */
SAI_ReadNonBlocking(base, handle->channel, handle->bitWidth, buffer, size);
/* Update the internal counter */
handle->saiQueue[handle->queueDriver].dataSize -= size;
handle->saiQueue[handle->queueDriver].data += size;
}
#else
if (base->RCSR & I2S_RCSR_FWF_MASK)
{
uint8_t size = MIN((handle->saiQueue[handle->queueDriver].dataSize), dataSize);
SAI_ReadNonBlocking(base, handle->channel, handle->bitWidth, buffer, size);
/* Update internal state */
handle->saiQueue[handle->queueDriver].dataSize -= size;
handle->saiQueue[handle->queueDriver].data += size;
}
#endif /* FSL_FEATURE_SAI_FIFO_COUNT */
/* If finished a blcok, call the callback function */
if (handle->saiQueue[handle->queueDriver].dataSize == 0U)
{
memset(&handle->saiQueue[handle->queueDriver], 0, sizeof(sai_transfer_t));
handle->queueDriver = (handle->queueDriver + 1) % SAI_XFER_QUEUE_SIZE;
if (handle->callback)
{
(handle->callback)(base, handle, kStatus_SAI_RxIdle, handle->userData);
}
}
/* If all data finished, just stop the transfer */
if (handle->saiQueue[handle->queueDriver].data == NULL)
{
SAI_TransferAbortReceive(base, handle);
}
}
#if defined(I2S0)
void I2S0_DriverIRQHandler(void)
{
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[0][1]) && ((I2S0->RCSR & kSAI_FIFORequestFlag) || (I2S0->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S0->RCSR & kSAI_FIFORequestInterruptEnable) || (I2S0->RCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[0][1]) && ((I2S0->RCSR & kSAI_FIFOWarningFlag) || (I2S0->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S0->RCSR & kSAI_FIFOWarningInterruptEnable) || (I2S0->RCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiRxIsr(I2S0, s_saiHandle[0][1]);
}
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[0][0]) && ((I2S0->TCSR & kSAI_FIFORequestFlag) || (I2S0->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S0->TCSR & kSAI_FIFORequestInterruptEnable) || (I2S0->TCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[0][0]) && ((I2S0->TCSR & kSAI_FIFOWarningFlag) || (I2S0->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S0->TCSR & kSAI_FIFOWarningInterruptEnable) || (I2S0->TCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiTxIsr(I2S0, s_saiHandle[0][0]);
}
}
void I2S0_Tx_DriverIRQHandler(void)
{
assert(s_saiHandle[0][0]);
s_saiTxIsr(I2S0, s_saiHandle[0][0]);
}
void I2S0_Rx_DriverIRQHandler(void)
{
assert(s_saiHandle[0][1]);
s_saiRxIsr(I2S0, s_saiHandle[0][1]);
}
#endif /* I2S0*/
#if defined(I2S1)
void I2S1_DriverIRQHandler(void)
{
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[1][1]) && ((I2S1->RCSR & kSAI_FIFORequestFlag) || (I2S1->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S1->RCSR & kSAI_FIFORequestInterruptEnable) || (I2S1->RCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[1][1]) && ((I2S1->RCSR & kSAI_FIFOWarningFlag) || (I2S1->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S1->RCSR & kSAI_FIFOWarningInterruptEnable) || (I2S1->RCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiRxIsr(I2S1, s_saiHandle[1][1]);
}
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[1][0]) && ((I2S1->TCSR & kSAI_FIFORequestFlag) || (I2S1->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S1->TCSR & kSAI_FIFORequestInterruptEnable) || (I2S1->TCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[1][0]) && ((I2S1->TCSR & kSAI_FIFOWarningFlag) || (I2S1->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S1->TCSR & kSAI_FIFOWarningInterruptEnable) || (I2S1->TCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiTxIsr(I2S1, s_saiHandle[1][0]);
}
}
void I2S1_Tx_DriverIRQHandler(void)
{
assert(s_saiHandle[1][0]);
s_saiTxIsr(I2S1, s_saiHandle[1][0]);
}
void I2S1_Rx_DriverIRQHandler(void)
{
assert(s_saiHandle[1][1]);
s_saiRxIsr(I2S1, s_saiHandle[1][1]);
}
#endif /* I2S1*/
#if defined(I2S2)
void I2S2_DriverIRQHandler(void)
{
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[2][1]) && ((I2S2->RCSR & kSAI_FIFORequestFlag) || (I2S2->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S2->RCSR & kSAI_FIFORequestInterruptEnable) || (I2S2->RCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[2][1]) && ((I2S2->RCSR & kSAI_FIFOWarningFlag) || (I2S2->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S2->RCSR & kSAI_FIFOWarningInterruptEnable) || (I2S2->RCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiRxIsr(I2S2, s_saiHandle[2][1]);
}
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[2][0]) && ((I2S2->TCSR & kSAI_FIFORequestFlag) || (I2S2->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S2->TCSR & kSAI_FIFORequestInterruptEnable) || (I2S2->TCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[2][0]) && ((I2S2->TCSR & kSAI_FIFOWarningFlag) || (I2S2->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S2->TCSR & kSAI_FIFOWarningInterruptEnable) || (I2S2->TCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiTxIsr(I2S2, s_saiHandle[2][0]);
}
}
void I2S2_Tx_DriverIRQHandler(void)
{
assert(s_saiHandle[2][0]);
s_saiTxIsr(I2S2, s_saiHandle[2][0]);
}
void I2S2_Rx_DriverIRQHandler(void)
{
assert(s_saiHandle[2][1]);
s_saiRxIsr(I2S2, s_saiHandle[2][1]);
}
#endif /* I2S2*/
#if defined(I2S3)
void I2S3_DriverIRQHandler(void)
{
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[3][1]) && ((I2S3->RCSR & kSAI_FIFORequestFlag) || (I2S3->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S3->RCSR & kSAI_FIFORequestInterruptEnable) || (I2S3->RCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[3][1]) && ((I2S3->RCSR & kSAI_FIFOWarningFlag) || (I2S3->RCSR & kSAI_FIFOErrorFlag)) &&
((I2S3->RCSR & kSAI_FIFOWarningInterruptEnable) || (I2S3->RCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiRxIsr(I2S3, s_saiHandle[3][1]);
}
#if defined(FSL_FEATURE_SAI_FIFO_COUNT) && (FSL_FEATURE_SAI_FIFO_COUNT > 1)
if ((s_saiHandle[3][0]) && ((I2S3->TCSR & kSAI_FIFORequestFlag) || (I2S3->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S3->TCSR & kSAI_FIFORequestInterruptEnable) || (I2S3->TCSR & kSAI_FIFOErrorInterruptEnable)))
#else
if ((s_saiHandle[3][0]) && ((I2S3->TCSR & kSAI_FIFOWarningFlag) || (I2S3->TCSR & kSAI_FIFOErrorFlag)) &&
((I2S3->TCSR & kSAI_FIFOWarningInterruptEnable) || (I2S3->TCSR & kSAI_FIFOErrorInterruptEnable)))
#endif
{
s_saiTxIsr(I2S3, s_saiHandle[3][0]);
}
}
void I2S3_Tx_DriverIRQHandler(void)
{
assert(s_saiHandle[3][0]);
s_saiTxIsr(I2S3, s_saiHandle[3][0]);
}
void I2S3_Rx_DriverIRQHandler(void)
{
assert(s_saiHandle[3][1]);
s_saiRxIsr(I2S3, s_saiHandle[3][1]);
}
#endif /* I2S3*/