Files
tinyUSB/demos/bsp/lpc43xx/CMSIS_LPC43xx_DriverLib/src/lpc43xx_sdmmc.c

695 lines
20 KiB
C
Raw Normal View History

2013-09-12 14:26:20 +07:00
/**********************************************************************
* $Id$ lpc43xx_sdmmc.c 2012-Aug-15
*//**
* @file lpc43xx_sdmmc.c
* @brief SD/MMC card access and data driver
* @version 1.0
* @date 15. Aug. 2012
* @author NXP MCU SW Application Team
*
* Copyright(C) 2011, NXP Semiconductor
* All rights reserved.
*
***********************************************************************
* Software that is described herein is for illustrative purposes only
* which provides customers with programming information regarding the
* products. This software is supplied "AS IS" without any warranties.
* NXP Semiconductors assumes no responsibility or liability for the
* use of the software, conveys no license or title under any patent,
* copyright, or mask work right to the product. NXP Semiconductors
* reserves the right to make changes in the software without
* notification. NXP Semiconductors also make no representation or
* warranty that such application will be suitable for the specified
* use without further testing or modification.
* Permission to use, copy, modify, and distribute this software and its
* documentation is hereby granted, under NXP Semiconductors'
* relevant copyright in the software, without fee, provided that it
* is used in conjunction with NXP Semiconductors microcontrollers. This
* copyright, permission, and disclaimer notice must appear in all copies of
* this code.
**********************************************************************/
/* Example group ----------------------------------------------------------- */
/** @addtogroup SDMMC
* @{
*/
/* Includes ------------------------------------------------------------------- */
#include "string.h"
#include "lpc_sdmmc.h"
#include "lpc43xx_sdif.h"
#include "lpc43xx_sdmmc.h"
/* If this source file built with example, the lpc43xx FW library configuration
* file in each example directory ("lpc43xx_libcfg.h") must be included,
* otherwise the default FW library configuration file must be included instead
*/
#ifdef __BUILD_WITH_EXAMPLE__
#include "lpc43xx_libcfg.h"
#else
#include "lpc43xx_libcfg_default.h"
#endif /* __BUILD_WITH_EXAMPLE__ */
#ifdef _SDMMC
/* Global instance of the current card */
static struct _mci_card_struct *g_card_info;
/* Pointer to event setup functions */
static MCI_EVSETUP_FUNC_T sdmmc_evsetup_cb;
/* Pointer to wait functions */
static MCI_WAIT_CB_FUNC_T sdmmc_wait_cb;
/* Pointer to mS delay functin */
static MCI_MSDELAY_FUNC_T sdmmc_msdelay_cb;
/* Helper definition: all SD error conditions in the status word */
#define SD_INT_ERROR (MCI_INT_RESP_ERR | MCI_INT_RCRC | MCI_INT_DCRC | \
MCI_INT_RTO | MCI_INT_DTO | MCI_INT_HTO | MCI_INT_FRUN | MCI_INT_HLE | \
MCI_INT_SBE | MCI_INT_EBE)
/*********************************************************************//**
* @brief Helper function to get a bit field withing multi-word
* buffer. Used to get fields with-in CSD & EXT-CSD
* structures.
* @param[in] start Starting bit
* @param[in] end Ending bit
* @param[in] data Pointer to data patternf or bit extraction
* @return None
**********************************************************************/
static uint32_t prv_get_bits(int32_t start, int32_t end, uint32_t* data)
{
uint32_t v;
uint32_t i = end >> 5;
uint32_t j = start & 0x1f;
if (i == (start >> 5))
v = (data[i] >> j);
else
v = ((data[i] << (32 - j)) | (data[start >> 5] >> j));
return (v & ((1 << (end - start + 1)) - 1));
}
/*********************************************************************//**
* @brief Function to execute a command
* @param[in] cmd Command with all flags set
* @param[in] arg Argument for the command
* @param[in] wait_status Status bits to poll for command completion
* @return 0 on success, or error code (-1)
**********************************************************************/
static int32_t sdmmc_execute_command(uint32_t cmd, uint32_t arg,
uint32_t wait_status)
{
int32_t step = (cmd & CMD_BIT_APP) ? 2 : 1;
int32_t status = 0;
uint32_t cmd_reg = 0;
if (!wait_status)
wait_status = (cmd & CMD_MASK_RESP) ? MCI_INT_CMD_DONE : MCI_INT_DATA_OVER;
/* Clear the interrupts & FIFOs*/
if (cmd & CMD_BIT_DATA)
{
/* reset all blocks */
LPC_SDMMC->CTRL |= MCI_CTRL_FIFO_RESET;
/* wait till resets clear */
while (LPC_SDMMC->CTRL & MCI_CTRL_FIFO_RESET);
/* Clear interrupt status */
LPC_SDMMC->RINTSTS = 0xFFFFFFFF;
}
/* also check error conditions */
wait_status |= MCI_INT_EBE | MCI_INT_SBE | MCI_INT_HLE |
MCI_INT_RTO | MCI_INT_RCRC | MCI_INT_RESP_ERR;
if (wait_status & MCI_INT_DATA_OVER)
wait_status |= MCI_INT_FRUN | MCI_INT_HTO | MCI_INT_DTO | MCI_INT_DCRC;
while (step)
{
sdif_set_clock((cmd & CMD_BIT_LS) ? SD_MMC_ENUM_CLOCK : g_card_info->speed);
/* Clear the interrupts */
LPC_SDMMC->RINTSTS = 0xFFFFFFFF;
sdmmc_evsetup_cb(wait_status);
switch (step)
{
case 1: /* Execute command */
cmd_reg = ((cmd & CMD_MASK_CMD) >> CMD_SHIFT_CMD) |
((cmd & CMD_BIT_INIT) ? MCI_CMD_INIT : 0) |
((cmd & CMD_BIT_DATA) ? (MCI_CMD_DAT_EXP | MCI_CMD_PRV_DAT_WAIT) : 0) |
(((cmd & CMD_MASK_RESP) == CMD_RESP_R2) ? MCI_CMD_RESP_LONG : 0) |
((cmd & CMD_MASK_RESP) ? MCI_CMD_RESP_EXP : 0) |
((cmd & CMD_BIT_WRITE) ? MCI_CMD_DAT_WR : 0) |
((cmd & CMD_BIT_STREAM) ? MCI_CMD_STRM_MODE : 0) |
((cmd & CMD_BIT_BUSY) ? MCI_CMD_STOP : 0) |
((cmd & CMD_BIT_AUTO_STOP) ? MCI_CMD_SEND_STOP : 0) |
MCI_CMD_START;
/* wait for previos data finsh for select/deselect commands */
if (((cmd & CMD_MASK_CMD) >> CMD_SHIFT_CMD) == MMC_SELECT_CARD)
{
cmd_reg |= MCI_CMD_PRV_DAT_WAIT;
}
/* wait for command to be accepted by CIU */
if (sdif_send_cmd(cmd_reg, arg) == 0)
--step;
break;
case 0:
return 0;
case 2: /* APP prefix */
cmd_reg = MMC_APP_CMD | MCI_CMD_RESP_EXP |
((cmd & CMD_BIT_INIT) ? MCI_CMD_INIT : 0) |
MCI_CMD_START;
if (sdif_send_cmd(cmd_reg, g_card_info->rca << 16) == 0)
--step;
break;
}
/* wait for command response */
status = sdmmc_wait_cb(wait_status);
/* We return an error if there is a timeout, even if we've fetched
a response */
if (status & SD_INT_ERROR)
return status;
if (status & MCI_INT_CMD_DONE)
{
switch (cmd & CMD_MASK_RESP)
{
case 0:
break;
case CMD_RESP_R1:
case CMD_RESP_R3:
sdif_get_response(&g_card_info->response[0]);
break;
case CMD_RESP_R2:
sdif_get_response(&g_card_info->response[0]);
break;
}
}
}
return 0;
}
/*********************************************************************//**
* @brief Checks whether card is acquired properly or not
* @return !0 if card has been acquired, otherwise 0
**********************************************************************/
static int32_t prv_card_acquired(void)
{
return (g_card_info->cid[0] != 0);
}
/*********************************************************************//**
* @brief Function to process the CSD & EXT-CSD of the card
* @param[in] None
* @return None
**********************************************************************/
static void prv_process_csd(void)
{
int32_t status = 0;
int32_t c_size = 0;
int32_t c_size_mult = 0;
int32_t mult = 0;
/* compute block length based on CSD response */
g_card_info->block_len = 1 << prv_get_bits(80, 83, g_card_info->csd);
if ((g_card_info->card_type & CARD_TYPE_HC) &&
(g_card_info->card_type & CARD_TYPE_SD))
{
/* See section 5.3.3 CSD Register (CSD Version 2.0) of SD2.0 spec
an explanation for the calculation of these values */
c_size = prv_get_bits(48, 63, (uint32_t*)g_card_info->csd) + 1;
g_card_info->blocknr = c_size << 10; /* 512 byte blocks */
}
else
{
/* See section 5.3 of the 4.1 revision of the MMC specs for
an explanation for the calculation of these values */
c_size = prv_get_bits(62, 73, (uint32_t*)g_card_info->csd);
c_size_mult = prv_get_bits(47, 49, (uint32_t*)g_card_info->csd);
mult = 1 << (c_size_mult + 2);
g_card_info->blocknr = (c_size + 1) * mult;
/* adjust blocknr to 512/block */
if (g_card_info->block_len > MMC_SECTOR_SIZE)
g_card_info->blocknr = g_card_info->blocknr *
(g_card_info->block_len >> 9);
/* get extended CSD for newer MMC cards CSD spec >= 4.0*/
if (((g_card_info->card_type & CARD_TYPE_SD) == 0) &&
(prv_get_bits(122, 125, (uint32_t*)g_card_info->csd) >= 4))
{
/* put card in trans state */
status = sdmmc_execute_command(CMD_SELECT_CARD,
g_card_info->rca << 16, 0);
/* set block size and byte count */
LPC_SDMMC->BLKSIZ = MMC_SECTOR_SIZE;
LPC_SDMMC->BYTCNT = MMC_SECTOR_SIZE;
/* send EXT_CSD command */
sdif_dma_setup((uint32_t) g_card_info->ext_csd, MMC_SECTOR_SIZE);
status = sdmmc_execute_command(CMD_SEND_EXT_CSD, 0, 0 |
MCI_INT_DATA_OVER);
if ((status & SD_INT_ERROR) == 0)
{
/* check EXT_CSD_VER is greater than 1.1 */
if ((g_card_info->ext_csd[48] & 0xFF) > 1)
g_card_info->blocknr = g_card_info->ext_csd[53]; /* bytes 212:215 represent sec count */
/* switch to 52MHz clock if card type is set to 1 or else set to 26MHz */
if ((g_card_info->ext_csd[49] & 0xFF) == 1)
{
/* for type 1 MMC cards high speed is 52MHz */
g_card_info->speed = MMC_HIGH_BUS_MAX_CLOCK;
}
else
{
/* for type 0 MMC cards high speed is 26MHz */
g_card_info->speed = MMC_LOW_BUS_MAX_CLOCK;
}
}
}
}
g_card_info->device_size = g_card_info->blocknr << 9; /* blocknr * 512 */
}
/*********************************************************************//**
* @brief Puts current selected card in trans state
* @param[in] None
* @return 0 on success, or error code (-1)
**********************************************************************/
static int32_t prv_set_trans_state(void)
{
uint32_t status;
/* get current state of the card */
status = sdmmc_execute_command(CMD_SEND_STATUS, g_card_info->rca << 16, 0);
if (status & MCI_INT_RTO)
{
/* unable to get the card state. So return immediatly. */
return -1;
}
/* check card state in response */
status = R1_CURRENT_STATE(g_card_info->response[0]);
switch (status)
{
case SDMMC_STBY_ST:
/* put card in 'Trans' state */
status = sdmmc_execute_command(CMD_SELECT_CARD, g_card_info->rca << 16, 0);
if (status != 0)
{
/* unable to put the card in Trans state. So return immediatly. */
return -1;
}
break;
case SDMMC_TRAN_ST:
/*do nothing */
break;
default:
/* card shouldn't be in other states so return */
return -1;
}
return 0;
}
/*********************************************************************//**
* @brief Sets card data width and block size
* @param[in] None
* @return 0 on success, or error code (-1)
**********************************************************************/
static int32_t prv_set_card_params(void)
{
uint32_t status;
#if SDIO_BUS_WIDTH > 1
if (g_card_info->card_type & CARD_TYPE_SD)
{
status = sdmmc_execute_command(CMD_SD_SET_WIDTH, 2, 0);
if (status != 0)
return -1;
/* if positive response */
LPC_SDMMC->CTYPE = MCI_CTYPE_4BIT;
}
#elif SDIO_BUS_WIDTH > 4
#error 8-bit mode not supported yet!
#endif
/* set block length */
LPC_SDMMC->BLKSIZ = MMC_SECTOR_SIZE;
status = sdmmc_execute_command(CMD_SET_BLOCKLEN, MMC_SECTOR_SIZE, 0);
if (status != 0)
return -1;
return 0;
}
/* Public Functions ----------------------------------------------------------- */
/** @defgroup SDMMC_Public_Functions
* @ingroup SDMMC
* @{
*/
/*********************************************************************//**
* @brief Function to enumerate the SD/MMC/SDHC/MMC+ cards
* @param[in] evsetup_cb Pointer to event setup function callback
* @param[in] waitfunc_cb Pointer to wait function callback
* @param[in] msdelay_func Pointer to function that delays
* @param[in] pcardinfo Pointer to pre-allocated card info structure
* @return 1 if a card is acquired, otherwise 0
**********************************************************************/
int32_t sdmmc_acquire(MCI_EVSETUP_FUNC_T evsetup_cb,
MCI_WAIT_CB_FUNC_T waitfunc_cb, MCI_MSDELAY_FUNC_T msdelay_func,
struct _mci_card_struct *pcardinfo)
{
int32_t status;
int32_t tries = 0;
uint32_t ocr = OCR_VOLTAGE_RANGE_MSK;
uint32_t r;
int32_t state = 0;
uint32_t command = 0;
g_card_info = pcardinfo;
/* Make sure callbacks are valid */
if ((waitfunc_cb == NULL) || (msdelay_func == NULL) ||
(evsetup_cb == NULL))
return 0;
sdmmc_evsetup_cb = evsetup_cb;
sdmmc_wait_cb = waitfunc_cb;
sdmmc_msdelay_cb = msdelay_func;
/* clear card struct */
memset(g_card_info, 0, sizeof(*g_card_info));
/* clear card type */
LPC_SDMMC->CTYPE = 0;
/* set high speed for the card as 20MHz */
g_card_info->speed = MMC_MAX_CLOCK;
status = sdmmc_execute_command(CMD_IDLE, 0, MCI_INT_CMD_DONE);
while (state < 100)
{
switch (state)
{
case 0: /* Setup for SD */
/* check if it is SDHC card */
status = sdmmc_execute_command( CMD_SD_SEND_IF_COND, SD_SEND_IF_ARG, 0);
if (!(status & MCI_INT_RTO))
{
/* check response has same echo pattern */
if ((g_card_info->response[0] & SD_SEND_IF_ECHO_MSK) == SD_SEND_IF_RESP)
ocr |= OCR_HC_CCS;
}
++state;
command = CMD_SD_OP_COND;
tries = INIT_OP_RETRIES;
/* assume SD card */
g_card_info->card_type |= CARD_TYPE_SD;
g_card_info->speed = SD_MAX_CLOCK;
break;
case 10: /* Setup for MMC */
/* start fresh for MMC crds */
g_card_info->card_type &= ~CARD_TYPE_SD;
status = sdmmc_execute_command(CMD_IDLE, 0, MCI_INT_CMD_DONE);
command = CMD_MMC_OP_COND;
tries = INIT_OP_RETRIES;
ocr |= OCR_HC_CCS;
++state;
/* for MMC cards high speed is 20MHz */
g_card_info->speed = MMC_MAX_CLOCK;
break;
case 1:
case 11:
status = sdmmc_execute_command(command, 0, 0);
if (status & MCI_INT_RTO)
state += 9; /* Mode unavailable */
else
++state;
break;
case 2: /* Initial OCR check */
case 12:
ocr = g_card_info->response[0] | (ocr & OCR_HC_CCS);
if (ocr & OCR_ALL_READY)
++state;
else
state += 2;
break;
case 3: /* Initial wait for OCR clear */
case 13:
while ((ocr & OCR_ALL_READY) && --tries > 0)
{
sdmmc_msdelay_cb(MS_ACQUIRE_DELAY);
status = sdmmc_execute_command(command, 0, 0);
ocr = g_card_info->response[0] | (ocr & OCR_HC_CCS);
}
if (ocr & OCR_ALL_READY)
state += 7;
else
++state;
break;
case 14:
/* for MMC cards set high capacity bit */
ocr |= OCR_HC_CCS;
case 4: /* Assign OCR */
tries = SET_OP_RETRIES;
ocr &= OCR_VOLTAGE_RANGE_MSK | OCR_HC_CCS; /* Mask for the bits we care about */
do
{
sdmmc_msdelay_cb(MS_ACQUIRE_DELAY);
status = sdmmc_execute_command(command, ocr, 0);
r = g_card_info->response[0];
} while (!(r & OCR_ALL_READY) && --tries > 0);
if (r & OCR_ALL_READY)
{
/* is it high capacity card */
g_card_info->card_type |= (r & OCR_HC_CCS);
++state;
}
else
state += 6;
break;
case 5: /* CID polling */
case 15:
status = sdmmc_execute_command(CMD_ALL_SEND_CID, 0, 0);
memcpy(&g_card_info->cid, &g_card_info->response[0], 16);
++state;
break;
case 6: /* RCA send, for SD get RCA */
status = sdmmc_execute_command(CMD_SD_SEND_RCA, 0, 0);
g_card_info->rca = (g_card_info->response[0]) >> 16;
++state;
break;
case 16: /* RCA assignment for MMC set to 1 */
g_card_info->rca = 1;
status = sdmmc_execute_command(CMD_MMC_SET_RCA, g_card_info->rca << 16, 0);
++state;
break;
case 7:
case 17:
status = sdmmc_execute_command(CMD_SEND_CSD, g_card_info->rca << 16, 0);
memcpy(&g_card_info->csd, &g_card_info->response[0], 16);
state = 100;
break;
default:
state += 100; /* break from while loop */
break;
}
}
/* Compute card size, block size and no. of blocks
based on CSD response recived. */
if (prv_card_acquired()) {
prv_process_csd();
/* Setup card data width and block size (once) */
if (prv_set_trans_state() != 0)
return 0;
if (prv_set_card_params() != 0)
return 0;
}
return prv_card_acquired();
}
/*********************************************************************//**
* @brief Get card's current state (idle, transfer, program, etc.)
* @param[in] None
* @return Current transfer state (0 -
**********************************************************************/
int32_t sdmmc_get_state(void)
{
uint32_t status;
/* get current state of the card */
status = sdmmc_execute_command(CMD_SEND_STATUS, g_card_info->rca << 16, 0);
if (status & MCI_INT_RTO)
return -1;
/* check card state in response */
return (int32_t) R1_CURRENT_STATE(g_card_info->response[0]);
}
/*********************************************************************//**
* @brief Get the device size of SD/MMC card
* @param[in] None
* @return Device size
**********************************************************************/
int32_t sdmmc_get_device_size(void)
{
return g_card_info->device_size;
}
/*********************************************************************//**
* @brief Performs the read of data from the SD/MMC card
* @param[in] buffer Pointer to data buffer to copy to
* @param[in] start_block Start block number
* @param[in] end_block End block number
* @return Bytes read, or 0 on error
**********************************************************************/
int32_t sdmmc_read_blocks(void* buffer, int32_t start_block,
int32_t end_block)
{
int32_t cbRead = (end_block - start_block + 1) * MMC_SECTOR_SIZE;
int32_t status = 0;
int32_t index;
/* if card is not acquired return immediately */
if ((end_block < start_block) || (start_block < 0) ||
(end_block > g_card_info->blocknr))
return 0;
/* put card in trans state */
if (prv_set_trans_state() != 0)
return 0;
/* set number of bytes to read */
LPC_SDMMC->BYTCNT = cbRead;
/* if high capacity card use block indexing */
if (g_card_info->card_type & CARD_TYPE_HC)
index = start_block;
else/*fix at 512 bytes*/
index = start_block << 9;//\* g_card_info->block_len;
sdif_dma_setup((uint32_t) buffer, cbRead);
/* Select single or multiple read based on number of blocks */
if (end_block == start_block)
status = sdmmc_execute_command(CMD_READ_SINGLE,
index, 0 | MCI_INT_DATA_OVER);
else
status = sdmmc_execute_command(CMD_READ_MULTIPLE,
index, 0 | MCI_INT_DATA_OVER);
if (status != 0)
cbRead = 0;
/*Wait for card program to finish*/
while (sdmmc_get_state() != SDMMC_TRAN_ST);
return cbRead;
}
/*********************************************************************//**
* @brief Performs write of data to the SD/MMC card
* @param[in] buffer Pointer to data buffer to copy to
* @param[in] start_block Start block number
* @param[in] end_block End block number
* @return Number of bytes actually written, or 0 on error
**********************************************************************/
int32_t sdmmc_write_blocks(void* buffer,
int32_t start_block,
int32_t end_block)
{
int32_t cbWrote = (end_block - start_block + 1) * MMC_SECTOR_SIZE;
int32_t status;
int32_t index;
/* if card is not acquired return immediately */
if ((end_block < start_block) || (start_block < 0) ||
(end_block > g_card_info->blocknr))
return 0;
/*Wait for card program to finish*/
while (sdmmc_get_state() != SDMMC_TRAN_ST);
/* put card in trans state */
if (prv_set_trans_state() != 0)
return 0;
/* set number of bytes to write */
LPC_SDMMC->BYTCNT = cbWrote;
/* if high capacity card use block indexing */
if (g_card_info->card_type & CARD_TYPE_HC)
index = start_block;
else/*fix at 512 bytes*/
index = start_block << 9;//* g_card_info->block_len;
sdif_dma_setup((uint32_t) buffer, cbWrote);
/*Wait for card program to finish*/
while (sdmmc_get_state() != SDMMC_TRAN_ST);
/* Select single or multiple write based on number of blocks */
if (end_block == start_block)
status = sdmmc_execute_command(CMD_WRITE_SINGLE,
index, 0 | MCI_INT_DATA_OVER);
else
status = sdmmc_execute_command(CMD_WRITE_MULTIPLE, index,
0 | MCI_INT_DATA_OVER);
if (status != 0)
cbWrote = 0;
return cbWrote;
}
/**
* @}
*/
#endif /* _SDMMC */
/**
* @}
*/