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

#include "fsl_sdhc.h"

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

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

/*! @brief Clock setting */
/* Max SD clock divisor from base clock */
#define SDHC_MAX_DVS       ((SDHC_SYSCTL_DVS_MASK >> SDHC_SYSCTL_DVS_SHIFT) + 1U)
#define SDHC_PREV_DVS(x)   ((x) -= 1U)
#define SDHC_MAX_CLKFS     ((SDHC_SYSCTL_SDCLKFS_MASK >> SDHC_SYSCTL_SDCLKFS_SHIFT) + 1U)
#define SDHC_PREV_CLKFS(x) ((x) >>= 1U)

/* Typedef for interrupt handler. */
typedef void (*sdhc_isr_t)(SDHC_Type *base, sdhc_handle_t *handle);

/*! @brief ADMA table configuration */
typedef struct _sdhc_adma_table_config
{
    uint32_t *admaTable;     /*!< ADMA table address, can't be null if transfer way is ADMA1/ADMA2 */
    uint32_t admaTableWords; /*!< ADMA table length united as words, can't be 0 if transfer way is ADMA1/ADMA2 */
} sdhc_adma_table_config_t;
/*! @brief check flag avalibility */
#define IS_SDHC_FLAG_SET(reg, flag) (((reg) & ((uint32_t)flag)) != 0UL)
/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*!
 * @brief Get the instance.
 *
 * @param base SDHC peripheral base address.
 * @return Instance number.
 */
static uint32_t SDHC_GetInstance(SDHC_Type *base);

/*!
 * @brief Start transfer according to current transfer state
 *
 * @param base SDHC peripheral base address.
 * @param command Command to be sent.
 * @param data Data to be transferred.
 * @param DMA mode selection
 */
static void SDHC_StartTransfer(SDHC_Type *base, sdhc_command_t *command, sdhc_data_t *data, sdhc_dma_mode_t dmaMode);

/*!
 * @brief Receive command response
 *
 * @param base SDHC peripheral base address.
 * @param command Command to be sent.
 */
static status_t SDHC_ReceiveCommandResponse(SDHC_Type *base, sdhc_command_t *command);

/*!
 * @brief Read DATAPORT when buffer enable bit is set.
 *
 * @param base SDHC peripheral base address.
 * @param data Data to be read.
 * @param transferredWords The number of data words have been transferred last time transaction.
 * @return The number of total data words have been transferred after this time transaction.
 */
static uint32_t SDHC_ReadDataPort(SDHC_Type *base, sdhc_data_t *data, uint32_t transferredWords);

/*!
 * @brief Read data by using DATAPORT polling way.
 *
 * @param base SDHC peripheral base address.
 * @param data Data to be read.
 * @retval kStatus_Fail Read DATAPORT failed.
 * @retval kStatus_Success Operate successfully.
 */
static status_t SDHC_ReadByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data);

/*!
 * @brief Write DATAPORT when buffer enable bit is set.
 *
 * @param base SDHC peripheral base address.
 * @param data Data to be read.
 * @param transferredWords The number of data words have been transferred last time.
 * @return The number of total data words have been transferred after this time transaction.
 */
static uint32_t SDHC_WriteDataPort(SDHC_Type *base, sdhc_data_t *data, uint32_t transferredWords);

/*!
 * @brief Write data by using DATAPORT polling way.
 *
 * @param base SDHC peripheral base address.
 * @param data Data to be transferred.
 * @retval kStatus_Fail Write DATAPORT failed.
 * @retval kStatus_Success Operate successfully.
 */
static status_t SDHC_WriteByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data);

/*!
 * @brief Send command by using polling way.
 *
 * @param base SDHC peripheral base address.
 * @param command Command to be sent.
 * @retval kStatus_Fail Send command failed.
 * @retval kStatus_Success Operate successfully.
 */
static status_t SDHC_SendCommandBlocking(SDHC_Type *base, sdhc_command_t *command);

/*!
 * @brief Transfer data by DATAPORT and polling way.
 *
 * @param base SDHC peripheral base address.
 * @param data Data to be transferred.
 * @retval kStatus_Fail Transfer data failed.
 * @retval kStatus_Success Operate successfully.
 */
static status_t SDHC_TransferByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data);

/*!
 * @brief Transfer data by ADMA2 and polling way.
 *
 * @param base SDHC peripheral base address.
 * @param data Data to be transferred.
 * @retval kStatus_Fail Transfer data failed.
 * @retval kStatus_Success Operate successfully.
 */
static status_t SDHC_TransferByAdma2Blocking(SDHC_Type *base, sdhc_data_t *data);

/*!
 * @brief Transfer data by polling way.
 *
 * @param dmaMode DMA mode.
 * @param base SDHC peripheral base address.
 * @param data Data to be transferred.
 * @retval kStatus_Fail Transfer data failed.
 * @retval kStatus_InvalidArgument Argument is invalid.
 * @retval kStatus_Success Operate successfully.
 */
static status_t SDHC_TransferDataBlocking(sdhc_dma_mode_t dmaMode, SDHC_Type *base, sdhc_data_t *data);

/*!
 * @brief Handle card detect interrupt.
 *
 * @param base SDHC peripheral base address.
 * @param handle SDHC handle.
 * @param interruptFlags Card detect related interrupt flags.
 */
static void SDHC_TransferHandleCardDetect(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags);

/*!
 * @brief Handle command interrupt.
 *
 * @param base SDHC peripheral base address.
 * @param handle SDHC handle.
 * @param interruptFlags Command related interrupt flags.
 */
static void SDHC_TransferHandleCommand(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags);

/*!
 * @brief Handle data interrupt.
 *
 * @param base SDHC peripheral base address.
 * @param handle SDHC handle.
 * @param interruptFlags Data related interrupt flags.
 */
static void SDHC_TransferHandleData(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags);

/*!
 * @brief Handle SDIO card interrupt signal.
 *
 * @param base SDHC peripheral base address.
 * @param handle SDHC handle.
 */
static void SDHC_TransferHandleSdioInterrupt(SDHC_Type *base, sdhc_handle_t *handle);

/*!
 * @brief Handle SDIO block gap event.
 *
 * @param base SDHC peripheral base address.
 * @param handle SDHC handle.
 */
static void SDHC_TransferHandleSdioBlockGap(SDHC_Type *base, sdhc_handle_t *handle);

/*******************************************************************************
 * Variables
 ******************************************************************************/
/*! @brief SDHC internal handle pointer array */
static sdhc_handle_t *s_sdhcHandle[FSL_FEATURE_SOC_SDHC_COUNT];

/*! @brief SDHC base pointer array */
static SDHC_Type *const s_sdhcBase[] = SDHC_BASE_PTRS;

/*! @brief SDHC IRQ name array */
static const IRQn_Type s_sdhcIRQ[] = SDHC_IRQS;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
/*! @brief SDHC clock array name */
static const clock_ip_name_t s_sdhcClock[] = SDHC_CLOCKS;
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

/* SDHC ISR for transactional APIs. */
#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050)
static sdhc_isr_t s_sdhcIsr = (sdhc_isr_t)DefaultISR;
#else
static sdhc_isr_t s_sdhcIsr;
#endif

/*******************************************************************************
 * Code
 ******************************************************************************/
static uint32_t SDHC_GetInstance(SDHC_Type *base)
{
    uint8_t instance = 0;

    while ((instance < ARRAY_SIZE(s_sdhcBase)) && (s_sdhcBase[instance] != base))
    {
        instance++;
    }

    assert(instance < ARRAY_SIZE(s_sdhcBase));

    return instance;
}

