/*
 * Copyright 2017-2019, 2020 NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "fsl_spi.h"

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

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

/*******************************************************************************
 * Variables
 ******************************************************************************/
#if defined(FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS) && (FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS)
/*! @brief SPI internal handle pointer array */
static spi_master_handle_t *s_spiHandle[FSL_FEATURE_SOC_SPI_COUNT];

/*! @brief IRQ name array */
static const IRQn_Type s_spiIRQ[] = SPI_IRQS;

/*! @brief Typedef for spi master interrupt handler. spi master and slave handle is the same. */
typedef void (*spi_isr_t)(SPI_Type *base, spi_master_handle_t *spiHandle);

/*! @brief Pointer to master IRQ handler for each instance. */
static spi_isr_t s_spiMasterIsr;
static spi_isr_t s_spiSlaveIsr;
#endif /* FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS */

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/* @brief Array to map SPI instance number to CLOCK names */
static const clock_ip_name_t s_spiClock[] = SPI_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_SDK_DISABLE_DRIVER_RESET_CONTROL) && FSL_SDK_DISABLE_DRIVER_RESET_CONTROL)
/* @brief Array to map SPI reset. */
static const SYSCON_RSTn_t s_spiReset[] = SPI_RSTS_N;
#endif /* FSL_SDK_DISABLE_DRIVER_RESET_CONTROL */

/*! @brief Array to map SPI instance number to base address. */
static const uint32_t s_spiBaseAddrs[FSL_FEATURE_SOC_SPI_COUNT] = SPI_BASE_ADDRS;

/* @brief Dummy data for each instance. This data is used when user's tx buffer is NULL*/
volatile uint16_t s_dummyData[FSL_FEATURE_SOC_SPI_COUNT] = {0};

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

#if defined(FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS) && (FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS)
/*!
 * @brief Send a piece of data for SPI.
 *
 * This function will check if TXDAT register ready, and write the data into it.
 * At the same time, this function updates the values in master handle structure.
 *
 * @param base SPI base pointer
 * @param handle Pointer to SPI master handle structure.
 */
static void SPI_SendTransfer(SPI_Type *base, spi_master_handle_t *handle)
{
    uint32_t tmp32    = 0U;
    uint32_t instance = SPI_GetInstance(base);

    /* If transmit is ready, write data to TXDAT register. */
    if (((uint32_t)kSPI_TxReadyFlag & SPI_GetStatusFlags(base)) != 0U)
    {
        if ((handle->txData) != NULL)
        {
            tmp32 = *(handle->txData++);
            handle->txRemainingBytes--;
            if (handle->dataWidth > (uint8_t)kSPI_Data8Bits)
            {
                tmp32 |= ((uint32_t)(*(handle->txData++)) << 8U);
                handle->txRemainingBytes--;
            }
        }
        else
        {
            tmp32 = (uint32_t)s_dummyData[instance];
            handle->txRemainingBytes--;
            if (handle->dataWidth > (uint8_t)kSPI_Data8Bits)
            {
                handle->txRemainingBytes--;
            }
        }
        /* If this transmit is the last to send, Set the control bits. */
        if (handle->txRemainingBytes == 0U)
        {
            base->TXCTL = handle->lastCommand;
        }

        base->TXDAT = tmp32;
    }
}

/*!
 * @brief Receive a piece of data for SPI.
 *
 * This function will check if RX register is ready, and write the data to destination address.
 * At the same time, this function updates the values in master handle structure.
 *
 * @param base SPI base pointer
 * @param handle Pointer to SPI master handle structure.
 */
static void SPI_ReceiveTransfer(SPI_Type *base, spi_master_handle_t *handle)
{
    uint32_t tmp32 = 0U;

    /* If receive is ready, read data from RXDAT register. */
    if (((uint32_t)kSPI_RxReadyFlag & SPI_GetStatusFlags(base)) != 0U)
    {
        tmp32 = SPI_ReadData(base);
        /* Check If receive buffer is NULL. */
        if ((handle->rxData) != NULL)
        {
            *(handle->rxData++) = (uint8_t)tmp32;
            handle->rxRemainingBytes--;
            if (handle->dataWidth > (uint8_t)kSPI_Data8Bits)
            {
                *(handle->rxData++) = (uint8_t)(tmp32 >> 8U);
                handle->rxRemainingBytes--;
            }
        }
    }
}
#endif /* FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS */

