/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2019 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_flexio_i2s.h"

/* Component ID definition, used by tools. */
#ifndef FSL_COMPONENT_ID
#define FSL_COMPONENT_ID "platform.drivers.flexio_i2s"
#endif

/*******************************************************************************
 * Definitations
 ******************************************************************************/
/*!@brief _sai_transfer_state*/
enum
{
    kFLEXIO_I2S_Busy = 0x0U, /*!< FLEXIO_I2S is busy */
    kFLEXIO_I2S_Idle,        /*!< Transfer is done. */
};

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/*!
 * @brief Receive a piece of data in non-blocking way.
 *
 * @param base FLEXIO I2S base pointer
 * @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 FLEXIO_I2S_ReadNonBlocking(FLEXIO_I2S_Type *base, uint8_t bitWidth, uint8_t *rxData, size_t size);

/*!
 * @brief sends a piece of data in non-blocking way.
 *
 * @param base FLEXIO I2S base pointer
 * @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 FLEXIO_I2S_WriteNonBlocking(FLEXIO_I2S_Type *base, uint8_t bitWidth, uint8_t *txData, size_t size);
/*******************************************************************************
 * Variables
 ******************************************************************************/

/*******************************************************************************
 * Code
 ******************************************************************************/

static uint32_t FLEXIO_I2S_GetInstance(FLEXIO_I2S_Type *base)
{
    return FLEXIO_GetInstance(base->flexioBase);
}

static void FLEXIO_I2S_WriteNonBlocking(FLEXIO_I2S_Type *base, uint8_t bitWidth, uint8_t *txData, size_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)(*txData);
            data |= (temp << (8U * j));
            txData++;
        }
        base->flexioBase->SHIFTBUFBIS[base->txShifterIndex] = data << (32U - bitWidth);
        data                                                = 0;
    }
}

static void FLEXIO_I2S_ReadNonBlocking(FLEXIO_I2S_Type *base, uint8_t bitWidth, uint8_t *rxData, size_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->flexioBase->SHIFTBUFBIS[base->rxShifterIndex]);
        for (j = 0; j < bytesPerWord; j++)
        {
            *rxData = (uint8_t)((data >> (8U * j)) & 0xFFU);
            rxData++;
        }
    }
}

/*!
 * brief Initializes the FlexIO I2S.
 *
 * This API configures FlexIO pins and shifter to I2S and configures the FlexIO I2S with a configuration structure.
 * The configuration structure can be filled by the user, or be set with default values by
 * FLEXIO_I2S_GetDefaultConfig().
 *
 * note  This API should be called at the beginning of the application to use
 * the FlexIO I2S driver. Otherwise, any access to the FlexIO I2S module can cause hard fault
 * because the clock is not enabled.
 *
 * param base FlexIO I2S base pointer
 * param config FlexIO I2S configure structure.
 */
