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

#include "fsl_dma.h"
#include "fsl_i2s_dma.h"
#include "fsl_flexcomm.h"
#include <string.h>

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

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

#define DMA_MAX_TRANSFER_BYTES (DMA_MAX_TRANSFER_COUNT * sizeof(uint32_t))
#define DMA_DESCRIPTORS (2U)

/*<! @brief Structure for statically allocated private data. */
typedef struct _i2s_dma_private_handle
{
    I2S_Type *base;           /*!< I2S base address */
    i2s_dma_handle_t *handle; /*!< I2S handle */

    volatile uint16_t enqueuedBytes[DMA_DESCRIPTORS]; /*!< Number of bytes being transferred by DMA descriptors */
    volatile uint8_t enqueuedBytesStart;              /*!< First item in enqueuedBytes (for reading) */
    volatile uint8_t enqueuedBytesEnd;                /*!< Last item in enqueuedBytes (for adding) */

    volatile uint8_t
        dmaDescriptorsUsed; /*!< Number of DMA descriptors with valid data (in queue, excluding initial descriptor) */
    volatile uint8_t
        descriptor; /*!< Index of next DMA descriptor in s_DmaDescriptors to be configured with data (does not include
                       I2S instance offset) */

    volatile uint8_t queueDescriptor;                         /*!< Queue index of buffer to be actually consumed by DMA
                                                                * (queueUser - advanced when user adds a buffer,
                                                                *  queueDescriptor - advanced when user buffer queued to DMA,
                                                                *  queueDriver - advanced when DMA queued buffer sent out to I2S) */
    volatile i2s_transfer_t descriptorQueue[I2S_NUM_BUFFERS]; /*!< Transfer data to be queued to DMA */

    volatile bool intA; /*!< If next scheduled DMA transfer will cause interrupt A or B */
} i2s_dma_private_handle_t;

/*! @brief I2S DMA transfer private state. */
enum _i2s_dma_state
{
    kI2S_DmaStateIdle = 0x0U, /*!< I2S is in idle state */
    kI2S_DmaStateTx,          /*!< I2S is busy transmitting data */
    kI2S_DmaStateRx,          /*!< I2S is busy receiving data */
};

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

static status_t I2S_EnqueueUserBuffer(I2S_Type *base, i2s_dma_handle_t *handle, i2s_transfer_t transfer);
static uint32_t I2S_GetInstance(I2S_Type *base);
static inline void I2S_DisableDMAInterrupts(i2s_dma_handle_t *handle);
static inline void I2S_EnableDMAInterrupts(i2s_dma_handle_t *handle);
static void I2S_TxEnableDMA(I2S_Type *base, bool enable);
static void I2S_RxEnableDMA(I2S_Type *base, bool enable);
static uint16_t I2S_GetTransferBytes(volatile i2s_transfer_t *transfer);
static status_t I2S_StartTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle);
static void I2S_AddTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle);

/*******************************************************************************
 * Variables
 ******************************************************************************/

/*<! @brief DMA transfer descriptors. */
#if defined(__ICCARM__)
#pragma data_alignment = 16
static dma_descriptor_t s_DmaDescriptors[DMA_DESCRIPTORS * FSL_FEATURE_SOC_I2S_COUNT];
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
__attribute__((aligned(16))) static dma_descriptor_t s_DmaDescriptors[DMA_DESCRIPTORS * FSL_FEATURE_SOC_I2S_COUNT];
#elif defined(__GNUC__)
__attribute__((aligned(16))) static dma_descriptor_t s_DmaDescriptors[DMA_DESCRIPTORS * FSL_FEATURE_SOC_I2S_COUNT];
#endif

/*<! @brief Buffer with dummy TX data. */
#if defined(__ICCARM__)
#pragma data_alignment = 4
static uint32_t s_DummyBufferTx = 0U;
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
__attribute__((aligned(4))) static uint32_t s_DummyBufferTx = 0U;
#elif defined(__GNUC__)
__attribute__((aligned(4))) static uint32_t s_DummyBufferTx = 0U;
#endif