/*!
 * @brief Get the instance for SPI module.
 *
 * @param base SPI base address
 */
/*! brief Returns instance number for SPI peripheral base address. */
uint32_t SPI_GetInstance(SPI_Type *base)
{
    assert(NULL != base);

    uint32_t i = 0U;

    for (i = 0; i < (uint32_t)FSL_FEATURE_SOC_SPI_COUNT; i++)
    {
        if ((uint32_t)base == s_spiBaseAddrs[i])
        {
            break;
        }
    }

    assert(i < (uint32_t)FSL_FEATURE_SOC_SPI_COUNT);
    return i;
}

/*!
 * brief Set up the dummy data. This API can change the default data to be transferred
 *        when users set the tx buffer to NULL.
 *
 * param base SPI peripheral address.
 * param dummyData Data to be transferred when tx buffer is NULL.
 */
void SPI_SetDummyData(SPI_Type *base, uint16_t dummyData)
{
    uint32_t instance     = SPI_GetInstance(base);
    s_dummyData[instance] = dummyData;
}

/* Set delay time for SPI transfer. */
/*!
 * brief Set delay time for transfer.
 *        the delay uint is SPI clock time, maximum value is 0xF.
 * param base SPI base pointer
 * param config configuration for delay option ref spi_delay_config_t.
 */
void SPI_SetTransferDelay(SPI_Type *base, const spi_delay_config_t *config)
{
    assert(NULL != config);
    /* Set the delay configuration. */
    base->DLY = (SPI_DLY_PRE_DELAY(config->preDelay) | SPI_DLY_POST_DELAY(config->postDelay) |
                 SPI_DLY_FRAME_DELAY(config->frameDelay) | SPI_DLY_TRANSFER_DELAY(config->transferDelay));
}

/*!
 * brief  Sets the SPI master configuration structure to default values.
 *
 * The purpose of this API is to get the configuration structure initialized for use in SPI_MasterInit().
 * User may use the initialized structure unchanged in SPI_MasterInit(), or modify
 * some fields of the structure before calling SPI_MasterInit(). After calling this API,
 * the master is ready to transfer.
 * Example:
   code
   spi_master_config_t config;
   SPI_MasterGetDefaultConfig(&config);
   endcode
 *
 * param config pointer to master config structure
 */
void SPI_MasterGetDefaultConfig(spi_master_config_t *config)
{
    assert(NULL != config);

    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));

    config->enableLoopback            = false;
    config->enableMaster              = true;
    config->clockPolarity             = kSPI_ClockPolarityActiveHigh;
    config->clockPhase                = kSPI_ClockPhaseFirstEdge;
    config->direction                 = kSPI_MsbFirst;
    config->baudRate_Bps              = 500000U;
    config->dataWidth                 = (uint8_t)kSPI_Data8Bits;
    config->sselNumber                = kSPI_Ssel0Assert;
    config->sselPolarity              = kSPI_SpolActiveAllLow;
    config->delayConfig.frameDelay    = 0U;
    config->delayConfig.postDelay     = 0U;
    config->delayConfig.preDelay      = 0U;
    config->delayConfig.transferDelay = 0U;
}

/*!
 * brief Initializes the SPI with master configuration.
 *
 * The configuration structure can be filled by user from scratch, or be set with default
 * values by SPI_MasterGetDefaultConfig(). After calling this API, the slave is ready to transfer.
 * Example
   code
   spi_master_config_t config = {
   .baudRate_Bps = 500000,
   ...
   };
   SPI_MasterInit(SPI0, &config);
   endcode
 *
 * param base SPI base pointer
 * param config pointer to master configuration structure
 * param srcClock_Hz Source clock frequency.
 */