void FLEXIO_I2S_Init(FLEXIO_I2S_Type *base, const flexio_i2s_config_t *config)
{
    assert((base != NULL) && (config != NULL));

    flexio_shifter_config_t shifterConfig = {0};
    flexio_timer_config_t timerConfig     = {0};

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Ungate flexio clock. */
    CLOCK_EnableClock(s_flexioClocks[FLEXIO_I2S_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

    /* reset Flexio */
    FLEXIO_Reset(base->flexioBase);

    /* Set shifter for I2S Tx data */
    shifterConfig.timerSelect   = base->bclkTimerIndex;
    shifterConfig.pinSelect     = base->txPinIndex;
    shifterConfig.timerPolarity = config->txTimerPolarity;
    shifterConfig.pinConfig     = kFLEXIO_PinConfigOutput;
    shifterConfig.pinPolarity   = config->txPinPolarity;
    shifterConfig.shifterMode   = kFLEXIO_ShifterModeTransmit;
    shifterConfig.inputSource   = kFLEXIO_ShifterInputFromPin;
    shifterConfig.shifterStop   = kFLEXIO_ShifterStopBitDisable;
    if (config->masterSlave == kFLEXIO_I2S_Master)
    {
        shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnShift;
    }
    else
    {
        shifterConfig.shifterStart = kFLEXIO_ShifterStartBitDisabledLoadDataOnEnable;
    }

    FLEXIO_SetShifterConfig(base->flexioBase, base->txShifterIndex, &shifterConfig);

    /* Set shifter for I2S Rx Data */
    shifterConfig.timerSelect   = base->bclkTimerIndex;
    shifterConfig.pinSelect     = base->rxPinIndex;
    shifterConfig.timerPolarity = config->rxTimerPolarity;
    shifterConfig.pinConfig     = kFLEXIO_PinConfigOutputDisabled;
    shifterConfig.pinPolarity   = config->rxPinPolarity;
    shifterConfig.shifterMode   = kFLEXIO_ShifterModeReceive;
    shifterConfig.inputSource   = kFLEXIO_ShifterInputFromPin;
    shifterConfig.shifterStop   = kFLEXIO_ShifterStopBitDisable;
    shifterConfig.shifterStart  = kFLEXIO_ShifterStartBitDisabledLoadDataOnEnable;

    FLEXIO_SetShifterConfig(base->flexioBase, base->rxShifterIndex, &shifterConfig);

    /* Set Timer to I2S frame sync */
    if (config->masterSlave == kFLEXIO_I2S_Master)
    {
        timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_PININPUT(base->txPinIndex);
        timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
        timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceExternal;
        timerConfig.pinConfig       = kFLEXIO_PinConfigOutput;
        timerConfig.pinSelect       = base->fsPinIndex;
        timerConfig.pinPolarity     = config->fsPinPolarity;
        timerConfig.timerMode       = kFLEXIO_TimerModeSingle16Bit;
        timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
        timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput;
        timerConfig.timerReset      = kFLEXIO_TimerResetNever;
        timerConfig.timerDisable    = kFLEXIO_TimerDisableNever;
        timerConfig.timerEnable     = kFLEXIO_TimerEnableOnPrevTimerEnable;
        timerConfig.timerStart      = kFLEXIO_TimerStartBitDisabled;
        timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
    }
    else
    {
        timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_PININPUT(base->bclkPinIndex);
        timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
        timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;
        timerConfig.pinConfig       = kFLEXIO_PinConfigOutputDisabled;
        timerConfig.pinSelect       = base->fsPinIndex;
        timerConfig.pinPolarity     = config->fsPinPolarity;
        timerConfig.timerMode       = kFLEXIO_TimerModeSingle16Bit;
        timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
        timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnTriggerInputShiftTriggerInput;
        timerConfig.timerReset      = kFLEXIO_TimerResetNever;
        timerConfig.timerDisable    = kFLEXIO_TimerDisableOnTimerCompare;
        timerConfig.timerEnable     = kFLEXIO_TimerEnableOnPinRisingEdge;
        timerConfig.timerStart      = kFLEXIO_TimerStartBitDisabled;
        timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
    }
    FLEXIO_SetTimerConfig(base->flexioBase, base->fsTimerIndex, &timerConfig);

    /* Set Timer to I2S bit clock */
    if (config->masterSlave == kFLEXIO_I2S_Master)
    {
        timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_SHIFTnSTAT(base->txShifterIndex);
        timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveLow;
        timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;
        timerConfig.pinSelect       = base->bclkPinIndex;
        timerConfig.pinConfig       = kFLEXIO_PinConfigOutput;
        timerConfig.pinPolarity     = config->bclkPinPolarity;
        timerConfig.timerMode       = kFLEXIO_TimerModeDual8BitBaudBit;
        timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
        timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnFlexIOClockShiftTimerOutput;
        timerConfig.timerReset      = kFLEXIO_TimerResetNever;
        timerConfig.timerDisable    = kFLEXIO_TimerDisableNever;
        timerConfig.timerEnable     = kFLEXIO_TimerEnableOnTriggerHigh;
        timerConfig.timerStart      = kFLEXIO_TimerStartBitEnabled;
        timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
    }
    else
    {
        timerConfig.triggerSelect   = FLEXIO_TIMER_TRIGGER_SEL_TIMn(base->fsTimerIndex);
        timerConfig.triggerPolarity = kFLEXIO_TimerTriggerPolarityActiveHigh;
        timerConfig.triggerSource   = kFLEXIO_TimerTriggerSourceInternal;
        timerConfig.pinSelect       = base->bclkPinIndex;
        timerConfig.pinConfig       = kFLEXIO_PinConfigOutputDisabled;
        timerConfig.pinPolarity     = config->bclkPinPolarity;
        timerConfig.timerMode       = kFLEXIO_TimerModeSingle16Bit;
        timerConfig.timerOutput     = kFLEXIO_TimerOutputOneNotAffectedByReset;
        timerConfig.timerDecrement  = kFLEXIO_TimerDecSrcOnPinInputShiftPinInput;
        timerConfig.timerReset      = kFLEXIO_TimerResetNever;
        timerConfig.timerDisable    = kFLEXIO_TimerDisableOnTimerCompareTriggerLow;
        timerConfig.timerEnable     = kFLEXIO_TimerEnableOnPinRisingEdgeTriggerHigh;
        timerConfig.timerStart      = kFLEXIO_TimerStartBitDisabled;
        timerConfig.timerStop       = kFLEXIO_TimerStopBitDisabled;
    }
    FLEXIO_SetTimerConfig(base->flexioBase, base->bclkTimerIndex, &timerConfig);

    /* If enable flexio I2S */
    if (config->enableI2S)
    {
        base->flexioBase->CTRL |= FLEXIO_CTRL_FLEXEN_MASK;
    }
    else
    {
        base->flexioBase->CTRL &= ~FLEXIO_CTRL_FLEXEN_MASK;
    }
}

/*!
 * brief  Sets the FlexIO I2S configuration structure to default values.
 *
 * The purpose of this API is to get the configuration structure initialized for use in FLEXIO_I2S_Init().
 * Users may use the initialized structure unchanged in FLEXIO_I2S_Init() or modify
 * some fields of the structure before calling FLEXIO_I2S_Init().
 *
 * param config pointer to master configuration structure
 */
void FLEXIO_I2S_GetDefaultConfig(flexio_i2s_config_t *config)
{
    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));

    config->masterSlave     = kFLEXIO_I2S_Master;
    config->enableI2S       = true;
    config->txPinPolarity   = kFLEXIO_PinActiveHigh;
    config->rxPinPolarity   = kFLEXIO_PinActiveHigh;
    config->bclkPinPolarity = kFLEXIO_PinActiveHigh;
    config->fsPinPolarity   = kFLEXIO_PinActiveLow;
    config->txTimerPolarity = kFLEXIO_ShifterTimerPolarityOnPositive;
    config->rxTimerPolarity = kFLEXIO_ShifterTimerPolarityOnNegitive;
}