static void SDHC_StartTransfer(SDHC_Type *base, sdhc_command_t *command, sdhc_data_t *data, sdhc_dma_mode_t dmaMode)
{
    uint32_t flags                            = 0U;
    sdhc_transfer_config_t sdhcTransferConfig = {0};

    /* Define the flag corresponding to each response type. */
    if (command->responseType == kCARD_ResponseTypeR1) /* Response 1 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48Flag | (uint32_t)kSDHC_EnableCrcCheckFlag |
                  (uint32_t)kSDHC_EnableIndexCheckFlag);
    }
    else if (command->responseType == kCARD_ResponseTypeR1b) /* Response 1 with busy */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48BusyFlag | (uint32_t)kSDHC_EnableCrcCheckFlag |
                  (uint32_t)kSDHC_EnableIndexCheckFlag);
    }
    else if (command->responseType == kCARD_ResponseTypeR2) /* Response 2 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength136Flag | (uint32_t)kSDHC_EnableCrcCheckFlag);
    }
    else if (command->responseType == kCARD_ResponseTypeR3) /* Response 3 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48Flag);
    }
    else if (command->responseType == kCARD_ResponseTypeR4) /* Response 4 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48Flag);
    }
    else if (command->responseType == kCARD_ResponseTypeR5) /* Response 5 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48Flag | (uint32_t)kSDHC_EnableCrcCheckFlag |
                  (uint32_t)kSDHC_EnableIndexCheckFlag);
    }
    else if (command->responseType == kCARD_ResponseTypeR5b) /* Response 5 with busy */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48BusyFlag | (uint32_t)kSDHC_EnableCrcCheckFlag |
                  (uint32_t)kSDHC_EnableIndexCheckFlag);
    }
    else if (command->responseType == kCARD_ResponseTypeR6) /* Response 6 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48Flag | (uint32_t)kSDHC_EnableCrcCheckFlag |
                  (uint32_t)kSDHC_EnableIndexCheckFlag);
    }
    else if (command->responseType == kCARD_ResponseTypeR7) /* Response 7 */
    {
        flags |= ((uint32_t)kSDHC_ResponseLength48Flag | (uint32_t)kSDHC_EnableCrcCheckFlag |
                  (uint32_t)kSDHC_EnableIndexCheckFlag);
    }
    else
    {
        /* Intentional empty for kCARD_ResponseTypeNone */
    }

    if (command->type == kCARD_CommandTypeAbort)
    {
        flags |= (uint32_t)kSDHC_CommandTypeAbortFlag;
    }

    if (data != NULL)
    {
        flags |= (uint32_t)kSDHC_DataPresentFlag;

        if (dmaMode != kSDHC_DmaModeNo)
        {
            flags |= (uint32_t)kSDHC_EnableDmaFlag;
        }

        if (data->rxData != NULL)
        {
            flags |= (uint32_t)kSDHC_DataReadFlag;
        }
        if (data->blockCount > 1U)
        {
            flags |= ((uint32_t)kSDHC_MultipleBlockFlag | (uint32_t)kSDHC_EnableBlockCountFlag);
            if (data->enableAutoCommand12)
            {
                /* Enable Auto command 12. */
                flags |= (uint32_t)kSDHC_EnableAutoCommand12Flag;
            }
        }

        sdhcTransferConfig.dataBlockSize  = data->blockSize;
        sdhcTransferConfig.dataBlockCount = data->blockCount;
    }
    else
    {
        sdhcTransferConfig.dataBlockSize  = 0U;
        sdhcTransferConfig.dataBlockCount = 0U;
    }

    sdhcTransferConfig.commandArgument = command->argument;
    sdhcTransferConfig.commandIndex    = command->index;
    sdhcTransferConfig.flags           = flags;
    SDHC_SetTransferConfig(base, &sdhcTransferConfig);
}

static status_t SDHC_ReceiveCommandResponse(SDHC_Type *base, sdhc_command_t *command)
{
    assert(command != NULL);

    uint32_t response0 = base->CMDRSP[0];
    uint32_t response1 = base->CMDRSP[1];
    uint32_t response2 = base->CMDRSP[2];

    if (command->responseType != kCARD_ResponseTypeNone)
    {
        command->response[0U] = response0;
        if (command->responseType == kCARD_ResponseTypeR2)
        {
            /* R3-R2-R1-R0(lowest 8 bit is invalid bit) has the same format as R2 format in SD specification document
            after removed internal CRC7 and end bit. */
            command->response[0U] <<= 8U;
            command->response[1U] = (response1 << 8U) | ((response0 & 0xFF000000U) >> 24U);
            command->response[2U] = (response2 << 8U) | ((response1 & 0xFF000000U) >> 24U);
            command->response[3U] = (base->CMDRSP[3] << 8U) | ((response2 & 0xFF000000U) >> 24U);
        }
    }

    /* check response error flag */
    if ((command->responseErrorFlags != 0U) &&
        ((command->responseType == kCARD_ResponseTypeR1) || (command->responseType == kCARD_ResponseTypeR1b) ||
         (command->responseType == kCARD_ResponseTypeR6) || (command->responseType == kCARD_ResponseTypeR5)))
    {
        if (((command->responseErrorFlags) & (command->response[0U])) != 0U)
        {
            return kStatus_SDHC_SendCommandFailed;
        }
    }

    return kStatus_Success;
}

static uint32_t SDHC_ReadDataPort(SDHC_Type *base, sdhc_data_t *data, uint32_t transferredWords)
{
    uint32_t i;
    uint32_t totalWords;
    uint32_t wordsCanBeRead; /* The words can be read at this time. */
    uint32_t readWatermark = ((base->WML & SDHC_WML_RDWML_MASK) >> SDHC_WML_RDWML_SHIFT);

    /*
     * Add non aligned access support ,user need make sure your buffer size is big
     * enough to hold the data,in other words,user need make sure the buffer size
     * is 4 byte aligned
     */
    if (data->blockSize % sizeof(uint32_t) != 0U)
    {
        data->blockSize +=
            sizeof(uint32_t) - (data->blockSize % sizeof(uint32_t)); /* make the block size as word-aligned */
    }

    totalWords = ((data->blockCount * data->blockSize) / sizeof(uint32_t));

    /* If watermark level is equal or bigger than totalWords, transfers totalWords data. */
    if (readWatermark >= totalWords)
    {
        wordsCanBeRead = totalWords;
    }
    /* If watermark level is less than totalWords and left words to be sent is equal or bigger than readWatermark,
    transfers watermark level words. */
    else if ((readWatermark < totalWords) && ((totalWords - transferredWords) >= readWatermark))
    {
        wordsCanBeRead = readWatermark;
    }
    /* If watermark level is less than totalWords and left words to be sent is less than readWatermark, transfers left
    words. */
    else
    {
        wordsCanBeRead = (totalWords - transferredWords);
    }

    i = 0U;
    while (i < wordsCanBeRead)
    {
        data->rxData[transferredWords++] = SDHC_ReadData(base);
        i++;
    }

    return transferredWords;
}

static status_t SDHC_ReadByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data)
{
    uint32_t totalWords;
    uint32_t transferredWords = 0U;
    status_t error            = kStatus_Success;

    /*
     * Add non aligned access support ,user need make sure your buffer size is big
     * enough to hold the data,in other words,user need make sure the buffer size
     * is 4 byte aligned
     */
    if (data->blockSize % sizeof(uint32_t) != 0U)
    {
        data->blockSize +=
            sizeof(uint32_t) - (data->blockSize % sizeof(uint32_t)); /* make the block size as word-aligned */
    }

    totalWords = ((data->blockCount * data->blockSize) / sizeof(uint32_t));

    while ((error == kStatus_Success) && (transferredWords < totalWords))
    {
        while (!(IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base),
                                  ((uint32_t)kSDHC_BufferReadReadyFlag | (uint32_t)kSDHC_DataErrorFlag))))
        {
        }

        if (IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base), kSDHC_DataErrorFlag))
        {
            if (!(data->enableIgnoreError))
            {
                error = kStatus_Fail;
            }
        }
        if (error == kStatus_Success)
        {
            transferredWords = SDHC_ReadDataPort(base, data, transferredWords);
        }
        /* clear buffer ready and error */
        SDHC_ClearInterruptStatusFlags(base, (uint32_t)kSDHC_BufferReadReadyFlag | (uint32_t)kSDHC_DataErrorFlag);
    }

    /* Clear data complete flag after the last read operation. */
    SDHC_ClearInterruptStatusFlags(base, (uint32_t)kSDHC_DataCompleteFlag | (uint32_t)kSDHC_DataErrorFlag);

    return error;
}

