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

#include "fsl_spi_dma.h"

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

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

/*<! Structure definition for spi_dma_private_handle_t. The structure is private. */
typedef struct _spi_dma_private_handle
{
    SPI_Type *base;
    spi_dma_handle_t *handle;
} spi_dma_private_handle_t;

/*! @brief SPI transfer state, which is used for SPI transactiaonl APIs' internal state. */
enum _spi_dma_states_t
{
    kSPI_Idle = 0x0, /*!< SPI is idle state */
    kSPI_Busy        /*!< SPI is busy tranferring data. */
};

typedef struct _spi_dma_txdummy
{
    uint32_t lastWord;
    uint32_t word;
} spi_dma_txdummy_t;

/*<! Private handle only used for internally. */
static spi_dma_private_handle_t s_dmaPrivateHandle[FSL_FEATURE_SOC_SPI_COUNT];

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

/*!
 * @brief DMA callback function for SPI send transfer.
 *
 * @param handle DMA handle pointer.
 * @param userData User data for DMA callback function.
 */
static void SPI_TxDMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t intmode);

/*!
 * @brief DMA callback function for SPI receive transfer.
 *
 * @param handle DMA handle pointer.
 * @param userData User data for DMA callback function.
 */
static void SPI_RxDMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t intmode);

/*******************************************************************************
 * Variables
 ******************************************************************************/
#if defined(__ICCARM__)
#pragma data_alignment                                        = 4
static spi_dma_txdummy_t s_txDummy[FSL_FEATURE_SOC_SPI_COUNT] = {0};
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
__attribute__((aligned(4))) static spi_dma_txdummy_t s_txDummy[FSL_FEATURE_SOC_SPI_COUNT] = {0};
#elif defined(__GNUC__)
__attribute__((aligned(4))) static spi_dma_txdummy_t s_txDummy[FSL_FEATURE_SOC_SPI_COUNT] = {0};
#endif

#if defined(__ICCARM__)
#pragma data_alignment = 4
static uint16_t s_rxDummy;
static uint32_t s_txLastWord[FSL_FEATURE_SOC_SPI_COUNT];
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
__attribute__((aligned(4))) static uint16_t s_rxDummy;
__attribute__((aligned(4))) static uint32_t s_txLastWord[FSL_FEATURE_SOC_SPI_COUNT];
#elif defined(__GNUC__)
__attribute__((aligned(4))) static uint16_t s_rxDummy;
__attribute__((aligned(4))) static uint32_t s_txLastWord[FSL_FEATURE_SOC_SPI_COUNT];
#endif

#if defined(__ICCARM__)
#pragma data_alignment                                                    = 16
static dma_descriptor_t s_spi_descriptor_table[FSL_FEATURE_SOC_SPI_COUNT] = {0};
#elif defined(__CC_ARM) || defined(__ARMCC_VERSION)
__attribute__((aligned(16))) static dma_descriptor_t s_spi_descriptor_table[FSL_FEATURE_SOC_SPI_COUNT] = {0};
#elif defined(__GNUC__)
__attribute__((aligned(16))) static dma_descriptor_t s_spi_descriptor_table[FSL_FEATURE_SOC_SPI_COUNT] = {0};
#endif

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