/*!
 * brief De-initializes the FlexIO I2S.
 *
 * Calling this API resets the FlexIO I2S shifter and timer config. After calling this API,
 * call the FLEXO_I2S_Init to use the FlexIO I2S module.
 *
 * param base FlexIO I2S base pointer
 */
void FLEXIO_I2S_Deinit(FLEXIO_I2S_Type *base)
{
    base->flexioBase->SHIFTCFG[base->txShifterIndex] = 0;
    base->flexioBase->SHIFTCTL[base->txShifterIndex] = 0;
    base->flexioBase->SHIFTCFG[base->rxShifterIndex] = 0;
    base->flexioBase->SHIFTCTL[base->rxShifterIndex] = 0;
    base->flexioBase->TIMCFG[base->fsTimerIndex]     = 0;
    base->flexioBase->TIMCMP[base->fsTimerIndex]     = 0;
    base->flexioBase->TIMCTL[base->fsTimerIndex]     = 0;
    base->flexioBase->TIMCFG[base->bclkTimerIndex]   = 0;
    base->flexioBase->TIMCMP[base->bclkTimerIndex]   = 0;
    base->flexioBase->TIMCTL[base->bclkTimerIndex]   = 0;
}

/*!
 * brief Enables the FlexIO I2S interrupt.
 *
 * This function enables the FlexIO UART interrupt.
 *
 * param base Pointer to FLEXIO_I2S_Type structure
 * param mask interrupt source
 */
void FLEXIO_I2S_EnableInterrupts(FLEXIO_I2S_Type *base, uint32_t mask)
{
    if ((mask & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyInterruptEnable) != 0UL)
    {
        FLEXIO_EnableShifterStatusInterrupts(base->flexioBase, 1UL << base->txShifterIndex);
    }
    if ((mask & (uint32_t)kFLEXIO_I2S_RxDataRegFullInterruptEnable) != 0UL)
    {
        FLEXIO_EnableShifterStatusInterrupts(base->flexioBase, 1UL << base->rxShifterIndex);
    }
}

/*!
 * brief Gets the FlexIO I2S status flags.
 *
 * param base Pointer to FLEXIO_I2S_Type structure
 * return Status flag, which are ORed by the enumerators in the _flexio_i2s_status_flags.
 */
uint32_t FLEXIO_I2S_GetStatusFlags(FLEXIO_I2S_Type *base)
{
    uint32_t status = 0;
    status = ((FLEXIO_GetShifterStatusFlags(base->flexioBase) & (1UL << base->txShifterIndex)) >> base->txShifterIndex);
    status |=
        (((FLEXIO_GetShifterStatusFlags(base->flexioBase) & (1UL << base->rxShifterIndex)) >> (base->rxShifterIndex))
         << 1U);
    return status;
}