/*<! @brief Buffer to fill with RX data to discard. */
#if defined(__ICCARM__)
#pragma data_alignment = 4
static uint32_t s_DummyBufferRx = 0U;
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
__attribute__((aligned(4))) static uint32_t s_DummyBufferRx = 0U;
#elif defined(__GNUC__)
__attribute__((aligned(4))) static uint32_t s_DummyBufferRx = 0U;
#endif

/*<! @brief Private array of data associated with available I2S peripherals. */
static i2s_dma_private_handle_t s_DmaPrivateHandle[FSL_FEATURE_SOC_I2S_COUNT];

/*<! @brief Base addresses of available I2S peripherals. */
static const uint32_t s_I2sBaseAddrs[FSL_FEATURE_SOC_I2S_COUNT] = I2S_BASE_ADDRS;

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

static status_t I2S_EnqueueUserBuffer(I2S_Type *base, i2s_dma_handle_t *handle, i2s_transfer_t transfer)
{
    uint32_t instance = I2S_GetInstance(base);
    i2s_dma_private_handle_t *privateHandle = &(s_DmaPrivateHandle[instance]);

    /* Validate input data and tranfer buffer */

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

    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 != 0U);
    if (transfer.dataSize == 0U)
    {
        /* No data to send or receive */
        return kStatus_InvalidArgument;
    }

    assert((transfer.dataSize % 4U) == 0U);
    if ((transfer.dataSize % 4U) != 0U)
    {
        /* Data length not multiply of 4 bytes */
        return kStatus_InvalidArgument;
    }

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

    /* Enqueue data */
    privateHandle->descriptorQueue[handle->queueUser].data = transfer.data;
    privateHandle->descriptorQueue[handle->queueUser].dataSize = transfer.dataSize;
    handle->i2sQueue[handle->queueUser].data = transfer.data;
    handle->i2sQueue[handle->queueUser].dataSize = transfer.dataSize;
    handle->queueUser = (handle->queueUser + 1U) % I2S_NUM_BUFFERS;

    return kStatus_Success;
}

static uint32_t I2S_GetInstance(I2S_Type *base)
{
    uint32_t i;

    for (i = 0U; i < ARRAY_SIZE(s_I2sBaseAddrs); i++)
    {
        if ((uint32_t)base == s_I2sBaseAddrs[i])
        {
            return i;
        }
    }

    assert(false);
    return 0U;
}

static inline void I2S_DisableDMAInterrupts(i2s_dma_handle_t *handle)
{
    DMA_DisableChannelInterrupts(handle->dmaHandle->base, handle->dmaHandle->channel);
}

static inline void I2S_EnableDMAInterrupts(i2s_dma_handle_t *handle)
{
    if (handle->state != kI2S_DmaStateIdle)
    {
        DMA_EnableChannelInterrupts(handle->dmaHandle->base, handle->dmaHandle->channel);
    }
}

/*!
 * brief Initializes handle for transfer of audio data.
 *
 * param base I2S base pointer.
 * param handle pointer to handle structure.
 * param dmaHandle pointer to dma handle structure.
 * param callback function to be called back when transfer is done or fails.
 * param userData pointer to data passed to callback.
 */
void I2S_TxTransferCreateHandleDMA(I2S_Type *base,
                                   i2s_dma_handle_t *handle,
                                   dma_handle_t *dmaHandle,
                                   i2s_dma_transfer_callback_t callback,
                                   void *userData)
{
    assert(handle);
    assert(dmaHandle);

    uint32_t instance = I2S_GetInstance(base);
    i2s_dma_private_handle_t *privateHandle = &(s_DmaPrivateHandle[instance]);

    memset(handle, 0U, sizeof(*handle));
    handle->state = kI2S_DmaStateIdle;
    handle->dmaHandle = dmaHandle;
    handle->completionCallback = callback;
    handle->userData = userData;

    memset(privateHandle, 0U, sizeof(*privateHandle));
    privateHandle->base = base;
    privateHandle->handle = handle;

    DMA_SetCallback(dmaHandle, I2S_DMACallback, privateHandle);
}