static void XferToFifoWR(spi_transfer_t *xfer, uint32_t *fifowr)
{
    *fifowr |= ((xfer->configFlags & (uint32_t)kSPI_FrameDelay) != 0U) ? (uint32_t)kSPI_FrameDelay : 0U;
    *fifowr |= ((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) ? (uint32_t)kSPI_FrameAssert : 0U;
}

static void SpiConfigToFifoWR(spi_config_t *config, uint32_t *fifowr)
{
    *fifowr |= ((uint32_t)SPI_DEASSERT_ALL & (~(uint32_t)SPI_DEASSERTNUM_SSEL((uint32_t)config->sselNum)));
    /* set width of data - range asserted at entry */
    *fifowr |= SPI_FIFOWR_LEN(config->dataWidth);
}

static void PrepareTxLastWord(spi_transfer_t *xfer, uint32_t *txLastWord, spi_config_t *config)
{
    if (config->dataWidth > kSPI_Data8Bits)
    {
        *txLastWord = (((uint32_t)xfer->txData[xfer->dataSize - 1U] << 8U) | (xfer->txData[xfer->dataSize - 2U]));
    }
    else
    {
        *txLastWord = xfer->txData[xfer->dataSize - 1U];
    }
    XferToFifoWR(xfer, txLastWord);
    SpiConfigToFifoWR(config, txLastWord);
}

static void SPI_SetupDummy(SPI_Type *base, spi_dma_txdummy_t *dummy, spi_transfer_t *xfer, spi_config_t *spi_config_p)
{
    uint32_t instance  = SPI_GetInstance(base);
    uint32_t dummydata = (uint32_t)s_dummyData[instance];
    dummydata |= (uint32_t)s_dummyData[instance] << 8U;

    dummy->word     = dummydata;
    dummy->lastWord = dummydata;

    XferToFifoWR(xfer, &dummy->word);
    XferToFifoWR(xfer, &dummy->lastWord);
    SpiConfigToFifoWR(spi_config_p, &dummy->word);
    SpiConfigToFifoWR(spi_config_p, &dummy->lastWord);
    /* Clear the end of transfer bit for continue word transfer. */
    dummy->word &= (~(uint32_t)kSPI_FrameAssert);
}

/*!
 * brief Initialize the SPI master DMA handle.
 *
 * This function initializes the SPI master DMA handle which can be used for other SPI master transactional APIs.
 * Usually, for a specified SPI instance, user need only call this API once to get the initialized handle.
 *
 * param base SPI peripheral base address.
 * param handle SPI handle pointer.
 * param callback User callback function called at the end of a transfer.
 * param userData User data for callback.
 * param txHandle DMA handle pointer for SPI Tx, the handle shall be static allocated by users.
 * param rxHandle DMA handle pointer for SPI Rx, the handle shall be static allocated by users.
 */
status_t SPI_MasterTransferCreateHandleDMA(SPI_Type *base,
                                           spi_dma_handle_t *handle,
                                           spi_dma_callback_t callback,
                                           void *userData,
                                           dma_handle_t *txHandle,
                                           dma_handle_t *rxHandle)
{
    uint32_t instance;

    /* check 'base' */
    assert(!(NULL == base));
    if (NULL == base)
    {
        return kStatus_InvalidArgument;
    }
    /* check 'handle' */
    assert(!(NULL == handle));
    if (NULL == handle)
    {
        return kStatus_InvalidArgument;
    }

    instance = SPI_GetInstance(base);

    (void)memset(handle, 0, sizeof(*handle));
    /* Set spi base to handle */
    handle->txHandle = txHandle;
    handle->rxHandle = rxHandle;
    handle->callback = callback;
    handle->userData = userData;

    /* Set SPI state to idle */
    handle->state = (uint8_t)kSPI_Idle;

    /* Set handle to global state */
    s_dmaPrivateHandle[instance].base   = base;
    s_dmaPrivateHandle[instance].handle = handle;

    /* Install callback for Tx dma channel */
    DMA_SetCallback(handle->txHandle, SPI_TxDMACallback, &s_dmaPrivateHandle[instance]);
    DMA_SetCallback(handle->rxHandle, SPI_RxDMACallback, &s_dmaPrivateHandle[instance]);

    return kStatus_Success;
}

/*!
 * brief Perform a non-blocking SPI transfer using DMA.
 *
 * note This interface returned immediately after transfer initiates, users should call
 * SPI_GetTransferStatus to poll the transfer status to check whether SPI transfer finished.
 *
 * param base SPI peripheral base address.
 * param handle SPI DMA handle pointer.
 * param xfer Pointer to dma transfer structure.
 * retval kStatus_Success Successfully start a transfer.
 * retval kStatus_InvalidArgument Input argument is invalid.
 * retval kStatus_SPI_Busy SPI is not idle, is running another transfer.
 */
status_t SPI_MasterTransferDMA(SPI_Type *base, spi_dma_handle_t *handle, spi_transfer_t *xfer)
{
    assert(!((NULL == handle) || (NULL == xfer)));

    uint32_t instance;
    status_t result = kStatus_Success;
    spi_config_t *spi_config_p;
    uint32_t address;

    if ((NULL == handle) || (NULL == xfer))
    {
        return kStatus_InvalidArgument;
    }

    /* Byte size is zero. */
    if (xfer->dataSize == 0U)
    {
        return kStatus_InvalidArgument;
    }
    /* cannot get instance from base address */
    instance = SPI_GetInstance(base);

    /* Check if the device is busy */
    if (handle->state == (uint8_t)kSPI_Busy)
    {
        return kStatus_SPI_Busy;
    }
    else
    {
        dma_transfer_config_t xferConfig = {0};
        spi_config_p                     = (spi_config_t *)SPI_GetConfig(base);

        handle->state        = (uint8_t)kSPI_Busy;
        handle->transferSize = xfer->dataSize;

        /* receive */
        SPI_EnableRxDMA(base, true);
        address = (uint32_t)&base->FIFORD;
        if (xfer->rxData != NULL)
        {
            DMA_PrepareTransfer(&xferConfig, (uint32_t *)address, xfer->rxData,
                                ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))),
                                xfer->dataSize, kDMA_PeripheralToMemory, NULL);
        }
        else
        {
            DMA_PrepareTransfer(&xferConfig, (uint32_t *)address, &s_rxDummy,
                                ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))),
                                xfer->dataSize, kDMA_StaticToStatic, NULL);
        }
        (void)DMA_SubmitTransfer(handle->rxHandle, &xferConfig);
        handle->rxInProgress = true;
        DMA_StartTransfer(handle->rxHandle);

        /* transmit */
        SPI_EnableTxDMA(base, true);
        address = (uint32_t)&base->FIFOWR;
        if (xfer->txData != NULL)
        {
            if ((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U)
            {
                PrepareTxLastWord(xfer, &s_txLastWord[instance], spi_config_p);
            }
            /* If end of tranfer function is enabled and data transfer frame is bigger then 1, use dma
             * descriptor to send the last data.
             */
            if (((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) &&
                ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize > 2U) : (xfer->dataSize > 1U)))
            {
                dma_xfercfg_t tmp_xfercfg;
                tmp_xfercfg.valid         = true;
                tmp_xfercfg.swtrig        = true;
                tmp_xfercfg.intA          = true;
                tmp_xfercfg.byteWidth     = sizeof(uint32_t);
                tmp_xfercfg.srcInc        = 0;
                tmp_xfercfg.dstInc        = 0;
                tmp_xfercfg.transferCount = 1;
                tmp_xfercfg.reload        = false;
                tmp_xfercfg.clrtrig       = false;
                tmp_xfercfg.intB          = false;
                /* Create chained descriptor to transmit last word */
                DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txLastWord[instance],
                                     (uint32_t *)address, NULL);

                DMA_PrepareTransfer(
                    &xferConfig, xfer->txData, (uint32_t *)address,
                    ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))),
                    ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize - 2U) : (xfer->dataSize - 1U)),
                    kDMA_MemoryToPeripheral, &s_spi_descriptor_table[instance]);
                /* Disable interrupts for first descriptor to avoid calling callback twice. */
                xferConfig.xfercfg.intA = false;
                xferConfig.xfercfg.intB = false;
                result                  = DMA_SubmitTransfer(handle->txHandle, &xferConfig);
                if (result != kStatus_Success)
                {
                    return result;
                }
            }
            else
            {
                DMA_PrepareTransfer(
                    &xferConfig, xfer->txData, (uint32_t *)address,
                    ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))),
                    xfer->dataSize, kDMA_MemoryToPeripheral, NULL);
                (void)DMA_SubmitTransfer(handle->txHandle, &xferConfig);
            }
        }
        else
        {
            /* Setup tx dummy data. */
            SPI_SetupDummy(base, &s_txDummy[instance], xfer, spi_config_p);
            if (((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) &&
                ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize > 2U) : (xfer->dataSize > 1U)))
            {
                dma_xfercfg_t tmp_xfercfg;
                tmp_xfercfg.valid         = true;
                tmp_xfercfg.swtrig        = true;
                tmp_xfercfg.intA          = true;
                tmp_xfercfg.byteWidth     = sizeof(uint32_t);
                tmp_xfercfg.srcInc        = 0;
                tmp_xfercfg.dstInc        = 0;
                tmp_xfercfg.transferCount = 1;
                tmp_xfercfg.reload        = false;
                tmp_xfercfg.clrtrig       = false;
                tmp_xfercfg.intB          = false;
                /* Create chained descriptor to transmit last word */
                DMA_CreateDescriptor(&s_spi_descriptor_table[instance], &tmp_xfercfg, &s_txDummy[instance].lastWord,
                                     (uint32_t *)address, NULL);
                /* Use common API to setup first descriptor */
                DMA_PrepareTransfer(
                    &xferConfig, &s_txDummy[instance].word, (uint32_t *)address,
                    ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))),
                    ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize - 2U) : (xfer->dataSize - 1U)),
                    kDMA_StaticToStatic, &s_spi_descriptor_table[instance]);
                /* Disable interrupts for first descriptor to avoid calling callback twice */
                xferConfig.xfercfg.intA = false;
                xferConfig.xfercfg.intB = false;
                result                  = DMA_SubmitTransfer(handle->txHandle, &xferConfig);
                if (result != kStatus_Success)
                {
                    return result;
                }
            }
            else
            {
                DMA_PrepareTransfer(
                    &xferConfig, &s_txDummy[instance].word, (uint32_t *)address,
                    ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (sizeof(uint16_t)) : (sizeof(uint8_t))),
                    xfer->dataSize, kDMA_StaticToStatic, NULL);
                result = DMA_SubmitTransfer(handle->txHandle, &xferConfig);
                if (result != kStatus_Success)
                {
                    return result;
                }
            }
        }

        handle->txInProgress = true;
        uint32_t tmpData     = 0U;
        XferToFifoWR(xfer, &tmpData);
        SpiConfigToFifoWR(spi_config_p, &tmpData);

        /* Setup the control info.
         * Halfword writes to just the control bits (offset 0xE22) doesn't push anything into the FIFO.
         * And the data access type of control bits must be uint16_t, byte writes or halfword writes to FIFOWR
         * will push the data and the current control bits into the FIFO.
         */
        if (((xfer->configFlags & (uint32_t)kSPI_FrameAssert) != 0U) &&
            ((spi_config_p->dataWidth > kSPI_Data8Bits) ? (xfer->dataSize == 2U) : (xfer->dataSize == 1U)))
        {
            *((uint16_t *)((uint32_t)&base->FIFOWR) + 1) = (uint16_t)(tmpData >> 16U);
        }
        else
        {
            /* Clear the SPI_FIFOWR_EOT_MASK bit when data is not the last. */
            tmpData &= (~(uint32_t)kSPI_FrameAssert);
            *((uint16_t *)((uint32_t)&base->FIFOWR) + 1) = (uint16_t)(tmpData >> 16U);
        }

        DMA_StartTransfer(handle->txHandle);
    }

    return result;
}