/*!
 * brief Disables the FlexIO I2S interrupt.
 *
 * This function enables the FlexIO UART interrupt.
 *
 * param base pointer to FLEXIO_I2S_Type structure
 * param mask interrupt source
 */
void FLEXIO_I2S_DisableInterrupts(FLEXIO_I2S_Type *base, uint32_t mask)
{
    if ((mask & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyInterruptEnable) != 0UL)
    {
        FLEXIO_DisableShifterStatusInterrupts(base->flexioBase, 1UL << base->txShifterIndex);
    }
    if ((mask & (uint32_t)kFLEXIO_I2S_RxDataRegFullInterruptEnable) != 0UL)
    {
        FLEXIO_DisableShifterStatusInterrupts(base->flexioBase, 1UL << base->rxShifterIndex);
    }
}

/*!
 * brief Configures the FlexIO I2S audio format in master mode.
 *
 * Audio format can be changed in run-time of FlexIO I2S. This function configures the sample rate and audio data
 * format to be transferred.
 *
 * param base Pointer to FLEXIO_I2S_Type structure
 * param format Pointer to FlexIO I2S audio data format structure.
 * param srcClock_Hz I2S master clock source frequency in Hz.
 */
void FLEXIO_I2S_MasterSetFormat(FLEXIO_I2S_Type *base, flexio_i2s_format_t *format, uint32_t srcClock_Hz)
{
    uint32_t timDiv  = srcClock_Hz / (format->sampleRate_Hz * format->bitWidth * 2U);
    uint32_t bclkDiv = 0;

    /* Shall keep bclk and fs div an integer */
    if ((timDiv % 2UL) != 0UL)
    {
        timDiv += 1U;
    }
    /* Set Frame sync timer cmp */
    base->flexioBase->TIMCMP[base->fsTimerIndex] = FLEXIO_TIMCMP_CMP(format->bitWidth * timDiv - 1U);

    /* Set bit clock timer cmp */
    bclkDiv                                        = ((timDiv / 2U - 1U) | ((format->bitWidth * 2UL - 1UL) << 8U));
    base->flexioBase->TIMCMP[base->bclkTimerIndex] = FLEXIO_TIMCMP_CMP(bclkDiv);
}

/*!
 * brief Configures the FlexIO I2S audio format in slave mode.
 *
 * Audio format can be changed in run-time of FlexIO I2S. This function configures the sample rate and audio data
 * format to be transferred.
 *
 * param base Pointer to FLEXIO_I2S_Type structure
 * param format Pointer to FlexIO I2S audio data format structure.
 */
void FLEXIO_I2S_SlaveSetFormat(FLEXIO_I2S_Type *base, flexio_i2s_format_t *format)
{
    /* Set Frame sync timer cmp */
    base->flexioBase->TIMCMP[base->fsTimerIndex] = FLEXIO_TIMCMP_CMP(format->bitWidth * 4UL - 3UL);

    /* Set bit clock timer cmp */
    base->flexioBase->TIMCMP[base->bclkTimerIndex] = FLEXIO_TIMCMP_CMP(format->bitWidth * 2UL - 1UL);
}

/*!
 * brief Sends data using a blocking method.
 *
 * note This function blocks via polling until data is ready to be sent.
 *
 * param base FlexIO I2S base pointer.
 * param bitWidth How many bits in a audio word, usually 8/16/24/32 bits.
 * param txData Pointer to the data to be written.
 * param size Bytes to be written.
 * retval kStatus_Success Successfully write data.
 * retval kStatus_FLEXIO_I2C_Timeout Timeout polling status flags.
 */
status_t FLEXIO_I2S_WriteBlocking(FLEXIO_I2S_Type *base, uint8_t bitWidth, uint8_t *txData, size_t size)
{
    uint32_t i           = 0;
    uint8_t bytesPerWord = bitWidth / 8U;
#if I2S_RETRY_TIMES
    uint32_t waitTimes = I2S_RETRY_TIMES;
#endif

    for (i = 0; i < size / bytesPerWord; i++)
    {
        /* Wait until it can write data */
#if I2S_RETRY_TIMES
        waitTimes = I2S_RETRY_TIMES;
        while (((FLEXIO_I2S_GetStatusFlags(base) & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyFlag) == 0UL) &&
               (--waitTimes != 0U))
        {
        }
        if (waitTimes == 0U)
        {
            return kStatus_FLEXIO_I2S_Timeout;
        }
#else
        while ((FLEXIO_I2S_GetStatusFlags(base) & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyFlag) == 0UL)
        {
        }
#endif

        FLEXIO_I2S_WriteNonBlocking(base, bitWidth, txData, bytesPerWord);
        txData = (uint8_t *)((uint32_t)txData + bytesPerWord);
    }

    /* Wait until the last data is sent */
#if I2S_RETRY_TIMES
    waitTimes = I2S_RETRY_TIMES;
    while (((FLEXIO_I2S_GetStatusFlags(base) & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyFlag) == 0UL) && (--waitTimes != 0U))
    {
    }
    if (waitTimes == 0U)
    {
        return kStatus_FLEXIO_I2S_Timeout;
    }
#else
    while ((FLEXIO_I2S_GetStatusFlags(base) & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyFlag) == 0UL)
    {
    }
#endif

    return kStatus_Success;
}

