1417 lines
49 KiB
C
1417 lines
49 KiB
C
/*
|
|
* Copyright (c) 2016, Freescale Semiconductor, Inc.
|
|
* Copyright 2016-2017 NXP
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* o Redistributions of source code must retain the above copyright notice, this list
|
|
* of conditions and the following disclaimer.
|
|
*
|
|
* o Redistributions in binary form must reproduce the above copyright notice, this
|
|
* list of conditions and the following disclaimer in the documentation and/or
|
|
* other materials provided with the distribution.
|
|
*
|
|
* o Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "fsl_sdhc.h"
|
|
|
|
/*******************************************************************************
|
|
* Definitions
|
|
******************************************************************************/
|
|
/*! @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;
|
|
|
|
/*******************************************************************************
|
|
* Prototypes
|
|
******************************************************************************/
|
|
/*!
|
|
* @brief Get the instance.
|
|
*
|
|
* @param base SDHC peripheral base address.
|
|
* @return Instance number.
|
|
*/
|
|
static uint32_t SDHC_GetInstance(SDHC_Type *base);
|
|
|
|
/*!
|
|
* @brief Set transfer interrupt.
|
|
*
|
|
* @param base SDHC peripheral base address.
|
|
* @param usingInterruptSignal True to use IRQ signal.
|
|
*/
|
|
static void SDHC_SetTransferInterrupt(SDHC_Type *base, bool usingInterruptSignal);
|
|
|
|
/*!
|
|
* @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 handle SDHC handle.
|
|
* @param interruptFlags Card detect related interrupt flags.
|
|
*/
|
|
static void SDHC_TransferHandleCardDetect(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 handle SDHC handle.
|
|
*/
|
|
static void SDHC_TransferHandleSdioInterrupt(sdhc_handle_t *handle);
|
|
|
|
/*!
|
|
* @brief Handle SDIO block gap event.
|
|
*
|
|
* @param handle SDHC handle.
|
|
*/
|
|
static void SDHC_TransferHandleSdioBlockGap(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. */
|
|
static sdhc_isr_t s_sdhcIsr;
|
|
|
|
/*******************************************************************************
|
|
* 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_SetTransferInterrupt(SDHC_Type *base, bool usingInterruptSignal)
|
|
{
|
|
uint32_t interruptEnabled; /* The Interrupt status flags to be enabled */
|
|
bool cardDetectDat3 = (bool)(base->PROCTL & SDHC_PROCTL_D3CD_MASK);
|
|
|
|
/* Disable all interrupts */
|
|
SDHC_DisableInterruptStatus(base, (uint32_t)kSDHC_AllInterruptFlags);
|
|
SDHC_DisableInterruptSignal(base, (uint32_t)kSDHC_AllInterruptFlags);
|
|
DisableIRQ(s_sdhcIRQ[SDHC_GetInstance(base)]);
|
|
|
|
interruptEnabled =
|
|
(kSDHC_CommandIndexErrorFlag | kSDHC_CommandCrcErrorFlag | kSDHC_CommandEndBitErrorFlag |
|
|
kSDHC_CommandTimeoutFlag | kSDHC_CommandCompleteFlag | kSDHC_DataTimeoutFlag | kSDHC_DataCrcErrorFlag |
|
|
kSDHC_DataEndBitErrorFlag | kSDHC_DataCompleteFlag | kSDHC_AutoCommand12ErrorFlag | kSDHC_BufferReadReadyFlag |
|
|
kSDHC_BufferWriteReadyFlag | kSDHC_DmaErrorFlag | kSDHC_DmaCompleteFlag);
|
|
if (cardDetectDat3)
|
|
{
|
|
interruptEnabled |= (kSDHC_CardInsertionFlag | kSDHC_CardRemovalFlag);
|
|
}
|
|
|
|
SDHC_EnableInterruptStatus(base, interruptEnabled);
|
|
if (usingInterruptSignal)
|
|
{
|
|
SDHC_EnableInterruptSignal(base, interruptEnabled);
|
|
}
|
|
}
|
|
|
|
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. */
|
|
switch (command->responseType)
|
|
{
|
|
case kCARD_ResponseTypeNone:
|
|
break;
|
|
case kCARD_ResponseTypeR1: /* Response 1 */
|
|
flags |= (kSDHC_ResponseLength48Flag | kSDHC_EnableCrcCheckFlag | kSDHC_EnableIndexCheckFlag);
|
|
break;
|
|
case kCARD_ResponseTypeR1b: /* Response 1 with busy */
|
|
flags |= (kSDHC_ResponseLength48BusyFlag | kSDHC_EnableCrcCheckFlag | kSDHC_EnableIndexCheckFlag);
|
|
break;
|
|
case kCARD_ResponseTypeR2: /* Response 2 */
|
|
flags |= (kSDHC_ResponseLength136Flag | kSDHC_EnableCrcCheckFlag);
|
|
break;
|
|
case kCARD_ResponseTypeR3: /* Response 3 */
|
|
flags |= (kSDHC_ResponseLength48Flag);
|
|
break;
|
|
case kCARD_ResponseTypeR4: /* Response 4 */
|
|
flags |= (kSDHC_ResponseLength48Flag);
|
|
break;
|
|
case kCARD_ResponseTypeR5: /* Response 5 */
|
|
flags |= (kSDHC_ResponseLength48Flag | kSDHC_EnableCrcCheckFlag | kSDHC_EnableIndexCheckFlag);
|
|
break;
|
|
case kCARD_ResponseTypeR5b: /* Response 5 with busy */
|
|
flags |= (kSDHC_ResponseLength48BusyFlag | kSDHC_EnableCrcCheckFlag | kSDHC_EnableIndexCheckFlag);
|
|
break;
|
|
case kCARD_ResponseTypeR6: /* Response 6 */
|
|
flags |= (kSDHC_ResponseLength48Flag | kSDHC_EnableCrcCheckFlag | kSDHC_EnableIndexCheckFlag);
|
|
break;
|
|
case kCARD_ResponseTypeR7: /* Response 7 */
|
|
flags |= (kSDHC_ResponseLength48Flag | kSDHC_EnableCrcCheckFlag | kSDHC_EnableIndexCheckFlag);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (command->type == kCARD_CommandTypeAbort)
|
|
{
|
|
flags |= kSDHC_CommandTypeAbortFlag;
|
|
}
|
|
|
|
if (data)
|
|
{
|
|
flags |= kSDHC_DataPresentFlag;
|
|
|
|
if (dmaMode != kSDHC_DmaModeNo)
|
|
{
|
|
flags |= kSDHC_EnableDmaFlag;
|
|
}
|
|
if (data->rxData)
|
|
{
|
|
flags |= kSDHC_DataReadFlag;
|
|
}
|
|
if (data->blockCount > 1U)
|
|
{
|
|
flags |= (kSDHC_MultipleBlockFlag | kSDHC_EnableBlockCountFlag);
|
|
if (data->enableAutoCommand12)
|
|
{
|
|
/* Enable Auto command 12. */
|
|
flags |= 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)
|
|
{
|
|
uint32_t i;
|
|
|
|
if (command->responseType != kCARD_ResponseTypeNone)
|
|
{
|
|
command->response[0U] = SDHC_GetCommandResponse(base, 0U);
|
|
if (command->responseType == kCARD_ResponseTypeR2)
|
|
{
|
|
command->response[1U] = SDHC_GetCommandResponse(base, 1U);
|
|
command->response[2U] = SDHC_GetCommandResponse(base, 2U);
|
|
command->response[3U] = SDHC_GetCommandResponse(base, 3U);
|
|
|
|
i = 4U;
|
|
/* 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. */
|
|
do
|
|
{
|
|
command->response[i - 1U] <<= 8U;
|
|
if (i > 1U)
|
|
{
|
|
command->response[i - 1U] |= ((command->response[i - 2U] & 0xFF000000U) >> 24U);
|
|
}
|
|
} while (i--);
|
|
}
|
|
}
|
|
/* 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 (!(SDHC_GetInterruptStatusFlags(base) & (kSDHC_BufferReadReadyFlag | kSDHC_DataErrorFlag)))
|
|
{
|
|
}
|
|
|
|
if (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, kSDHC_BufferReadReadyFlag | kSDHC_DataErrorFlag);
|
|
}
|
|
|
|
/* Clear data complete flag after the last read operation. */
|
|
SDHC_ClearInterruptStatusFlags(base, kSDHC_DataCompleteFlag | 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 (!(SDHC_GetInterruptStatusFlags(base) & (kSDHC_BufferWriteReadyFlag | kSDHC_DataErrorFlag)))
|
|
{
|
|
}
|
|
|
|
if (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, (kSDHC_BufferWriteReadyFlag | kSDHC_DataErrorFlag));
|
|
}
|
|
|
|
/* Wait write data complete or data transfer error after the last writing operation. */
|
|
while (!(SDHC_GetInterruptStatusFlags(base) & (kSDHC_DataCompleteFlag | kSDHC_DataErrorFlag)))
|
|
{
|
|
}
|
|
if (SDHC_GetInterruptStatusFlags(base) & kSDHC_DataErrorFlag)
|
|
{
|
|
if (!(data->enableIgnoreError))
|
|
{
|
|
error = kStatus_Fail;
|
|
}
|
|
}
|
|
|
|
SDHC_ClearInterruptStatusFlags(base, (kSDHC_DataCompleteFlag | 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 (!(SDHC_GetInterruptStatusFlags(base) & (kSDHC_CommandCompleteFlag | kSDHC_CommandErrorFlag)))
|
|
{
|
|
}
|
|
|
|
if (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, (kSDHC_CommandCompleteFlag | kSDHC_CommandErrorFlag));
|
|
|
|
return error;
|
|
}
|
|
|
|
static status_t SDHC_TransferByDataPortBlocking(SDHC_Type *base, sdhc_data_t *data)
|
|
{
|
|
status_t error = kStatus_Success;
|
|
|
|
if (data->rxData)
|
|
{
|
|
error = SDHC_ReadByDataPortBlocking(base, data);
|
|
}
|
|
else
|
|
{
|
|
error = SDHC_WriteByDataPortBlocking(base, data);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
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 (!(SDHC_GetInterruptStatusFlags(base) & (kSDHC_DataCompleteFlag | kSDHC_DataErrorFlag | kSDHC_DmaErrorFlag)))
|
|
{
|
|
}
|
|
if (SDHC_GetInterruptStatusFlags(base) & (kSDHC_DataErrorFlag | kSDHC_DmaErrorFlag))
|
|
{
|
|
if (!(data->enableIgnoreError))
|
|
{
|
|
error = kStatus_Fail;
|
|
}
|
|
}
|
|
SDHC_ClearInterruptStatusFlags(
|
|
base, (kSDHC_DataCompleteFlag | kSDHC_DmaCompleteFlag | kSDHC_DataErrorFlag | 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;
|
|
|
|
switch (dmaMode)
|
|
{
|
|
case kSDHC_DmaModeNo:
|
|
error = SDHC_TransferByDataPortBlocking(base, data);
|
|
break;
|
|
#if defined FSL_SDHC_ENABLE_ADMA1
|
|
case kSDHC_DmaModeAdma1:
|
|
error = SDHC_TransferByAdma1Blocking(base, data);
|
|
break;
|
|
#endif /* FSL_SDHC_ENABLE_ADMA1 */
|
|
case kSDHC_DmaModeAdma2:
|
|
error = SDHC_TransferByAdma2Blocking(base, data);
|
|
break;
|
|
default:
|
|
error = kStatus_InvalidArgument;
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static void SDHC_TransferHandleCardDetect(sdhc_handle_t *handle, uint32_t interruptFlags)
|
|
{
|
|
if (interruptFlags & kSDHC_CardInsertionFlag)
|
|
{
|
|
if (handle->callback.CardInserted)
|
|
{
|
|
handle->callback.CardInserted();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (handle->callback.CardRemoved)
|
|
{
|
|
handle->callback.CardRemoved();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SDHC_TransferHandleCommand(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags)
|
|
{
|
|
assert(handle->command);
|
|
|
|
if ((interruptFlags & kSDHC_CommandErrorFlag) && (!(handle->data)) && (handle->callback.TransferComplete))
|
|
{
|
|
handle->callback.TransferComplete(base, handle, kStatus_SDHC_SendCommandFailed, handle->userData);
|
|
}
|
|
else
|
|
{
|
|
/* Receive response */
|
|
SDHC_ReceiveCommandResponse(base, handle->command);
|
|
if ((!(handle->data)) && (handle->callback.TransferComplete))
|
|
{
|
|
handle->callback.TransferComplete(base, handle, kStatus_Success, handle->userData);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SDHC_TransferHandleData(SDHC_Type *base, sdhc_handle_t *handle, uint32_t interruptFlags)
|
|
{
|
|
assert(handle->data);
|
|
|
|
if ((!(handle->data->enableIgnoreError)) && (interruptFlags & (kSDHC_DataErrorFlag | kSDHC_DmaErrorFlag)) &&
|
|
(handle->callback.TransferComplete))
|
|
{
|
|
handle->callback.TransferComplete(base, handle, kStatus_SDHC_TransferDataFailed, handle->userData);
|
|
}
|
|
else
|
|
{
|
|
if (interruptFlags & kSDHC_BufferReadReadyFlag)
|
|
{
|
|
handle->transferredWords = SDHC_ReadDataPort(base, handle->data, handle->transferredWords);
|
|
}
|
|
else if (interruptFlags & kSDHC_BufferWriteReadyFlag)
|
|
{
|
|
handle->transferredWords = SDHC_WriteDataPort(base, handle->data, handle->transferredWords);
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
if ((interruptFlags & kSDHC_DataCompleteFlag) && (handle->callback.TransferComplete))
|
|
{
|
|
handle->callback.TransferComplete(base, handle, kStatus_Success, handle->userData);
|
|
}
|
|
else
|
|
{
|
|
/* Do nothing when DMA complete flag is set. Wait until data complete flag is set. */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SDHC_TransferHandleSdioInterrupt(sdhc_handle_t *handle)
|
|
{
|
|
if (handle->callback.SdioInterrupt)
|
|
{
|
|
handle->callback.SdioInterrupt();
|
|
}
|
|
}
|
|
|
|
static void SDHC_TransferHandleSdioBlockGap(sdhc_handle_t *handle)
|
|
{
|
|
if (handle->callback.SdioBlockGap)
|
|
{
|
|
handle->callback.SdioBlockGap();
|
|
}
|
|
}
|
|
|
|
void SDHC_Init(SDHC_Type *base, const sdhc_config_t *config)
|
|
{
|
|
assert(config);
|
|
#if !defined FSL_SDHC_ENABLE_ADMA1
|
|
assert(config->dmaMode != kSDHC_DmaModeAdma1);
|
|
#endif /* FSL_SDHC_ENABLE_ADMA1 */
|
|
assert((config->writeWatermarkLevel >= 1U) && (config->writeWatermarkLevel <= 128U));
|
|
assert((config->readWatermarkLevel >= 1U) && (config->readWatermarkLevel <= 128U));
|
|
|
|
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. */
|
|
SDHC_Reset(base, kSDHC_ResetAll, 100);
|
|
|
|
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. */
|
|
SDHC_SetTransferInterrupt(base, false);
|
|
}
|
|
|
|
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 */
|
|
}
|
|
|
|
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 ((base->SYSCTL & mask))
|
|
{
|
|
if (!timeout)
|
|
{
|
|
break;
|
|
}
|
|
timeout--;
|
|
}
|
|
|
|
return ((!timeout) ? false : true);
|
|
}
|
|
|
|
void SDHC_GetCapability(SDHC_Type *base, sdhc_capability_t *capability)
|
|
{
|
|
assert(capability);
|
|
|
|
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 = (512U << maxBlockLength);
|
|
/* Other attributes not in HTCAPBLT register. */
|
|
capability->maxBlockCount = SDHC_MAX_BLOCK_COUNT;
|
|
capability->flags = (htCapability & (kSDHC_SupportAdmaFlag | kSDHC_SupportHighSpeedFlag | kSDHC_SupportDmaFlag |
|
|
kSDHC_SupportSuspendResumeFlag | kSDHC_SupportV330Flag));
|
|
#if defined FSL_FEATURE_SDHC_HAS_V300_SUPPORT && FSL_FEATURE_SDHC_HAS_V300_SUPPORT
|
|
capability->flags |= (htCapability & kSDHC_SupportV300Flag);
|
|
#endif
|
|
#if defined FSL_FEATURE_SDHC_HAS_V180_SUPPORT && FSL_FEATURE_SDHC_HAS_V180_SUPPORT
|
|
capability->flags |= (htCapability & kSDHC_SupportV180Flag);
|
|
#endif
|
|
/* eSDHC on all kinetis boards will support 4/8 bit data bus width. */
|
|
capability->flags |= (kSDHC_Support4BitFlag | kSDHC_Support8BitFlag);
|
|
}
|
|
|
|
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 != 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 / 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 (!(base->PRSSTAT & SDHC_PRSSTAT_SDSTB_MASK))
|
|
{
|
|
}
|
|
/* Enable the SD clock. */
|
|
base->SYSCTL |= SDHC_SYSCTL_SDCLKEN_MASK;
|
|
|
|
return nearestFrequency;
|
|
}
|
|
|
|
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 (base->SYSCTL & SDHC_SYSCTL_INITA_MASK)
|
|
{
|
|
if (!timeout)
|
|
{
|
|
break;
|
|
}
|
|
timeout--;
|
|
}
|
|
|
|
return ((!timeout) ? false : true);
|
|
}
|
|
|
|
void SDHC_SetTransferConfig(SDHC_Type *base, const sdhc_transfer_config_t *config)
|
|
{
|
|
assert(config);
|
|
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)));
|
|
}
|
|
|
|
void SDHC_EnableSdioControl(SDHC_Type *base, uint32_t mask, bool enable)
|
|
{
|
|
uint32_t proctl = base->PROCTL;
|
|
uint32_t vendor = base->VENDOR;
|
|
|
|
if (enable)
|
|
{
|
|
if (mask & kSDHC_StopAtBlockGapFlag)
|
|
{
|
|
proctl |= SDHC_PROCTL_SABGREQ_MASK;
|
|
}
|
|
if (mask & kSDHC_ReadWaitControlFlag)
|
|
{
|
|
proctl |= SDHC_PROCTL_RWCTL_MASK;
|
|
}
|
|
if (mask & kSDHC_InterruptAtBlockGapFlag)
|
|
{
|
|
proctl |= SDHC_PROCTL_IABG_MASK;
|
|
}
|
|
if (mask & kSDHC_ExactBlockNumberReadFlag)
|
|
{
|
|
vendor |= SDHC_VENDOR_EXBLKNU_MASK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mask & kSDHC_StopAtBlockGapFlag)
|
|
{
|
|
proctl &= ~SDHC_PROCTL_SABGREQ_MASK;
|
|
}
|
|
if (mask & kSDHC_ReadWaitControlFlag)
|
|
{
|
|
proctl &= ~SDHC_PROCTL_RWCTL_MASK;
|
|
}
|
|
if (mask & kSDHC_InterruptAtBlockGapFlag)
|
|
{
|
|
proctl &= ~SDHC_PROCTL_IABG_MASK;
|
|
}
|
|
if (mask & kSDHC_ExactBlockNumberReadFlag)
|
|
{
|
|
vendor &= ~SDHC_VENDOR_EXBLKNU_MASK;
|
|
}
|
|
}
|
|
|
|
base->PROCTL = proctl;
|
|
base->VENDOR = vendor;
|
|
}
|
|
|
|
void SDHC_SetMmcBootConfig(SDHC_Type *base, const sdhc_boot_config_t *config)
|
|
{
|
|
assert(config);
|
|
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;
|
|
}
|
|
|
|
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) || (!tableWords)) && ((dmaMode == kSDHC_DmaModeAdma1) || (dmaMode == kSDHC_DmaModeAdma2))) ||
|
|
(!data) || (!dataBytes)
|
|
#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) != 0U))
|
|
#if defined FSL_SDHC_ENABLE_ADMA1
|
|
|| ((dmaMode == kSDHC_DmaModeAdma1) && (((uint32_t)startAddress % SDHC_ADMA1_LENGTH_ALIGN) != 0U))
|
|
#endif
|
|
)
|
|
{
|
|
error = kStatus_SDHC_DMADataBufferAddrNotAlign;
|
|
}
|
|
else
|
|
{
|
|
switch (dmaMode)
|
|
{
|
|
case kSDHC_DmaModeNo:
|
|
break;
|
|
#if defined FSL_SDHC_ENABLE_ADMA1
|
|
case 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 *)(table);
|
|
for (i = 0U; i < entries; i += 2U)
|
|
{
|
|
/* Each descriptor for ADMA1 is 32-bit in length */
|
|
if ((dataBytes - sizeof(uint32_t) * (startAddress - data)) <=
|
|
SDHC_ADMA1_DESCRIPTOR_MAX_LENGTH_PER_ENTRY)
|
|
{
|
|
/* The last piece of data, setting end flag in descriptor */
|
|
adma1EntryAddress[i] = ((uint32_t)(dataBytes - sizeof(uint32_t) * (startAddress - data))
|
|
<< SDHC_ADMA1_DESCRIPTOR_LENGTH_SHIFT);
|
|
adma1EntryAddress[i] |= kSDHC_Adma1DescriptorTypeSetLength;
|
|
adma1EntryAddress[i + 1U] =
|
|
((uint32_t)(startAddress) << SDHC_ADMA1_DESCRIPTOR_ADDRESS_SHIFT);
|
|
adma1EntryAddress[i + 1U] |=
|
|
(kSDHC_Adma1DescriptorTypeTransfer | kSDHC_Adma1DescriptorEndFlag);
|
|
}
|
|
else
|
|
{
|
|
adma1EntryAddress[i] = ((uint32_t)SDHC_ADMA1_DESCRIPTOR_MAX_LENGTH_PER_ENTRY
|
|
<< SDHC_ADMA1_DESCRIPTOR_LENGTH_SHIFT);
|
|
adma1EntryAddress[i] |= kSDHC_Adma1DescriptorTypeSetLength;
|
|
adma1EntryAddress[i + 1U] =
|
|
((uint32_t)(startAddress) << SDHC_ADMA1_DESCRIPTOR_ADDRESS_SHIFT);
|
|
adma1EntryAddress[i + 1U] |= 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;
|
|
/* disable the buffer ready flag in DMA mode */
|
|
SDHC_DisableInterruptSignal(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
SDHC_DisableInterruptStatus(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
}
|
|
break;
|
|
#endif /* FSL_SDHC_ENABLE_ADMA1 */
|
|
case 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 *)(table);
|
|
for (i = 0U; i < entries; i++)
|
|
{
|
|
/* Each descriptor for ADMA2 is 64-bit in length */
|
|
if ((dataBytes - sizeof(uint32_t) * (startAddress - 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 - sizeof(uint32_t) * (startAddress - data))
|
|
<< SDHC_ADMA2_DESCRIPTOR_LENGTH_SHIFT);
|
|
adma2EntryAddress[i].attribute |=
|
|
(kSDHC_Adma2DescriptorTypeTransfer | 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 |= 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;
|
|
/* disable the buffer read flag in DMA mode */
|
|
SDHC_DisableInterruptSignal(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
SDHC_DisableInterruptStatus(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
status_t SDHC_TransferBlocking(SDHC_Type *base, uint32_t *admaTable, uint32_t admaTableWords, sdhc_transfer_t *transfer)
|
|
{
|
|
assert(transfer);
|
|
|
|
status_t error = kStatus_Success;
|
|
sdhc_dma_mode_t dmaMode = (sdhc_dma_mode_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) || (data && (data->blockCount > SDHC_MAX_BLOCK_COUNT)))
|
|
{
|
|
return kStatus_InvalidArgument;
|
|
}
|
|
|
|
/* Wait until command/data bus out of busy status. */
|
|
while (SDHC_GetPresentStatusFlags(base) & kSDHC_CommandInhibitFlag)
|
|
{
|
|
}
|
|
while (data && (SDHC_GetPresentStatusFlags(base) & kSDHC_DataInhibitFlag))
|
|
{
|
|
}
|
|
|
|
/* Update ADMA descriptor table according to different DMA mode(no DMA, ADMA1, ADMA2).*/
|
|
if (data && (NULL != admaTable))
|
|
{
|
|
error =
|
|
SDHC_SetAdmaTableConfig(base, dmaMode, admaTable, admaTableWords,
|
|
(data->rxData ? data->rxData : data->txData), (data->blockCount * data->blockSize));
|
|
/* in this situation , we disable the DMA instead of polling transfer mode */
|
|
if (error == kStatus_SDHC_DMADataBufferAddrNotAlign)
|
|
{
|
|
dmaMode = kSDHC_DmaModeNo;
|
|
SDHC_EnableInterruptStatus(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
}
|
|
else if (error != kStatus_Success)
|
|
{
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
}
|
|
}
|
|
|
|
/* Send command and receive data. */
|
|
SDHC_StartTransfer(base, command, data, dmaMode);
|
|
if (kStatus_Success != SDHC_SendCommandBlocking(base, command))
|
|
{
|
|
return kStatus_SDHC_SendCommandFailed;
|
|
}
|
|
else if (data && (kStatus_Success != SDHC_TransferDataBlocking(dmaMode, base, data)))
|
|
{
|
|
return kStatus_SDHC_TransferDataFailed;
|
|
}
|
|
else
|
|
{
|
|
}
|
|
|
|
return kStatus_Success;
|
|
}
|
|
|
|
void SDHC_TransferCreateHandle(SDHC_Type *base,
|
|
sdhc_handle_t *handle,
|
|
const sdhc_transfer_callback_t *callback,
|
|
void *userData)
|
|
{
|
|
assert(handle);
|
|
assert(callback);
|
|
|
|
/* Zero the handle. */
|
|
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;
|
|
|
|
/* Enable interrupt in NVIC. */
|
|
SDHC_SetTransferInterrupt(base, true);
|
|
|
|
/* save IRQ handler */
|
|
s_sdhcIsr = SDHC_TransferHandleIRQ;
|
|
|
|
EnableIRQ(s_sdhcIRQ[SDHC_GetInstance(base)]);
|
|
}
|
|
|
|
status_t SDHC_TransferNonBlocking(
|
|
SDHC_Type *base, sdhc_handle_t *handle, uint32_t *admaTable, uint32_t admaTableWords, sdhc_transfer_t *transfer)
|
|
{
|
|
assert(transfer);
|
|
|
|
sdhc_dma_mode_t dmaMode = (sdhc_dma_mode_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) || (data && (data->blockCount > SDHC_MAX_BLOCK_COUNT)))
|
|
{
|
|
return kStatus_InvalidArgument;
|
|
}
|
|
|
|
/* Wait until command/data bus out of busy status. */
|
|
if ((SDHC_GetPresentStatusFlags(base) & kSDHC_CommandInhibitFlag) ||
|
|
(data && (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 != admaTable))
|
|
{
|
|
error =
|
|
SDHC_SetAdmaTableConfig(base, dmaMode, admaTable, admaTableWords,
|
|
(data->rxData ? data->rxData : data->txData), (data->blockCount * data->blockSize));
|
|
/* in this situation , we disable the DMA instead of polling transfer mode */
|
|
if (error == kStatus_SDHC_DMADataBufferAddrNotAlign)
|
|
{
|
|
/* change to polling mode */
|
|
dmaMode = kSDHC_DmaModeNo;
|
|
SDHC_EnableInterruptSignal(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
SDHC_EnableInterruptStatus(base, kSDHC_BufferReadReadyFlag | kSDHC_BufferWriteReadyFlag);
|
|
}
|
|
else if (error != kStatus_Success)
|
|
{
|
|
return error;
|
|
}
|
|
else
|
|
{
|
|
}
|
|
}
|
|
|
|
/* Save command and data into handle before transferring. */
|
|
handle->command = command;
|
|
handle->data = data;
|
|
handle->interruptFlags = 0U;
|
|
/* transferredWords will only be updated in ISR when transfer way is DATAPORT. */
|
|
handle->transferredWords = 0U;
|
|
|
|
SDHC_StartTransfer(base, command, data, dmaMode);
|
|
|
|
return kStatus_Success;
|
|
}
|
|
|
|
void SDHC_TransferHandleIRQ(SDHC_Type *base, sdhc_handle_t *handle)
|
|
{
|
|
assert(handle);
|
|
|
|
uint32_t interruptFlags;
|
|
|
|
interruptFlags = SDHC_GetInterruptStatusFlags(base);
|
|
handle->interruptFlags = interruptFlags;
|
|
|
|
if (interruptFlags & kSDHC_CardDetectFlag)
|
|
{
|
|
SDHC_TransferHandleCardDetect(handle, (interruptFlags & kSDHC_CardDetectFlag));
|
|
}
|
|
if (interruptFlags & kSDHC_CommandFlag)
|
|
{
|
|
SDHC_TransferHandleCommand(base, handle, (interruptFlags & kSDHC_CommandFlag));
|
|
}
|
|
if (interruptFlags & kSDHC_DataFlag)
|
|
{
|
|
SDHC_TransferHandleData(base, handle, (interruptFlags & kSDHC_DataFlag));
|
|
}
|
|
if (interruptFlags & kSDHC_CardInterruptFlag)
|
|
{
|
|
SDHC_TransferHandleSdioInterrupt(handle);
|
|
}
|
|
if (interruptFlags & kSDHC_BlockGapEventFlag)
|
|
{
|
|
SDHC_TransferHandleSdioBlockGap(handle);
|
|
}
|
|
|
|
SDHC_ClearInterruptStatusFlags(base, interruptFlags);
|
|
}
|
|
|
|
#if defined(SDHC)
|
|
void SDHC_DriverIRQHandler(void)
|
|
{
|
|
assert(s_sdhcHandle[0]);
|
|
|
|
s_sdhcIsr(SDHC, s_sdhcHandle[0]);
|
|
}
|
|
#endif
|