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

#include "fsl_dma.h"

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

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

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

/*!
 * @brief Get instance number for DMA.
 *
 * @param base DMA peripheral base address.
 */
static uint32_t DMA_GetInstance(DMA_Type *base);

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

/*! @brief Array to map DMA instance number to base pointer. */
static DMA_Type *const s_dmaBases[] = DMA_BASE_PTRS;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/*! @brief Array to map DMA instance number to clock name. */
static const clock_ip_name_t s_dmaClockName[] = DMA_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

/*! @brief Array to map DMA instance number to IRQ number. */
static const IRQn_Type s_dmaIRQNumber[][FSL_FEATURE_DMA_MODULE_CHANNEL] = DMA_CHN_IRQS;

/*! @brief Pointers to transfer handle for each DMA channel. */
static dma_handle_t *s_DMAHandle[FSL_FEATURE_DMA_MODULE_CHANNEL * FSL_FEATURE_SOC_DMA_COUNT];

/*******************************************************************************
 * Code
 ******************************************************************************/
static uint32_t DMA_GetInstance(DMA_Type *base)
{
    uint32_t instance;

    /* Find the instance index from base address mappings. */
    for (instance = 0; instance < ARRAY_SIZE(s_dmaBases); instance++)
    {
        if (s_dmaBases[instance] == base)
        {
            break;
        }
    }

    assert(instance < ARRAY_SIZE(s_dmaBases));

    return instance;
}

/*!
 * brief Initializes the DMA peripheral.
 *
 * This function ungates the DMA clock.
 *
 * param base DMA peripheral base address.
 */