/*!
 * brief Begins or queue sending of the given data.
 *
 * param base I2S base pointer.
 * param handle pointer to handle structure.
 * param transfer data buffer.
 *
 * retval kStatus_Success
 * retval kStatus_I2S_Busy if all queue slots are occupied with unsent buffers.
 */
status_t I2S_TxTransferSendDMA(I2S_Type *base, i2s_dma_handle_t *handle, i2s_transfer_t transfer)
{
    status_t status;

    I2S_DisableDMAInterrupts(handle);

    /* Enqueue transfer buffer */
    status = I2S_EnqueueUserBuffer(base, handle, transfer);
    if (status != kStatus_Success)
    {
        I2S_EnableDMAInterrupts(handle);
        return status;
    }

    /* Initialize DMA transfer */
    if (handle->state == kI2S_DmaStateIdle)
    {
        handle->state = kI2S_DmaStateTx;
        status = I2S_StartTransferDMA(base, handle);
        if (status != kStatus_Success)
        {
            I2S_EnableDMAInterrupts(handle);
            return status;
        }
    }

    I2S_AddTransferDMA(base, handle);
    I2S_EnableDMAInterrupts(handle);

    return kStatus_Success;
}

/*!
 * brief Aborts transfer of data.
 *
 * param base I2S base pointer.
 * param handle pointer to handle structure.
 */
void I2S_TransferAbortDMA(I2S_Type *base, i2s_dma_handle_t *handle)
{
    assert(handle);
    assert(handle->dmaHandle);

    uint32_t instance = I2S_GetInstance(base);
    i2s_dma_private_handle_t *privateHandle = &(s_DmaPrivateHandle[instance]);

    I2S_DisableDMAInterrupts(handle);

    /* Abort operation */
    DMA_AbortTransfer(handle->dmaHandle);

    if (handle->state == kI2S_DmaStateTx)
    {
        /* Wait until all transmitted data get out of FIFO */
        while ((base->FIFOSTAT & I2S_FIFOSTAT_TXEMPTY_MASK) == 0U)
        {
        }
        /* The last piece of valid data can be still being transmitted from I2S at this moment */

        /* Write additional data to FIFO */
        base->FIFOWR = 0U;
        while ((base->FIFOSTAT & I2S_FIFOSTAT_TXEMPTY_MASK) == 0U)
        {
        }
        /* At this moment the additional data are out of FIFO, starting being transmitted.
         * This means the preceding valid data has been just transmitted and we can stop I2S. */
        I2S_TxEnableDMA(base, false);
    }
    else
    {
        I2S_RxEnableDMA(base, false);
    }

    I2S_Disable(base);

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

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

    /* Clear internal state */
    memset((void *)&(privateHandle->descriptorQueue), 0U, sizeof(privateHandle->descriptorQueue));
    memset((void *)&(privateHandle->enqueuedBytes), 0U, sizeof(privateHandle->enqueuedBytes));
    privateHandle->enqueuedBytesStart = 0U;
    privateHandle->enqueuedBytesEnd = 0U;
    privateHandle->dmaDescriptorsUsed = 0U;
    privateHandle->descriptor = 0U;
    privateHandle->queueDescriptor = 0U;
    privateHandle->intA = false;
}

/*!
 * brief Initializes handle for reception of audio data.
 *
 * param base I2S base pointer.
 * param handle pointer to handle structure.
 * param dmaHandle pointer to dma handle structure.
 * param callback function to be called back when transfer is done or fails.
 * param userData pointer to data passed to callback.
 */
void I2S_RxTransferCreateHandleDMA(I2S_Type *base,
                                   i2s_dma_handle_t *handle,
                                   dma_handle_t *dmaHandle,
                                   i2s_dma_transfer_callback_t callback,
                                   void *userData)
{
    I2S_TxTransferCreateHandleDMA(base, handle, dmaHandle, callback, userData);
}

/*!
 * brief Begins or queue reception of data into given buffer.
 *
 * param base I2S base pointer.
 * param handle pointer to handle structure.
 * param transfer data buffer.
 *
 * retval kStatus_Success
 * retval kStatus_I2S_Busy if all queue slots are occupied with buffers
 *         which are not full.
 */
