Newer
Older
mbed-os / targets / TARGET_NXP / TARGET_MCUXpresso_MCUS / TARGET_LPC54114 / drivers / fsl_i2s_dma.c
@Mahadevan Mahesh Mahadevan Mahesh on 20 Jul 2017 21 KB Add mbed support for LPCXpresso54114 board
/*
 * Copyright (c) 2016, Freescale Semiconductor, Inc.
 *
 * 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 Freescale Semiconductor, Inc. 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_dma.h"
#include "fsl_i2s_dma.h"
#include "fsl_flexcomm.h"
#include <string.h>

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

#define DMA_MAX_TRANSFER_BYTES (DMA_MAX_TRANSFER_COUNT * sizeof(uint32_t))
#define DMA_DESCRIPTORS (2U)
#define ENQUEUED_BYTES_BUFFER_SIZE (DMA_DESCRIPTORS + 1U)
#define I2S_FIFO_DEPTH (8U)

/*<! @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[ENQUEUED_BYTES_BUFFER_SIZE]; /*!< 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 bool initialDescriptor;               /*!< Initial DMA descriptor transfer not finished yet */
    volatile uint8_t
        dmaDescriptorsUsed; /*!< Number of DMA descriptors with valid data (in queue, excluding initial descriptor) */
    volatile uint8_t descriptor;      /*!< Index of next descriptor to be configured with data */
    volatile uint8_t queueDescriptor; /*!< Queue index of buffer to be actually consumed by DMA */
    volatile i2s_transfer_t descriptorQueue[I2S_NUM_BUFFERS]; /*!< Transfer data as queued to descriptors for DMA */
} 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 void I2S_TxEnableDMA(I2S_Type *base, bool enable);
static void I2S_RxEnableDMA(I2S_Type *base, bool enable);
static status_t I2S_StartTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle);
static status_t I2S_AddTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle, const uint16_t maxSize);

/*******************************************************************************
 * 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)
__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)
__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. */
#if defined(__ICCARM__)
#pragma data_alignment = 4
static uint32_t s_DummyBufferRx = 0U;
#elif defined(__CC_ARM)
__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 < FSL_FEATURE_SOC_I2S_COUNT; i++)
    {
        if ((uint32_t)base == s_I2sBaseAddrs[i])
        {
            return i;
        }
    }

    assert(false);
    return 0U;
}

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;
    privateHandle->initialDescriptor = false;

    DMA_SetCallback(dmaHandle, I2S_DMACallback, privateHandle);
}

status_t I2S_TxTransferSendDMA(I2S_Type *base, i2s_dma_handle_t *handle, i2s_transfer_t transfer)
{
    /* Enqueue transfer buffer */
    status_t status = I2S_EnqueueUserBuffer(base, handle, transfer);
    if (status != kStatus_Success)
    {
        return status;
    }

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

    return kStatus_Success;
}

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]);

    /* Abort operation */
    DMA_AbortTransfer(handle->dmaHandle);
    if (handle->state == kI2S_DmaStateTx)
    {
        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->initialDescriptor = false;
    privateHandle->dmaDescriptorsUsed = 0U;
    privateHandle->descriptor = 0U;
    privateHandle->queueDescriptor = 0U;
}

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);
}