/*!
 * brief Receives a piece of data using a blocking method.
 *
 * note This function blocks via polling until data is ready to be sent.
 *
 * param base FlexIO I2S base pointer
 * param bitWidth How many bits in a audio word, usually 8/16/24/32 bits.
 * param rxData Pointer to the data to be read.
 * param size Bytes to be read.
 * retval kStatus_Success Successfully read data.
 * retval kStatus_FLEXIO_I2C_Timeout Timeout polling status flags.
 */
status_t FLEXIO_I2S_ReadBlocking(FLEXIO_I2S_Type *base, uint8_t bitWidth, uint8_t *rxData, size_t size)
{
    uint32_t i           = 0;
    uint8_t bytesPerWord = bitWidth / 8U;
#if I2S_RETRY_TIMES
    uint32_t waitTimes = I2S_RETRY_TIMES;
#endif

    for (i = 0; i < size / bytesPerWord; i++)
    {
        /* Wait until data is received */
#if I2S_RETRY_TIMES
        waitTimes = I2S_RETRY_TIMES;
        while ((!((FLEXIO_GetShifterStatusFlags(base->flexioBase) & (1UL << base->rxShifterIndex)) != 0UL)) &&
               (--waitTimes != 0U))
        {
        }
        if (waitTimes == 0U)
        {
            return kStatus_FLEXIO_I2S_Timeout;
        }
#else
        while (!((FLEXIO_GetShifterStatusFlags(base->flexioBase) & (1UL << base->rxShifterIndex)) != 0UL))
        {
        }
#endif

        FLEXIO_I2S_ReadNonBlocking(base, bitWidth, rxData, bytesPerWord);
        rxData = (uint8_t *)((uint32_t)rxData + bytesPerWord);
    }
    return kStatus_Success;
}

/*!
 * brief Initializes the FlexIO I2S handle.
 *
 * This function initializes the FlexIO I2S handle which can be used for other
 * FlexIO I2S transactional APIs. Call this API once to get the
 * initialized handle.
 *
 * param base Pointer to FLEXIO_I2S_Type structure
 * param handle Pointer to flexio_i2s_handle_t structure to store the transfer state.
 * param callback FlexIO I2S callback function, which is called while finished a block.
 * param userData User parameter for the FlexIO I2S callback.
 */
void FLEXIO_I2S_TransferTxCreateHandle(FLEXIO_I2S_Type *base,
                                       flexio_i2s_handle_t *handle,
                                       flexio_i2s_callback_t callback,
                                       void *userData)
{
    assert(handle != NULL);

    IRQn_Type flexio_irqs[] = FLEXIO_IRQS;

    /* Zero the handle. */
    (void)memset(handle, 0, sizeof(*handle));

    /* Store callback and user data. */
    handle->callback = callback;
    handle->userData = userData;

    /* Save the context in global variables to support the double weak mechanism. */
    (void)FLEXIO_RegisterHandleIRQ(base, handle, FLEXIO_I2S_TransferTxHandleIRQ);

    /* Set the TX/RX state. */
    handle->state = (uint32_t)kFLEXIO_I2S_Idle;

    /* Enable interrupt in NVIC. */
    (void)EnableIRQ(flexio_irqs[FLEXIO_I2S_GetInstance(base)]);
}

/*!
 * brief Initializes the FlexIO I2S receive handle.
 *
 * This function initializes the FlexIO I2S handle which can be used for other
 * FlexIO I2S transactional APIs. Call this API once to get the
 * initialized handle.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure to store the transfer state.
 * param callback FlexIO I2S callback function, which is called while finished a block.
 * param userData User parameter for the FlexIO I2S callback.
 */
