/* ---------------------------------------------------------------------------- | |
* 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); | |
} |