static uint32_t SDHC_WriteDataPort(SDHC_Type *base, sdhc_data_t *data, uint32_t transferredWords)
{
    uint32_t i;
    uint32_t totalWords;
    uint32_t wordsCanBeWrote; /* Words can be wrote at this time. */
    uint32_t writeWatermark = ((base->WML & SDHC_WML_WRWML_MASK) >> SDHC_WML_WRWML_SHIFT);

    /*
     * Add non aligned access support ,user need make sure your buffer size is big
     * enough to hold the data,in other words,user need make sure the buffer size
     * is 4 byte aligned
     */
    if (data->blockSize % sizeof(uint32_t) != 0U)
    {
        data->blockSize +=
            sizeof(uint32_t) - (data->blockSize % sizeof(uint32_t)); /* make the block size as word-aligned */
    }

    totalWords = ((data->blockCount * data->blockSize) / sizeof(uint32_t));

    /* If watermark level is equal or bigger than totalWords, transfers totalWords data.*/
    if (writeWatermark >= totalWords)
    {
        wordsCanBeWrote = totalWords;
    }
    /* If watermark level is less than totalWords and left words to be sent is equal or bigger than watermark,
    transfers watermark level words. */
    else if ((writeWatermark < totalWords) && ((totalWords - transferredWords) >= writeWatermark))
    {
        wordsCanBeWrote = writeWatermark;
    }
    /* If watermark level is less than totalWords and left words to be sent is less than watermark, transfers left
    words. */
    else
    {
        wordsCanBeWrote = (totalWords - transferredWords);
    }

    i = 0U;
    while (i < wordsCanBeWrote)
    {
        SDHC_WriteData(base, data->txData[transferredWords++]);
        i++;
    }

    return transferredWords;
}

static status_t SDHC_WriteByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data)
{
    uint32_t totalWords;
    uint32_t transferredWords = 0U;
    status_t error            = kStatus_Success;

    /*
     * Add non aligned access support ,user need make sure your buffer size is big
     * enough to hold the data,in other words,user need make sure the buffer size
     * is 4 byte aligned
     */
    if (data->blockSize % sizeof(uint32_t) != 0U)
    {
        data->blockSize +=
            sizeof(uint32_t) - (data->blockSize % sizeof(uint32_t)); /* make the block size as word-aligned */
    }

    totalWords = (data->blockCount * data->blockSize) / sizeof(uint32_t);

    while ((error == kStatus_Success) && (transferredWords < totalWords))
    {
        while (!(IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base),
                                  ((uint32_t)kSDHC_BufferWriteReadyFlag | (uint32_t)kSDHC_DataErrorFlag))))
        {
        }

        if (IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base), kSDHC_DataErrorFlag))
        {
            if (!(data->enableIgnoreError))
            {
                error = kStatus_Fail;
            }
        }
        if (error == kStatus_Success)
        {
            transferredWords = SDHC_WriteDataPort(base, data, transferredWords);
        }

        /* Clear buffer enable flag to trigger transfer. Clear error flag when SDHC encounter error. */
        SDHC_ClearInterruptStatusFlags(base, ((uint32_t)kSDHC_BufferWriteReadyFlag | (uint32_t)kSDHC_DataErrorFlag));
    }

    /* Wait write data complete or data transfer error after the last writing operation. */
    while (!(IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base),
                              ((uint32_t)kSDHC_DataCompleteFlag | (uint32_t)kSDHC_DataErrorFlag))))
    {
    }
    if (IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base), kSDHC_DataErrorFlag))
    {
        if (!(data->enableIgnoreError))
        {
            error = kStatus_Fail;
        }
    }

    SDHC_ClearInterruptStatusFlags(base, ((uint32_t)kSDHC_DataCompleteFlag | (uint32_t)kSDHC_DataErrorFlag));

    return error;
}

static status_t SDHC_SendCommandBlocking(SDHC_Type *base, sdhc_command_t *command)
{
    status_t error = kStatus_Success;

    /* Wait command complete or SDHC encounters error. */
    while (!(IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base),
                              ((uint32_t)kSDHC_CommandCompleteFlag | (uint32_t)kSDHC_CommandErrorFlag))))
    {
    }

    if (IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base), kSDHC_CommandErrorFlag))
    {
        error = kStatus_Fail;
    }
    /* Receive response when command completes successfully. */
    if (error == kStatus_Success)
    {
        error = SDHC_ReceiveCommandResponse(base, command);
    }

    SDHC_ClearInterruptStatusFlags(base, ((uint32_t)kSDHC_CommandCompleteFlag | (uint32_t)kSDHC_CommandErrorFlag));

    return error;
}

static status_t SDHC_TransferByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data)
{
    status_t error = kStatus_Success;

    if (data->rxData != NULL)
    {
        error = SDHC_ReadByDataPortBlocking(base, data);
        if (error != kStatus_Success)
        {
            return error;
        }
    }
    else
    {
        error = SDHC_WriteByDataPortBlocking(base, data);
        if (error != kStatus_Success)
        {
            return error;
        }
    }

    return kStatus_Success;
}

static status_t SDHC_TransferByAdma2Blocking(SDHC_Type *base, sdhc_data_t *data)
{
    status_t error = kStatus_Success;

    /* Wait data complete or SDHC encounters error. */
    while (!(IS_SDHC_FLAG_SET(
        SDHC_GetInterruptStatusFlags(base),
        ((uint32_t)kSDHC_DataCompleteFlag | (uint32_t)kSDHC_DataErrorFlag | (uint32_t)kSDHC_DmaErrorFlag))))
    {
    }
    if (IS_SDHC_FLAG_SET(SDHC_GetInterruptStatusFlags(base),
                         ((uint32_t)kSDHC_DataErrorFlag | (uint32_t)kSDHC_DmaErrorFlag)))
    {
        if (!(data->enableIgnoreError))
        {
            error = kStatus_Fail;
        }
    }
    SDHC_ClearInterruptStatusFlags(base, ((uint32_t)kSDHC_DataCompleteFlag | (uint32_t)kSDHC_DmaCompleteFlag |
                                          (uint32_t)kSDHC_DataErrorFlag | (uint32_t)kSDHC_DmaErrorFlag));

    return error;
}

#if defined FSL_SDHC_ENABLE_ADMA1
#define SDHC_TransferByAdma1Blocking(base, data) SDHC_TransferByAdma2Blocking(base, data)
#endif /* FSL_SDHC_ENABLE_ADMA1 */

static status_t SDHC_TransferDataBlocking(sdhc_dma_mode_t dmaMode, SDHC_Type *base, sdhc_data_t *data)
{
    status_t error = kStatus_Success;

    if (dmaMode == kSDHC_DmaModeNo)
    {
        error = SDHC_TransferByDataPortBlocking(base, data);
        if (error != kStatus_Success)
        {
            return error;
        }
    }
#if defined FSL_SDHC_ENABLE_ADMA1
    else if (dmaMode == kSDHC_DmaModeAdma1)
    {
        error = SDHC_TransferByAdma1Blocking(base, data);
        if (error != kStatus_Success)
        {
            return error;
        }
    }
#endif /* FSL_SDHC_ENABLE_ADMA1 */

    /*kSDHC_DmaModeAdma2*/
    else
    {
        error = SDHC_TransferByAdma2Blocking(base, data);
        if (error != kStatus_Success)
        {
            return error;
        }
    }

    return kStatus_Success;
}