void FLEXIO_I2S_TransferRxCreateHandle(FLEXIO_I2S_Type *base,
                                       flexio_i2s_handle_t *handle,
                                       flexio_i2s_callback_t callback,
                                       void *userData)
{
    assert(handle != NULL);

    IRQn_Type flexio_irqs[] = FLEXIO_IRQS;

    /* Zero the handle. */
    (void)memset(handle, 0, sizeof(*handle));

    /* Store callback and user data. */
    handle->callback = callback;
    handle->userData = userData;

    /* Save the context in global variables to support the double weak mechanism. */
    (void)FLEXIO_RegisterHandleIRQ(base, handle, FLEXIO_I2S_TransferRxHandleIRQ);

    /* Set the TX/RX state. */
    handle->state = (uint32_t)kFLEXIO_I2S_Idle;

    /* Enable interrupt in NVIC. */
    (void)EnableIRQ(flexio_irqs[FLEXIO_I2S_GetInstance(base)]);
}

/*!
 * brief Configures the FlexIO I2S audio format.
 *
 * Audio format can be changed at run-time of FlexIO I2S. This function configures the sample rate and audio data
 * format to be transferred.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle FlexIO I2S handle pointer.
 * param format Pointer to audio data format structure.
 * param srcClock_Hz FlexIO I2S bit clock source frequency in Hz. This parameter should be 0 while in slave mode.
 */
void FLEXIO_I2S_TransferSetFormat(FLEXIO_I2S_Type *base,
                                  flexio_i2s_handle_t *handle,
                                  flexio_i2s_format_t *format,
                                  uint32_t srcClock_Hz)
{
    assert((handle != NULL) && (format != NULL));

    /* Set the bitWidth to handle */
    handle->bitWidth = format->bitWidth;

    /* Set sample rate */
    if (srcClock_Hz != 0UL)
    {
        /* It is master */
        FLEXIO_I2S_MasterSetFormat(base, format, srcClock_Hz);
    }
    else
    {
        FLEXIO_I2S_SlaveSetFormat(base, format);
    }
}

/*!
 * brief Performs an interrupt non-blocking send transfer on FlexIO I2S.
 *
 * note The API returns immediately after transfer initiates.
 * Call FLEXIO_I2S_GetRemainingBytes to poll the transfer status and check whether
 * the transfer is finished. If the return status is 0, the transfer is finished.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure which stores the transfer state
 * param xfer Pointer to flexio_i2s_transfer_t structure
 * retval kStatus_Success Successfully start the data transmission.
 * retval kStatus_FLEXIO_I2S_TxBusy Previous transmission still not finished, data not all written to TX register yet.
 * retval kStatus_InvalidArgument The input parameter is invalid.
 */
status_t FLEXIO_I2S_TransferSendNonBlocking(FLEXIO_I2S_Type *base,
                                            flexio_i2s_handle_t *handle,
                                            flexio_i2s_transfer_t *xfer)
{
    assert(handle != NULL);

    /* Check if the queue is full */
    if (handle->queue[handle->queueUser].data != NULL)
    {
        return kStatus_FLEXIO_I2S_QueueFull;
    }
    if ((xfer->dataSize == 0U) || (xfer->data == NULL))
    {
        return kStatus_InvalidArgument;
    }

    /* Add into queue */
    handle->queue[handle->queueUser].data     = xfer->data;
    handle->queue[handle->queueUser].dataSize = xfer->dataSize;
    handle->transferSize[handle->queueUser]   = xfer->dataSize;
    handle->queueUser                         = (handle->queueUser + 1U) % FLEXIO_I2S_XFER_QUEUE_SIZE;

    /* Set the state to busy */
    handle->state = (uint32_t)kFLEXIO_I2S_Busy;

    FLEXIO_I2S_EnableInterrupts(base, kFLEXIO_I2S_TxDataRegEmptyInterruptEnable);

    /* Enable Tx transfer */
    FLEXIO_I2S_Enable(base, true);

    return kStatus_Success;
}

/*!
 * brief Performs an interrupt non-blocking receive transfer on FlexIO I2S.
 *
 * note The API returns immediately after transfer initiates.
 * Call FLEXIO_I2S_GetRemainingBytes to poll the transfer status to check whether
 * the transfer is finished. If the return status is 0, the transfer is finished.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure which stores the transfer state
 * param xfer Pointer to flexio_i2s_transfer_t structure
 * retval kStatus_Success Successfully start the data receive.
 * retval kStatus_FLEXIO_I2S_RxBusy Previous receive still not finished.
 * retval kStatus_InvalidArgument The input parameter is invalid.
 */
