blob: 12c7bbc3bebaee07c9557d6a4824ba334adcfe6b [file] [log] [blame]
/* ----------------------------------------------------------------------------
* SAM Software Package License
* ----------------------------------------------------------------------------
* Copyright (c) 2015, Atmel Corporation
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Atmel's name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
* ----------------------------------------------------------------------------
*/
/** \addtogroup sdmmc_module Working with Multi Media Cards and
* Secure Digital Memory Cards
* \ingroup peripherals_module
* The SDMMC driver provides the interface to configure and use
* the SDMMC peripheral.
* \n
*
* For more accurate information, please look at the SDMMC
* section of the Datasheet.
*
* Related files:\n
* \ref sdmmc.c\n
* \ref sdmmc.h\n
*/
/*@{*/
/*@}*/
/**
* \file
*
* Driver for MMC and SD Cards using the SDMMC IP.
*
*/
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "chip.h"
#include "trace.h"
#include "intmath.h"
#include "timer.h"
#include "peripherals/pmc.h"
#include "peripherals/tc.h"
#include "peripherals/l2cc.h"
#include "peripherals/sdmmc.h"
#include "libsdmmc/sdmmc_hal.h"
#include "libsdmmc/sdmmc_api.h" /* Included for debug functions only */
#include <assert.h>
#include <string.h>
/*----------------------------------------------------------------------------
* Local definitions
*----------------------------------------------------------------------------*/
/** Device status */
#define STAT_ADDRESS_OUT_OF_RANGE (1UL << 31)
#define STAT_ADDRESS_MISALIGN (1UL << 30)
#define STAT_BLOCK_LEN_ERROR (1UL << 29)
#define STAT_ERASE_SEQ_ERROR (1UL << 28)
#define STAT_ERASE_PARAM (1UL << 27)
#define STAT_WP_VIOLATION (1UL << 26)
#define STAT_DEVICE_IS_LOCKED (1UL << 25)
#define STAT_LOCK_UNLOCK_FAILED (1UL << 24)
#define STAT_COM_CRC_ERROR (1UL << 23)
#define STAT_ILLEGAL_COMMAND (1UL << 22)
#define STAT_DEVICE_ECC_FAILED (1UL << 21)
#define STAT_CC_ERROR (1UL << 20)
#define STAT_ERROR (1UL << 19)
#define STAT_CID_OVERWRITE (1UL << 16)
#define STAT_ERASE_SKIP (1UL << 15)
#define STAT_CARD_ECC_DISABLED (1UL << 14)
#define STAT_ERASE_RESET (1UL << 13)
#define STAT_CURRENT_STATE (0xfUL << 9)
#define STAT_READY_FOR_DATA (1UL << 8)
#define STAT_SWITCH_ERROR (1UL << 7)
#define STAT_EXCEPTION_EVENT (1UL << 6)
#define STAT_APP_CMD (1UL << 5)
/** Device state */
#define STATE_TRANSFER 0x4
#define STATE_SENDING_DATA 0x5
#define STATE_RECEIVE_DATA 0x6
#define STATE_PROGRAMMING 0x7
/** Driver state */
#define MCID_OFF 0 /**< Device not powered */
#define MCID_IDLE 1 /**< Idle */
#define MCID_LOCKED 2 /**< Locked for specific slot */
#define MCID_CMD 3 /**< Processing the command */
#define MCID_ERROR 4 /**< Command error */
/** A software event, never raised by the hardware, specific to this driver */
#define SDMMC_NISTR_CUSTOM_EVT (0x1u << 13)
union uint32_u {
uint32_t word;
uint8_t bytes[4];
};
/*----------------------------------------------------------------------------
* Local functions
*----------------------------------------------------------------------------*/
static uint32_t sdmmc_send_command(void *set, sSdmmcCommand *cmd);
static uint8_t sdmmc_cancel_command(struct sdmmc_set *set);
static void sdmmc_reset_peripheral(struct sdmmc_set *set)
{
assert(set);
Sdmmc *regs = set->regs;
uint8_t mc1r, tcr;
/* First, save the few settings we'll want to restore. */
mc1r = regs->SDMMC_MC1R;
tcr = regs->SDMMC_TCR;
/* Reset the peripheral. This will reset almost all registers. */
regs->SDMMC_SRR |= SDMMC_SRR_SWRSTALL;
while (regs->SDMMC_SRR & SDMMC_SRR_SWRSTALL) ;
/* Restore specific register fields */
if (mc1r & SDMMC_MC1R_FCD)
regs->SDMMC_MC1R |= SDMMC_MC1R_FCD;
regs->SDMMC_TCR = (regs->SDMMC_TCR & ~SDMMC_TCR_DTCVAL_Msk)
| (tcr & SDMMC_TCR_DTCVAL_Msk);
/* Apply our unconditional custom settings */
/* When using DMA, use the 32-bit Advanced DMA 2 mode */
regs->SDMMC_HC1R = (regs->SDMMC_HC1R & ~SDMMC_HC1R_DMASEL_Msk)
| SDMMC_HC1R_DMASEL_ADMA32;
/* Configure maximum AHB burst size */
regs->SDMMC_ACR = (regs->SDMMC_ACR & ~SDMMC_ACR_BMAX_Msk)
| SDMMC_ACR_BMAX_INCR16;
}
static void sdmmc_power_device(struct sdmmc_set *set, bool on)
{
assert(set);
Sdmmc *regs = set->regs;
uint32_t timer_res_prv, usec = 0;
uint8_t mc1r;
if ((on && set->state != MCID_OFF) || (!on && set->state == MCID_OFF))
return;
if (on) {
trace_debug("Power the device on\n\r");
/* Power the signals to/from the device */
regs->SDMMC_PCR |= SDMMC_PCR_SDBPWR;
set->state = MCID_IDLE;
return;
}
trace_debug("Release and power the device off\n\r");
if (set->state == MCID_CMD)
sdmmc_cancel_command(set);
/* Hardware-reset the e.MMC, move it to the pre-idle state.
* Note that this will only be effective on systems where
* 1) the RST_n e.MMC input is wired to the SDMMCx_RSTN PIO, and
* 2) the hardware reset functionality of the device has been
* enabled by software (!) Refer to ECSD register byte 162. */
timer_res_prv = timer_get_resolution();
/* Generate a pulse on SDMMCx_RSTN. Satisfy tRSTW >= 1 usec.
* The timer driver can't cope with periodic interrupts triggered as
* frequently as one interrupt per microsecond. Extend to 10 usec. */
timer_configure(10);
mc1r = regs->SDMMC_MC1R;
regs->SDMMC_MC1R = mc1r | SDMMC_MC1R_RSTN;
timer_sleep(1);
regs->SDMMC_MC1R = mc1r;
/* Wait for either tRSCA = 200 usec or 74 device clock cycles, as per
* the e.MMC Electrical Standard. */
if (set->dev_freq != 0)
usec = ROUND_INT_DIV(74 * 1000000UL / 10UL, set->dev_freq);
usec = usec < 20 ? 20 : usec;
timer_sleep(usec);
timer_configure(timer_res_prv);
/* Stop both the output clock and the SDMMC internal clock */
regs->SDMMC_CCR &= ~(SDMMC_CCR_SDCLKEN | SDMMC_CCR_INTCLKEN);
set->dev_freq = 0;
/* Cut the power rail supplying signals to/from the device */
regs->SDMMC_PCR &= ~SDMMC_PCR_SDBPWR;
/* Reset the peripheral. This will reset almost all registers. */
sdmmc_reset_peripheral(set);
set->state = MCID_OFF;
}
static uint8_t sdmmc_get_bus_width(struct sdmmc_set *set)
{
assert(set);
const uint8_t hc1r = set->regs->SDMMC_HC1R;
if (hc1r & SDMMC_HC1R_EXTDW)
return 8;
else if (hc1r & SDMMC_HC1R_DW)
return 4;
else
return 1;
}
static uint8_t sdmmc_set_bus_width(struct sdmmc_set *set, uint8_t bits)
{
assert(set);
Sdmmc *regs = set->regs;
uint8_t hc1r_prv, hc1r;
if (bits != 1 && bits != 4 && bits != 8)
return SDMMC_PARAM;
if (bits == 8 && !(regs->SDMMC_CA0R & SDMMC_CA0R_ED8SUP)) {
trace_error("This slot doesn't support an 8-bit data bus\n\r");
return SDMMC_PARAM;
}
hc1r = hc1r_prv = regs->SDMMC_HC1R;
if (bits == 8 && hc1r & SDMMC_HC1R_EXTDW)
return SDMMC_OK;
else if (bits == 8)
hc1r |= SDMMC_HC1R_EXTDW;
else {
hc1r &= ~SDMMC_HC1R_EXTDW;
if (bits == 4)
hc1r |= SDMMC_HC1R_DW;
else
hc1r &= ~SDMMC_HC1R_DW;
if (hc1r == hc1r_prv)
return SDMMC_OK;
}
regs->SDMMC_HC1R = hc1r;
return SDMMC_OK;
}
static bool sdmmc_get_speed_mode(struct sdmmc_set *set)
{
assert(set);
if (set->regs->SDMMC_MC1R & SDMMC_MC1R_DDR)
return true;
if (set->regs->SDMMC_HC1R & SDMMC_HC1R_HSEN)
return true;
return false;
}
static uint8_t sdmmc_set_speed_mode(struct sdmmc_set *set, bool high_speed)
{
assert(set);
Sdmmc *regs = set->regs;
uint8_t hc1r, mc1r;
bool enable_dev_clock;
if (high_speed && !(regs->SDMMC_CA0R & SDMMC_CA0R_HSSUP)) {
trace_error("This slot doesn't support High Speed Mode\n\r");
return SDMMC_PARAM;
}
#ifndef NDEBUG
if (high_speed && !(regs->SDMMC_CCR & (SDMMC_CCR_USDCLKFSEL_Msk
| SDMMC_CCR_SDCLKFSEL_Msk))) {
trace_error("Incompatible with the current clock config\n\r");
return SDMMC_STATE;
}
#endif
mc1r = regs->SDMMC_MC1R;
if (high_speed && mc1r & SDMMC_MC1R_DDR)
return SDMMC_OK;
if (!high_speed && mc1r & SDMMC_MC1R_DDR)
regs->SDMMC_MC1R = mc1r & ~SDMMC_MC1R_DDR;
hc1r = regs->SDMMC_HC1R;
if ((hc1r & SDMMC_HC1R_HSEN) == (high_speed ? SDMMC_HC1R_HSEN : 0))
return SDMMC_OK;
hc1r ^= SDMMC_HC1R_HSEN;
/* Avoid generating glitches on the device clock */
enable_dev_clock = regs->SDMMC_HC2R & SDMMC_HC2R_PVALEN
&& regs->SDMMC_CCR & SDMMC_CCR_SDCLKEN;
if (enable_dev_clock)
regs->SDMMC_CCR &= ~SDMMC_CCR_SDCLKEN;
/* Now change the Speed Mode */
regs->SDMMC_HC1R = hc1r;
if (enable_dev_clock)
regs->SDMMC_CCR |= SDMMC_CCR_SDCLKEN;
return SDMMC_OK;
}
static void sdmmc_set_device_clock(struct sdmmc_set *set, uint32_t freq)
{
assert(set);
assert(freq);
Sdmmc *regs = set->regs;
uint32_t base_freq, div, low_freq, up_freq, new_freq;
uint32_t mult_freq, p_div, p_mode_freq;
uint16_t shval;
bool use_prog_mode = false;
#ifndef NDEBUG
if (regs->SDMMC_HC2R & SDMMC_HC2R_PVALEN)
trace_error("Preset values enabled though not implemented\n\r");
#endif
/* In the Divided Clock Mode scenario, compute the divider */
base_freq = (regs->SDMMC_CA0R & SDMMC_CA0R_BASECLKF_Msk) >> SDMMC_CA0R_BASECLKF_Pos;
base_freq *= 1000000UL;
/* DIV = FBASECLK / (2 * FSDCLK) */
div = base_freq / (2 * freq);
if (div >= 0x3ff)
div = 0x3ff;
else {
up_freq = base_freq / (div == 0 ? 1UL : 2 * div);
low_freq = base_freq / (2 * (div + 1UL));
if (up_freq > freq && (up_freq - freq) > (freq - low_freq))
div += 1;
}
new_freq = base_freq / (div == 0 ? 1UL : 2 * div);
/* Now, in the Programmable Clock Mode scenario, compute the divider.
* First, retrieve the frequency of the Generated Clock feeding this
* peripheral. */
/* TODO fix CLKMULT value in CA1R capability register: the default value
* is 32 whereas the real value is 40.5 */
mult_freq = (regs->SDMMC_CA1R & SDMMC_CA1R_CLKMULT_Msk) >> SDMMC_CA1R_CLKMULT_Pos;
if (mult_freq != 0)
#if 0
mult_freq = base_freq * (mult_freq + 1);
#else
mult_freq = pmc_get_gck_clock(set->id);
#endif
if (mult_freq != 0) {
/* DIV = FMULTCLK / FSDCLK - 1 */
p_div = ROUND_INT_DIV(mult_freq, freq);
if (p_div > 0x3ff)
p_div = 0x3ff;
else if (p_div != 0)
p_div = p_div - 1;
p_mode_freq = mult_freq / (p_div + 1);
if (ABS_DIFF(freq, p_mode_freq) < ABS_DIFF(freq, new_freq)) {
use_prog_mode = true;
div = p_div;
new_freq = p_mode_freq;
}
}
/* Stop both the output clock and the SDMMC internal clock */
shval = regs->SDMMC_CCR & ~SDMMC_CCR_SDCLKEN & ~SDMMC_CCR_INTCLKEN;
regs->SDMMC_CCR = shval;
set->dev_freq = new_freq;
/* Select the clock mode */
if (use_prog_mode)
shval |= SDMMC_CCR_CLKGSEL;
else
shval &= ~SDMMC_CCR_CLKGSEL;
/* Set the clock divider, and start the SDMMC internal clock */
shval = (shval & ~SDMMC_CCR_USDCLKFSEL_Msk & ~SDMMC_CCR_SDCLKFSEL_Msk)
| SDMMC_CCR_USDCLKFSEL(div >> 8) | SDMMC_CCR_SDCLKFSEL(div & 0xff)
| SDMMC_CCR_INTCLKEN;
regs->SDMMC_CCR = shval;
while (!(regs->SDMMC_CCR & SDMMC_CCR_INTCLKS)) ;
/* Now start the output clock */
regs->SDMMC_CCR |= SDMMC_CCR_SDCLKEN;
}
static uint8_t sdmmc_build_dma_table(struct sdmmc_set *set, sSdmmcCommand *cmd)
{
assert(set);
assert(set->table);
assert(set->table_size);
assert(cmd->pData);
assert(cmd->wBlockSize);
assert(cmd->wNbBlocks);
uint32_t *line = NULL;
uint32_t data_len = (uint32_t)cmd->wNbBlocks
* (uint32_t)cmd->wBlockSize;
uint32_t ram_addr = (uint32_t)cmd->pData;
uint32_t ram_bound = ram_addr + data_len;
uint32_t line_ix, line_cnt;
uint8_t rc = SDMMC_OK;
#if 0 && !defined(NDEBUG)
trace_debug("Configuring DMA for a %luB transfer %s %p\n\r",
data_len, cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX ? "from" : "to",
cmd->pData);
#endif
/* Verify that cmd->pData is word-aligned */
if ((uint32_t)cmd->pData & 0x3)
return SDMMC_PARAM;
/* Compute the size of the descriptor table for this transfer */
line_cnt = (data_len - 1 + SDMMC_DMADL_TRAN_LEN_MAX)
/ SDMMC_DMADL_TRAN_LEN_MAX;
/* If it won't fit into the allocated buffer, resize the transfer */
if (line_cnt > set->table_size) {
line_cnt = set->table_size;
data_len = line_cnt * SDMMC_DMADL_TRAN_LEN_MAX;
data_len /= cmd->wBlockSize;
if (data_len == 0)
return SDMMC_NOT_SUPPORTED;
cmd->wNbBlocks = (uint16_t)data_len;
data_len *= cmd->wBlockSize;
ram_bound = ram_addr + data_len;
rc = SDMMC_CHANGED;
}
/* Fill the table */
for (line_ix = 0, line = set->table; line_ix < line_cnt;
line_ix++, line += SDMMC_DMADL_SIZE) {
if (line_ix + 1 < line_cnt) {
line[0] = SDMMC_DMA0DL_LEN_MAX
| SDMMC_DMA0DL_ATTR_ACT_TRAN
| SDMMC_DMA0DL_ATTR_VALID;
line[1] = SDMMC_DMA1DL_ADDR(ram_addr);
ram_addr += SDMMC_DMADL_TRAN_LEN_MAX;
}
else {
line[0] = ram_bound - ram_addr
< SDMMC_DMADL_TRAN_LEN_MAX
? SDMMC_DMA0DL_LEN(ram_bound - ram_addr)
: SDMMC_DMA0DL_LEN_MAX;
line[0] |= SDMMC_DMA0DL_ATTR_ACT_TRAN
| SDMMC_DMA0DL_ATTR_END | SDMMC_DMA0DL_ATTR_VALID;
line[1] = SDMMC_DMA1DL_ADDR(ram_addr);
}
#if 0 && !defined(NDEBUG)
trace_debug("DMA descriptor: %luB @ 0x%lx%c\n\r",
(line[0] & SDMMC_DMA0DL_LEN_Msk) >> SDMMC_DMA0DL_LEN_Pos,
line[1], line[0] & SDMMC_DMA0DL_ATTR_END ? '.' : ' ');
#endif
}
/* Clean the underlying cache lines, to ensure the DMA gets our table
* when it reads from RAM.
* CPU access to the table is write-only, peripheral/DMA access is read-
* only, hence there is no need to invalidate. */
l2cc_clean_region((uint32_t)set->table, (uint32_t)line);
return rc;
}
/**
* \brief Retrieve command response from the SDMMC peripheral.
* The response may be retrieved once per command.
*/
static void sdmmc_get_response(struct sdmmc_set *set, sSdmmcCommand *cmd,
bool complete, uint32_t *out)
{
assert(set);
assert(cmd);
assert(cmd->cmdOp.bmBits.respType <= 7);
assert(out);
const bool first_call = set->resp_len == 0;
const bool has_data = cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX
|| cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX;
uint32_t resp;
uint8_t ix;
if (first_call) {
switch (cmd->cmdOp.bmBits.respType) {
case 2:
/* R2 response is 120-bit long, split in
* 32+32+32+24 bits this way:
* RR[0] = R[ 39: 8]
* RR[1] = R[ 71: 40]
* RR[2] = R[103: 72]
* RR[3] = R[127:104]
* Shift data the way libsdmmc expects it,
* that is:
* pResp[0] = R[127: 96]
* pResp[1] = R[ 95: 64]
* pResp[2] = R[ 63: 32]
* pResp[3] = R[ 31: 0]
* The CRC7 and the end bit aren't provided,
* just hard-code their default values. */
out[3] = 0x000000ff;
for (ix = 0; ix < 4; ix++) {
resp = set->regs->SDMMC_RR[ix];
if (ix < 3)
out[2 - ix] = resp >> 24 & 0xff;
out[3 - ix] |= resp << 8 & 0xffffff00;
}
set->resp_len = 4;
break;
case 1: case 3: case 4: case 5: case 6: case 7:
/* The nominal response is 32-bit long */
out[0] = set->regs->SDMMC_RR[0];
set->resp_len = 1;
break;
case 0:
default:
break;
}
}
if (has_data && (cmd->bCmd == 18 || cmd->bCmd == 25) && ((first_call
&& set->use_set_blk_cnt) || (complete && !set->use_set_blk_cnt))) {
resp = set->regs->SDMMC_RR[3];
#if 0 && !defined(NDEBUG)
trace_debug("Auto CMD%d returned status 0x%lx\n\r",
set->use_set_blk_cnt ? 23 : 12, resp);
#endif
if (!set->use_set_blk_cnt)
/* We return a single response to the application: the
* device status returned by CMD18 or CMD25, combined
* with the device status just returned by Auto CMD12.
* Retain the status bits from only CMD18 or CMD25, and
* combine the exception bits from both. */
out[0] |= resp & ~STAT_DEVICE_IS_LOCKED
& ~STAT_CARD_ECC_DISABLED & ~STAT_CURRENT_STATE
& ~STAT_READY_FOR_DATA & ~STAT_EXCEPTION_EVENT
& ~STAT_APP_CMD;
#ifndef NDEBUG
resp = (resp & STAT_CURRENT_STATE) >> 9;
if (set->use_set_blk_cnt && resp != STATE_TRANSFER)
trace_warning("Auto CMD23 returned state %lx\n\r", resp)
else if (!set->use_set_blk_cnt && cmd->bCmd == 18
&& resp != STATE_SENDING_DATA)
trace_warning("CMD18 switched to state %lx\n\r", resp)
else if (!set->use_set_blk_cnt && cmd->bCmd == 25
&& resp != STATE_RECEIVE_DATA && resp != STATE_PROGRAMMING)
trace_warning("CMD25 switched to state %lx\n\r", resp)
#endif
}
}
/**
* \brief Fetch events from the SDMMC peripheral, handle them, and proceed to
* the subsequent step, w.r.t. the SD/MMC command being processed.
* \warning This implementation suits LITTLE ENDIAN hosts only.
*/
static void sdmmc_poll(struct sdmmc_set *set)
{
assert(set);
assert(set->state != MCID_OFF);
Sdmmc *regs = set->regs;
sSdmmcCommand *cmd = set->cmd;
uint16_t events, errors, acesr;
bool has_data;
if (set->state != MCID_CMD)
return;
assert(cmd);
has_data = cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX
|| cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX;
Fetch:
/* Fetch normal events */
events = regs->SDMMC_NISTR;
if (set->expect_auto_end && !(set->timer->TC_SR & TC_SR_CLKSTA))
events |= SDMMC_NISTR_CUSTOM_EVT;
if (!events)
return;
errors = 0;
/* Check the global error flag */
if (events & SDMMC_NISTR_ERRINT) {
errors = regs->SDMMC_EISTR;
events &= ~SDMMC_NISTR_ERRINT;
/* Clear error interrupts */
regs->SDMMC_EISTR = errors;
if (errors & SDMMC_EISTR_CURLIM)
cmd->bStatus = SDMMC_NOT_INITIALIZED;
else if (errors & SDMMC_EISTR_CMDCRC)
cmd->bStatus = SDMMC_ERR_IO;
else if (errors & SDMMC_EISTR_CMDTEO)
cmd->bStatus = SDMMC_NO_RESPONSE;
else if (errors & (SDMMC_EISTR_CMDEND | SDMMC_EISTR_CMDIDX))
cmd->bStatus = SDMMC_ERR_IO;
/* TODO if SDMMC_NISTR_TRFC and only SDMMC_EISTR_DATTEO then
* ignore SDMMC_EISTR_DATTEO */
else if (errors & SDMMC_EISTR_DATTEO)
cmd->bStatus = SDMMC_ERR_IO;
else if (errors & (SDMMC_EISTR_DATCRC | SDMMC_EISTR_DATEND))
cmd->bStatus = SDMMC_ERR_IO;
else if (errors & SDMMC_EISTR_ACMD) {
acesr = regs->SDMMC_ACESR;
if (acesr & SDMMC_ACESR_ACMD12NE)
cmd->bStatus = SDMMC_ERR;
else if (acesr & SDMMC_ACESR_ACMDCRC)
cmd->bStatus = SDMMC_ERR_IO;
else if (acesr & SDMMC_ACESR_ACMDTEO)
cmd->bStatus = SDMMC_NO_RESPONSE;
else if (acesr & (SDMMC_ACESR_ACMDEND | SDMMC_ACESR_ACMDIDX))
cmd->bStatus = SDMMC_ERR_IO;
else
cmd->bStatus = SDMMC_ERR;
}
else if (errors & SDMMC_EISTR_ADMA) {
cmd->bStatus = SDMMC_PARAM;
trace_error("ADMA error 0x%x at desc. line[%lu]\n\r",
regs->SDMMC_AESR, (regs->SDMMC_ASA0R -
(uint32_t)set->table) / (SDMMC_DMADL_SIZE * 4UL));
}
else if (errors & SDMMC_EISTR_BOOTAE)
cmd->bStatus = SDMMC_STATE;
else
cmd->bStatus = SDMMC_ERR;
set->state = cmd->bCmd == 12 ? MCID_LOCKED : MCID_ERROR;
/* Reset CMD and DAT lines.
* Resetting DAT lines also aborts the DMA transfer - if any -
* and resets the DMA circuit. */
regs->SDMMC_SRR |= SDMMC_SRR_SWRSTDAT | SDMMC_SRR_SWRSTCMD;
while (regs->SDMMC_SRR & (SDMMC_SRR_SWRSTDAT
| SDMMC_SRR_SWRSTCMD)) ;
trace_warning("CMD%u ended with error flags %04x, cmd status "
"%s\n\r", cmd->bCmd, errors, SD_StringifyRetCode(cmd->bStatus));
goto End;
}
/* No error. Give priority to the low-latency event that signals the
* completion of the Auto CMD12 command, hence of the whole multiple-
* block data transfer. */
if (events & SDMMC_NISTR_CUSTOM_EVT) {
#ifndef NDEBUG
if (!(set->regs->SDMMC_PSR & SDMMC_PSR_CMDLL))
trace_warning("Command still ongoing\n\r");
#endif
if (cmd->pResp)
sdmmc_get_response(set, cmd, true, cmd->pResp);
goto Succeed;
}
/* First, expect completion of the command */
if (events & SDMMC_NISTR_CMDC) {
/* Clear this normal interrupt */
regs->SDMMC_NISTR = SDMMC_NISTR_CMDC;
events &= ~SDMMC_NISTR_CMDC;
#ifndef NDEBUG
if (cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX
&& !set->table && set->blk_index != cmd->wNbBlocks
&& !(regs->SDMMC_PSR & SDMMC_PSR_WTACT))
trace_warning("Write transfer not started\n\r")
else if (cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX
&& !set->table && set->blk_index != cmd->wNbBlocks
&& !(regs->SDMMC_PSR & SDMMC_PSR_RTACT))
trace_warning("Read transfer not started\n\r")
#endif
/* Retrieve command response */
if (cmd->pResp)
sdmmc_get_response(set, cmd, false, cmd->pResp);
if (!has_data && !cmd->cmdOp.bmBits.checkBsy)
goto Succeed;
}
/* Expect the next incoming block of data */
if (events & SDMMC_NISTR_BRDRDY
&& cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX && !set->table) {
/* FIXME may be optimized by looping while PSR.BUFRDEN == 1 */
uint8_t *in, *out, *bound;
union uint32_u val;
uint16_t count;
/* Clear this normal interrupt */
regs->SDMMC_NISTR = SDMMC_NISTR_BRDRDY;
events &= ~SDMMC_NISTR_BRDRDY;
if (set->blk_index >= cmd->wNbBlocks) {
trace_error("Excess of incoming data\n\r");
cmd->bStatus = SDMMC_ERR_IO;
set->state = MCID_ERROR;
goto End;
}
out = cmd->pData + set->blk_index * (uint32_t)cmd->wBlockSize;
count = cmd->wBlockSize & ~0x3;
for (bound = out + count; out < bound; out += 4) {
#ifndef NDEBUG
if (!(regs->SDMMC_PSR & SDMMC_PSR_BUFRDEN))
trace_error("Unexpected Buffer Read Disable status\n\r");
#endif
val.word = regs->SDMMC_BDPR;
out[0] = val.bytes[0];
out[1] = val.bytes[1];
out[2] = val.bytes[2];
out[3] = val.bytes[3];
}
if (count < cmd->wBlockSize) {
#ifndef NDEBUG
if (!(regs->SDMMC_PSR & SDMMC_PSR_BUFRDEN))
trace_error("Unexpected Buffer Read Disable status\n\r");
#endif
val.word = regs->SDMMC_BDPR;
count = cmd->wBlockSize - count;
for (in = val.bytes, bound = out + count;
out < bound; in++, out++)
*out = *in;
}
#if 0 && !defined(NDEBUG)
if (regs->SDMMC_PSR & SDMMC_PSR_BUFRDEN)
trace_warning("Renewed Buffer Read Enable status\n\r");
#endif
set->blk_index++;
}
/* Expect the Buffer Data Port to be ready to accept the next
* outgoing block of data */
if (events & SDMMC_NISTR_BWRRDY
&& cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX && !set->table
&& set->blk_index < cmd->wNbBlocks) {
/* FIXME may be optimized by looping while PSR.BUFWREN == 1 */
uint8_t *in, *out, *bound;
union uint32_u val;
uint16_t count;
/* Clear this normal interrupt */
regs->SDMMC_NISTR = SDMMC_NISTR_BWRRDY;
events &= ~SDMMC_NISTR_BWRRDY;
in = cmd->pData + set->blk_index * (uint32_t)cmd->wBlockSize;
count = cmd->wBlockSize & ~0x3;
for (bound = in + count; in < bound; in += 4) {
val.bytes[0] = in[0];
val.bytes[1] = in[1];
val.bytes[2] = in[2];
val.bytes[3] = in[3];
#ifndef NDEBUG
if (!(regs->SDMMC_PSR & SDMMC_PSR_BUFWREN))
trace_error("Unexpected Buffer Write Disable status\n\r");
#endif
regs->SDMMC_BDPR = val.word;
}
if (count < cmd->wBlockSize) {
count = cmd->wBlockSize - count;
for (val.word = 0, out = val.bytes, bound = in + count;
in < bound; in++, out++)
*out = *in;
#ifndef NDEBUG
if (!(regs->SDMMC_PSR & SDMMC_PSR_BUFWREN))
trace_error("Unexpected Buffer Write Disable status\n\r");
#endif
regs->SDMMC_BDPR = val.word;
}
#if 0 && !defined(NDEBUG)
if (regs->SDMMC_PSR & SDMMC_PSR_BUFWREN)
trace_warning("Renewed Buffer Write Enable status\n\r");
#endif
set->blk_index++;
}
#ifndef NDEBUG
else if (events & SDMMC_NISTR_BWRRDY
&& cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX && !set->table
&& set->blk_index >= cmd->wNbBlocks)
trace_warning("Excess Buffer Write Ready status\n\r");
#endif
/* Expect completion of either the data transfer or the busy state. */
if (events & SDMMC_NISTR_TRFC) {
/* Deviation from the SD Host Controller Specification:
* the Auto CMD12 command/response (when enabled) is still in
* progress. We are on our own to figure out when CMD12 will
* have completed.
* In the meantime:
* 1. errors affecting the CMD12 command - essentially
* SDMMC_EISTR_ACMD - have not been detected yet.
* 2. SDMMC_RR[3] is not yet valid.
* Our workaround here consists in generating a third event
* further to Transfer Complete, after a predefined amount of
* time, sufficient for CMD12 to complete.
* Refer to sdmmc_send_command(), which has prepared our Timer/
* Counter for this purpose. */
if (has_data && (cmd->bCmd == 18 || cmd->bCmd == 25)
&& !set->use_set_blk_cnt) {
set->timer->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
set->expect_auto_end = true;
}
/* Clear this normal interrupt */
regs->SDMMC_NISTR = SDMMC_NISTR_TRFC;
events &= ~SDMMC_NISTR_TRFC;
/* Deviation from the SD Host Controller Specification:
* there are cases, notably CMD7 with address and R1b, where the
* Command Complete interrupt does not occur. In such cases,
* the command response has not been retrieved yet. */
if (!set->expect_auto_end && cmd->pResp)
sdmmc_get_response(set, cmd, true, cmd->pResp);
#ifndef NDEBUG
if (regs->SDMMC_PSR & SDMMC_PSR_WTACT)
trace_error("Write transfer still active\n\r");
if (regs->SDMMC_PSR & SDMMC_PSR_RTACT)
trace_error("Read transfer still active\n\r");
#endif
if (has_data && !set->table
&& set->blk_index != cmd->wNbBlocks) {
trace_error("Incomplete data transfer\n\r");
cmd->bStatus = SDMMC_ERR_IO;
set->state = MCID_ERROR;
goto End;
}
else if (has_data && set->table
&& cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX)
l2cc_invalidate_region((uint32_t)cmd->pData,
(uint32_t)cmd->pData + (uint32_t)cmd->wNbBlocks
* (uint32_t)cmd->wBlockSize);
if (!set->expect_auto_end)
goto Succeed;
}
#ifndef NDEBUG
if (events)
trace_warning("Unhandled NISTR events: 0x%04x\n\r", events);
#endif
if (events)
regs->SDMMC_NISTR = events;
goto Fetch;
Succeed:
set->state = MCID_LOCKED;
End:
/* Clear residual normal interrupts, if any */
if (events)
regs->SDMMC_NISTR = events;
#if 0 && !defined(NDEBUG)
if (set->resp_len == 1)
trace_debug("CMD%u got response %08lx\n\r", cmd->bCmd,
cmd->pResp[0])
else if (set->resp_len == 4)
trace_debug("CMD%u got response %08lx %08lx %08lx %08lx\n\r",
cmd->bCmd, cmd->pResp[0], cmd->pResp[1], cmd->pResp[2],
cmd->pResp[3])
#endif
/* Release command */
set->cmd = NULL;
set->resp_len = 0;
set->blk_index = 0;
set->expect_auto_end = false;
/* Invoke the end-of-command fSdmmcCallback function, if provided */
if (cmd->fCallback)
(cmd->fCallback)(cmd->bStatus, cmd->pArg);
}
/**
* \brief Check if the command is finished.
*/
static bool sdmmc_is_busy(struct sdmmc_set *set)
{
assert(set->state != MCID_OFF);
if (set->use_polling)
sdmmc_poll(set);
if (set->state == MCID_CMD)
return true;
return false;
}
static uint8_t sdmmc_cancel_command(struct sdmmc_set *set)
{
assert(set);
assert(set->state != MCID_OFF);
Sdmmc *regs = set->regs;
sSdmmcCommand *cmd = set->cmd;
uint32_t response; /* The R1 response is 32-bit long */
uint32_t timer_res_prv, usec, rc;
sSdmmcCommand stop_cmd = {
.pResp = &response,
.cmdOp.wVal = SDMMC_CMD_CSTOP | SDMMC_CMD_bmBUSY,
.bCmd = 12,
};
if (set->state != MCID_CMD && set->state != MCID_ERROR)
return SDMMC_STATE;
trace_debug("Requested to cancel CMD%u\n\r", set->cmd ? set->cmd->bCmd : 99);
if (set->state == MCID_ERROR) {
set->state = MCID_LOCKED;
return SDMMC_OK;
}
assert(cmd);
/* Asynchronous Abort, if a data transfer has been started */
if (cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX
|| cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX) {
/* May the CMD line still be busy, reset it */
if (regs->SDMMC_PSR & SDMMC_PSR_CMDINHC) {
regs->SDMMC_SRR |= SDMMC_SRR_SWRSTCMD;
while (regs->SDMMC_SRR & SDMMC_SRR_SWRSTCMD) ;
}
/* Issue the STOP_TRANSMISSION command. */
set->state = MCID_LOCKED;
set->cmd = NULL;
set->resp_len = 0;
set->blk_index = 0;
set->expect_auto_end = false;
rc = sdmmc_send_command(set, &stop_cmd);
if (rc == SDMMC_OK) {
timer_res_prv = timer_get_resolution();
timer_configure(10);
for (usec = 0; set->state == MCID_CMD && usec < 500000; usec+= 10) {
timer_sleep(1);
sdmmc_poll(set);
}
timer_configure(timer_res_prv);
}
}
/* Reset CMD and DATn lines */
regs->SDMMC_SRR |= SDMMC_SRR_SWRSTDAT | SDMMC_SRR_SWRSTCMD;
while (regs->SDMMC_SRR & (SDMMC_SRR_SWRSTDAT | SDMMC_SRR_SWRSTCMD)) ;
/* Release command */
cmd->bStatus = SDMMC_ERROR_USER_CANCEL;
set->state = MCID_LOCKED;
set->cmd = NULL;
set->resp_len = 0;
set->blk_index = 0;
set->expect_auto_end = false;
/* Invoke the end-of-command fSdmmcCallback function, if provided */
if (cmd->fCallback)
(cmd->fCallback)(cmd->bStatus, cmd->pArg);
return SDMMC_OK;
}
/*----------------------------------------------------------------------------
* HAL for the SD/MMC library
*----------------------------------------------------------------------------*/
/**
* Here is the fSdmmcLock-type callback.
* Lock the driver for slot N access.
* TODO implement, once used by the library.
*/
static uint32_t sdmmc_lock(void *_set, uint8_t slot)
{
assert(_set);
if (slot > 0) {
return SDMMC_ERROR_PARAM;
}
return SDMMC_OK;
}
/**
* Here is the fSdmmcRelease-type callback.
* Release the driver.
* TODO implement, once used by the library.
*/
static uint32_t sdmmc_release(void *_set)
{
assert(_set);
return SDMMC_OK;
}
/**
* Here is the fSdmmcIOCtrl-type callback.
* IO control functions.
* \param _set Pointer to driver instance data (struct sdmmc_set).
* \param bCtl IO control code.
* \param param IO control parameter. Optional, depends on the IO control code.
* \return Return code, from the eSDMMC_RC enumeration.
*/
static uint32_t sdmmc_control(void *_set, uint32_t bCtl, uint32_t param)
{
assert(_set);
struct sdmmc_set *set = (struct sdmmc_set *)_set;
uint32_t rc = SDMMC_OK, *param_u32 = (uint32_t *)param;
uint8_t byte;
#ifndef NDEBUG
if (bCtl != SDMMC_IOCTL_BUSY_CHECK && bCtl != SDMMC_IOCTL_GET_DEVICE)
trace_debug("%s(%lu)\n\r", SD_StringifyIOCtrl(bCtl),
param ? *param_u32 : 0);
#endif
switch (bCtl) {
case SDMMC_IOCTL_GET_DEVICE:
if (!param)
return SDMMC_ERROR_PARAM;
*param_u32 = set->regs->SDMMC_PSR & SDMMC_PSR_CARDINS ? 1 : 0;
break;
case SDMMC_IOCTL_POWER:
if (!param)
return SDMMC_ERROR_PARAM;
sdmmc_power_device(set, *param_u32 ? true : false);
break;
case SDMMC_IOCTL_RESET:
/* Release the device. The device may have been removed. */
sdmmc_power_device(set, false);
break;
case SDMMC_IOCTL_GET_BUSMODE:
if (!param)
return SDMMC_ERROR_PARAM;
byte = sdmmc_get_bus_width(set);
*param_u32 = byte;
break;
case SDMMC_IOCTL_SET_BUSMODE:
if (!param)
return SDMMC_ERROR_PARAM;
if (*param_u32 > 0xff)
return SDMMC_ERROR_PARAM;
rc = sdmmc_set_bus_width(set, (uint8_t)*param_u32);
byte = sdmmc_get_bus_width(set);
trace_debug("Using a %u-bit data bus\n\r", byte);
break;
case SDMMC_IOCTL_GET_HSMODE:
if (!param)
return SDMMC_ERROR_PARAM;
*param_u32 = set->regs->SDMMC_CA0R & SDMMC_CA0R_HSSUP ? 1 : 0;
break;
case SDMMC_IOCTL_SET_HSMODE:
if (!param)
return SDMMC_ERROR_PARAM;
rc = sdmmc_set_speed_mode(set, *param_u32 ? true : false);
*param_u32 = sdmmc_get_speed_mode(set) ? 1 : 0;
trace_debug("Using %s mode\n\r", *param_u32 ? "High Speed" : "Default Speed");
break;
case SDMMC_IOCTL_SET_CLOCK:
if (!param)
return SDMMC_ERROR_PARAM;
if (*param_u32 == 0)
return SDMMC_ERROR_PARAM;
sdmmc_set_device_clock(set, *param_u32);
trace_debug("Clocking the device at %lu Hz\n\r", set->dev_freq);
if (set->dev_freq != *param_u32) {
rc = SDMMC_CHANGED;
*param_u32 = set->dev_freq;
}
break;
case SDMMC_IOCTL_SET_LENPREFIX:
if (!param)
return SDMMC_ERROR_PARAM;
set->use_set_blk_cnt = *param_u32 ? true : false;
*param_u32 = set->use_set_blk_cnt ? 1 : 0;
break;
case SDMMC_IOCTL_GET_XFERCOMPL:
if (!param)
return SDMMC_ERROR_PARAM;
*param_u32 = 1;
break;
case SDMMC_IOCTL_BUSY_CHECK:
if (!param)
return SDMMC_ERROR_PARAM;
if (set->state == MCID_OFF)
*param_u32 = 0;
else
*param_u32 = sdmmc_is_busy(set) ? 1 : 0;
break;
case SDMMC_IOCTL_CANCEL_CMD:
if (set->state == MCID_OFF)
rc = SDMMC_STATE;
else
rc = sdmmc_cancel_command(set);
break;
case SDMMC_IOCTL_GET_CLOCK:
case SDMMC_IOCTL_SET_BOOTMODE:
case SDMMC_IOCTL_GET_BOOTMODE:
default:
rc = SDMMC_ERROR_NOT_SUPPORT;
break;
}
#ifndef NDEBUG
if (rc != SDMMC_OK && rc != SDMMC_CHANGED && bCtl != SDMMC_IOCTL_BUSY_CHECK)
trace_error("%s ended with %s\n\r", SD_StringifyIOCtrl(bCtl), SD_StringifyRetCode(rc));
#endif
return rc;
}
/**
* Here is the fSdmmcSendCommand-type callback.
* SD/MMC command.
* \param _set Pointer to driver instance data (struct sdmmc_set).
* \param cmd Pointer to the command to be sent. Owned by the caller. Shall
* remain valid until the command is completed or stopped. For commands which
* transfer data, mind the peripheral and DMA alignment requirements that the
* external data buffer shall meet. Especially when DMA is used to read from the
* device, in which case the buffer shall be aligned on entire cache lines.
* \return Return code, from the eSDMMC_RC enumeration. If SDMMC_OK, the command
* has been issued and the caller should:
* 1. poll on sdmmc_is_busy(),
* 2. once finished, check the result of the command in cmd->bStatus.
* TODO in future when libsdmmc will set it: call sSdmmcCommand::fCallback.
*/
static uint32_t sdmmc_send_command(void *_set, sSdmmcCommand *cmd)
{
assert(_set);
assert(cmd);
assert(cmd->bCmd <= 63);
struct sdmmc_set *set = (struct sdmmc_set *)_set;
Sdmmc *regs = set->regs;
const bool stop_xfer = cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_STOPXFR;
const bool has_data = cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX
|| cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_RX;
const bool multiple_xfer = cmd->bCmd == 18 || cmd->bCmd == 25;
const bool blk_count_prefix = (cmd->bCmd == 18 || cmd->bCmd == 25)
&& set->use_set_blk_cnt;
const bool stop_xfer_suffix = (cmd->bCmd == 18 || cmd->bCmd == 25)
&& !set->use_set_blk_cnt;
uint32_t timer_res_prv, usec, eister, mask, cycles;
uint16_t cr, tmr;
uint8_t rc = SDMMC_OK, mc1r;
if (set->state == MCID_OFF)
return SDMMC_STATE;
if (cmd->cmdOp.bmBits.powerON == cmd->cmdOp.bmBits.sendCmd) {
trace_error("Invalid command\n\r");
return SDMMC_ERROR_PARAM;
}
if (stop_xfer && cmd->bCmd != 12 && cmd->bCmd != 52) {
trace_error("Inconsistent abort command\n\r");
return SDMMC_ERROR_PARAM;
}
if (cmd->cmdOp.bmBits.powerON) {
/* Special call, no command to send this time */
/* Wait for 74 SD Clock cycles, as per SD Card specification.
* The e.MMC Electrical Standard specifies tRSCA >= 200 usec. */
if (set->dev_freq == 0) {
trace_error("Shall enable the device clock first\n\r");
return SDMMC_ERROR_STATE;
}
timer_res_prv = timer_get_resolution();
usec = ROUND_INT_DIV(74 * 1000000UL, set->dev_freq);
timer_configure(usec < 200 ? 200 : usec);
timer_sleep(1);
timer_configure(timer_res_prv);
return SDMMC_OK;
}
if (has_data && (cmd->wNbBlocks == 0 || cmd->wBlockSize == 0
|| cmd->pData == NULL)) {
trace_error("Invalid data\n\r");
return SDMMC_ERROR_PARAM;
}
if (has_data && cmd->wBlockSize > set->blk_size) {
trace_error("%u-byte data block size not supported\n\r", cmd->wBlockSize);
return SDMMC_ERROR_PARAM;
}
if (has_data && set->table) {
/* Using DMA. Prepare the descriptor table. */
rc = sdmmc_build_dma_table(set, cmd);
if (rc != SDMMC_OK && rc != SDMMC_CHANGED)
return rc;
if (cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX)
/* Ensure the outgoing data can be fetched directly from
* RAM */
l2cc_clean_region((uint32_t)cmd->pData,
(uint32_t)cmd->pData + (uint32_t)cmd->wNbBlocks
* (uint32_t)cmd->wBlockSize);
}
if (multiple_xfer && !has_data)
trace_warning("Inconsistent data\n\r");
if (sdmmc_is_busy(set)) {
trace_error("Concurrent command\n\r");
return SDMMC_ERROR_BUSY;
}
set->state = MCID_CMD;
set->cmd = cmd;
set->resp_len = 0;
set->blk_index = 0;
set->expect_auto_end = false;
cmd->bStatus = rc;
tmr = (regs->SDMMC_TMR & ~SDMMC_TMR_MSBSEL & ~SDMMC_TMR_DTDSEL
& ~SDMMC_TMR_ACMDEN_Msk & ~SDMMC_TMR_BCEN & ~SDMMC_TMR_DMAEN)
| SDMMC_TMR_ACMDEN_DIS;
mc1r = (regs->SDMMC_MC1R & ~SDMMC_MC1R_OPD & ~SDMMC_MC1R_CMDTYP_Msk)
| SDMMC_MC1R_CMDTYP_NORMAL;
cr = (regs->SDMMC_CR & ~SDMMC_CR_CMDIDX_Msk & ~SDMMC_CR_CMDTYP_Msk
& ~SDMMC_CR_DPSEL & ~SDMMC_CR_RESPTYP_Msk)
| SDMMC_CR_CMDIDX(cmd->bCmd) | SDMMC_CR_CMDTYP_NORMAL
| SDMMC_CR_CMDICEN | SDMMC_CR_CMDCCEN;
eister = SDMMC_EISTER_BOOTAE | SDMMC_EISTER_ADMA
| SDMMC_EISTER_ACMD | SDMMC_EISTER_CURLIM | SDMMC_EISTER_DATEND
| SDMMC_EISTER_DATCRC | SDMMC_EISTER_DATTEO | SDMMC_EISTER_CMDIDX
| SDMMC_EISTER_CMDEND | SDMMC_EISTER_CMDCRC | SDMMC_EISTER_CMDTEO;
if (cmd->cmdOp.bmBits.odON)
mc1r |= SDMMC_MC1R_OPD;
switch (cmd->cmdOp.bmBits.respType) {
case 2:
cr |= SDMMC_CR_RESPTYP_RL136;
/* R2 response doesn't include the command index */
eister &= ~SDMMC_EISTER_CMDIDX;
break;
case 3:
/* R3 response includes neither the command index nor the CRC */
eister &= ~(SDMMC_EISTER_CMDIDX | SDMMC_EISTER_CMDCRC);
case 1:
case 4:
if (cmd->cmdOp.bmBits.respType == 4 && cmd->cmdOp.bmBits.ioCmd)
/* SDIO R4 response includes neither the command index nor the CRC */
eister &= ~(SDMMC_EISTER_CMDIDX | SDMMC_EISTER_CMDCRC);
case 5:
case 6:
case 7:
cr |= cmd->cmdOp.bmBits.checkBsy ? SDMMC_CR_RESPTYP_RL48BUSY
: SDMMC_CR_RESPTYP_RL48;
break;
default:
/* No response, ignore response time-out error */
cr |= SDMMC_CR_RESPTYP_NORESP;
eister &= ~SDMMC_EISTER_CMDTEO;
break;
}
if (stop_xfer) {
tmr |= SDMMC_TMR_MSBSEL | SDMMC_TMR_BCEN;
/* TODO consider BGCR:STPBGR (pause) */
/* TODO in case of SDIO consider CR:CMDTYP = ABORT */
/* Ignore data errors */
eister = eister & ~SDMMC_EISTER_ADMA & ~SDMMC_EISTER_DATEND
& ~SDMMC_EISTER_DATCRC & ~SDMMC_EISTER_DATTEO;
}
else if (has_data) {
cr |= SDMMC_CR_DPSEL;
tmr |= cmd->cmdOp.bmBits.xfrData == SDMMC_CMD_TX
? SDMMC_TMR_DTDSEL_WR : SDMMC_TMR_DTDSEL_RD;
if (blk_count_prefix)
tmr = (tmr & ~SDMMC_TMR_ACMDEN_Msk)
| SDMMC_TMR_ACMDEN_ACMD23;
else if (stop_xfer_suffix)
tmr = (tmr & ~SDMMC_TMR_ACMDEN_Msk)
| SDMMC_TMR_ACMDEN_ACMD12;
/* TODO check if this is fine for SDIO too (byte or block transfer) (cmd->cmdOp.bmBits.ioCmd, cmd->wBlockSize) */
if (multiple_xfer || cmd->wNbBlocks > 1)
tmr |= SDMMC_TMR_MSBSEL | SDMMC_TMR_BCEN;
if (set->table)
tmr |= SDMMC_TMR_DMAEN;
}
/* Enable normal interrupts */
regs->SDMMC_NISTER |= SDMMC_NISTER_BRDRDY | SDMMC_NISTER_BWRRDY
| SDMMC_NISTER_TRFC | SDMMC_NISTER_CMDC;
assert(!(regs->SDMMC_NISTER & SDMMC_NISTR_CUSTOM_EVT));
/* Enable error interrupts */
regs->SDMMC_EISTER = eister;
/* Clear all interrupt status flags */
regs->SDMMC_NISTR = SDMMC_NISTR_ERRINT | SDMMC_NISTR_BOOTAR
| SDMMC_NISTR_CINT | SDMMC_NISTR_CREM | SDMMC_NISTR_CINS
| SDMMC_NISTR_BRDRDY | SDMMC_NISTR_BWRRDY | SDMMC_NISTR_DMAINT
| SDMMC_NISTR_BLKGE | SDMMC_NISTR_TRFC | SDMMC_NISTR_CMDC;
regs->SDMMC_EISTR = SDMMC_EISTR_BOOTAE | SDMMC_EISTR_ADMA
| SDMMC_EISTR_ACMD | SDMMC_EISTR_CURLIM | SDMMC_EISTR_DATEND
| SDMMC_EISTR_DATCRC | SDMMC_EISTR_DATTEO | SDMMC_EISTR_CMDIDX
| SDMMC_EISTR_CMDEND | SDMMC_EISTR_CMDCRC | SDMMC_EISTR_CMDTEO;
/* Wait for the CMD and DATn lines to be ready */
mask = SDMMC_PSR_CMDINHC;
if (has_data || (cmd->cmdOp.bmBits.checkBsy && !stop_xfer))
mask |= SDMMC_PSR_CMDINHD;
while (regs->SDMMC_PSR & mask) ;
/* Issue the command */
if (has_data) {
if (blk_count_prefix)
regs->SDMMC_SSAR = SDMMC_SSAR_ARG2(cmd->wNbBlocks);
if (set->table)
regs->SDMMC_ASA0R =
SDMMC_ASA0R_ADMASA((uint32_t)set->table);
regs->SDMMC_BSR = (regs->SDMMC_BSR & ~SDMMC_BSR_BLKSIZE_Msk)
| SDMMC_BSR_BLKSIZE(cmd->wBlockSize);
}
if (stop_xfer)
regs->SDMMC_BCR = SDMMC_BCR_BLKCNT(0);
else if (has_data && (multiple_xfer || cmd->wNbBlocks > 1))
regs->SDMMC_BCR = SDMMC_BCR_BLKCNT(cmd->wNbBlocks);
regs->SDMMC_ARG1R = cmd->dwArg;
if (has_data || stop_xfer)
regs->SDMMC_TMR = tmr;
regs->SDMMC_MC1R = mc1r;
regs->SDMMC_CR = cr;
/* In the case of Auto CMD12, we'll need to generate an extra event.
* Have our Timer/Counter ready for this. */
if (has_data && stop_xfer_suffix) {
/* Considering the multiple block read mode,
* 1. Assuming Transfer Complete is raised upon successful
* reception of the End bit of the last data packet,
* 2. A SD/eMMC protocol analyzer shows that the CMD12 command
* token is fully transmitted 1 or 2 device clock cycles
* later,
* 3. The device may take up to 64 clock cycles (NCR) before
* initiating the CMD12 response token,
* 4. The code length of the CMD12 response token (R1) is 48
* bits, hence 48 device clock cycles.
* The sum of the above timings is the maximum time CMD12 will
* take to complete. */
cycles = pmc_get_peripheral_clock(set->tc_id)
/ (set->dev_freq / (2ul + 64ul + 48ul));
/* The Timer operates with RC >= 1 */
set->timer->TC_RC = cycles ? cycles : 1;
}
return SDMMC_OK;
}
static sSdHalFunctions sdHal = {
.fLock = sdmmc_lock,
.fRelease = sdmmc_release,
.fCommand = sdmmc_send_command,
.fIOCtrl = sdmmc_control,
};
/*----------------------------------------------------------------------------
* Exported functions
*----------------------------------------------------------------------------*/
bool sdmmc_initialize(struct sdmmc_set *set, Sdmmc *regs, uint32_t periph_id,
uint32_t tc_id, uint32_t tc_ch, uint32_t *dma_buf, uint32_t dma_buf_size)
{
assert(set);
assert(regs);
assert(periph_id <= 0xff);
assert(tc_ch < TCCHANNEL_NUMBER);
Tc * const tc_module = get_tc_addr_from_id(tc_id);
uint32_t base_freq, power, val;
const uint8_t max_exp = (SDMMC_TCR_DTCVAL_Msk >> SDMMC_TCR_DTCVAL_Pos) - 1;
uint8_t exp;
assert(tc_module);
memset(set, 0, sizeof(*set));
set->id = periph_id;
set->regs = regs;
set->tc_id = tc_id;
set->timer = &tc_module->TC_CHANNEL[tc_ch];
set->table_size = dma_buf ? dma_buf_size / SDMMC_DMADL_SIZE : 0;
set->table = set->table_size ? dma_buf : NULL;
set->use_polling = true;
set->use_set_blk_cnt = false;
set->state = MCID_OFF;
val = (regs->SDMMC_CA0R & SDMMC_CA0R_MAXBLKL_Msk) >> SDMMC_CA0R_MAXBLKL_Pos;
set->blk_size = val <= 0x2 ? 512 << val : 512;
/* Prepare our Timer/Counter */
tc_configure(tc_module, tc_ch, TC_CMR_WAVE | TC_CMR_WAVSEL_UP
| TC_CMR_CPCDIS | TC_CMR_BURST_NONE | TC_CMR_TCCLKS_TIMER_CLOCK2);
set->timer->TC_EMR |= TC_EMR_NODIVCLK;
/* Perform the initial I/O calibration sequence, manually.
* Allow tSTARTUP = 2 usec for the analog circuitry to start up.
* CNTVAL = fHCLOCK / (4 * (1 / tSTARTUP)) */
val = pmc_get_peripheral_clock(periph_id);
val = ROUND_INT_DIV(val, 4 * 500000UL);
assert(!(val << SDMMC_CALCR_CNTVAL_Pos & ~SDMMC_CALCR_CNTVAL_Msk));
regs->SDMMC_CALCR = (regs->SDMMC_CALCR & ~SDMMC_CALCR_CNTVAL_Msk) | SDMMC_CALCR_CNTVAL(val);
regs->SDMMC_CALCR |= SDMMC_CALCR_EN;
while (regs->SDMMC_CALCR & SDMMC_CALCR_EN) ;
val = regs->SDMMC_CALCR;
trace_info("Result of output impedance calibration: CALN=%lu, CALP=%lu.\n\r",
(val & SDMMC_CALCR_CALN_Msk) >> SDMMC_CALCR_CALN_Pos,
(val & SDMMC_CALCR_CALP_Msk) >> SDMMC_CALCR_CALP_Pos);
/* Set DAT line timeout error to occur after 500 ms waiting delay.
* 500 ms is the timeout value to implement when writing to SDXC cards.
*/
base_freq = (regs->SDMMC_CA0R & SDMMC_CA0R_TEOCLKF_Msk) >> SDMMC_CA0R_TEOCLKF_Pos;
base_freq *= regs->SDMMC_CA0R & SDMMC_CA0R_TEOCLKU ? 1000000UL : 1000UL;
/* 2 ^ (DTCVAL + 13) = TIMEOUT * FTEOCLK = FTEOCLK / 2 */
val = base_freq / 2;
for (exp = 31, power = 1UL << 31; !(val & power) && power != 0;
exp--, power >>= 1) ;
if (power == 0) {
trace_warning("FTEOCLK is unknown\n\r");
exp = max_exp;
}
else {
exp = exp + 1 - 13;
exp = exp <= max_exp ? exp : max_exp;
}
regs->SDMMC_TCR = (regs->SDMMC_TCR & ~SDMMC_TCR_DTCVAL_Msk)
| SDMMC_TCR_DTCVAL(exp);
trace_debug("Set DAT line timeout to %lu ms\n\r", (10UL << (exp + 13UL))
/ (base_freq / 100UL));
/* Reset the peripheral. This will reset almost all registers.
* It doesn't affect I/O calibration however. */
sdmmc_reset_peripheral(set);
/* As sdmmc_reset_peripheral deliberately preserves MC1R.FCD, this field
* has yet to be initialized. */
regs->SDMMC_MC1R &= ~SDMMC_MC1R_FCD;
return true;
}
/**
* \brief Initialize the SD/MMC library instance for SD/MMC bus mode (versus
* SPI mode, not supported by this driver). Provide it with the HAL callback
* functions implemented here.
* \param pSd Pointer to SD/MMC library instance data.
* \param pDrv Pointer to driver instance data (struct sdmmc_set).
* \param bSlot Slot number.
*/
void SDD_InitializeSdmmcMode(sSdCard *pSd, void *pDrv, uint8_t bSlot)
{
SDD_Initialize(pSd, pDrv, bSlot, &sdHal);
}