/*!
 * brief Transfers a block of data using a DMA method.
 *
 * This function using polling way to do the first half transimission and using DMA way to
 * do the srcond half transimission, the transfer mechanism is half-duplex.
 * When do the second half transimission, code will return right away. When all data is transferred,
 * the callback function is called.
 *
 * param base SPI base pointer
 * param handle A pointer to the spi_master_dma_handle_t structure which stores the transfer state.
 * param transfer A pointer to the spi_half_duplex_transfer_t structure.
 * return status of status_t.
 */
status_t SPI_MasterHalfDuplexTransferDMA(SPI_Type *base, spi_dma_handle_t *handle, spi_half_duplex_transfer_t *xfer)
{
    assert((xfer != NULL) && (handle != NULL));
    spi_transfer_t tempXfer = {0};
    status_t status;

    if (xfer->isTransmitFirst)
    {
        tempXfer.txData   = xfer->txData;
        tempXfer.rxData   = NULL;
        tempXfer.dataSize = xfer->txDataSize;
    }
    else
    {
        tempXfer.txData   = NULL;
        tempXfer.rxData   = xfer->rxData;
        tempXfer.dataSize = xfer->rxDataSize;
    }
    /* If the pcs pin keep assert between transmit and receive. */
    if (xfer->isPcsAssertInTransfer)
    {
        tempXfer.configFlags = (xfer->configFlags) & (~(uint32_t)kSPI_FrameAssert);
    }
    else
    {
        tempXfer.configFlags = (xfer->configFlags) | (uint32_t)kSPI_FrameAssert;
    }

    status = SPI_MasterTransferBlocking(base, &tempXfer);
    if (status != kStatus_Success)
    {
        return status;
    }

    if (xfer->isTransmitFirst)
    {
        tempXfer.txData   = NULL;
        tempXfer.rxData   = xfer->rxData;
        tempXfer.dataSize = xfer->rxDataSize;
    }
    else
    {
        tempXfer.txData   = xfer->txData;
        tempXfer.rxData   = NULL;
        tempXfer.dataSize = xfer->txDataSize;
    }
    tempXfer.configFlags = xfer->configFlags;

    status = SPI_MasterTransferDMA(base, handle, &tempXfer);

    return status;
}

