/*
 * 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_i2s.h"
#include "fsl_flexcomm.h"
#include <string.h>

/*******************************************************************************
 * Definitions
 ******************************************************************************/

/* TODO - absent in device header files, should be there */
#define I2S_FIFOCFG_TXI2SE0_MASK (0x4U)
#define I2S_FIFOCFG_TXI2SE0_SHIFT (2U)
#define I2S_FIFOCFG_TXI2SE0(x) (((uint32_t)(((uint32_t)(x)) << I2S_FIFOCFG_TXI2SE0_SHIFT)) & I2S_FIFOCFG_TXI2SE0_MASK)
#define I2S_FIFOCFG_PACK48_MASK (0x8U)
#define I2S_FIFOCFG_PACK48_SHIFT (3U)
#define I2S_FIFOCFG_PACK48(x) (((uint32_t)(((uint32_t)(x)) << I2S_FIFOCFG_PACK48_SHIFT)) & I2S_FIFOCFG_PACK48_MASK)

/*! @brief I2S states. */
enum _i2s_state
{
    kI2S_StateIdle = 0x0,             /*!< Not performing transfer */
    kI2S_StateTx,                     /*!< Performing transmit */
    kI2S_StateTxWaitToWriteDummyData, /*!< Wait on FIFO in order to write final dummy data there */
    kI2S_StateTxWaitForEmptyFifo,     /*!< Wait for FIFO to be flushed */
    kI2S_StateRx,                     /*!< Performing receive */
};

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

static void I2S_Config(I2S_Type *base, const i2s_config_t *config);
static status_t I2S_ValidateBuffer(i2s_handle_t *handle, i2s_transfer_t *transfer);

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

void I2S_TxInit(I2S_Type *base, const i2s_config_t *config)
{
    uint32_t cfg = 0U;
    uint32_t trig = 0U;

    FLEXCOMM_Init(base, FLEXCOMM_PERIPH_I2S_TX);
    I2S_Config(base, config);

    /* Configure FIFO */

    cfg |= I2S_FIFOCFG_ENABLETX(1U);                 /* enable TX FIFO */
    cfg |= I2S_FIFOCFG_EMPTYTX(1U);                  /* empty TX FIFO */
    cfg |= I2S_FIFOCFG_TXI2SE0(config->txEmptyZero); /* transmit zero when buffer becomes empty or last item */
    cfg |= I2S_FIFOCFG_PACK48(config->pack48);       /* set pack 48-bit format or not */
    trig |= I2S_FIFOTRIG_TXLVLENA(1U);               /* enable TX FIFO trigger */
    trig |= I2S_FIFOTRIG_TXLVL(config->watermark);   /* set TX FIFO trigger level */

    base->FIFOCFG = cfg;
    base->FIFOTRIG = trig;
}

void I2S_RxInit(I2S_Type *base, const i2s_config_t *config)
{
    uint32_t cfg = 0U;
    uint32_t trig = 0U;

    FLEXCOMM_Init(base, FLEXCOMM_PERIPH_I2S_RX);
    I2S_Config(base, config);

    /* Configure FIFO */

    cfg |= I2S_FIFOCFG_ENABLERX(1U);               /* enable RX FIFO */
    cfg |= I2S_FIFOCFG_EMPTYRX(1U);                /* empty RX FIFO */
    cfg |= I2S_FIFOCFG_PACK48(config->pack48);     /* set pack 48-bit format or not */
    trig |= I2S_FIFOTRIG_RXLVLENA(1U);             /* enable RX FIFO trigger */
    trig |= I2S_FIFOTRIG_RXLVL(config->watermark); /* set RX FIFO trigger level */

    base->FIFOCFG = cfg;
    base->FIFOTRIG = trig;
}

void I2S_TxGetDefaultConfig(i2s_config_t *config)
{
    config->masterSlave = kI2S_MasterSlaveNormalMaster;
    config->mode = kI2S_ModeI2sClassic;
    config->rightLow = false;
    config->leftJust = false;
    config->pdmData = false;
    config->sckPol = false;
    config->wsPol = false;
    config->divider = 1U;
    config->oneChannel = false;
    config->dataLength = 16U;
    config->frameLength = 32U;
    config->position = 0U;
    config->watermark = 4U;
    config->txEmptyZero = true;
    config->pack48 = false;
}

