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

#include "fsl_dmic_dma.h"
#include "fsl_dmic.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/

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

/*! @brief DMIC transfer state, which is used for DMIC transactiaonl APIs' internal state. */
enum _dmic_dma_states_t
{
    kDMIC_Idle = 0x0, /*!< DMIC is idle state */
    kDMIC_Busy        /*!< DMIC is busy tranferring data. */
};
/*******************************************************************************
 * Prototypes
 ******************************************************************************/

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

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

static void DMIC_TransferReceiveDMACallback(dma_handle_t *handle, void *param, bool transferDone, uint32_t intmode)
{
    assert(handle);
    assert(param);

    dmic_dma_handle_t *dmicHandle = (dmic_dma_handle_t *)param;

    /* if no link transfer, dmic status set to IDLE. */
    if (dmicHandle->desLink == NULL)
    {
        dmicHandle->state = kDMIC_Idle;
    }

    if (dmicHandle->callback)
    {
        dmicHandle->callback(dmicHandle->base, dmicHandle, kStatus_DMIC_Idle, dmicHandle->userData);
    }
}

/*!
 * brief Install DMA descriptor memory.
 *
 * This function used to register DMA descriptor memory for linked transfer, a typical case is ping pong
 * transfer which will request more than one DMA descriptor memory space.
 * User should be take care about the address of DMA descriptor pool which required align with 512BYTE.
 *
 * param handle Pointer to DMA channel transfer handle.
 * param headAddr DMA head descriptor address.
 * param linkAddr DMA link descriptor address.
 * param num DMA link descriptor number.
 */
void DMIC_InstallDMADescriptorMemory(dmic_dma_handle_t *handle, void *linkAddr, size_t linkNum)
{
    assert(handle != NULL);

    handle->desLink = (dma_descriptor_t *)linkAddr;
    handle->linkNum = linkNum;
}

/*!
 * brief Initializes the DMIC handle which is used in transactional functions.
 * param base DMIC peripheral base address.
 * param handle Pointer to dmic_dma_handle_t structure.
 * param callback Callback function.
 * param userData User data.
 * param rxDmaHandle User-requested DMA handle for RX DMA transfer.
 */
status_t DMIC_TransferCreateHandleDMA(DMIC_Type *base,
                                      dmic_dma_handle_t *handle,
                                      dmic_dma_transfer_callback_t callback,
                                      void *userData,
                                      dma_handle_t *rxDmaHandle)
{
    assert(NULL != base);
    assert(NULL != handle);
    assert(NULL != rxDmaHandle);

    memset(handle, 0, sizeof(*handle));

    handle->callback    = callback;
    handle->userData    = userData;
    handle->rxDmaHandle = rxDmaHandle;

    /* Set DMIC state to idle */
    handle->state = kDMIC_Idle;
    /* register callback. */
    DMA_SetCallback(rxDmaHandle, DMIC_TransferReceiveDMACallback, handle);

    return kStatus_Success;
}

/*!
 * brief Receives data using DMA.
 *
 * This function receives data using DMA. This is a non-blocking function, which returns
 * right away. When all data is received, the receive callback function is called.
 *
 * param base USART peripheral base address.
 * param handle Pointer to usart_dma_handle_t structure.
 * param xfer DMIC DMA transfer structure. See #dmic_transfer_t.
 * param dmic_channel DMIC start channel number
 * retval kStatus_Success
 */