status_t I2S_RxTransferReceiveDMA(I2S_Type *base, i2s_dma_handle_t *handle, i2s_transfer_t transfer)
{
    status_t status;

    I2S_DisableDMAInterrupts(handle);

    /* Enqueue transfer buffer */
    status = I2S_EnqueueUserBuffer(base, handle, transfer);
    if (status != kStatus_Success)
    {
        I2S_EnableDMAInterrupts(handle);
        return status;
    }

    /* Initialize DMA transfer */
    if (handle->state == kI2S_DmaStateIdle)
    {
        handle->state = kI2S_DmaStateRx;
        status = I2S_StartTransferDMA(base, handle);
        if (status != kStatus_Success)
        {
            I2S_EnableDMAInterrupts(handle);
            return status;
        }
    }

    I2S_AddTransferDMA(base, handle);
    I2S_EnableDMAInterrupts(handle);

    return kStatus_Success;
}

static void I2S_TxEnableDMA(I2S_Type *base, bool enable)
{
    if (enable)
    {
        base->FIFOCFG |= I2S_FIFOCFG_DMATX_MASK;
    }
    else
    {
        base->FIFOCFG &= (~I2S_FIFOCFG_DMATX_MASK);
        base->FIFOCFG |= I2S_FIFOCFG_EMPTYTX_MASK;
    }
}

static void I2S_RxEnableDMA(I2S_Type *base, bool enable)
{
    if (enable)
    {
        base->FIFOCFG |= I2S_FIFOCFG_DMARX_MASK;
    }
    else
    {
        base->FIFOCFG &= (~I2S_FIFOCFG_DMARX_MASK);
        base->FIFOCFG |= I2S_FIFOCFG_EMPTYRX_MASK;
    }
}

static uint16_t I2S_GetTransferBytes(volatile i2s_transfer_t *transfer)
{
    assert(transfer);

    uint16_t transferBytes;

    if (transfer->dataSize >= (2 * DMA_MAX_TRANSFER_BYTES))
    {
        transferBytes = DMA_MAX_TRANSFER_BYTES;
    }
    else if (transfer->dataSize > DMA_MAX_TRANSFER_BYTES)
    {
        transferBytes = transfer->dataSize / 2U;
        if ((transferBytes % 4U) != 0U)
        {
            transferBytes -= (transferBytes % 4U);
        }
    }
    else
    {
        transferBytes = transfer->dataSize;
    }

    return transferBytes;
}