status_t I2S_RxTransferReceiveDMA(I2S_Type *base, i2s_dma_handle_t *handle, i2s_transfer_t transfer)
{
    /* Enqueue transfer buffer */
    status_t status = I2S_EnqueueUserBuffer(base, handle, transfer);
    if (status != kStatus_Success)
    {
        return status;
    }

    /* Initialize DMA transfer */
    if (handle->state == kI2S_DmaStateIdle)
    {
        handle->state = kI2S_DmaStateRx;
        return I2S_StartTransferDMA(base, 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 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;
    i2s_transfer_t volatile *transfer;
    uint16_t transferBytes;
    uint32_t instance;
    uint32_t i;

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

    /*
     * Divide first buffer between initial DMA descriptor and chained DMA descriptors.
     * This allows to enqueue more data before entire buffer is processed
     * and thus could prevent audio drop-outs due to requeing.
     * But user is expected to enqueue more data in order for this to work.
     */
    transferBytes = (transfer->dataSize / (DMA_DESCRIPTORS + 1U));

    if (transferBytes > DMA_MAX_TRANSFER_BYTES)
    {
        transferBytes = DMA_MAX_TRANSFER_BYTES;
    }

    if ((transferBytes % 4U) != 0U)
    {
        transferBytes -= (transferBytes % 4U);
    }

    assert(transferBytes > 0U);
    if (transferBytes == 0U)
    {
        /*
         * TODO buffer too small to be divided for multiple descriptors
         * return invalid argument status for now
         */
        return kStatus_InvalidArgument;
    }

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

    privateHandle->initialDescriptor = true; /* For IRQ handler to know that it will be fired for first descriptor */
    privateHandle->enqueuedBytes[privateHandle->enqueuedBytesEnd] = transferBytes;
    privateHandle->enqueuedBytesEnd = (privateHandle->enqueuedBytesEnd + 1U) % ENQUEUED_BYTES_BUFFER_SIZE;

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

    /* Prepare chained descriptors to transfer 2nd and 3rd part of initial data */
    for (i = 0; i < DMA_DESCRIPTORS; i++)
    {
        if (i == (DMA_DESCRIPTORS - 1))
        {
            /* Last buffer will take the rest of data (because of possible truncating by division) */
            transferBytes = transfer->dataSize;
            if (transferBytes > DMA_MAX_TRANSFER_BYTES)
            {
                transferBytes = DMA_MAX_TRANSFER_BYTES;
            }
        }

        I2S_AddTransferDMA(base, handle, transferBytes);
    }

    /* 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 status_t I2S_AddTransferDMA(I2S_Type *base, i2s_dma_handle_t *handle, const uint16_t maxSize)
{
    dma_xfercfg_t xfercfg;
    i2s_transfer_t volatile *transfer;
    uint16_t transferBytes;
    uint32_t instance;
    i2s_dma_private_handle_t *privateHandle;
    dma_descriptor_t *descriptor;
    dma_descriptor_t *nextDescriptor;

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

    if (privateHandle->dmaDescriptorsUsed >= DMA_DESCRIPTORS)
    {
        /* No unprocessed DMA transfer descriptor */
        return kStatus_I2S_Busy;
    }

    transfer = &(privateHandle->descriptorQueue[privateHandle->queueDescriptor]);

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

    if (transfer->dataSize == 0U)
    {
        /* Currently nothing to enqueue, use dummy buffer instead */

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

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

        /* TODO use API for this once it is available in DMA driver */
        descriptor->xfercfg |= DMA_CHANNEL_XFERCFG_CFGVALID(1U);
        handle->dmaHandle->base->COMMON->SETVALID = (1U << (handle->dmaHandle->channel));

        return kStatus_Success;
    }
    else if (transfer->dataSize > maxSize)
    {
        /* Take part of data - DMA transfer limitation */
        transferBytes = maxSize;
    }
    else
    {
        /* Entire buffer can fit into a single descriptor */
        transferBytes = transfer->dataSize;
        privateHandle->queueDescriptor = (privateHandle->queueDescriptor + 1U) % I2S_NUM_BUFFERS;
    }

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

    /* Configure descriptor */

    xfercfg.valid = false;
    xfercfg.reload = true;
    xfercfg.swtrig = false;
    xfercfg.clrtrig = false;
    xfercfg.intA = true;
    xfercfg.intB = false;
    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);

    DMA_CreateDescriptor(
        descriptor, &xfercfg, (handle->state == kI2S_DmaStateTx) ? (void *)transfer->data : (void *)&(base->FIFORD),
        (handle->state == kI2S_DmaStateTx) ? (void *)&(base->FIFOWR) : (void *)transfer->data, (void *)nextDescriptor);

    /* TODO use API for this once it is available in DMA driver */
    descriptor->xfercfg |= DMA_CHANNEL_XFERCFG_CFGVALID(1U);
    handle->dmaHandle->base->COMMON->SETVALID = (1U << (handle->dmaHandle->channel));

    /* Advance internal state */
    privateHandle->dmaDescriptorsUsed++;
    transfer->dataSize -= transferBytes;
    transfer->data += transferBytes;

    return kStatus_Success;
}

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)
    {
        return;
    }

    if (i2sHandle->i2sQueue[i2sHandle->queueDriver].dataSize == 0U)
    {
        /*
         * All user buffers processed before, dataSize already zero,
         * this means at least one dummy buffer sent/received
         * and no new buffer enqueued by user meanwhile.
         */

        I2S_TransferAbortDMA(base, i2sHandle);

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

        return;
    }

    if (privateHandle->initialDescriptor)
    {
        /* Initial descriptor finished, decrease amount of data to be processed */

        assert(privateHandle->enqueuedBytes[privateHandle->enqueuedBytesStart] > 0U);

        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) % ENQUEUED_BYTES_BUFFER_SIZE;

        privateHandle->initialDescriptor = false;
    }
    else
    {
        if (privateHandle->dmaDescriptorsUsed > 0U)
        {
            /* Finished one of chained descriptors, decrease amount of data to be processed */

            assert(privateHandle->enqueuedBytes[privateHandle->enqueuedBytesStart] > 0U);

            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) % ENQUEUED_BYTES_BUFFER_SIZE;

            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);
        }
    }

    /* Enqueue new chunk of data to DMA (if any) */
    I2S_AddTransferDMA(base, i2sHandle, DMA_MAX_TRANSFER_BYTES);
}