void I2S_RxGetDefaultConfig(i2s_config_t *config)
{
    config->masterSlave = kI2S_MasterSlaveNormalSlave;
    config->mode = kI2S_ModeI2sClassic;
    config->rightLow = false;
    config->leftJust = false;
    config->pdmData = false;
    config->sckPol = false;
    config->wsPol = false;
    config->divider = 1U;
    config->oneChannel = false;
    config->dataLength = 16U;
    config->frameLength = 32U;
    config->position = 0U;
    config->watermark = 4U;
    config->txEmptyZero = false;
    config->pack48 = false;
}

static void I2S_Config(I2S_Type *base, const i2s_config_t *config)
{
    assert(config);

    uint32_t cfg1 = 0U;
    uint32_t cfg2 = 0U;

    /* set master/slave configuration */
    cfg1 |= I2S_CFG1_MSTSLVCFG(config->masterSlave);

    /* set I2S mode */
    cfg1 |= I2S_CFG1_MODE(config->mode);

    /* set right low (channel swap) */
    cfg1 |= I2S_CFG1_RIGHTLOW(config->rightLow);

    /* set data justification */
    cfg1 |= I2S_CFG1_LEFTJUST(config->leftJust);

    /* set source to PDM dmic */
    cfg1 |= I2S_CFG1_PDMDATA(config->pdmData);

    /* set SCLK polarity */
    cfg1 |= I2S_CFG1_SCK_POL(config->sckPol);

    /* set WS polarity */
    cfg1 |= I2S_CFG1_WS_POL(config->wsPol);

    /* set mono mode */
    cfg1 |= I2S_CFG1_ONECHANNEL(config->oneChannel);

    /* set data length */
    cfg1 |= I2S_CFG1_DATALEN(config->dataLength - 1U);

    /* set frame length */
    cfg2 |= I2S_CFG2_FRAMELEN(config->frameLength - 1U);

    /* set data position of this channel pair within the frame */
    cfg2 |= I2S_CFG2_POSITION(config->position);

    /* write to registers */
    base->CFG1 = cfg1;
    base->CFG2 = cfg2;

    /* set the clock divider */
    base->DIV = I2S_DIV_DIV(config->divider - 1U);
}

void I2S_Deinit(I2S_Type *base)
{
    /* TODO gate FLEXCOMM clock via FLEXCOMM driver */
}

void I2S_TxEnable(I2S_Type *base, bool enable)
{
    if (enable)
    {
        I2S_EnableInterrupts(base, kI2S_TxErrorFlag | kI2S_TxLevelFlag);
        I2S_Enable(base);
    }
    else
    {
        I2S_DisableInterrupts(base, kI2S_TxErrorFlag | kI2S_TxLevelFlag);
        I2S_Disable(base);
        base->FIFOCFG |= I2S_FIFOCFG_EMPTYTX_MASK;
    }
}

void I2S_RxEnable(I2S_Type *base, bool enable)
{
    if (enable)
    {
        I2S_EnableInterrupts(base, kI2S_RxErrorFlag | kI2S_RxLevelFlag);
        I2S_Enable(base);
    }
    else
    {
        I2S_DisableInterrupts(base, kI2S_RxErrorFlag | kI2S_RxLevelFlag);
        I2S_Disable(base);
        base->FIFOCFG |= I2S_FIFOCFG_EMPTYRX_MASK;
    }
}