static status_t I2S_StartTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle)
{
    status_t status;
    dma_transfer_config_t xferConfig = {0};
    i2s_dma_private_handle_t *privateHandle;
    volatile i2s_transfer_t *transfer;
    uint16_t transferBytes;
    uint32_t instance;
    int i;
    dma_descriptor_t *descriptor;
    dma_descriptor_t *nextDescriptor;
    dma_xfercfg_t xfercfg;

    instance = I2S_GetInstance(base);
    privateHandle = &(s_DmaPrivateHandle[instance]);
    transfer = &(privateHandle->descriptorQueue[privateHandle->queueDescriptor]);

    transferBytes = I2S_GetTransferBytes(transfer);

    /* Prepare transfer of data via initial DMA transfer descriptor */
    DMA_PrepareTransfer(
        &xferConfig,
        (void *)((handle->state == kI2S_DmaStateTx) ? (uint32_t)transfer->data : (uint32_t)(&(base->FIFORD))),
        (void *)((handle->state == kI2S_DmaStateTx) ? (uint32_t)(&(base->FIFOWR)) : (uint32_t)transfer->data),
        sizeof(uint32_t), transferBytes,
        (handle->state == kI2S_DmaStateTx) ? kDMA_MemoryToPeripheral : kDMA_PeripheralToMemory,
        (void *)&(s_DmaDescriptors[(instance * DMA_DESCRIPTORS) + 0U]));

    /* Initial descriptor is stored in another place in memory, but treat it as another descriptor for simplicity */
    privateHandle->dmaDescriptorsUsed = 1U;
    privateHandle->intA = false;

    privateHandle->enqueuedBytes[privateHandle->enqueuedBytesEnd] = transferBytes;
    privateHandle->enqueuedBytesEnd = (privateHandle->enqueuedBytesEnd + 1U) % DMA_DESCRIPTORS;

    transfer->dataSize -= transferBytes;
    transfer->data += transferBytes;

    if (transfer->dataSize == 0U)
    {
        transfer->data = NULL;
        privateHandle->queueDescriptor = (privateHandle->queueDescriptor + 1U) % I2S_NUM_BUFFERS;
    }

    /* Link the DMA descriptors for the case when no additional transfer is queued before the initial one finishes */
    for (i = 0; i < DMA_DESCRIPTORS; i++)
    {
        descriptor = &(s_DmaDescriptors[(instance * DMA_DESCRIPTORS) + i]);
        nextDescriptor = &(s_DmaDescriptors[(instance * DMA_DESCRIPTORS) + ((i + 1) % DMA_DESCRIPTORS)]);

        xfercfg.valid = true;
        xfercfg.reload = true;
        xfercfg.swtrig = false;
        xfercfg.clrtrig = false;
        xfercfg.intA = false;
        xfercfg.intB = false;
        xfercfg.byteWidth = sizeof(uint32_t);
        xfercfg.srcInc = 0U;
        xfercfg.dstInc = 0U;
        xfercfg.transferCount = 8U;

        DMA_CreateDescriptor(
            descriptor, &xfercfg,
            ((handle->state == kI2S_DmaStateTx) ? (void *)&s_DummyBufferTx : (void *)(uint32_t)(&(base->FIFORD))),
            ((handle->state == kI2S_DmaStateTx) ? (void *)(uint32_t)(&(base->FIFOWR)) : (void *)&s_DummyBufferRx),
            (void *)nextDescriptor);
    }

    /* Submit and start initial DMA transfer */

    if (handle->state == kI2S_DmaStateTx)
    {
        I2S_TxEnableDMA(base, true);
    }
    else
    {
        I2S_RxEnableDMA(base, true);
    }

    status = DMA_SubmitTransfer(handle->dmaHandle, &xferConfig);
    if (status != kStatus_Success)
    {
        return status;
    }

    DMA_StartTransfer(handle->dmaHandle);

    I2S_Enable(base);
    return kStatus_Success;
}

static void I2S_AddTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle)
{
    dma_xfercfg_t xfercfg;
    volatile i2s_transfer_t *transfer;
    uint16_t transferBytes;
    uint32_t instance;
    i2s_dma_private_handle_t *privateHandle;
    dma_descriptor_t *descriptor;
    dma_descriptor_t *nextDescriptor;
    uint32_t srcAddr = 0, destAddr = 0;

    instance = I2S_GetInstance(base);
    privateHandle = &(s_DmaPrivateHandle[instance]);

    while (privateHandle->dmaDescriptorsUsed < DMA_DESCRIPTORS)
    {
        transfer = &(privateHandle->descriptorQueue[privateHandle->queueDescriptor]);

        if (transfer->dataSize == 0U)
        {
            /* Nothing to be added */
            return;
        }

        /* Determine currently configured descriptor and the other which it will link to */
        descriptor = &(s_DmaDescriptors[(instance * DMA_DESCRIPTORS) + privateHandle->descriptor]);
        privateHandle->descriptor = (privateHandle->descriptor + 1U) % DMA_DESCRIPTORS;
        nextDescriptor = &(s_DmaDescriptors[(instance * DMA_DESCRIPTORS) + privateHandle->descriptor]);

        transferBytes = I2S_GetTransferBytes(transfer);
        privateHandle->enqueuedBytes[privateHandle->enqueuedBytesEnd] = transferBytes;
        privateHandle->enqueuedBytesEnd = (privateHandle->enqueuedBytesEnd + 1U) % DMA_DESCRIPTORS;

        /* Configure descriptor */

        xfercfg.valid = true;
        xfercfg.reload = true;
        xfercfg.swtrig = false;
        xfercfg.clrtrig = false;
        xfercfg.intA = privateHandle->intA;
        xfercfg.intB = !privateHandle->intA;
        xfercfg.byteWidth = sizeof(uint32_t);
        xfercfg.srcInc = (handle->state == kI2S_DmaStateTx) ? 1U : 0U;
        xfercfg.dstInc = (handle->state == kI2S_DmaStateTx) ? 0U : 1U;
        xfercfg.transferCount = transferBytes / sizeof(uint32_t);
        srcAddr = ((handle->state == kI2S_DmaStateTx) ? (uint32_t)transfer->data : (uint32_t) & (base->FIFORD));
        destAddr = ((handle->state == kI2S_DmaStateTx) ? (uint32_t) & (base->FIFOWR) : (uint32_t)transfer->data);

        DMA_CreateDescriptor(descriptor, &xfercfg, (void *)srcAddr, (void *)destAddr, (void *)nextDescriptor);

        /* Advance internal state */

        privateHandle->dmaDescriptorsUsed++;
        privateHandle->intA = !privateHandle->intA;

        transfer->dataSize -= transferBytes;
        transfer->data += transferBytes;
        if (transfer->dataSize == 0U)
        {
            transfer->data = NULL;
            privateHandle->queueDescriptor = (privateHandle->queueDescriptor + 1U) % I2S_NUM_BUFFERS;
        }
    }
}