static void SDHC_TransferHandleCardDetect(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags)
{
    if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_CardInsertionFlag))
    {
        if (handle->callback.CardInserted != NULL)
        {
            handle->callback.CardInserted(base, handle->userData);
        }
    }
    else
    {
        if (handle->callback.CardRemoved != NULL)
        {
            handle->callback.CardRemoved(base, handle->userData);
        }
    }
}

static void SDHC_TransferHandleCommand(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags)
{
    assert(handle->command != NULL);

    if ((IS_SDHC_FLAG_SET(interruptFlags, kSDHC_CommandErrorFlag)) && (handle->data == NULL) &&
        (handle->callback.TransferComplete != NULL))
    {
        handle->callback.TransferComplete(base, handle, kStatus_SDHC_SendCommandFailed, handle->userData);
    }
    else
    {
        /* Receive response */
        if (kStatus_Success != SDHC_ReceiveCommandResponse(base, handle->command))
        {
            if (handle->callback.TransferComplete != NULL)
            {
                handle->callback.TransferComplete(base, handle, kStatus_SDHC_SendCommandFailed, handle->userData);
            }
        }
        else
        {
            if (handle->callback.TransferComplete != NULL)
            {
                handle->callback.TransferComplete(base, handle, kStatus_SDHC_TransferCommandComplete, handle->userData);
            }
        }
    }

    SDHC_DisableInterruptSignal(base, kSDHC_CommandFlag);
    handle->command = NULL;
}

static void SDHC_TransferHandleData(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags)
{
    assert(handle->data != NULL);

    status_t transferStatus   = kStatus_SDHC_BusyTransferring;
    uint32_t transferredWords = handle->transferredWords;

    if ((!(handle->data->enableIgnoreError)) &&
        (IS_SDHC_FLAG_SET(interruptFlags, ((uint32_t)kSDHC_DataErrorFlag | (uint32_t)kSDHC_DmaErrorFlag))))
    {
        transferStatus = kStatus_SDHC_TransferDataFailed;
    }
    else
    {
        if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_BufferReadReadyFlag))
        {
            handle->transferredWords = SDHC_ReadDataPort(base, handle->data, transferredWords);
        }
        else if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_BufferWriteReadyFlag))
        {
            handle->transferredWords = SDHC_WriteDataPort(base, handle->data, transferredWords);
        }
        else
        {
            /* Intentional empty */
        }

        if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_DataCompleteFlag))
        {
            transferStatus = kStatus_SDHC_TransferDataComplete;
        }
    }

    if ((transferStatus != kStatus_SDHC_BusyTransferring) && (handle->callback.TransferComplete != NULL))
    {
        handle->callback.TransferComplete(base, handle, transferStatus, handle->userData);
        SDHC_DisableInterruptSignal(base, (uint32_t)kSDHC_DataFlag | (uint32_t)kSDHC_DataDMAFlag);
        handle->data = NULL;
    }
}

static void SDHC_TransferHandleSdioInterrupt(SDHC_Type *base, sdhc_handle_t *handle)
{
    if (handle->callback.SdioInterrupt != NULL)
    {
        handle->callback.SdioInterrupt(base, handle->userData);
    }
}

static void SDHC_TransferHandleSdioBlockGap(SDHC_Type *base, sdhc_handle_t *handle)
{
    if (handle->callback.SdioBlockGap != NULL)
    {
        handle->callback.SdioBlockGap(base, handle->userData);
    }
}

/*!
 * brief SDHC module initialization function.
 *
 * Configures the SDHC according to the user configuration.
 *
 * Example:
   code
   sdhc_config_t config;
   config.cardDetectDat3 = false;
   config.endianMode = kSDHC_EndianModeLittle;
   config.dmaMode = kSDHC_DmaModeAdma2;
   config.readWatermarkLevel = 128U;
   config.writeWatermarkLevel = 128U;
   SDHC_Init(SDHC, &config);
   endcode
 *
 * param base SDHC peripheral base address.
 * param config SDHC configuration information.
 * retval kStatus_Success Operate successfully.
 */