static status_t I2S_ValidateBuffer(i2s_handle_t *handle, i2s_transfer_t *transfer)
{
    assert(transfer->data);
    if (!transfer->data)
    {
        return kStatus_InvalidArgument;
    }

    assert(transfer->dataSize > 0U);
    if (transfer->dataSize <= 0U)
    {
        return kStatus_InvalidArgument;
    }

    if (handle->dataLength == 4U)
    {
        /* No alignment and data length requirements */
    }
    else if ((handle->dataLength >= 5U) && (handle->dataLength <= 8U))
    {
        assert((((uint32_t)transfer->data) % 2U) == 0U);
        if ((((uint32_t)transfer->data) % 2U) != 0U)
        {
            /* Data not 2-bytes aligned */
            return kStatus_InvalidArgument;
        }

        assert((transfer->dataSize % 2U) == 0U);
        if ((transfer->dataSize % 2U) != 0U)
        {
            /* Data not in pairs of left/right channel bytes */
            return kStatus_InvalidArgument;
        }
    }
    else if ((handle->dataLength >= 9U) && (handle->dataLength <= 16U))
    {
        assert((((uint32_t)transfer->data) % 4U) == 0U);
        if ((((uint32_t)transfer->data) % 4U) != 0U)
        {
            /* Data not 4-bytes aligned */
            return kStatus_InvalidArgument;
        }

        assert((transfer->dataSize % 4U) == 0U);
        if ((transfer->dataSize % 4U) != 0U)
        {
            /* Data lenght not multiply of 4 */
            return kStatus_InvalidArgument;
        }
    }
    else if ((handle->dataLength >= 17U) && (handle->dataLength <= 24U))
    {
        assert((transfer->dataSize % 6U) == 0U);
        if ((transfer->dataSize % 6U) != 0U)
        {
            /* Data lenght not multiply of 6 */
            return kStatus_InvalidArgument;
        }

        assert(!((handle->pack48) && ((((uint32_t)transfer->data) % 4U) != 0U)));
        if ((handle->pack48) && ((((uint32_t)transfer->data) % 4U) != 0U))
        {
            /* Data not 4-bytes aligned */
            return kStatus_InvalidArgument;
        }
    }
    else /* if (handle->dataLength >= 25U) */
    {
        assert((((uint32_t)transfer->data) % 4U) == 0U);
        if ((((uint32_t)transfer->data) % 4U) != 0U)
        {
            /* Data not 4-bytes aligned */
            return kStatus_InvalidArgument;
        }

        if (handle->oneChannel)
        {
            assert((transfer->dataSize % 4U) == 0U);
            if ((transfer->dataSize % 4U) != 0U)
            {
                /* Data lenght not multiply of 4 */
                return kStatus_InvalidArgument;
            }
        }
        else
        {
            assert((transfer->dataSize % 8U) == 0U);
            if ((transfer->dataSize % 8U) != 0U)
            {
                /* Data lenght not multiply of 8 */
                return kStatus_InvalidArgument;
            }
        }
    }

    return kStatus_Success;
}

void I2S_TxTransferCreateHandle(I2S_Type *base, i2s_handle_t *handle, i2s_transfer_callback_t callback, void *userData)
{
    assert(handle);

    /* Clear out the handle */
    memset(handle, 0U, sizeof(*handle));

    /* Save callback and user data */
    handle->completionCallback = callback;
    handle->userData = userData;

    /* Remember some items set previously by configuration */
    handle->watermark = ((base->FIFOTRIG & I2S_FIFOTRIG_TXLVL_MASK) >> I2S_FIFOTRIG_TXLVL_SHIFT);
    handle->oneChannel = ((base->CFG1 & I2S_CFG1_ONECHANNEL_MASK) >> I2S_CFG1_ONECHANNEL_SHIFT);
    handle->dataLength = ((base->CFG1 & I2S_CFG1_DATALEN_MASK) >> I2S_CFG1_DATALEN_SHIFT) + 1U;
    handle->pack48 = ((base->FIFOCFG & I2S_FIFOCFG_PACK48_MASK) >> I2S_FIFOCFG_PACK48_SHIFT);

    handle->useFifo48H = false;

    /* Register IRQ handling */
    FLEXCOMM_SetIRQHandler(base, (flexcomm_irq_handler_t)(uintptr_t)I2S_TxHandleIRQ, handle);
}