status_t DMIC_TransferReceiveDMA(DMIC_Type *base, dmic_dma_handle_t *handle, dmic_transfer_t *xfer, uint32_t channel)
{
    assert(handle);
    assert(handle->rxDmaHandle);
    assert(xfer);

    dma_channel_config_t transferConfig = {0U};
    uint32_t srcAddr                    = (uint32_t)&base->CHANNEL[channel].FIFO_DATA;
    uint32_t desNum                     = 0U;
    dma_descriptor_t *linkDesc          = (handle->desLink != NULL) ? &(handle->desLink[desNum + 1U]) : NULL;
    dmic_transfer_t *currentTransfer    = xfer->linkTransfer;
    bool loopEnd = false, intA = true;
    if ((xfer->linkTransfer != NULL) && (handle->desLink == NULL))
    {
        return kStatus_InvalidArgument;
    }

    if (handle->state == kDMIC_Busy)
    {
        return kStatus_DMIC_Busy;
    }

    while (currentTransfer)
    {
        /* set up linked descriptor */
        DMA_SetupDescriptor(&handle->desLink[desNum],
                            DMA_CHANNEL_XFER(currentTransfer->linkTransfer != NULL ? true : false, false, intA, !intA,
                                             currentTransfer->dataWidth, kDMA_AddressInterleave0xWidth,
                                             currentTransfer->dataAddrInterleaveSize, currentTransfer->dataSize),
                            (void *)srcAddr, currentTransfer->data, linkDesc);

        intA = intA == true ? false : true;
        /* break for wrap transfer */
        if (loopEnd)
        {
            break;
        }

        if (++desNum == handle->linkNum)
        {
            return kStatus_Fail;
        }

        linkDesc = &handle->desLink[desNum + 1U];

        currentTransfer = currentTransfer->linkTransfer;
        /* if current transfer need wrap, then create one more descriptor, since the first descriptor cannot be used
         * anymore, this is
         * the limitation of the DMA module
         */
        if (currentTransfer == xfer)
        {
            linkDesc = handle->desLink; /* point to the first one */
            loopEnd  = true;
            continue;
        }
    }
    /* transferSize make sense to non link transfer only */
    handle->transferSize += xfer->dataSize;

    /* code to keep compatibility for the case that not use link transfer */
    if ((xfer->dataWidth != kDMA_Transfer16BitWidth) && (xfer->dataWidth != kDMA_Transfer32BitWidth))
    {
        xfer->dataWidth = kDMA_Transfer16BitWidth; /* use 16bit width as default value */
    }
    /* code to keep compatibility for the case that not use link transfer*/
    if ((xfer->dataAddrInterleaveSize == kDMA_AddressInterleave0xWidth) ||
        (xfer->dataAddrInterleaveSize > kDMA_AddressInterleave4xWidth))
    {
        xfer->dataAddrInterleaveSize = kDMA_AddressInterleave1xWidth; /* use interleave1Xwidth as default value. */
    }

    /* prepare channel tranfer */
    DMA_PrepareChannelTransfer(
        &transferConfig, (void *)srcAddr, xfer->data,
        DMA_CHANNEL_XFER(xfer->linkTransfer == NULL ? false : true, false, intA, !intA, xfer->dataWidth,
                         kDMA_AddressInterleave0xWidth, xfer->dataAddrInterleaveSize, xfer->dataSize),
        kDMA_PeripheralToMemory, NULL, handle->desLink);
    /* Submit transfer. */
    DMA_SubmitChannelTransfer(handle->rxDmaHandle, &transferConfig);

    /* enable channel */
    DMIC_EnableChannnel(DMIC0, 1 << channel);
    /* enable dmic channel dma request */
    DMIC_EnableChannelDma(DMIC0, (dmic_channel_t)channel, true);

    /* start transfer */
    DMA_StartTransfer(handle->rxDmaHandle);

    handle->state = kDMIC_Busy;

    return kStatus_Success;
}

/*!
 * brief Aborts the received data using DMA.
 *
 * This function aborts the received data using DMA.
 *
 * param base DMIC peripheral base address
 * param handle Pointer to dmic_dma_handle_t structure
 */
void DMIC_TransferAbortReceiveDMA(DMIC_Type *base, dmic_dma_handle_t *handle)
{
    assert(NULL != handle);
    assert(NULL != handle->rxDmaHandle);

    /* Stop transfer. */
    DMA_AbortTransfer(handle->rxDmaHandle);
    handle->state = kDMIC_Idle;
}

/*!
 * brief Get the number of bytes that have been received.
 *
 * This function gets the number of bytes that have been received.
 * Note: Do not trying to use this api to get the number of received bytes, it make no sense to link transfer.
 * param base DMIC peripheral base address.
 * param handle DMIC handle pointer.
 * param count Receive bytes count.
 * retval kStatus_NoTransferInProgress No receive in progress.
 * retval kStatus_InvalidArgument Parameter is invalid.
 * retval kStatus_Success Get successfully through the parameter count;
 */
status_t DMIC_TransferGetReceiveCountDMA(DMIC_Type *base, dmic_dma_handle_t *handle, uint32_t *count)
{
    assert(handle);
    assert(handle->rxDmaHandle);
    assert(count);

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

    *count = handle->transferSize - DMA_GetRemainingBytes(handle->rxDmaHandle->base, handle->rxDmaHandle->channel);

    return kStatus_Success;
}