static void SPI_RxDMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t intmode)
{
    spi_dma_private_handle_t *privHandle = (spi_dma_private_handle_t *)userData;
    spi_dma_handle_t *spiHandle          = privHandle->handle;
    SPI_Type *base                       = privHandle->base;

    /* change the state */
    spiHandle->rxInProgress = false;

    /* All finished, call the callback */
    if ((spiHandle->txInProgress == false) && (spiHandle->rxInProgress == false))
    {
        spiHandle->state = (uint8_t)kSPI_Idle;
        if (spiHandle->callback != NULL)
        {
            (spiHandle->callback)(base, spiHandle, kStatus_Success, spiHandle->userData);
        }
    }
}

static void SPI_TxDMACallback(dma_handle_t *handle, void *userData, bool transferDone, uint32_t intmode)
{
    spi_dma_private_handle_t *privHandle = (spi_dma_private_handle_t *)userData;
    spi_dma_handle_t *spiHandle          = privHandle->handle;
    SPI_Type *base                       = privHandle->base;

    /* change the state */
    spiHandle->txInProgress = false;

    /* All finished, call the callback */
    if ((spiHandle->txInProgress == false) && (spiHandle->rxInProgress == false))
    {
        spiHandle->state = (uint8_t)kSPI_Idle;
        if (spiHandle->callback != NULL)
        {
            (spiHandle->callback)(base, spiHandle, kStatus_Success, spiHandle->userData);
        }
    }
}