status_t I2S_TxTransferNonBlocking(I2S_Type *base, i2s_handle_t *handle, i2s_transfer_t transfer)
{
    assert(handle);
    if (!handle)
    {
        return kStatus_InvalidArgument;
    }

    status_t result;

    result = I2S_ValidateBuffer(handle, &transfer);
    if (result != kStatus_Success)
    {
        return result;
    }

    if (handle->i2sQueue[handle->queueUser].dataSize)
    {
        /* Previously prepared buffers not processed yet */
        return kStatus_I2S_Busy;
    }

    handle->state = kI2S_StateTx;
    handle->i2sQueue[handle->queueUser].data = transfer.data;
    handle->i2sQueue[handle->queueUser].dataSize = transfer.dataSize;
    handle->queueUser = (handle->queueUser + 1U) % I2S_NUM_BUFFERS;

    base->FIFOTRIG = (base->FIFOTRIG & (~I2S_FIFOTRIG_TXLVL_MASK)) | I2S_FIFOTRIG_TXLVL(handle->watermark);
    I2S_TxEnable(base, true);

    return kStatus_Success;
}

void I2S_TxTransferAbort(I2S_Type *base, i2s_handle_t *handle)
{
    assert(handle);

    /* Disable I2S operation and interrupts */
    I2S_TxEnable(base, false);

    /* Reset state */
    handle->state = kI2S_StateIdle;

    /* Clear transfer queue */
    memset((void *)&handle->i2sQueue, 0U, sizeof(i2s_transfer_t) * I2S_NUM_BUFFERS);
    handle->queueDriver = 0U;
    handle->queueUser = 0U;
}

void I2S_RxTransferCreateHandle(I2S_Type *base, i2s_handle_t *handle, i2s_transfer_callback_t callback, void *userData)
{
    assert(handle);

    /* Clear out the handle */
    memset(handle, 0U, sizeof(*handle));

    /* Save callback and user data */
    handle->completionCallback = callback;
    handle->userData = userData;

    /* Remember some items set previously by configuration */
    handle->watermark = ((base->FIFOTRIG & I2S_FIFOTRIG_RXLVL_MASK) >> I2S_FIFOTRIG_RXLVL_SHIFT);
    handle->oneChannel = ((base->CFG1 & I2S_CFG1_ONECHANNEL_MASK) >> I2S_CFG1_ONECHANNEL_SHIFT);
    handle->dataLength = ((base->CFG1 & I2S_CFG1_DATALEN_MASK) >> I2S_CFG1_DATALEN_SHIFT) + 1U;
    handle->pack48 = ((base->FIFOCFG & I2S_FIFOCFG_PACK48_MASK) >> I2S_FIFOCFG_PACK48_SHIFT);

    handle->useFifo48H = false;

    /* Register IRQ handling */
    FLEXCOMM_SetIRQHandler(base, (flexcomm_irq_handler_t)(uintptr_t)I2S_RxHandleIRQ, handle);
}

status_t I2S_RxTransferNonBlocking(I2S_Type *base, i2s_handle_t *handle, i2s_transfer_t transfer)
{
    assert(handle);
    if (!handle)
    {
        return kStatus_InvalidArgument;
    }

    status_t result;

    result = I2S_ValidateBuffer(handle, &transfer);
    if (result != kStatus_Success)
    {
        return result;
    }

    if (handle->i2sQueue[handle->queueUser].dataSize)
    {
        /* Previously prepared buffers not processed yet */
        return kStatus_I2S_Busy;
    }

    handle->state = kI2S_StateRx;
    handle->i2sQueue[handle->queueUser].data = transfer.data;
    handle->i2sQueue[handle->queueUser].dataSize = transfer.dataSize;
    handle->queueUser = (handle->queueUser + 1U) % I2S_NUM_BUFFERS;

    base->FIFOTRIG = (base->FIFOTRIG & (~I2S_FIFOTRIG_RXLVL_MASK)) | I2S_FIFOTRIG_RXLVL(handle->watermark);
    I2S_RxEnable(base, true);

    return kStatus_Success;
}

void I2S_RxTransferAbort(I2S_Type *base, i2s_handle_t *handle)
{
    assert(handle);

    /* Disable I2S operation and interrupts */
    I2S_RxEnable(base, false);

    /* Reset state */
    handle->state = kI2S_StateIdle;

    /* Clear transfer queue */
    memset((void *)&handle->i2sQueue, 0U, sizeof(i2s_transfer_t) * I2S_NUM_BUFFERS);
    handle->queueDriver = 0U;
    handle->queueUser = 0U;
}