void SDHC_Init(SDHC_Type *base, const sdhc_config_t *config)
{
    assert(config != NULL);
#if !defined FSL_SDHC_ENABLE_ADMA1
    assert(config->dmaMode != kSDHC_DmaModeAdma1);
#endif /* FSL_SDHC_ENABLE_ADMA1 */
    assert((config->writeWatermarkLevel >= 1UL) && (config->writeWatermarkLevel <= 128UL));
    assert((config->readWatermarkLevel >= 1UL) && (config->readWatermarkLevel <= 128UL));

    uint32_t proctl;
    uint32_t wml;

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Enable SDHC clock. */
    CLOCK_EnableClock(s_sdhcClock[SDHC_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */

    /* Reset SDHC. */
    base->SYSCTL |= SDHC_SYSCTL_RSTA_MASK | SDHC_SYSCTL_RSTC_MASK | SDHC_SYSCTL_RSTD_MASK;

    proctl = base->PROCTL;
    wml    = base->WML;

    proctl &= ~(SDHC_PROCTL_D3CD_MASK | SDHC_PROCTL_EMODE_MASK | SDHC_PROCTL_DMAS_MASK);
    /* Set DAT3 as card detection pin */
    if (config->cardDetectDat3)
    {
        proctl |= SDHC_PROCTL_D3CD_MASK;
    }
    /* Endian mode and DMA mode */
    proctl |= (SDHC_PROCTL_EMODE(config->endianMode) | SDHC_PROCTL_DMAS(config->dmaMode));

    /* Watermark level */
    wml &= ~(SDHC_WML_RDWML_MASK | SDHC_WML_WRWML_MASK);
    wml |= (SDHC_WML_RDWML(config->readWatermarkLevel) | SDHC_WML_WRWML(config->writeWatermarkLevel));

    base->WML    = wml;
    base->PROCTL = proctl;

    /* Disable all clock auto gated off feature because of DAT0 line logic(card buffer full status) can't be updated
    correctly when clock auto gated off is enabled. */
    base->SYSCTL |= (SDHC_SYSCTL_PEREN_MASK | SDHC_SYSCTL_HCKEN_MASK | SDHC_SYSCTL_IPGEN_MASK);

    /* Enable interrupt status but doesn't enable interrupt signal, clear all the interrupt status */
    base->IRQSTATEN = kSDHC_AllInterruptFlags;
    base->IRQSIGEN  = 0U;
    base->IRQSTAT   = kSDHC_AllInterruptFlags;
}

/*!
 * brief Deinitializes the SDHC.
 *
 * param base SDHC peripheral base address.
 */
void SDHC_Deinit(SDHC_Type *base)
{
#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Disable clock. */
    CLOCK_DisableClock(s_sdhcClock[SDHC_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
}

/*!
 * brief Resets the SDHC.
 *
 * param base SDHC peripheral base address.
 * param mask The reset type mask(_sdhc_reset).
 * param timeout Timeout for reset.
 * retval true Reset successfully.
 * retval false Reset failed.
 */
bool SDHC_Reset(SDHC_Type *base, uint32_t mask, uint32_t timeout)
{
    base->SYSCTL |= (mask & (SDHC_SYSCTL_RSTA_MASK | SDHC_SYSCTL_RSTC_MASK | SDHC_SYSCTL_RSTD_MASK));
    /* Delay some time to wait reset success. */
    while (IS_SDHC_FLAG_SET(base->SYSCTL, mask))
    {
        if (timeout == 0UL)
        {
            break;
        }
        timeout--;
    }

    return (timeout == 0UL ? false : true);
}

/*!
 * brief Gets the capability information.
 *
 * param base SDHC peripheral base address.
 * param capability Structure to save capability information.
 */
void SDHC_GetCapability(SDHC_Type *base, sdhc_capability_t *capability)
{
    assert(capability != NULL);

    uint32_t htCapability;
    uint32_t hostVer;
    uint32_t maxBlockLength;

    hostVer      = base->HOSTVER;
    htCapability = base->HTCAPBLT;

    /* Get the capability of SDHC. */
    capability->specVersion    = ((hostVer & SDHC_HOSTVER_SVN_MASK) >> SDHC_HOSTVER_SVN_SHIFT);
    capability->vendorVersion  = ((hostVer & SDHC_HOSTVER_VVN_MASK) >> SDHC_HOSTVER_VVN_SHIFT);
    maxBlockLength             = ((htCapability & SDHC_HTCAPBLT_MBL_MASK) >> SDHC_HTCAPBLT_MBL_SHIFT);
    capability->maxBlockLength = (512UL << maxBlockLength);
    /* Other attributes not in HTCAPBLT register. */
    capability->maxBlockCount = SDHC_MAX_BLOCK_COUNT;
    capability->flags = (htCapability & ((uint32_t)kSDHC_SupportAdmaFlag | (uint32_t)kSDHC_SupportHighSpeedFlag |
                                         (uint32_t)kSDHC_SupportDmaFlag | (uint32_t)kSDHC_SupportSuspendResumeFlag |
                                         (uint32_t)kSDHC_SupportV330Flag));
#if defined FSL_FEATURE_SDHC_HAS_V300_SUPPORT && FSL_FEATURE_SDHC_HAS_V300_SUPPORT
    capability->flags |= (htCapability & (uint32_t)kSDHC_SupportV300Flag);
#endif
#if defined FSL_FEATURE_SDHC_HAS_V180_SUPPORT && FSL_FEATURE_SDHC_HAS_V180_SUPPORT
    capability->flags |= (htCapability & (uint32_t)kSDHC_SupportV180Flag);
#endif
    /* eSDHC on all kinetis boards will support 4/8 bit data bus width. */
    capability->flags |= ((uint32_t)kSDHC_Support4BitFlag | (uint32_t)kSDHC_Support8BitFlag);
}

/*!
 * brief Sets the SD bus clock frequency.
 *
 * param base SDHC peripheral base address.
 * param srcClock_Hz SDHC source clock frequency united in Hz.
 * param busClock_Hz SD bus clock frequency united in Hz.
 *
 * return The nearest frequency of busClock_Hz configured to SD bus.
 */
uint32_t SDHC_SetSdClock(SDHC_Type *base, uint32_t srcClock_Hz, uint32_t busClock_Hz)
{
    assert(srcClock_Hz != 0U);
    assert((busClock_Hz != 0U) && (busClock_Hz <= srcClock_Hz));

    uint32_t totalDiv         = 0U;
    uint32_t divisor          = 0U;
    uint32_t prescaler        = 0U;
    uint32_t sysctl           = 0U;
    uint32_t nearestFrequency = 0U;

    /* calucate total divisor first */
    totalDiv = srcClock_Hz / busClock_Hz;

    if (totalDiv > (SDHC_MAX_CLKFS * SDHC_MAX_DVS))
    {
        return 0UL;
    }

    if (totalDiv != 0U)
    {
        /* calucate the divisor (srcClock_Hz / divisor) <= busClock_Hz */
        if ((srcClock_Hz / totalDiv) > busClock_Hz)
        {
            totalDiv++;
        }

        /* divide the total divisor to div and prescaler */
        if (totalDiv > SDHC_MAX_DVS)
        {
            prescaler = totalDiv / SDHC_MAX_DVS;
            /* prescaler must be a value which equal 2^n and smaller than SDHC_MAX_CLKFS */
            while (((SDHC_MAX_CLKFS % prescaler) != 0U) || (prescaler == 1U))
            {
                prescaler++;
            }
            /* calucate the divisor */
            divisor = totalDiv / prescaler;
            /* fine tuning the divisor until divisor * prescaler >= totalDiv */
            while ((divisor * prescaler) < totalDiv)
            {
                divisor++;
            }

            nearestFrequency = srcClock_Hz / (divisor == 0U ? 1U : divisor) / prescaler;
        }
        else
        {
            divisor          = totalDiv;
            prescaler        = 0U;
            nearestFrequency = srcClock_Hz / divisor;
        }
    }
    /* in this condition , srcClock_Hz = busClock_Hz, */
    else
    {
        /* total divider = 1U */
        divisor          = 0U;
        prescaler        = 0U;
        nearestFrequency = srcClock_Hz;
    }

    /* calucate the value write to register */
    if (divisor != 0U)
    {
        SDHC_PREV_DVS(divisor);
    }
    /* calucate the value write to register */
    if (prescaler != 0U)
    {
        SDHC_PREV_CLKFS(prescaler);
    }

    /* Disable SD clock. It should be disabled before changing the SD clock frequency.*/
    base->SYSCTL &= ~SDHC_SYSCTL_SDCLKEN_MASK;

    /* Set the SD clock frequency divisor, SD clock frequency select, data timeout counter value. */
    sysctl = base->SYSCTL;
    sysctl &= ~(SDHC_SYSCTL_DVS_MASK | SDHC_SYSCTL_SDCLKFS_MASK | SDHC_SYSCTL_DTOCV_MASK);
    sysctl |= (SDHC_SYSCTL_DVS(divisor) | SDHC_SYSCTL_SDCLKFS(prescaler) | SDHC_SYSCTL_DTOCV(0xEU));
    base->SYSCTL = sysctl;

    /* Wait until the SD clock is stable. */
    while (!(IS_SDHC_FLAG_SET(base->PRSSTAT, SDHC_PRSSTAT_SDSTB_MASK)))
    {
    }
    /* Enable the SD clock. */
    base->SYSCTL |= SDHC_SYSCTL_SDCLKEN_MASK;

    return nearestFrequency;
}

/*!
 * brief Sends 80 clocks to the card to set it to the active state.
 *
 * This function must be called each time the card is inserted to ensure that the card can receive the command
 * correctly.
 *
 * param base SDHC peripheral base address.
 * param timeout Timeout to initialize card.
 * retval true Set card active successfully.
 * retval false Set card active failed.
 */
bool SDHC_SetCardActive(SDHC_Type *base, uint32_t timeout)
{
    base->SYSCTL |= SDHC_SYSCTL_INITA_MASK;
    /* Delay some time to wait card become active state. */
    while (IS_SDHC_FLAG_SET(base->SYSCTL, SDHC_SYSCTL_INITA_MASK))
    {
        if (timeout == 0UL)
        {
            break;
        }
        timeout--;
    }

    return (timeout == 0UL ? false : true);
}

/*!
 * brief Sets the card transfer-related configuration.
 *
 * This function fills the card transfer-related command argument/transfer flag/data size. The command and data are sent
 by
 * SDHC after calling this function.
 *
 * Example:
   code
   sdhc_transfer_config_t transferConfig;
   transferConfig.dataBlockSize = 512U;
   transferConfig.dataBlockCount = 2U;
   transferConfig.commandArgument = 0x01AAU;
   transferConfig.commandIndex = 8U;
   transferConfig.flags |= (kSDHC_EnableDmaFlag | kSDHC_EnableAutoCommand12Flag | kSDHC_MultipleBlockFlag);
   SDHC_SetTransferConfig(SDHC, &transferConfig);
   endcode
 *
 * param base SDHC peripheral base address.
 * param config Command configuration structure.
 */
void SDHC_SetTransferConfig(SDHC_Type *base, const sdhc_transfer_config_t *config)
{
    assert(config != NULL);
    assert(config->dataBlockSize <= (SDHC_BLKATTR_BLKSIZE_MASK >> SDHC_BLKATTR_BLKSIZE_SHIFT));
    assert(config->dataBlockCount <= (SDHC_BLKATTR_BLKCNT_MASK >> SDHC_BLKATTR_BLKCNT_SHIFT));

    base->BLKATTR = ((base->BLKATTR & ~(SDHC_BLKATTR_BLKSIZE_MASK | SDHC_BLKATTR_BLKCNT_MASK)) |
                     (SDHC_BLKATTR_BLKSIZE(config->dataBlockSize) | SDHC_BLKATTR_BLKCNT(config->dataBlockCount)));
    base->CMDARG  = config->commandArgument;
    base->XFERTYP = (((config->commandIndex << SDHC_XFERTYP_CMDINX_SHIFT) & SDHC_XFERTYP_CMDINX_MASK) |
                     (config->flags & (SDHC_XFERTYP_DMAEN_MASK | SDHC_XFERTYP_MSBSEL_MASK | SDHC_XFERTYP_DPSEL_MASK |
                                       SDHC_XFERTYP_CMDTYP_MASK | SDHC_XFERTYP_BCEN_MASK | SDHC_XFERTYP_CICEN_MASK |
                                       SDHC_XFERTYP_CCCEN_MASK | SDHC_XFERTYP_RSPTYP_MASK | SDHC_XFERTYP_DTDSEL_MASK |
                                       SDHC_XFERTYP_AC12EN_MASK)));
}

/*!
 * brief Enables or disables the SDIO card control.
 *
 * param base SDHC peripheral base address.
 * param mask SDIO card control flags mask(_sdhc_sdio_control_flag).
 * param enable True to enable, false to disable.
 */
void SDHC_EnableSdioControl(SDHC_Type *base, uint32_t mask, bool enable)
{
    uint32_t proctl = base->PROCTL;
    uint32_t vendor = base->VENDOR;

    if (enable)
    {
        if (IS_SDHC_FLAG_SET(mask, kSDHC_StopAtBlockGapFlag))
        {
            proctl |= SDHC_PROCTL_SABGREQ_MASK;
        }
        if (IS_SDHC_FLAG_SET(mask, kSDHC_ReadWaitControlFlag))
        {
            proctl |= SDHC_PROCTL_RWCTL_MASK;
        }
        if (IS_SDHC_FLAG_SET(mask, kSDHC_InterruptAtBlockGapFlag))
        {
            proctl |= SDHC_PROCTL_IABG_MASK;
        }
        if (IS_SDHC_FLAG_SET(mask, kSDHC_ExactBlockNumberReadFlag))
        {
            vendor |= SDHC_VENDOR_EXBLKNU_MASK;
        }
    }
    else
    {
        if (IS_SDHC_FLAG_SET(mask, kSDHC_StopAtBlockGapFlag))
        {
            proctl &= ~SDHC_PROCTL_SABGREQ_MASK;
        }
        if (IS_SDHC_FLAG_SET(mask, kSDHC_ReadWaitControlFlag))
        {
            proctl &= ~SDHC_PROCTL_RWCTL_MASK;
        }
        if (IS_SDHC_FLAG_SET(mask, kSDHC_InterruptAtBlockGapFlag))
        {
            proctl &= ~SDHC_PROCTL_IABG_MASK;
        }
        if (IS_SDHC_FLAG_SET(mask, kSDHC_ExactBlockNumberReadFlag))
        {
            vendor &= ~SDHC_VENDOR_EXBLKNU_MASK;
        }
    }

    base->PROCTL = proctl;
    base->VENDOR = vendor;
}

/*!
 * brief Configures the MMC boot feature.
 *
 * Example:
   code
   sdhc_boot_config_t config;
   config.ackTimeoutCount = 4;
   config.bootMode = kSDHC_BootModeNormal;
   config.blockCount = 5;
   config.enableBootAck = true;
   config.enableBoot = true;
   config.enableAutoStopAtBlockGap = true;
   SDHC_SetMmcBootConfig(SDHC, &config);
   endcode
 *
 * param base SDHC peripheral base address.
 * param config The MMC boot configuration information.
 */
void SDHC_SetMmcBootConfig(SDHC_Type *base, const sdhc_boot_config_t *config)
{
    assert(config != NULL);
    assert(config->ackTimeoutCount <= (SDHC_MMCBOOT_DTOCVACK_MASK >> SDHC_MMCBOOT_DTOCVACK_SHIFT));
    assert(config->blockCount <= (SDHC_MMCBOOT_BOOTBLKCNT_MASK >> SDHC_MMCBOOT_BOOTBLKCNT_SHIFT));

    uint32_t mmcboot = 0U;

    mmcboot = (SDHC_MMCBOOT_DTOCVACK(config->ackTimeoutCount) | SDHC_MMCBOOT_BOOTMODE(config->bootMode) |
               SDHC_MMCBOOT_BOOTBLKCNT(config->blockCount));
    if (config->enableBootAck)
    {
        mmcboot |= SDHC_MMCBOOT_BOOTACK_MASK;
    }
    if (config->enableBoot)
    {
        mmcboot |= SDHC_MMCBOOT_BOOTEN_MASK;
    }
    if (config->enableAutoStopAtBlockGap)
    {
        mmcboot |= SDHC_MMCBOOT_AUTOSABGEN_MASK;
    }
    base->MMCBOOT = mmcboot;
}

/*!
 * brief Sets the ADMA descriptor table configuration.
 *
 * param base SDHC peripheral base address.
 * param dmaMode DMA mode.
 * param table ADMA table address.
 * param tableWords ADMA table buffer length united as Words.
 * param data Data buffer address.
 * param dataBytes Data length united as bytes.
 * retval kStatus_OutOfRange ADMA descriptor table length isn't enough to describe data.
 * retval kStatus_Success Operate successfully.
 */
status_t SDHC_SetAdmaTableConfig(SDHC_Type *base,
                                 sdhc_dma_mode_t dmaMode,
                                 uint32_t *table,
                                 uint32_t tableWords,
                                 const uint32_t *data,
                                 uint32_t dataBytes)
{
    status_t error               = kStatus_Success;
    const uint32_t *startAddress = data;
    uint32_t entries;
    uint32_t i;
#if defined FSL_SDHC_ENABLE_ADMA1
    sdhc_adma1_descriptor_t *adma1EntryAddress;
#endif
    sdhc_adma2_descriptor_t *adma2EntryAddress;

    if ((((table == NULL) || (tableWords == 0UL)) &&
         ((dmaMode == kSDHC_DmaModeAdma1) || (dmaMode == kSDHC_DmaModeAdma2))) ||
        (data == NULL) || (dataBytes == 0UL)
#if !defined FSL_SDHC_ENABLE_ADMA1
        || (dmaMode == kSDHC_DmaModeAdma1)
#endif
    )
    {
        error = kStatus_InvalidArgument;
    }
    else if (((dmaMode == kSDHC_DmaModeAdma2) && (((uint32_t)startAddress % SDHC_ADMA2_LENGTH_ALIGN) != 0UL))
#if defined FSL_SDHC_ENABLE_ADMA1
             || ((dmaMode == kSDHC_DmaModeAdma1) && (((uint32_t)startAddress % SDHC_ADMA1_LENGTH_ALIGN) != 0UL))
#endif
    )
    {
        error = kStatus_SDHC_DMADataBufferAddrNotAlign;
    }
    else
    {
#if defined FSL_SDHC_ENABLE_ADMA1
        if (dmaMode == kSDHC_DmaModeAdma1)
        {
            /*
             * Add non aligned access support ,user need make sure your buffer size is big
             * enough to hold the data,in other words,user need make sure the buffer size
             * is 4 byte aligned
             */
            if (dataBytes % sizeof(uint32_t) != 0U)
            {
                dataBytes +=
                    sizeof(uint32_t) - (dataBytes % sizeof(uint32_t)); /* make the data length as word-aligned */
            }

            /* Check if ADMA descriptor's number is enough. */
            entries = ((dataBytes / SDHC_ADMA1_DESCRIPTOR_MAX_LENGTH_PER_ENTRY) + 1U);
            /* ADMA1 needs two descriptors to finish a transfer */
            entries <<= 1U;
            if (entries > ((tableWords * sizeof(uint32_t)) / sizeof(sdhc_adma1_descriptor_t)))
            {
                error = kStatus_OutOfRange;
            }
            else
            {
                adma1EntryAddress = (sdhc_adma1_descriptor_t *)(uint32_t)(table);
                for (i = 0U; i < entries; i += 2U)
                {
                    /* Each descriptor for ADMA1 is 32-bit in length */
                    if ((dataBytes - ((uint32_t)startAddress - (uint32_t)data)) <=
                        SDHC_ADMA1_DESCRIPTOR_MAX_LENGTH_PER_ENTRY)
                    {
                        /* The last piece of data, setting end flag in descriptor */
                        adma1EntryAddress[i] = ((uint32_t)(dataBytes - ((uint32_t)startAddress - (uint32_t)data))
                                                << SDHC_ADMA1_DESCRIPTOR_LENGTH_SHIFT);
                        adma1EntryAddress[i] |= (uint32_t)kSDHC_Adma1DescriptorTypeSetLength;
                        adma1EntryAddress[i + 1U] = (uint32_t)(startAddress);
                        adma1EntryAddress[i + 1U] |=
                            ((uint32_t)kSDHC_Adma1DescriptorTypeTransfer | (uint32_t)kSDHC_Adma1DescriptorEndFlag);
                    }
                    else
                    {
                        adma1EntryAddress[i] = ((uint32_t)SDHC_ADMA1_DESCRIPTOR_MAX_LENGTH_PER_ENTRY
                                                << SDHC_ADMA1_DESCRIPTOR_LENGTH_SHIFT);
                        adma1EntryAddress[i] |= (uint32_t)kSDHC_Adma1DescriptorTypeSetLength;
                        adma1EntryAddress[i + 1U] = ((uint32_t)(startAddress));
                        adma1EntryAddress[i + 1U] |= (uint32_t)kSDHC_Adma1DescriptorTypeTransfer;
                        startAddress += SDHC_ADMA1_DESCRIPTOR_MAX_LENGTH_PER_ENTRY / sizeof(uint32_t);
                    }
                }

                /* When use ADMA, disable simple DMA */
                base->DSADDR  = 0U;
                base->ADSADDR = (uint32_t)table;
            }
        }
        else
#endif /* FSL_SDHC_ENABLE_ADMA1 */
            if (dmaMode == kSDHC_DmaModeAdma2)
        {
            /*
             * Add non aligned access support ,user need make sure your buffer size is big
             * enough to hold the data,in other words,user need make sure the buffer size
             * is 4 byte aligned
             */
            if (dataBytes % sizeof(uint32_t) != 0U)
            {
                dataBytes +=
                    sizeof(uint32_t) - (dataBytes % sizeof(uint32_t)); /* make the data length as word-aligned */
            }

            /* Check if ADMA descriptor's number is enough. */
            entries = ((dataBytes / SDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY) + 1U);
            if (entries > ((tableWords * sizeof(uint32_t)) / sizeof(sdhc_adma2_descriptor_t)))
            {
                error = kStatus_OutOfRange;
            }
            else
            {
                adma2EntryAddress = (sdhc_adma2_descriptor_t *)(uint32_t)(table);
                for (i = 0U; i < entries; i++)
                {
                    /* Each descriptor for ADMA2 is 64-bit in length */
                    if ((dataBytes - ((uint32_t)startAddress - (uint32_t)data)) <=
                        SDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY)
                    {
                        /* The last piece of data, setting end flag in descriptor */
                        adma2EntryAddress[i].address   = startAddress;
                        adma2EntryAddress[i].attribute = ((dataBytes - ((uint32_t)startAddress - (uint32_t)data))
                                                          << SDHC_ADMA2_DESCRIPTOR_LENGTH_SHIFT);
                        adma2EntryAddress[i].attribute |=
                            ((uint32_t)kSDHC_Adma2DescriptorTypeTransfer | (uint32_t)kSDHC_Adma2DescriptorEndFlag);
                    }
                    else
                    {
                        adma2EntryAddress[i].address = startAddress;
                        adma2EntryAddress[i].attribute =
                            (((SDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY / sizeof(uint32_t)) * sizeof(uint32_t))
                             << SDHC_ADMA2_DESCRIPTOR_LENGTH_SHIFT);
                        adma2EntryAddress[i].attribute |= (uint32_t)kSDHC_Adma2DescriptorTypeTransfer;
                        startAddress += (SDHC_ADMA2_DESCRIPTOR_MAX_LENGTH_PER_ENTRY / sizeof(uint32_t));
                    }
                }

                /* When use ADMA, disable simple DMA */
                base->DSADDR  = 0U;
                base->ADSADDR = (uint32_t)table;
            }
        }
        else
        {
            /* Intentional empty */
        }
    }

    return error;
}

/*!
 * brief Transfers the command/data using a blocking method.
 *
 * This function waits until the command response/data is received or the SDHC encounters an error by polling the status
 * flag.
 * This function support non word align data addr transfer support, if data buffer addr is not align in DMA mode,
 * the API will continue finish the transfer by polling IO directly
 * The application must not call this API in multiple threads at the same time. Because of that this API doesn't support
 * the re-entry mechanism.
 *
 * note There is no need to call the API 'SDHC_TransferCreateHandle' when calling this API.
 *
 * param base SDHC peripheral base address.
 * param admaTable ADMA table address, can't be null if transfer way is ADMA1/ADMA2.
 * param admaTableWords ADMA table length united as words, can't be 0 if transfer way is ADMA1/ADMA2.
 * param transfer Transfer content.
 * retval kStatus_InvalidArgument Argument is invalid.
 * retval kStatus_SDHC_PrepareAdmaDescriptorFailed Prepare ADMA descriptor failed.
 * retval kStatus_SDHC_SendCommandFailed Send command failed.
 * retval kStatus_SDHC_TransferDataFailed Transfer data failed.
 * retval kStatus_Success Operate successfully.
 */
status_t SDHC_TransferBlocking(SDHC_Type *base, uint32_t *admaTable, uint32_t admaTableWords, sdhc_transfer_t *transfer)
{
    assert(transfer != NULL);

    status_t error = kStatus_Success;
    sdhc_dma_mode_t dmaMode =
        (sdhc_dma_mode_t)(uint8_t)((base->PROCTL & SDHC_PROCTL_DMAS_MASK) >> SDHC_PROCTL_DMAS_SHIFT);
    sdhc_command_t *command = transfer->command;
    sdhc_data_t *data       = transfer->data;

    /* make sure the cmd/block count is valid */
    if ((command == NULL) || ((data != NULL) && (data->blockCount > SDHC_MAX_BLOCK_COUNT)))
    {
        return kStatus_InvalidArgument;
    }

    /* Wait until command/data bus out of busy status. */
    while (IS_SDHC_FLAG_SET(SDHC_GetPresentStatusFlags(base), kSDHC_CommandInhibitFlag))
    {
    }
    while ((data != NULL) && (IS_SDHC_FLAG_SET(SDHC_GetPresentStatusFlags(base), kSDHC_DataInhibitFlag)))
    {
    }

    /* Update ADMA descriptor table according to different DMA mode(no DMA, ADMA1, ADMA2).*/
    if ((data != NULL) && (NULL != admaTable))
    {
        error = SDHC_SetAdmaTableConfig(base, (sdhc_dma_mode_t)dmaMode, admaTable, admaTableWords,
                                        (data->rxData != NULL ? data->rxData : data->txData),
                                        (data->blockCount * data->blockSize));
        /* in this situation , we disable the DMA instead of polling transfer mode */
        if (error != kStatus_Success)
        {
            dmaMode = (sdhc_dma_mode_t)kSDHC_DmaModeNo;
        }
    }

    /* Send command and receive data. */
    SDHC_StartTransfer(base, command, data, (sdhc_dma_mode_t)dmaMode);

    error = SDHC_SendCommandBlocking(base, command);
    if (kStatus_Success != error)
    {
        return error;
    }

    if (data != NULL)
    {
        error = SDHC_TransferDataBlocking((sdhc_dma_mode_t)dmaMode, base, data);
        if (kStatus_Success != error)
        {
            return error;
        }
    }

    return kStatus_Success;
}

/*!
 * brief Creates the SDHC handle.
 *
 * param base SDHC peripheral base address.
 * param handle SDHC handle pointer.
 * param callback Structure pointer to contain all callback functions.
 * param userData Callback function parameter.
 */
void SDHC_TransferCreateHandle(SDHC_Type *base,
                               sdhc_handle_t *handle,
                               const sdhc_transfer_callback_t *callback,
                               void *userData)
{
    assert(handle != NULL);
    assert(callback != NULL);

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

    /* Set the callback. */
    handle->callback.CardInserted     = callback->CardInserted;
    handle->callback.CardRemoved      = callback->CardRemoved;
    handle->callback.SdioInterrupt    = callback->SdioInterrupt;
    handle->callback.SdioBlockGap     = callback->SdioBlockGap;
    handle->callback.TransferComplete = callback->TransferComplete;
    handle->userData                  = userData;

    /* Save the handle in global variables to support the double weak mechanism. */
    s_sdhcHandle[SDHC_GetInstance(base)] = handle;

    /* save IRQ handler */
    s_sdhcIsr = SDHC_TransferHandleIRQ;

    (void)EnableIRQ(s_sdhcIRQ[SDHC_GetInstance(base)]);
}

/*!
 * brief Transfers the command/data using an interrupt and an asynchronous method.
 *
 * This function sends a command and data and returns immediately. It doesn't wait the transfer complete or encounter an
 * error.
 * This function support non word align data addr transfer support, if data buffer addr is not align in DMA mode,
 * the API will continue finish the transfer by polling IO directly
 * The application must not call this API in multiple threads at the same time. Because of that this API doesn't support
 * the re-entry mechanism.
 *
 * note Call the API 'SDHC_TransferCreateHandle' when calling this API.
 *
 * param base SDHC peripheral base address.
 * param handle SDHC handle.
 * param admaTable ADMA table address, can't be null if transfer way is ADMA1/ADMA2.
 * param admaTableWords ADMA table length united as words, can't be 0 if transfer way is ADMA1/ADMA2.
 * param transfer Transfer content.
 * retval kStatus_InvalidArgument Argument is invalid.
 * retval kStatus_SDHC_BusyTransferring Busy transferring.
 * retval kStatus_SDHC_PrepareAdmaDescriptorFailed Prepare ADMA descriptor failed.
 * retval kStatus_Success Operate successfully.
 */
status_t SDHC_TransferNonBlocking(
    SDHC_Type *base, sdhc_handle_t *handle, uint32_t *admaTable, uint32_t admaTableWords, sdhc_transfer_t *transfer)
{
    assert(transfer != NULL);

    sdhc_dma_mode_t dmaMode =
        (sdhc_dma_mode_t)(uint8_t)((base->PROCTL & SDHC_PROCTL_DMAS_MASK) >> SDHC_PROCTL_DMAS_SHIFT);
    status_t error          = kStatus_Success;
    sdhc_command_t *command = transfer->command;
    sdhc_data_t *data       = transfer->data;

    /* make sure cmd/block count is valid */
    if ((command == NULL) || ((data != NULL) && (data->blockCount > SDHC_MAX_BLOCK_COUNT)))
    {
        return kStatus_InvalidArgument;
    }

    /* Wait until command/data bus out of busy status. */
    if ((IS_SDHC_FLAG_SET(SDHC_GetPresentStatusFlags(base), kSDHC_CommandInhibitFlag)) ||
        ((data != NULL) && (IS_SDHC_FLAG_SET(SDHC_GetPresentStatusFlags(base), kSDHC_DataInhibitFlag))))
    {
        return kStatus_SDHC_BusyTransferring;
    }

    /* Update ADMA descriptor table according to different DMA mode(no DMA, ADMA1, ADMA2).*/
    if ((data != NULL) && (NULL != admaTable))
    {
        error = SDHC_SetAdmaTableConfig(base, dmaMode, admaTable, admaTableWords,
                                        (data->rxData != NULL ? data->rxData : data->txData),
                                        (data->blockCount * data->blockSize));
        /* in this situation , we disable the DMA instead of polling transfer mode */
        if (error != kStatus_Success)
        {
            /* change to polling mode */
            dmaMode = kSDHC_DmaModeNo;
        }
    }

    /* Save command and data into handle before transferring. */
    handle->command = command;
    handle->data    = data;
    /* transferredWords will only be updated in ISR when transfer way is DATAPORT. */
    handle->transferredWords = 0U;

    /* enable interrupt per transfer request */
    if (handle->data != NULL)
    {
        SDHC_ClearInterruptStatusFlags(
            base,
            (uint32_t)(dmaMode == kSDHC_DmaModeNo ? kSDHC_DataFlag : kSDHC_DataDMAFlag) | (uint32_t)kSDHC_CommandFlag);
        SDHC_EnableInterruptSignal(base, (uint32_t)(dmaMode == kSDHC_DmaModeNo ? kSDHC_DataFlag : kSDHC_DataDMAFlag) |
                                             (uint32_t)kSDHC_CommandFlag);
    }
    else
    {
        SDHC_ClearInterruptStatusFlags(base, kSDHC_CommandFlag);
        SDHC_EnableInterruptSignal(base, kSDHC_CommandFlag);
    }

    SDHC_StartTransfer(base, command, data, dmaMode);

    return kStatus_Success;
}

/*!
 * brief IRQ handler for the SDHC.
 *
 * This function deals with the IRQs on the given host controller.
 *
 * param base SDHC peripheral base address.
 * param handle SDHC handle.
 */
void SDHC_TransferHandleIRQ(SDHC_Type *base, sdhc_handle_t *handle)
{
    assert(handle != NULL);

    uint32_t interruptFlags;

    interruptFlags = SDHC_GetEnabledInterruptStatusFlags(base);

    if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_CardDetectFlag))
    {
        SDHC_TransferHandleCardDetect(base, handle, interruptFlags);
    }
    if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_CommandFlag))
    {
        SDHC_TransferHandleCommand(base, handle, interruptFlags);
    }
    if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_DataFlag))
    {
        SDHC_TransferHandleData(base, handle, interruptFlags);
    }
    if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_CardInterruptFlag))
    {
        SDHC_TransferHandleSdioInterrupt(base, handle);
    }
    if (IS_SDHC_FLAG_SET(interruptFlags, kSDHC_BlockGapEventFlag))
    {
        SDHC_TransferHandleSdioBlockGap(base, handle);
    }

    SDHC_ClearInterruptStatusFlags(base, interruptFlags);
}

#if defined(SDHC)
void SDHC_DriverIRQHandler(void);
void SDHC_DriverIRQHandler(void)
{
    assert(s_sdhcHandle[0] != NULL);

    s_sdhcIsr(SDHC, s_sdhcHandle[0]);
    SDK_ISR_EXIT_BARRIER;
}
#endif