/*!
 * brief Abort a SPI transfer using DMA.
 *
 * param base SPI peripheral base address.
 * param handle SPI DMA handle pointer.
 */
void SPI_MasterTransferAbortDMA(SPI_Type *base, spi_dma_handle_t *handle)
{
    assert(NULL != handle);

    /* Stop tx transfer first */
    DMA_AbortTransfer(handle->txHandle);
    /* Then rx transfer */
    DMA_AbortTransfer(handle->rxHandle);

    /* Set the handle state */
    handle->txInProgress = false;
    handle->rxInProgress = false;
    handle->state        = (uint8_t)kSPI_Idle;
}

/*!
 * brief Gets the master DMA transfer remaining bytes.
 *
 * This function gets the master DMA transfer remaining bytes.
 *
 * param base SPI peripheral base address.
 * param handle A pointer to the spi_dma_handle_t structure which stores the transfer state.
 * param count A number of bytes transferred by the non-blocking transaction.
 * return status of status_t.
 */
status_t SPI_MasterTransferGetCountDMA(SPI_Type *base, spi_dma_handle_t *handle, size_t *count)
{
    assert(handle != NULL);

    if (NULL == count)
    {
        return kStatus_InvalidArgument;
    }

    /* Catch when there is not an active transfer. */
    if (handle->state != (uint8_t)kSPI_Busy)
    {
        *count = 0;
        return kStatus_NoTransferInProgress;
    }

    size_t bytes;

    bytes = DMA_GetRemainingBytes(handle->rxHandle->base, handle->rxHandle->channel);

    *count = handle->transferSize - bytes;

    return kStatus_Success;
}