status_t I2S_TransferGetCount(I2S_Type *base, i2s_handle_t *handle, size_t *count)
{
    assert(handle);
    if (!handle)
    {
        return kStatus_InvalidArgument;
    }

    assert(count);
    if (!count)
    {
        return kStatus_InvalidArgument;
    }

    if (handle->state == kI2S_StateIdle)
    {
        return kStatus_NoTransferInProgress;
    }

    *count = handle->transferCount;

    return kStatus_Success;
}

status_t I2S_TransferGetErrorCount(I2S_Type *base, i2s_handle_t *handle, size_t *count)
{
    assert(handle);
    if (!handle)
    {
        return kStatus_InvalidArgument;
    }

    assert(count);
    if (!count)
    {
        return kStatus_InvalidArgument;
    }

    if (handle->state == kI2S_StateIdle)
    {
        return kStatus_NoTransferInProgress;
    }

    *count = handle->errorCount;

    return kStatus_Success;
}

void I2S_TxHandleIRQ(I2S_Type *base, i2s_handle_t *handle)
{
    uint32_t intstat = base->FIFOINTSTAT;
    uint32_t data;

    if (intstat & I2S_FIFOINTSTAT_TXERR_MASK)
    {
        handle->errorCount++;

        /* Clear TX error interrupt flag */
        base->FIFOSTAT = I2S_FIFOSTAT_TXERR(1U);
    }

    if (intstat & I2S_FIFOINTSTAT_TXLVL_MASK)
    {
        if (handle->state == kI2S_StateTx)
        {
            /* Send data */

            while ((base->FIFOSTAT & I2S_FIFOSTAT_TXNOTFULL_MASK) &&
                   (handle->i2sQueue[handle->queueDriver].dataSize > 0U))
            {
                /* Write output data */
                if (handle->dataLength == 4U)
                {
                    data = *(handle->i2sQueue[handle->queueDriver].data);
                    base->FIFOWR = ((data & 0xF0U) << 12U) | (data & 0xFU);
                    handle->i2sQueue[handle->queueDriver].data++;
                    handle->transferCount++;
                    handle->i2sQueue[handle->queueDriver].dataSize--;
                }
                else if (handle->dataLength <= 8U)
                {
                    data = *((uint16_t *)handle->i2sQueue[handle->queueDriver].data);
                    base->FIFOWR = ((data & 0xFF00U) << 8U) | (data & 0xFFU);
                    handle->i2sQueue[handle->queueDriver].data += sizeof(uint16_t);
                    handle->transferCount += sizeof(uint16_t);
                    handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint16_t);
                }
                else if (handle->dataLength <= 16U)
                {
                    base->FIFOWR = *((uint32_t *)(handle->i2sQueue[handle->queueDriver].data));
                    handle->i2sQueue[handle->queueDriver].data += sizeof(uint32_t);
                    handle->transferCount += sizeof(uint32_t);
                    handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint32_t);
                }
                else if (handle->dataLength <= 24U)
                {
                    if (handle->pack48)
                    {
                        if (handle->useFifo48H)
                        {
                            base->FIFOWR48H = *((uint16_t *)(handle->i2sQueue[handle->queueDriver].data));
                            handle->i2sQueue[handle->queueDriver].data += sizeof(uint16_t);
                            handle->transferCount += sizeof(uint16_t);
                            handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint16_t);
                            handle->useFifo48H = false;
                        }
                        else
                        {
                            base->FIFOWR = *((uint32_t *)(handle->i2sQueue[handle->queueDriver].data));
                            handle->i2sQueue[handle->queueDriver].data += sizeof(uint32_t);
                            handle->transferCount += sizeof(uint32_t);
                            handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint32_t);
                            handle->useFifo48H = true;
                        }
                    }
                    else
                    {
                        data = (uint32_t)(*(handle->i2sQueue[handle->queueDriver].data++));
                        data |= ((uint32_t)(*(handle->i2sQueue[handle->queueDriver].data++))) << 8U;
                        data |= ((uint32_t)(*(handle->i2sQueue[handle->queueDriver].data++))) << 16U;
                        if (handle->useFifo48H)
                        {
                            base->FIFOWR48H = data;
                            handle->useFifo48H = false;
                        }
                        else
                        {
                            base->FIFOWR = data;
                            handle->useFifo48H = true;
                        }
                        handle->transferCount += 3U;
                        handle->i2sQueue[handle->queueDriver].dataSize -= 3U;
                    }
                }
                else /* if (handle->dataLength <= 32U) */
                {
                    base->FIFOWR = *((uint32_t *)(handle->i2sQueue[handle->queueDriver].data));
                    handle->i2sQueue[handle->queueDriver].data += sizeof(uint32_t);
                    handle->transferCount += sizeof(uint32_t);
                    handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint32_t);
                }

                if (handle->i2sQueue[handle->queueDriver].dataSize == 0U)
                {
                    /* Actual data buffer sent out, switch to a next one */
                    handle->queueDriver = (handle->queueDriver + 1U) % I2S_NUM_BUFFERS;

                    /* Notify user */
                    if (handle->completionCallback)
                    {
                        handle->completionCallback(base, handle, kStatus_I2S_BufferComplete, handle->userData);
                    }

                    /* Check if the next buffer contains anything to send */
                    if (handle->i2sQueue[handle->queueDriver].dataSize == 0U)
                    {
                        /* Everything has been written to FIFO */
                        handle->state = kI2S_StateTxWaitToWriteDummyData;
                        break;
                    }
                }
            }
        }
        else if (handle->state == kI2S_StateTxWaitToWriteDummyData)
        {
            /* Write dummy data */
            if ((handle->dataLength > 16U) && (handle->dataLength < 25U))
            {
                if (handle->useFifo48H)
                {
                    base->FIFOWR48H = 0U;
                    handle->useFifo48H = false;
                }
                else
                {
                    base->FIFOWR = 0U;
                    base->FIFOWR48H = 0U;
                }
            }
            else
            {
                base->FIFOWR = 0U;
            }

            /* Next time invoke this handler when FIFO becomes empty (TX level 0) */
            base->FIFOTRIG &= ~I2S_FIFOTRIG_TXLVL_MASK;
            handle->state = kI2S_StateTxWaitForEmptyFifo;
        }
        else if (handle->state == kI2S_StateTxWaitForEmptyFifo)
        {
            /* FIFO, including additional dummy data, has been emptied now,
             * all relevant data should have been output from peripheral */

            /* Stop transfer */
            I2S_Disable(base);
            I2S_DisableInterrupts(base, kI2S_TxErrorFlag | kI2S_TxLevelFlag);
            base->FIFOCFG |= I2S_FIFOCFG_EMPTYTX_MASK;

            /* Reset state */
            handle->state = kI2S_StateIdle;

            /* Notify user */
            if (handle->completionCallback)
            {
                handle->completionCallback(base, handle, kStatus_I2S_Done, handle->userData);
            }
        }
        else
        {
            /* Do nothing */
        }

        /* Clear TX level interrupt flag */
        base->FIFOSTAT = I2S_FIFOSTAT_TXLVL(1U);
    }
}