status_t FLEXIO_I2S_TransferReceiveNonBlocking(FLEXIO_I2S_Type *base,
                                               flexio_i2s_handle_t *handle,
                                               flexio_i2s_transfer_t *xfer)
{
    assert(handle != NULL);

    /* Check if the queue is full */
    if (handle->queue[handle->queueUser].data != NULL)
    {
        return kStatus_FLEXIO_I2S_QueueFull;
    }

    if ((xfer->dataSize == 0U) || (xfer->data == NULL))
    {
        return kStatus_InvalidArgument;
    }

    /* Add into queue */
    handle->queue[handle->queueUser].data     = xfer->data;
    handle->queue[handle->queueUser].dataSize = xfer->dataSize;
    handle->transferSize[handle->queueUser]   = xfer->dataSize;
    handle->queueUser                         = (handle->queueUser + 1U) % FLEXIO_I2S_XFER_QUEUE_SIZE;

    /* Set state to busy */
    handle->state = (uint32_t)kFLEXIO_I2S_Busy;

    /* Enable interrupt */
    FLEXIO_I2S_EnableInterrupts(base, kFLEXIO_I2S_RxDataRegFullInterruptEnable);

    /* Enable Rx transfer */
    FLEXIO_I2S_Enable(base, true);

    return kStatus_Success;
}

/*!
 * brief Aborts the current send.
 *
 * note This API can be called at any time when interrupt non-blocking transfer initiates
 * to abort the transfer in a early time.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure which stores the transfer state
 */
void FLEXIO_I2S_TransferAbortSend(FLEXIO_I2S_Type *base, flexio_i2s_handle_t *handle)
{
    assert(handle != NULL);

    /* Stop Tx transfer and disable interrupt */
    FLEXIO_I2S_DisableInterrupts(base, kFLEXIO_I2S_TxDataRegEmptyInterruptEnable);
    handle->state = (uint32_t)kFLEXIO_I2S_Idle;

    /* Clear the queue */
    (void)memset(handle->queue, 0, sizeof(flexio_i2s_transfer_t) * FLEXIO_I2S_XFER_QUEUE_SIZE);
    handle->queueDriver = 0;
    handle->queueUser   = 0;
}

/*!
 * brief Aborts the current receive.
 *
 * note This API can be called at any time when interrupt non-blocking transfer initiates
 * to abort the transfer in a early time.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure which stores the transfer state
 */
void FLEXIO_I2S_TransferAbortReceive(FLEXIO_I2S_Type *base, flexio_i2s_handle_t *handle)
{
    assert(handle != NULL);

    /* Stop rx transfer and disable interrupt */
    FLEXIO_I2S_DisableInterrupts(base, kFLEXIO_I2S_RxDataRegFullInterruptEnable);
    handle->state = (uint32_t)kFLEXIO_I2S_Idle;

    /* Clear the queue */
    (void)memset(handle->queue, 0, sizeof(flexio_i2s_transfer_t) * FLEXIO_I2S_XFER_QUEUE_SIZE);
    handle->queueDriver = 0;
    handle->queueUser   = 0;
}

/*!
 * brief Gets the remaining bytes to be sent.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure which stores the transfer state
 * param count Bytes sent.
 * retval kStatus_Success Succeed get the transfer count.
 * retval kStatus_NoTransferInProgress There is not a non-blocking transaction currently in progress.
 */
status_t FLEXIO_I2S_TransferGetSendCount(FLEXIO_I2S_Type *base, flexio_i2s_handle_t *handle, size_t *count)
{
    assert(handle != NULL);

    status_t status     = kStatus_Success;
    uint8_t queueDriver = handle->queueDriver;

    if (handle->state != (uint32_t)kFLEXIO_I2S_Busy)
    {
        status = kStatus_NoTransferInProgress;
    }
    else
    {
        *count = (handle->transferSize[queueDriver] - handle->queue[queueDriver].dataSize);
    }

    return status;
}

/*!
 * brief Gets the remaining bytes to be received.
 *
 * param base Pointer to FLEXIO_I2S_Type structure.
 * param handle Pointer to flexio_i2s_handle_t structure which stores the transfer state
 * return count Bytes received.
 * retval kStatus_Success Succeed get the transfer count.
 * retval kStatus_NoTransferInProgress There is not a non-blocking transaction currently in progress.
 */
status_t FLEXIO_I2S_TransferGetReceiveCount(FLEXIO_I2S_Type *base, flexio_i2s_handle_t *handle, size_t *count)
{
    assert(handle != NULL);

    status_t status     = kStatus_Success;
    uint8_t queueDriver = handle->queueDriver;

    if (handle->state != (uint32_t)kFLEXIO_I2S_Busy)
    {
        status = kStatus_NoTransferInProgress;
    }
    else
    {
        *count = (handle->transferSize[queueDriver] - handle->queue[queueDriver].dataSize);
    }

    return status;
}