status_t SPI_MasterInit(SPI_Type *base, const spi_master_config_t *config, uint32_t srcClock_Hz)
{
    uint32_t instance = 0U;
    status_t result   = 0;
    uint32_t tmp      = 0U;

    /* assert params */
    assert(!((NULL == base) || (NULL == config) || (0U == srcClock_Hz)));

    /* Get instance number */
    instance = SPI_GetInstance(base);

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable the clock. */
    CLOCK_EnableClock(s_spiClock[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_SDK_DISABLE_DRIVER_RESET_CONTROL) && FSL_SDK_DISABLE_DRIVER_RESET_CONTROL)
    /* Reset the module. */
    RESET_PeripheralReset(s_spiReset[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_RESET_CONTROL */

    /* set divider */
    result = SPI_MasterSetBaudRate(base, config->baudRate_Bps, srcClock_Hz);
    if (kStatus_Success != result)
    {
        return result;
    }
    /* Set CFG register to configure phase/polarity/direction/master mode/loopback/ssel pin select. */
    tmp |= (SPI_CFG_CPHA(config->clockPhase) | SPI_CFG_CPOL(config->clockPolarity) | SPI_CFG_LSBF(config->direction) |
            SPI_CFG_MASTER(1) | SPI_CFG_LOOP(config->enableLoopback) |
            ((uint32_t)config->sselPolarity & (uint32_t)kSPI_SpolActiveAllHigh));
    base->CFG = tmp;

    /* Set delay configuration. */
    SPI_SetTransferDelay(base, &(config->delayConfig));

    /* Set dummy data. */
    SPI_SetDummyData(base, (uint8_t)SPI_DUMMYDATA);

    /* Set TXCTL register. */
    base->TXCTL |= (SPI_TXCTL_LEN(config->dataWidth) | ((uint32_t)config->sselNumber & (uint32_t)kSPI_SselDeAssertAll));

    /* Enable the SPI module. */
    SPI_Enable(base, config->enableMaster);

    return kStatus_Success;
}

/*!
 * brief  Sets the SPI slave configuration structure to default values.
 *
 * The purpose of this API is to get the configuration structure initialized for use in SPI_SlaveInit().
 * Modify some fields of the structure before calling SPI_SlaveInit().
 * Example:
   code
   spi_slave_config_t config;
   SPI_SlaveGetDefaultConfig(&config);
   endcode
 *
 * param config pointer to slave configuration structure
 */
void SPI_SlaveGetDefaultConfig(spi_slave_config_t *config)
{
    assert(NULL != config);

    /* Initializes the configure structure to zero. */
    (void)memset(config, 0, sizeof(*config));

    config->enableSlave   = true;
    config->clockPolarity = kSPI_ClockPolarityActiveHigh;
    config->clockPhase    = kSPI_ClockPhaseFirstEdge;
    config->direction     = kSPI_MsbFirst;
    config->dataWidth     = (uint8_t)kSPI_Data8Bits;
    config->sselPolarity  = kSPI_SpolActiveAllLow;
}

/*!
 * brief Initializes the SPI with slave configuration.
 *
 * The configuration structure can be filled by user from scratch or be set with
 * default values by SPI_SlaveGetDefaultConfig().
 * After calling this API, the slave is ready to transfer.
 * Example
   code
    spi_slave_config_t config = {
    .polarity = kSPI_ClockPolarityActiveHigh;
    .phase = kSPI_ClockPhaseFirstEdge;
    .direction = kSPI_MsbFirst;
    ...
    };
    SPI_SlaveInit(SPI0, &config);
   endcode
 *
 * param base SPI base pointer
 * param config pointer to slave configuration structure
 */
status_t SPI_SlaveInit(SPI_Type *base, const spi_slave_config_t *config)
{
    uint32_t instance = 0U;
    uint32_t tmp      = 0U;

    /* assert params */
    assert(!((NULL == base) || (NULL == config)));
    /* Get the instance of SPI. */
    instance = SPI_GetInstance(base);

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable the clock. */
    CLOCK_EnableClock(s_spiClock[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

#if !(defined(FSL_SDK_DISABLE_DRIVER_RESET_CONTROL) && FSL_SDK_DISABLE_DRIVER_RESET_CONTROL)
    /* Reset the module. */
    RESET_PeripheralReset(s_spiReset[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_RESET_CONTROL */

    /* Set confiuration for phase/polarity/direction/active level for SSEL pins. */
    tmp |= SPI_CFG_CPHA(config->clockPhase) | SPI_CFG_CPOL(config->clockPolarity) | SPI_CFG_LSBF(config->direction) |
           ((uint32_t)config->sselPolarity & (uint32_t)kSPI_SpolActiveAllHigh);
    base->CFG = tmp;

    /* Set dummy data. */
    SPI_SetDummyData(base, (uint8_t)SPI_DUMMYDATA);

    /* Set TXCTL register. */
    base->TXCTL |= SPI_TXCTL_LEN(config->dataWidth);

    /* Enable the SPI module. */
    SPI_Enable(base, config->enableSlave);

    return kStatus_Success;
}

/*!
 * brief De-initializes the SPI.
 *
 * Calling this API resets the SPI module, gates the SPI clock.
 * Disable the fifo if enabled.
 * The SPI module can't work unless calling the SPI_MasterInit/SPI_SlaveInit to initialize module.
 *
 * param base SPI base pointer
 */
void SPI_Deinit(SPI_Type *base)
{
    /* Assert arguments */
    assert(NULL != base);
    uint32_t instance = SPI_GetInstance(base);
    /* Disable SPI module before shutting down the clock. */
    SPI_Enable(base, false);

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Disable the clock. */
    CLOCK_DisableClock(s_spiClock[instance]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Sets the baud rate for SPI transfer. This is only used in master.
 *
 * param base SPI base pointer
 * param baudrate_Bps baud rate needed in Hz.
 * param srcClock_Hz SPI source clock frequency in Hz.
 */
status_t SPI_MasterSetBaudRate(SPI_Type *base, uint32_t baudrate_Bps, uint32_t srcClock_Hz)
{
    uint32_t tmp;

    /* assert params */
    assert(!((NULL == base) || (0U == baudrate_Bps) || (0U == srcClock_Hz)));

    /* calculate baudrate */
    tmp = (srcClock_Hz / baudrate_Bps) - 1U;
    if (tmp > 0xFFFFU)
    {
        return kStatus_SPI_BaudrateNotSupport;
    }
    base->DIV &= ~SPI_DIV_DIVVAL_MASK;
    base->DIV |= SPI_DIV_DIVVAL(tmp);
    return kStatus_Success;
}

/*!
 * brief Writes a data control info and data into the SPI TX register directly.
 *
 * param base SPI base pointer
 * param value needs to be write.
 */
void SPI_WriteDataWithConfigFlags(SPI_Type *base, uint16_t data, uint32_t configFlags)
{
    uint32_t control = 0;
    /* check params */
    assert(NULL != base);
    /* Read origin command from TXCTL register. */
    control = base->TXCTL & (~(SPI_TXDATCTL_EOT_MASK | SPI_TXDATCTL_EOF_MASK | SPI_TXDATCTL_RXIGNORE_MASK));
    /* Mask configFlags */
    control |= (configFlags & (SPI_TXDATCTL_EOT_MASK | SPI_TXDATCTL_EOF_MASK | SPI_TXDATCTL_RXIGNORE_MASK));
    /* Write data and command to register. */
    base->TXDATCTL = (data | control);
}

/*!
 * brief Transfers a block of data using a polling method.
 *
 * param base SPI base pointer
 * param xfer pointer to spi_xfer_config_t structure
 * retval kStatus_Success Successfully start a transfer.
 * retval kStatus_InvalidArgument Input argument is invalid.
 * retval kStatus_SPI_Timeout The transfer timed out and was aborted.
 */
status_t SPI_MasterTransferBlocking(SPI_Type *base, spi_transfer_t *xfer)
{
    uint32_t tx_ctrl = 0, last_ctrl = 0;
    uint32_t tmp32, remainingBytes, dataWidth;
    uint32_t instance = SPI_GetInstance(base);
#if SPI_RETRY_TIMES
    uint32_t waitTimes;
#endif
    /* Check params */
    assert(!((NULL == base) || (NULL == xfer) || ((NULL == xfer->txData) && (NULL == xfer->rxData))));

    remainingBytes = xfer->dataSize;
    /* Read datawidth and ssel info from TXCTL. */
    tx_ctrl   = base->TXCTL & (SPI_TXCTL_LEN_MASK | SPI_TXCTL_RXIGNORE_MASK | SPI_TXCTL_EOF_MASK | SPI_TXCTL_EOT_MASK |
                             (uint32_t)kSPI_SselDeAssertAll);
    dataWidth = ((tx_ctrl & SPI_TXCTL_LEN_MASK) >> SPI_TXCTL_LEN_SHIFT);

    /* Set end of frame configuration. */
    tx_ctrl |= ((xfer->configFlags & (uint32_t)kSPI_EndOfFrame) != 0U) ? (uint32_t)kSPI_EndOfFrame : 0U;
    /* Set ignore configuration. */
    tx_ctrl |= (xfer->configFlags & (uint32_t)kSPI_ReceiveIgnore);

    /* If rxData is NULL, ignore the receive. */
    if (NULL == xfer->rxData)
    {
        tx_ctrl |= (uint32_t)kSPI_ReceiveIgnore;
    }

    /* Setup last command for transfer. */
    last_ctrl = tx_ctrl;
    /* Set end of transfer configuration for last command. */
    last_ctrl |= ((xfer->configFlags & (uint32_t)kSPI_EndOfTransfer) != 0U) ? (uint32_t)kSPI_EndOfTransfer : 0U;

    /* If only on frame to be sent, set the command to last command. */
    if (((remainingBytes == 1U) && (dataWidth < (uint32_t)kSPI_Data9Bits)) ||
        ((remainingBytes == 2U) && (dataWidth >= (uint32_t)kSPI_Data9Bits)))
    {
        SPI_WriteConfigFlags(base, last_ctrl);
    }
    else
    {
        SPI_WriteConfigFlags(base, tx_ctrl);
    }

    /* Index of loop */
    while (remainingBytes != 0U)
    {
        tmp32 = 0U;
#if SPI_RETRY_TIMES
        waitTimes = SPI_RETRY_TIMES;
        while (((base->STAT & SPI_STAT_TXRDY_MASK) == 0U) && (--waitTimes != 0U))
#else
        while ((base->STAT & SPI_STAT_TXRDY_MASK) == 0U)
#endif
        {
        }
#if SPI_RETRY_TIMES
        if (waitTimes == 0U)
        {
            return kStatus_SPI_Timeout;
        }
#endif

        /* If txdata is not NULL. */
        if (xfer->txData != NULL)
        {
            tmp32 = *(xfer->txData++);
            if (dataWidth > (uint32_t)kSPI_Data8Bits)
            {
                tmp32 |= ((uint32_t)(*(xfer->txData++))) << 8U;
            }
        }
        else
        {
            tmp32 = (uint32_t)s_dummyData[instance];
        }
        if ((dataWidth > (uint32_t)kSPI_Data8Bits) ? (remainingBytes == 2U) : (remainingBytes == 1U))
        {
            base->TXDATCTL = (tmp32 | last_ctrl);
        }
        else
        {
            /* Write data to the Transmit register. */
            base->TXDAT = tmp32;
        }
        /* If the RX ignore bits is not set. */
        if ((xfer->configFlags & (uint32_t)kSPI_ReceiveIgnore) == 0U)
        {
            /* Read data from the receive register. */
#if SPI_RETRY_TIMES
            waitTimes = SPI_RETRY_TIMES;
            while (((base->STAT & SPI_STAT_RXRDY_MASK) == 0U) && (--waitTimes != 0U))
#else
            while ((base->STAT & SPI_STAT_RXRDY_MASK) == 0U)
#endif
            {
            }
#if SPI_RETRY_TIMES
            if (waitTimes == 0U)
            {
                return kStatus_SPI_Timeout;
            }
#endif
            tmp32 = base->RXDAT;

            /* If receive buffer is not NULL. */
            if (xfer->rxData != NULL)
            {
                *(xfer->rxData++) = (uint8_t)tmp32;
                if (dataWidth > (uint32_t)kSPI_Data8Bits)
                {
                    *(xfer->rxData++) = (uint8_t)(tmp32 >> 8U);
                }
            }
        }
        remainingBytes--;
        if (dataWidth > (uint32_t)kSPI_Data8Bits)
        {
            remainingBytes--;
        }
    }

    /* Note that: the MSTIDLE status is related to the EOT bit, if the EOT is not set, the MSTIDLE bit will never be set
     * even though there is no data in the FIFO and no data will be shifted by the bus line. so, please don't check the
     * MSTIDLE status if the EOT bit is not set.
     */
    if ((xfer->configFlags & (uint32_t)kSPI_EndOfTransfer) != 0U)
    {
#if SPI_RETRY_TIMES
        waitTimes = SPI_RETRY_TIMES;
        while (((base->STAT & SPI_STAT_MSTIDLE_MASK) == 0U) && (--waitTimes != 0U))
#else
        while ((base->STAT & SPI_STAT_MSTIDLE_MASK) == 0U)
#endif
        {
        }
#if SPI_RETRY_TIMES
        if (waitTimes == 0U)
        {
            return kStatus_SPI_Timeout;
        }
#endif
    }
    return kStatus_Success;
}

#if defined(FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS) && (FSL_SDK_ENABLE_SPI_DRIVER_TRANSACTIONAL_APIS)
/*!
 * brief Initializes the SPI master handle.
 *
 * This function initializes the SPI master handle which can be used for other SPI master transactional APIs. Usually,
 * for a specified SPI instance, call this API once to get the initialized handle.
 *
 * param base SPI peripheral base address.
 * param handle SPI handle pointer.
 * param callback Callback function.
 * param userData User data.
 */
status_t SPI_MasterTransferCreateHandle(SPI_Type *base,
                                        spi_master_handle_t *handle,
                                        spi_master_callback_t callback,
                                        void *userData)
{
    /* check 'base' and 'handle'. */
    assert((NULL != base) || (NULL != handle));

    /* Get SPI instance by 'base' param */
    uint32_t instance = SPI_GetInstance(base);

    (void)memset(handle, 0, sizeof(*handle));
    handle->dataWidth     = (uint8_t)((base->TXCTL & SPI_TXCTL_LEN_MASK) >> SPI_TXCTL_LEN_SHIFT);
    handle->callback      = callback;
    handle->userData      = userData;
    s_spiHandle[instance] = handle;
    s_spiMasterIsr        = SPI_MasterTransferHandleIRQ;
    /* Enable SPI NVIC */
    (void)EnableIRQ(s_spiIRQ[instance]);

    return kStatus_Success;
}

/*!
 * brief Initializes the SPI slave handle.
 *
 * This function initializes the SPI slave handle which can be used for other SPI slave transactional APIs. Usually,
 * for a specified SPI instance, call this API once to get the initialized handle.
 *
 * param base SPI peripheral base address.
 * param handle SPI handle pointer.
 * param callback Callback function.
 * param userData User data.
 */
status_t SPI_SlaveTransferCreateHandle(SPI_Type *base,
                                       spi_slave_handle_t *handle,
                                       spi_slave_callback_t callback,
                                       void *userData)
{
    (void)SPI_MasterTransferCreateHandle(base, handle, callback, userData);
    s_spiSlaveIsr = SPI_SlaveTransferHandleIRQ;
    return kStatus_Success;
}

/*!
 * brief Performs a non-blocking SPI interrupt transfer.
 *
 * param base SPI peripheral base address.
 * param handle pointer to spi_master_handle_t structure which stores the transfer state
 * param xfer pointer to spi_xfer_config_t 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_MasterTransferNonBlocking(SPI_Type *base, spi_master_handle_t *handle, spi_transfer_t *xfer)
{
    /* check params */
    assert(
        !((NULL == base) || (NULL == handle) || (NULL == xfer) || ((NULL == xfer->txData) && (NULL == xfer->rxData))));
    uint16_t temp = 0U;
    uint32_t instance;

    /* dataSize (in bytes) is not aligned to 16bit (2B) transfer */
    if ((handle->dataWidth > (uint8_t)kSPI_Data8Bits) && ((xfer->dataSize & 0x1U) != 0U))
    {
        return kStatus_InvalidArgument;
    }

    /* Check if SPI is busy */
    if (handle->state == (uint32_t)kStatus_SPI_Busy)
    {
        return kStatus_SPI_Busy;
    }

    /* Set the handle information */
    handle->txData = xfer->txData;
    handle->rxData = xfer->rxData;
    /* Set count */
    handle->txRemainingBytes = (NULL == xfer->txData) ? 0U : xfer->dataSize;
    handle->rxRemainingBytes = (NULL == xfer->rxData) ? 0U : xfer->dataSize;
    handle->totalByteCount   = xfer->dataSize;
    /* If the rxData is NULL, ignore the receive. */
    if (NULL == xfer->rxData)
    {
        xfer->configFlags |= (uint32_t)kSPI_ReceiveIgnore;
    }

    /* If only on frame to be sent, set the command to last command. */
    if (((xfer->dataSize == 1U) && (handle->dataWidth < (uint8_t)kSPI_Data9Bits)) ||
        ((xfer->dataSize == 2U) && (handle->dataWidth >= (uint8_t)kSPI_Data9Bits)))
    {
        SPI_WriteConfigFlags(base, xfer->configFlags);
    }
    else
    {
        SPI_WriteConfigFlags(base, (xfer->configFlags & (~SPI_TXDATCTL_EOT_MASK)));
    }

    /* Set the last command. */
    handle->lastCommand = base->TXCTL & (SPI_TXCTL_LEN_MASK | SPI_TXCTL_RXIGNORE_MASK | SPI_TXCTL_EOF_MASK |
                                         SPI_TXCTL_EOT_MASK | (uint32_t)kSPI_SselDeAssertAll);
    if ((xfer->configFlags & (uint32_t)kSPI_EndOfTransfer) != 0U)
    {
        handle->lastCommand |= SPI_TXDATCTL_EOT_MASK;
    }
    /* Set the SPI state to busy */
    handle->state = (uint32_t)kStatus_SPI_Busy;

    /* Write data to TXDAT register to trigger a SPI receive. */
    if (NULL == handle->txData)
    {
        instance = SPI_GetInstance(base);
        temp     = s_dummyData[instance];
    }
    else
    {
        temp = *handle->txData++;
        handle->txRemainingBytes--;
        if (handle->dataWidth > (uint8_t)kSPI_Data8Bits)
        {
            temp |= (uint16_t)(*handle->txData++) << 8U;
            handle->txRemainingBytes--;
        }
    }

    SPI_WriteData(base, temp);

    /* Enable generating IRQ.
     * If RX ignore bit was set, only enable TX ready interrupt, otherwise,
     * enable RX ready interrupt.
     */
    if (((uint32_t)kSPI_ReceiveIgnore & xfer->configFlags) != 0U)
    {
        SPI_EnableInterrupts(base, (uint32_t)kSPI_TxReadyInterruptEnable);
    }
    else
    {
        SPI_EnableInterrupts(base, (uint32_t)kSPI_RxReadyInterruptEnable);
    }

    return kStatus_Success;
}

/*!
 * brief Performs a non-blocking SPI slave interrupt transfer.
 *
 * note The API returns immediately after the transfer initialization is finished.
 *
 * param base SPI peripheral base address.
 * param handle pointer to spi_master_handle_t structure which stores the transfer state
 * param xfer pointer to spi_xfer_config_t 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_SlaveTransferNonBlocking(SPI_Type *base, spi_slave_handle_t *handle, spi_transfer_t *xfer)
{
    status_t status = kStatus_Success;

    s_spiSlaveIsr = SPI_SlaveTransferHandleIRQ;
    status        = SPI_MasterTransferNonBlocking(base, handle, xfer);

    return status;
}

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

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

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

    *count = handle->totalByteCount - handle->rxRemainingBytes;
    return kStatus_Success;
}

/*!
 * brief SPI master aborts a transfer using an interrupt.
 *
 * This function aborts a transfer using an interrupt.
 *
 * param base SPI peripheral base address.
 * param handle Pointer to the spi_master_handle_t structure which stores the transfer state.
 */
void SPI_MasterTransferAbort(SPI_Type *base, spi_master_handle_t *handle)
{
    assert(NULL != handle);

    SPI_DisableInterrupts(base, (uint32_t)kSPI_TxReadyInterruptEnable | (uint32_t)kSPI_RxReadyInterruptEnable);

    handle->state            = (uint32_t)kStatus_SPI_Idle;
    handle->txRemainingBytes = 0U;
    handle->rxRemainingBytes = 0U;
}

/*!
 * brief Interrupts the handler for the SPI.
 *
 * param base SPI peripheral base address.
 * param handle pointer to spi_master_handle_t structure which stores the transfer state.
 */
void SPI_MasterTransferHandleIRQ(SPI_Type *base, spi_master_handle_t *handle)
{
    assert((NULL != base) && (NULL != handle));

    /* IRQ behaviour:
     * - First interrupt is triggered by receive ready interrupt. The transfer function then
     *   tries read data and transmit data interleaved that results to strategy to process
     *   as many items as possible.
     * - In last interrupt, the last state is known by empty rxBuffer and txBuffer. If there
     *   is nothing to receive or send - both operations have been finished and interrupts can be disabled.
     *   If the callback function is not NULL, trigger it.
     */

    /* Data to send or read or expected to receive */
    if ((handle->rxRemainingBytes) != 0U)
    {
        SPI_ReceiveTransfer(base, handle);
    }
    if ((handle->txRemainingBytes) != 0U)
    {
        SPI_SendTransfer(base, handle);
    }
    if ((0U == handle->txRemainingBytes) && (0U == handle->rxRemainingBytes))
    {
        /* Only finalize the transfer when kSPI_TxReadyFlag is set which means
           the tx register is empty and all data is sent out to bus */
        if ((SPI_GetStatusFlags(base) & (uint32_t)kSPI_TxReadyFlag) != 0U)
        {
            /* Disable TX and RX interrupt. */
            SPI_DisableInterrupts(base, (uint32_t)kSPI_RxReadyInterruptEnable | (uint32_t)kSPI_TxReadyInterruptEnable);

            /* Set transfer state to idle. */
            handle->state = (uint32_t)kStatus_SPI_Idle;
            /* If callback is not NULL, call this function. */
            if (handle->callback != NULL)
            {
                (handle->callback)(base, handle, handle->state, handle->userData);
            }
        }
    }
}

/*!
 * brief Interrupts a handler for the SPI slave.
 *
 * param base SPI peripheral base address.
 * param handle pointer to spi_slave_handle_t structure which stores the transfer state
 */
void SPI_SlaveTransferHandleIRQ(SPI_Type *base, spi_slave_handle_t *handle)
{
    assert((NULL != base) && (NULL != handle));

    /* IRQ behaviour:
     * - First interrupt is triggered by receive ready interrupt. The transfer function then
     *   tries read data and transmit data interleaved that results to strategy to process
     *   as many items as possible.
     * - In the last interrupt, the last state is known by empty rxBuffer. If there is nothing
     *   to receive or send - both operations have been finished and interrupt can be disabled.
     *   If the callback function is not NULL, call it.
     */

    /* Sending data to TXDAT first in case of data missing. */
    if (handle->txRemainingBytes != 0U)
    {
        SPI_SendTransfer(base, handle);
    }

    /* Read data from RXDAT. */
    if (handle->rxRemainingBytes != 0U)
    {
        SPI_ReceiveTransfer(base, handle);
    }

    if ((0U == handle->txRemainingBytes) && (0U == handle->rxRemainingBytes))
    {
        /* Only finalize the transfer when kSPI_TxReadyFlag is set which means
           the tx register is empty and all data is sent out to bus */
        if ((SPI_GetStatusFlags(base) & (uint32_t)kSPI_TxReadyFlag) != 0U)
        {
            /* Disable RX interrupt. */
            SPI_DisableInterrupts(base, (uint32_t)kSPI_RxReadyInterruptEnable | (uint32_t)kSPI_TxReadyInterruptEnable);
            /* Set transfer state to idle. */
            handle->state = (uint32_t)kStatus_SPI_Idle;
            /* If callback is not NULL, call this function. */
            if (handle->callback != NULL)
            {
                (handle->callback)(base, handle, handle->state, handle->userData);
            }
        }
    }
}

static void SPI_CommonIRQHandler(SPI_Type *base, void *param)
{
    if (SPI_IsMaster(base))
    {
        s_spiMasterIsr(base, (spi_master_handle_t *)param);
    }
    else
    {
        s_spiSlaveIsr(base, (spi_slave_handle_t *)param);
    }
}

#if defined(SPI0)
void SPI0_DriverIRQHandler(void);
void SPI0_DriverIRQHandler(void)
{
    assert(s_spiHandle[0] != NULL);
    SPI_CommonIRQHandler(SPI0, s_spiHandle[0]);
}
#endif

#if defined(SPI1)
void SPI1_DriverIRQHandler(void);
void SPI1_DriverIRQHandler(void)
{
    assert(s_spiHandle[1] != NULL);
    SPI_CommonIRQHandler(SPI1, s_spiHandle[1]);
}
#endif
#endif /* FSL_SDK_ENABLE_SPI_TRANSACTIONAL_API */