void I2S_RxHandleIRQ(I2S_Type *base, i2s_handle_t *handle)
{
    uint32_t intstat = base->FIFOINTSTAT;
    uint32_t data;

    if (intstat & I2S_FIFOINTSTAT_RXERR_MASK)
    {
        handle->errorCount++;

        /* Clear RX error interrupt flag */
        base->FIFOSTAT = I2S_FIFOSTAT_RXERR(1U);
    }

    if (intstat & I2S_FIFOINTSTAT_RXLVL_MASK)
    {
        while ((base->FIFOSTAT & I2S_FIFOSTAT_RXNOTEMPTY_MASK) && (handle->i2sQueue[handle->queueDriver].dataSize > 0U))
        {
            /* Read input data */
            if (handle->dataLength == 4U)
            {
                data = base->FIFORD;
                *(handle->i2sQueue[handle->queueDriver].data) = ((data & 0x000F0000U) >> 12U) | (data & 0x0000000FU);
                handle->i2sQueue[handle->queueDriver].data++;
                handle->transferCount++;
                handle->i2sQueue[handle->queueDriver].dataSize--;
            }
            else if (handle->dataLength <= 8U)
            {
                data = base->FIFORD;
                *((uint16_t *)handle->i2sQueue[handle->queueDriver].data) = ((data >> 8U) & 0xFF00U) | (data & 0xFFU);
                handle->i2sQueue[handle->queueDriver].data += sizeof(uint16_t);
                handle->transferCount += sizeof(uint16_t);
                handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint16_t);
            }
            else if (handle->dataLength <= 16U)
            {
                data = base->FIFORD;
                *((uint32_t *)handle->i2sQueue[handle->queueDriver].data) = data;
                handle->i2sQueue[handle->queueDriver].data += sizeof(uint32_t);
                handle->transferCount += sizeof(uint32_t);
                handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint32_t);
            }
            else if (handle->dataLength <= 24U)
            {
                if (handle->pack48)
                {
                    if (handle->useFifo48H)
                    {
                        data = base->FIFORD48H;
                        handle->useFifo48H = false;

                        *((uint16_t *)handle->i2sQueue[handle->queueDriver].data) = data;
                        handle->i2sQueue[handle->queueDriver].data += sizeof(uint16_t);
                        handle->transferCount += sizeof(uint16_t);
                        handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint16_t);
                    }
                    else
                    {
                        data = base->FIFORD;
                        handle->useFifo48H = true;

                        *((uint32_t *)handle->i2sQueue[handle->queueDriver].data) = data;
                        handle->i2sQueue[handle->queueDriver].data += sizeof(uint32_t);
                        handle->transferCount += sizeof(uint32_t);
                        handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint32_t);
                    }
                }
                else
                {
                    if (handle->useFifo48H)
                    {
                        data = base->FIFORD48H;
                        handle->useFifo48H = false;
                    }
                    else
                    {
                        data = base->FIFORD;
                        handle->useFifo48H = true;
                    }

                    *(handle->i2sQueue[handle->queueDriver].data++) = data & 0xFFU;
                    *(handle->i2sQueue[handle->queueDriver].data++) = (data >> 8U) & 0xFFU;
                    *(handle->i2sQueue[handle->queueDriver].data++) = (data >> 16U) & 0xFFU;
                    handle->transferCount += 3U;
                    handle->i2sQueue[handle->queueDriver].dataSize -= 3U;
                }
            }
            else /* if (handle->dataLength <= 32U) */
            {
                data = base->FIFORD;
                *((uint32_t *)handle->i2sQueue[handle->queueDriver].data) = data;
                handle->i2sQueue[handle->queueDriver].data += sizeof(uint32_t);
                handle->transferCount += sizeof(uint32_t);
                handle->i2sQueue[handle->queueDriver].dataSize -= sizeof(uint32_t);
            }

            if (handle->i2sQueue[handle->queueDriver].dataSize == 0U)
            {
                /* Actual data buffer filled with input data, switch to a next one */
                handle->queueDriver = (handle->queueDriver + 1U) % I2S_NUM_BUFFERS;

                /* Notify user */
                if (handle->completionCallback)
                {
                    handle->completionCallback(base, handle, kStatus_I2S_BufferComplete, handle->userData);
                }

                if (handle->i2sQueue[handle->queueDriver].dataSize == 0U)
                {
                    /* No other buffer prepared to receive data into */

                    /* Disable I2S operation and interrupts */
                    I2S_Disable(base);
                    I2S_DisableInterrupts(base, kI2S_RxErrorFlag | kI2S_RxLevelFlag);
                    base->FIFOCFG |= I2S_FIFOCFG_EMPTYRX_MASK;

                    /* Reset state */
                    handle->state = kI2S_StateIdle;

                    /* Notify user */
                    if (handle->completionCallback)
                    {
                        handle->completionCallback(base, handle, kStatus_I2S_Done, handle->userData);
                    }

                    /* Clear RX level interrupt flag */
                    base->FIFOSTAT = I2S_FIFOSTAT_RXLVL(1U);

                    return;
                }
            }
        }

        /* Clear RX level interrupt flag */
        base->FIFOSTAT = I2S_FIFOSTAT_RXLVL(1U);
    }
}