/*!
 * brief Tx interrupt handler.
 *
 * param i2sBase Pointer to FLEXIO_I2S_Type structure.
 * param i2sHandle Pointer to flexio_i2s_handle_t structure
 */
void FLEXIO_I2S_TransferTxHandleIRQ(void *i2sBase, void *i2sHandle)
{
    assert(i2sHandle != NULL);

    flexio_i2s_handle_t *handle = (flexio_i2s_handle_t *)i2sHandle;
    FLEXIO_I2S_Type *base       = (FLEXIO_I2S_Type *)i2sBase;
    uint8_t *buffer             = handle->queue[handle->queueDriver].data;
    uint8_t dataSize            = handle->bitWidth / 8U;

    /* Handle error */
    if ((FLEXIO_GetShifterErrorFlags(base->flexioBase) & (1UL << base->txShifterIndex)) != 0UL)
    {
        FLEXIO_ClearShifterErrorFlags(base->flexioBase, (1UL << base->txShifterIndex));
    }
    /* Handle transfer */
    if (((FLEXIO_I2S_GetStatusFlags(base) & (uint32_t)kFLEXIO_I2S_TxDataRegEmptyFlag) != 0UL) &&
        (handle->queue[handle->queueDriver].data != NULL))
    {
        FLEXIO_I2S_WriteNonBlocking(base, handle->bitWidth, buffer, dataSize);

        /* Update internal counter */
        handle->queue[handle->queueDriver].dataSize -= dataSize;
        handle->queue[handle->queueDriver].data =
            (uint8_t *)((uint32_t)handle->queue[handle->queueDriver].data + dataSize);
    }

    /* If finished a block, call the callback function */
    if ((handle->queue[handle->queueDriver].dataSize == 0U) && (handle->queue[handle->queueDriver].data != NULL))
    {
        (void)memset(&handle->queue[handle->queueDriver], 0, sizeof(flexio_i2s_transfer_t));
        handle->queueDriver = (handle->queueDriver + 1U) % FLEXIO_I2S_XFER_QUEUE_SIZE;
        if (handle->callback != NULL)
        {
            (handle->callback)(base, handle, kStatus_Success, handle->userData);
        }
    }

    /* If all data finished, just stop the transfer */
    if (handle->queue[handle->queueDriver].data == NULL)
    {
        FLEXIO_I2S_TransferAbortSend(base, handle);
    }
}

/*!
 * brief Rx interrupt handler.
 *
 * param i2sBase Pointer to FLEXIO_I2S_Type structure.
 * param i2sHandle Pointer to flexio_i2s_handle_t structure.
 */
void FLEXIO_I2S_TransferRxHandleIRQ(void *i2sBase, void *i2sHandle)
{
    assert(i2sHandle != NULL);

    flexio_i2s_handle_t *handle = (flexio_i2s_handle_t *)i2sHandle;
    FLEXIO_I2S_Type *base       = (FLEXIO_I2S_Type *)i2sBase;
    uint8_t *buffer             = handle->queue[handle->queueDriver].data;
    uint8_t dataSize            = handle->bitWidth / 8U;

    /* Handle transfer */
    if (((FLEXIO_I2S_GetStatusFlags(base) & (uint32_t)kFLEXIO_I2S_RxDataRegFullFlag) != 0UL) &&
        (handle->queue[handle->queueDriver].data != NULL))
    {
        FLEXIO_I2S_ReadNonBlocking(base, handle->bitWidth, buffer, dataSize);

        /* Update internal state */
        handle->queue[handle->queueDriver].dataSize -= dataSize;
        handle->queue[handle->queueDriver].data =
            (uint8_t *)((uint32_t)handle->queue[handle->queueDriver].data + dataSize);
    }

    /* If finished a block, call the callback function */
    if ((handle->queue[handle->queueDriver].dataSize == 0U) && (handle->queue[handle->queueDriver].data != NULL))
    {
        (void)memset(&handle->queue[handle->queueDriver], 0, sizeof(flexio_i2s_transfer_t));
        handle->queueDriver = (handle->queueDriver + 1U) % FLEXIO_I2S_XFER_QUEUE_SIZE;
        if (handle->callback != NULL)
        {
            (handle->callback)(base, handle, kStatus_Success, handle->userData);
        }
    }

    /* If all data finished, just stop the transfer */
    if (handle->queue[handle->queueDriver].data == NULL)
    {
        FLEXIO_I2S_TransferAbortReceive(base, handle);
    }
}