/*!
 * brief Invoked from DMA interrupt handler.
 *
 * param handle pointer to DMA handle structure.
 * param userData argument for user callback.
 * param transferDone if transfer was done.
 * param tcds
 */
void I2S_DMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t tcds)
{
    i2s_dma_private_handle_t *privateHandle = (i2s_dma_private_handle_t *)userData;
    i2s_dma_handle_t *i2sHandle = privateHandle->handle;
    I2S_Type *base = privateHandle->base;

    if ((!transferDone) || (i2sHandle->state == kI2S_DmaStateIdle))
    {
        return;
    }

    if (privateHandle->dmaDescriptorsUsed > 0U)
    {
        /* Finished descriptor, decrease amount of data to be processed */

        i2sHandle->i2sQueue[i2sHandle->queueDriver].dataSize -=
            privateHandle->enqueuedBytes[privateHandle->enqueuedBytesStart];
        i2sHandle->i2sQueue[i2sHandle->queueDriver].data +=
            privateHandle->enqueuedBytes[privateHandle->enqueuedBytesStart];
        privateHandle->enqueuedBytes[privateHandle->enqueuedBytesStart] = 0U;
        privateHandle->enqueuedBytesStart = (privateHandle->enqueuedBytesStart + 1U) % DMA_DESCRIPTORS;

        privateHandle->dmaDescriptorsUsed--;

        if (i2sHandle->i2sQueue[i2sHandle->queueDriver].dataSize == 0U)
        {
            /* Entire user buffer sent or received - advance to next one */
            i2sHandle->i2sQueue[i2sHandle->queueDriver].data = NULL;
            i2sHandle->queueDriver = (i2sHandle->queueDriver + 1U) % I2S_NUM_BUFFERS;

            /* Notify user about buffer completion */
            if (i2sHandle->completionCallback)
            {
                (i2sHandle->completionCallback)(base, i2sHandle, kStatus_I2S_BufferComplete, i2sHandle->userData);
            }
        }
    }

    if (i2sHandle->i2sQueue[i2sHandle->queueDriver].dataSize == 0U)
    {
        /* All user buffers processed */
        I2S_TransferAbortDMA(base, i2sHandle);

        /* Notify user about completion of the final buffer */
        if (i2sHandle->completionCallback)
        {
            (i2sHandle->completionCallback)(base, i2sHandle, kStatus_I2S_Done, i2sHandle->userData);
        }
    }
    else
    {
        /* Enqueue another user buffer to DMA if it could not be done when in I2S_Rx/TxTransferSendDMA */
        I2S_AddTransferDMA(base, i2sHandle);
    }
}