void DMA_Init(DMA_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    CLOCK_EnableClock(s_dmaClockName[DMA_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Deinitializes the DMA peripheral.
 *
 * This function gates the DMA clock.
 *
 * param base DMA peripheral base address.
 */
void DMA_Deinit(DMA_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    CLOCK_DisableClock(s_dmaClockName[DMA_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Resets the DMA channel.
 *
 * Sets all register values to reset values and enables
 * the cycle steal and auto stop channel request features.
 *
 * param base DMA peripheral base address.
 * param channel DMA channel number.
 */
void DMA_ResetChannel(DMA_Type *base, uint32_t channel)
{
    assert(channel < (uint32_t)FSL_FEATURE_DMA_MODULE_CHANNEL);

    /* clear all status bit */
    base->DMA[channel].DSR_BCR |= DMA_DSR_BCR_DONE(true);
    /* clear all registers */
    base->DMA[channel].SAR     = 0;
    base->DMA[channel].DAR     = 0;
    base->DMA[channel].DSR_BCR = 0;
    /* enable cycle steal and enable auto disable channel request */
    base->DMA[channel].DCR = DMA_DCR_D_REQ(true) | DMA_DCR_CS(true);
}

/*!
 * brief Configures the DMA transfer attribute.
 *
 * This function configures the transfer attribute including the source address,
 * destination address, transfer size, and so on.
 * This example shows how to set up the dma_transfer_config_t
 * parameters and how to call the DMA_ConfigBasicTransfer function.
 * code
 *   dma_transfer_config_t transferConfig;
 *   memset(&transferConfig, 0, sizeof(transferConfig));
 *   transferConfig.srcAddr = (uint32_t)srcAddr;
 *   transferConfig.destAddr = (uint32_t)destAddr;
 *   transferConfig.enbaleSrcIncrement = true;
 *   transferConfig.enableDestIncrement = true;
 *   transferConfig.srcSize = kDMA_Transfersize32bits;
 *   transferConfig.destSize = kDMA_Transfersize32bits;
 *   transferConfig.transferSize = sizeof(uint32_t) * BUFF_LENGTH;
 *   DMA_SetTransferConfig(DMA0, 0, &transferConfig);
 * endcode
 *
 * param base DMA peripheral base address.
 * param channel DMA channel number.
 * param config Pointer to the DMA transfer configuration structure.
 */
void DMA_SetTransferConfig(DMA_Type *base, uint32_t channel, const dma_transfer_config_t *config)
{
    assert(channel < (uint32_t)FSL_FEATURE_DMA_MODULE_CHANNEL);
    assert(config != NULL);

    uint32_t tmpreg;

    /* Set source address */
    base->DMA[channel].SAR = config->srcAddr;
    /* Set destination address */
    base->DMA[channel].DAR = config->destAddr;
    /* Set transfer bytes */
    base->DMA[channel].DSR_BCR = DMA_DSR_BCR_BCR(config->transferSize);
    /* Set DMA Control Register */
    tmpreg = base->DMA[channel].DCR;
    tmpreg &= ~(DMA_DCR_DSIZE_MASK | DMA_DCR_DINC_MASK | DMA_DCR_SSIZE_MASK | DMA_DCR_SINC_MASK);
    tmpreg |= (DMA_DCR_DSIZE(config->destSize) | DMA_DCR_DINC(config->enableDestIncrement) |
               DMA_DCR_SSIZE(config->srcSize) | DMA_DCR_SINC(config->enableSrcIncrement));
    base->DMA[channel].DCR = tmpreg;
}

/*!
 * brief Configures the DMA channel link feature.
 *
 * This function allows DMA channels to have their transfers linked. The current DMA channel
 * triggers a DMA request to the linked channels (LCH1 or LCH2) depending on the channel link
 * type.
 * Perform a link to channel LCH1 after each cycle-steal transfer followed by a link to LCH2
 * after the BCR decrements to 0 if the type is kDMA_ChannelLinkChannel1AndChannel2.
 * Perform a link to LCH1 after each cycle-steal transfer if the type is kDMA_ChannelLinkChannel1.
 * Perform a link to LCH1 after the BCR decrements to 0 if the type is kDMA_ChannelLinkChannel1AfterBCR0.
 *
 * param base DMA peripheral base address.
 * param channel DMA channel number.
 * param config Pointer to the channel link configuration structure.
 */
void DMA_SetChannelLinkConfig(DMA_Type *base, uint32_t channel, const dma_channel_link_config_t *config)
{
    assert(channel < (uint32_t)FSL_FEATURE_DMA_MODULE_CHANNEL);
    assert(config != NULL);

    uint32_t tmpreg;

    tmpreg = base->DMA[channel].DCR;
    tmpreg &= ~(DMA_DCR_LINKCC_MASK | DMA_DCR_LCH1_MASK | DMA_DCR_LCH2_MASK);
    tmpreg |= (DMA_DCR_LINKCC(config->linkType) | DMA_DCR_LCH1(config->channel1) | DMA_DCR_LCH2(config->channel2));
    base->DMA[channel].DCR = tmpreg;
}

/*!
 * brief Sets the DMA modulo for the DMA transfer.
 *
 * This function defines a specific address range specified to be the value after (SAR + SSIZE)/(DAR + DSIZE)
 * calculation is performed or the original register value. It provides the ability to implement a circular
 * data queue easily.
 *
 * param base DMA peripheral base address.
 * param channel DMA channel number.
 * param srcModulo source address modulo.
 * param destModulo destination address modulo.
 */
void DMA_SetModulo(DMA_Type *base, uint32_t channel, dma_modulo_t srcModulo, dma_modulo_t destModulo)
{
    assert(channel < (uint32_t)FSL_FEATURE_DMA_MODULE_CHANNEL);

    uint32_t tmpreg;

    tmpreg = base->DMA[channel].DCR;
    tmpreg &= ~(DMA_DCR_SMOD_MASK | DMA_DCR_DMOD_MASK);
    tmpreg |= (DMA_DCR_SMOD(srcModulo) | DMA_DCR_DMOD(destModulo));
    base->DMA[channel].DCR = tmpreg;
}

/*!
 * brief Creates the DMA handle.
 *
 * This function is called first if using the transactional API for the DMA. This function
 * initializes the internal state of the DMA handle.
 *
 * param handle DMA handle pointer. The DMA handle stores callback function and
 *               parameters.
 * param base DMA peripheral base address.
 * param channel DMA channel number.
 */
void DMA_CreateHandle(dma_handle_t *handle, DMA_Type *base, uint32_t channel)
{
    assert(handle != NULL);
    assert(channel < (uint32_t)FSL_FEATURE_DMA_MODULE_CHANNEL);

    uint32_t dmaInstance;
    uint32_t channelIndex;

    /* Zero the handle */
    (void)memset(handle, 0, sizeof(*handle));

    handle->base    = base;
    handle->channel = (uint8_t)channel;
    /* Get the DMA instance number */
    dmaInstance  = DMA_GetInstance(base);
    channelIndex = (dmaInstance * (uint32_t)FSL_FEATURE_DMA_MODULE_CHANNEL) + channel;
    /* Store handle */
    s_DMAHandle[channelIndex] = handle;
    /* Enable NVIC interrupt. */
    (void)EnableIRQ(s_dmaIRQNumber[dmaInstance][channelIndex]);
}

/*!
 * brief Prepares the DMA transfer configuration structure.
 *
 * This function prepares the transfer configuration structure according to the user input.
 * The difference between this function and DMA_PrepareTransfer is that this function expose the address increment
 * parameter to application, but in DMA_PrepareTransfer, only parts of the address increment option can be selected by
 * dma_transfer_type_t.
 *
 * param config Pointer to the user configuration structure of type dma_transfer_config_t.
 * param srcAddr DMA transfer source address.
 * param srcWidth DMA transfer source address width (byte).
 * param destAddr DMA transfer destination address.
 * param destWidth DMA transfer destination address width (byte).
 * param transferBytes DMA transfer bytes to be transferred.
 * param srcIncrement source address increment type.
 * param destIncrement dest address increment type.
 */
void DMA_PrepareTransferConfig(dma_transfer_config_t *config,
                               void *srcAddr,
                               uint32_t srcWidth,
                               void *destAddr,
                               uint32_t destWidth,
                               uint32_t transferBytes,
                               dma_addr_increment_t srcIncrement,
                               dma_addr_increment_t destIncrement)
{
    assert(config != NULL);
    assert(srcAddr != NULL);
    assert(destAddr != NULL);
    assert((srcWidth == 1UL) || (srcWidth == 2UL) || (srcWidth == 4UL));
    assert((destWidth == 1UL) || (destWidth == 2UL) || (destWidth == 4UL));

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

    config->srcAddr      = (uint32_t)(uint32_t *)srcAddr;
    config->destAddr     = (uint32_t)(uint32_t *)destAddr;
    config->transferSize = transferBytes;

    if (srcWidth == 1UL)
    {
        config->srcSize = kDMA_Transfersize8bits;
    }
    else if (srcWidth == 2UL)
    {
        config->srcSize = kDMA_Transfersize16bits;
    }
    else
    {
        config->srcSize = kDMA_Transfersize32bits;
    }

    if (destWidth == 1UL)
    {
        config->destSize = kDMA_Transfersize8bits;
    }
    else if (destWidth == 2UL)
    {
        config->destSize = kDMA_Transfersize16bits;
    }
    else
    {
        config->destSize = kDMA_Transfersize32bits;
    }

    config->enableSrcIncrement  = srcIncrement == kDMA_AddrNoIncrement ? false : true;
    config->enableDestIncrement = destIncrement == kDMA_AddrNoIncrement ? false : true;
}

/*!
 * brief Prepares the DMA transfer configuration structure.
 *
 * This function prepares the transfer configuration structure according to the user input.
 *
 * param config Pointer to the user configuration structure of type dma_transfer_config_t.
 * param srcAddr DMA transfer source address.
 * param srcWidth DMA transfer source address width (byte).
 * param destAddr DMA transfer destination address.
 * param destWidth DMA transfer destination address width (byte).
 * param transferBytes DMA transfer bytes to be transferred.
 * param type DMA transfer type.
 */
void DMA_PrepareTransfer(dma_transfer_config_t *config,
                         void *srcAddr,
                         uint32_t srcWidth,
                         void *destAddr,
                         uint32_t destWidth,
                         uint32_t transferBytes,
                         dma_transfer_type_t type)
{
    assert(config != NULL);
    assert(srcAddr != NULL);
    assert(destAddr != NULL);
    assert((srcWidth == 1UL) || (srcWidth == 2UL) || (srcWidth == 4UL));
    assert((destWidth == 1UL) || (destWidth == 2UL) || (destWidth == 4UL));

    dma_addr_increment_t srcIncrement = false, destIncrement = false;

    if (type == kDMA_MemoryToMemory)
    {
        srcIncrement  = kDMA_AddrIncrementPerTransferWidth;
        destIncrement = kDMA_AddrIncrementPerTransferWidth;
    }
    else if (type == kDMA_PeripheralToMemory)
    {
        srcIncrement  = kDMA_AddrNoIncrement;
        destIncrement = kDMA_AddrIncrementPerTransferWidth;
    }
    else
    {
        srcIncrement  = kDMA_AddrIncrementPerTransferWidth;
        destIncrement = kDMA_AddrNoIncrement;
    }

    DMA_PrepareTransferConfig(config, srcAddr, srcWidth, destAddr, destWidth, transferBytes, srcIncrement,
                              destIncrement);
}

/*!
 * brief Sets the DMA callback function.
 *
 * This callback is called in the DMA IRQ handler. Use the callback to do something
 * after the current transfer complete.
 *
 * param handle DMA handle pointer.
 * param callback DMA callback function pointer.
 * param userData Parameter for callback function. If it is not needed, just set to NULL.
 */
void DMA_SetCallback(dma_handle_t *handle, dma_callback callback, void *userData)
{
    assert(handle != NULL);

    handle->callback = callback;
    handle->userData = userData;
}

/*!
 * brief Submits the DMA transfer request.
 *
 * This function submits the DMA transfer request according to the transfer configuration structure.
 *
 * param handle DMA handle pointer.
 * param config Pointer to DMA transfer configuration structure.
 * param options Additional configurations for transfer. Use
 *                the defined dma_transfer_options_t type.
 * retval kStatus_DMA_Success It indicates that the DMA submit transfer request succeeded.
 * retval kStatus_DMA_Busy It indicates that the DMA is busy. Submit transfer request is not allowed.
 * note This function can't process multi transfer request.
 */
status_t DMA_SubmitTransfer(dma_handle_t *handle, const dma_transfer_config_t *config, uint32_t options)
{
    assert(handle != NULL);
    assert(config != NULL);

    /* Check if DMA is busy */
    if ((handle->base->DMA[handle->channel].DSR_BCR & DMA_DSR_BCR_BSY_MASK) != 0UL)
    {
        return kStatus_DMA_Busy;
    }
    DMA_ResetChannel(handle->base, handle->channel);
    DMA_SetTransferConfig(handle->base, handle->channel, config);
    if ((options & (uint32_t)kDMA_EnableInterrupt) != 0UL)
    {
        DMA_EnableInterrupts(handle->base, handle->channel);
    }
    return kStatus_Success;
}

/*!
 * brief DMA aborts a transfer.
 *
 * This function disables the channel request and clears all status bits.
 * Submit another transfer after calling this API.
 *
 * param handle DMA handle pointer.
 */
void DMA_AbortTransfer(dma_handle_t *handle)
{
    assert(handle != NULL);

    handle->base->DMA[handle->channel].DCR &= ~DMA_DCR_ERQ_MASK;
    /* clear all status bit */
    handle->base->DMA[handle->channel].DSR_BCR |= DMA_DSR_BCR_DONE(true);
}

/*!
 * brief DMA IRQ handler for current transfer complete.
 *
 * This function clears the channel interrupt flag and calls
 * the callback function if it is not NULL.
 *
 * param handle DMA handle pointer.
 */
void DMA_HandleIRQ(dma_handle_t *handle)
{
    assert(handle != NULL);

    /* Clear interrupt pending bit */
    DMA_ClearChannelStatusFlags(handle->base, handle->channel, kDMA_TransactionsDoneFlag);
    if (handle->callback != NULL)
    {
        (handle->callback)(handle, handle->userData);
    }
}

#if defined(FSL_FEATURE_DMA_MODULE_CHANNEL) && (FSL_FEATURE_DMA_MODULE_CHANNEL == 4U)
void DMA0_DriverIRQHandler(void);
void DMA0_DriverIRQHandler(void)
{
    DMA_HandleIRQ(s_DMAHandle[0]);
    SDK_ISR_EXIT_BARRIER;
}

void DMA1_DriverIRQHandler(void);
void DMA1_DriverIRQHandler(void)
{
    DMA_HandleIRQ(s_DMAHandle[1]);
    SDK_ISR_EXIT_BARRIER;
}

void DMA2_DriverIRQHandler(void);
void DMA2_DriverIRQHandler(void)
{
    DMA_HandleIRQ(s_DMAHandle[2]);
    SDK_ISR_EXIT_BARRIER;
}

void DMA3_DriverIRQHandler(void);
void DMA3_DriverIRQHandler(void)
{
    DMA_HandleIRQ(s_DMAHandle[3]);
    SDK_ISR_EXIT_BARRIER;
}
#endif /* FSL_FEATURE_DMA_MODULE_CHANNEL */
