diff --git a/boards/slwstk6220a/include/periph_conf.h b/boards/slwstk6220a/include/periph_conf.h index 34a9c29b39..b9ec74f81f 100644 --- a/boards/slwstk6220a/include/periph_conf.h +++ b/boards/slwstk6220a/include/periph_conf.h @@ -83,6 +83,7 @@ static const adc_chan_conf_t adc_channel_config[] = { static const dac_conf_t dac_config[] = { { .dev = DAC0, + .ref = dacRefVDD, .cmu = cmuClock_DAC0, } }; @@ -91,7 +92,6 @@ static const dac_chan_conf_t dac_channel_config[] = { { .dev = 0, .index = 1, - .ref = dacRefVDD, } }; diff --git a/boards/stk3600/include/periph_conf.h b/boards/stk3600/include/periph_conf.h index 45b00afad1..021663b7e0 100644 --- a/boards/stk3600/include/periph_conf.h +++ b/boards/stk3600/include/periph_conf.h @@ -84,6 +84,7 @@ static const adc_chan_conf_t adc_channel_config[] = { static const dac_conf_t dac_config[] = { { .dev = DAC0, + .ref = dacRefVDD, .cmu = cmuClock_DAC0, } }; @@ -92,7 +93,6 @@ static const dac_chan_conf_t dac_channel_config[] = { { .dev = 0, .index = 1, - .ref = dacRefVDD, } }; diff --git a/boards/stk3700/include/periph_conf.h b/boards/stk3700/include/periph_conf.h index 9df1df3965..2b2e51bd00 100644 --- a/boards/stk3700/include/periph_conf.h +++ b/boards/stk3700/include/periph_conf.h @@ -84,6 +84,7 @@ static const adc_chan_conf_t adc_channel_config[] = { static const dac_conf_t dac_config[] = { { .dev = DAC0, + .ref = dacRefVDD, .cmu = cmuClock_DAC0, } }; @@ -92,7 +93,6 @@ static const dac_chan_conf_t dac_channel_config[] = { { .dev = 0, .index = 1, - .ref = dacRefVDD, } }; diff --git a/cpu/efm32/include/periph_cpu.h b/cpu/efm32/include/periph_cpu.h index b60018246d..77952752ea 100644 --- a/cpu/efm32/include/periph_cpu.h +++ b/cpu/efm32/include/periph_cpu.h @@ -79,6 +79,7 @@ typedef struct { */ typedef struct { DAC_TypeDef *dev; /**< DAC device used */ + DAC_Ref_TypeDef ref; /**< DAC voltage reference */ CMU_Clock_TypeDef cmu; /**< the device CMU channel */ } dac_conf_t; @@ -88,7 +89,6 @@ typedef struct { typedef struct { uint8_t dev; /**< device index */ uint8_t index; /**< channel index */ - DAC_Ref_TypeDef ref; /**< channel voltage reference */ } dac_chan_conf_t; #endif diff --git a/cpu/efm32/periph/dac.c b/cpu/efm32/periph/dac.c index 7d5a412385..8d3773bafd 100644 --- a/cpu/efm32/periph/dac.c +++ b/cpu/efm32/periph/dac.c @@ -45,6 +45,7 @@ int8_t dac_init(dac_t line) /* reset and initialize peripheral */ DAC_Init_TypeDef init = DAC_INIT_DEFAULT; + init.reference = dac_config[dev].ref; DAC_Reset(dac_config[dev].dev); DAC_Init(dac_config[dev].dev, &init); diff --git a/cpu/sam0_common/include/sdhc.h b/cpu/sam0_common/include/sdhc.h index f9ed76e582..3691eb0e40 100644 --- a/cpu/sam0_common/include/sdhc.h +++ b/cpu/sam0_common/include/sdhc.h @@ -95,6 +95,7 @@ typedef struct { /** This SD stack uses the maximum block size authorized (512 bytes) */ #define SD_MMC_BLOCK_SIZE 512 /**< SD card block size */ #define SDHC_SLOW_CLOCK_HZ 400000 /**< Clock frequency on init */ +#define SDHC_FAST_CLOCK_HZ 25000000 /**< Clock frequency after init */ /** * @brief Initialize the SD host controller diff --git a/cpu/sam0_common/sam0_sdhc/sdhc.c b/cpu/sam0_common/sam0_sdhc/sdhc.c index 95f04e67a9..d4252a17a0 100644 --- a/cpu/sam0_common/sam0_sdhc/sdhc.c +++ b/cpu/sam0_common/sam0_sdhc/sdhc.c @@ -50,6 +50,7 @@ #include #include "periph_cpu.h" #include "periph/pm.h" +#include "macros/math.h" #include "vendor/sd_mmc_protocol.h" #include "sdhc.h" #include "time_units.h" @@ -66,6 +67,10 @@ #define SDHC_CLOCK_SLOW SAM0_GCLK_TIMER #endif +#ifndef SDHC_ENABLE_HS +#define SDHC_ENABLE_HS 1 +#endif + /** * @brief The board can overwrite this if only a single SDHC instance is used * to save 80 Bytes of ROM. @@ -98,17 +103,18 @@ static bool _init_transfer(sdhc_state_t *state, uint32_t cmd, uint32_t arg, uint uint16_t num_blocks); static bool sdio_test_type(sdhc_state_t *state); -/** SD/MMC transfer rate unit codes (10K) list */ -static const uint32_t transfer_units[] = { 10, 100, 1000, 10000, 0, 0, 0, 0 }; -/** SD transfer multiplier factor codes (1/10) list */ -static const uint8_t transfer_multiplier[16] = -{ 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; - static bool _card_detect(sdhc_state_t *state) { return state->dev->PSR.bit.CARDINS; } +static inline void _clock_sdcard(sdhc_state_t *state, bool on) +{ + (void)state; + + SDHC_DEV->CCR.bit.SDCLKEN = on; +} + static bool _check_mask(uint32_t val, uint32_t mask) { return (val & mask) == mask; @@ -126,26 +132,73 @@ static void _delay(unsigned us) } } -static void _reset_all(sdhc_state_t *state) + +/** + * @brief Reset the entire SDHC peripheral or a part of it + * + * @param state SDHC device context + * @param type Reset type + * [SDHC_SRR_SWRSTALL | SDHC_SRR_SWRSTCMD | SDHC_SRR_SWRSTDAT] + */ +static void _reset_sdhc(sdhc_state_t *state, uint8_t type) { - SDHC_DEV->SRR.reg = SDHC_SRR_SWRSTALL; - while (SDHC_DEV->SRR.bit.SWRSTALL) {} - state->need_init = true; - state->error = 0; + SDHC_DEV->SRR.reg = type; + while (SDHC_DEV->SRR.reg & type) {} + + if (type == SDHC_SRR_SWRSTALL) { + /* trigger card_init */ + state->need_init = true; + state->error = 0; + } } -static uint32_t _wait_for_event(sdhc_state_t *state) +/** + * @brief Wait for a given event while checking for errors + * + * @param state SDHC device context + * @param event Event to wait for [SDHC_NISTR_*] + * @param error_mask Mask of errors to be checked [SDHC_EISTR_*] + * @param reset Reset type in case of errors + * [SDHC_SRR_SWRSTALL | SDHC_SRR_SWRSTCMD | SDHC_SRR_SWRSTDAT] + * + * @return true if event occurred or false on error + */ +static bool _wait_for_event(sdhc_state_t *state, + uint16_t event, uint16_t error_mask, + uint8_t reset) { - uint32_t res; + /* wait for the event given by event mask */ + do { + /* check for any error in error mask */ + if (SDHC_DEV->EISTR.reg & error_mask) { + state->error = SDHC_DEV->EISTR.reg; + SDHC_DEV->EISTR.reg = SDHC_EISTR_MASK; + if (IS_USED(ENABLE_DEBUG)) { + DEBUG("sdhc error: %x, ", state->error); + switch (reset) { + case SDHC_SRR_SWRSTCMD: + DEBUG("reset CMD\n"); + break; + case SDHC_SRR_SWRSTDAT: + DEBUG("reset DAT\n"); + break; + case SDHC_SRR_SWRSTALL: + DEBUG("reset ALL\n"); + break; + default: + assert(false); + break; + } + } + _reset_sdhc(state, reset); + return false; + } + } while (!(SDHC_DEV->NISTR.reg & event)); - /* SDHC runs off CPU clock - block IDLE so that the clock does not stop */ - pm_block(3); - mutex_lock(&state->sync); - pm_unblock(3); + /* clear the event */ + SDHC_DEV->NISTR.reg = event; - res = state->error; - state->error = 0; - return res; + return true; } static void _init_clocks(sdhc_state_t *state) @@ -207,6 +260,7 @@ int sdhc_init(sdhc_state_t *state) { bool f8; uint32_t response; + int res = 0; /* set the initial clock slow, single bit and normal speed */ state->type = CARD_TYPE_SD; @@ -220,7 +274,9 @@ int sdhc_init(sdhc_state_t *state) state->sync = _init_locked; _init_clocks(state); - _reset_all(state); + _reset_sdhc(state, SDHC_SRR_SWRSTALL); + + SDHC_DEV->NISIER.reg = NISTR_CARD_DETECT; SDHC_DEV->TCR.reg = 14; /* max timeout is 14 or about 1sec */ SDHC_DEV->PCR.reg = SDHC_PCR_SDBPWR | SDHC_PCR_SDBVSEL_3V3; @@ -236,14 +292,16 @@ int sdhc_init(sdhc_state_t *state) for (int i = 0; i < 2; i++) { /* we do this step twice before failing */ if (!sdhc_send_cmd(state, SDMMC_MCI_CMD0_GO_IDLE_STATE, 0)) { if (i == 1) { - return -EIO; + res = -EIO; + goto out; } } /* Test for SD version 2 */ if (!sdhc_send_cmd(state, SD_CMD8_SEND_IF_COND, SD_CMD8_PATTERN | SD_CMD8_HIGH_VOLTAGE)) { if (i == 1) { /* bad card */ - return -EIO; + res = -EIO; + goto out; } } else { @@ -260,49 +318,62 @@ int sdhc_init(sdhc_state_t *state) } } if (!sdio_test_type(state)) { - return -EIO; + res = -EIO; + goto out; } if (state->type & CARD_TYPE_SDIO) { - return -ENOTSUP; + res = -ENOTSUP; + goto out; } /* Try to get the SD card's operating condition */ if (!_test_voltage(state, f8)) { state->type = CARD_TYPE_UNKNOWN; - return -EIO; + res = -EIO; + goto out; } /* SD MEMORY, Put the Card in Identify Mode * Note: The CID is not used in this stack */ if (!sdhc_send_cmd(state, SDMMC_CMD2_ALL_SEND_CID, 0)) { - return -EIO; + res = -EIO; + goto out; } /* Ask the card to publish a new relative address (RCA).*/ if (!sdhc_send_cmd(state, SD_CMD3_SEND_RELATIVE_ADDR, 0)) { - return -EIO; + res = -EIO; + goto out; } state->rca = (uint16_t)(SDHC_DEV->RR[0].reg >> 16); /* SD MEMORY, Get the Card-Specific Data */ if (!_test_capacity(state)) { - return -EIO; + res = -EIO; + goto out; } /* Put it into Transfer Mode */ if (!sdhc_send_cmd(state, SDMMC_CMD7_SELECT_CARD_CMD, (uint32_t)state->rca << 16)) { - return -EIO; + res = -EIO; + goto out; } /* SD MEMORY, Read the SCR to get card version */ if (!_test_version(state)) { - return -EIO; + res = -EIO; + goto out; } if (!_test_bus_width(state)) { - return -EIO; + res = -EIO; + goto out; } + /* all SD Cards should support this clock at that point */ + state->clock = SDHC_FAST_CLOCK_HZ; + /* update the host controller to the detected changes in bus_width and clock */ _set_hc(state); /* if it is high speed capable, (well it is) */ - if (SDHC_DEV->CA0R.bit.HSSUP) { + if (IS_USED(SDHC_ENABLE_HS) && SDHC_DEV->CA0R.bit.HSSUP) { if (!_test_high_speed(state)) { - return -EIO; + res = -EIO; + goto out; } } @@ -310,11 +381,15 @@ int sdhc_init(sdhc_state_t *state) _set_hc(state); if (!sdhc_send_cmd(state, SDMMC_CMD16_SET_BLOCKLEN, SD_MMC_BLOCK_SIZE)) { - return -EIO; + res = -EIO; + goto out; } state->need_init = false; - return 0; + +out: + _clock_sdcard(state, 0); + return res; } bool sdhc_send_cmd(sdhc_state_t *state, uint32_t cmd, uint32_t arg) @@ -355,15 +430,10 @@ bool sdhc_send_cmd(sdhc_state_t *state, uint32_t cmd, uint32_t arg) : SDHC_EISTR_CMDTEO | SDHC_EISTR_CMDEND | SDHC_EISTR_CMDIDX | SDHC_EISTR_DATTEO | SDHC_EISTR_DATEND | SDHC_EISTR_ADMA | SDHC_EISTR_CMDCRC | SDHC_EISTR_DATCRC; - SDHC_DEV->NISIER.reg = SDHC_NISTR_CMDC | NISTR_CARD_DETECT; - SDHC_DEV->EISIER.reg = eis; - SDHC_DEV->ARG1R.reg = arg; /* setup the argument register */ SDHC_DEV->CR.reg = command; /* send command */ - if (_wait_for_event(state)) { - SDHC_DEV->SRR.reg = SDHC_SRR_SWRSTCMD; /* reset command */ - while (SDHC_DEV->SRR.bit.SWRSTCMD) {} + if (!_wait_for_event(state, SDHC_NISTR_CMDC, eis, SDHC_SRR_SWRSTCMD)) { return false; } @@ -384,32 +454,23 @@ static void _set_speed(sdhc_state_t *state, uint32_t fsdhc) { (void)state; - uint32_t div; - if (SDHC_DEV->CCR.bit.SDCLKEN) { /* wait for command/data to go inactive */ while (SDHC_DEV->PSR.reg & (SDHC_PSR_CMDINHC | SDHC_PSR_CMDINHD)) {} /* disable the clock */ - SDHC_DEV->CCR.reg &= ~SDHC_CCR_SDCLKEN; + SDHC_DEV->CCR.reg = 0; } - /* since both examples use divided clock rather than programmable - just use divided here */ - SDHC_DEV->CCR.reg &= ~SDHC_CCR_CLKGSEL; /* divided clock */ + uint32_t div = DIV_ROUND_UP(sam0_gclk_freq(SDHC_CLOCK), fsdhc) - 1; - /* Fsdclk = Fsdhc_core/(2 * div) */ - div = (sam0_gclk_freq(SDHC_CLOCK) / fsdhc) / 2; - - /* high speed div must not be 0 */ - if (SDHC_DEV->HC1R.bit.HSEN && (div == 0)) { - div = 1; - } + DEBUG("sdhc: switch to %lu Hz (div %lu) -> %lu Hz\n", + fsdhc, div, sam0_gclk_freq(SDHC_CLOCK) / (div + 1)); /* write the 10 bit clock divider */ - SDHC_DEV->CCR.reg &= ~(SDHC_CCR_USDCLKFSEL_Msk | SDHC_CCR_SDCLKFSEL_Msk); - SDHC_DEV->CCR.reg |= SDHC_CCR_SDCLKFSEL(div) | SDHC_CCR_USDCLKFSEL(div >> 8); - SDHC_DEV->CCR.reg |= SDHC_CCR_INTCLKEN; /* enable internal clock */ + SDHC_DEV->CCR.reg = SDHC_CCR_SDCLKFSEL(div) | SDHC_CCR_USDCLKFSEL(div >> 8) + | SDHC_CCR_CLKGSEL | SDHC_CCR_INTCLKEN; while (!SDHC_DEV->CCR.bit.INTCLKS) {} /* wait for clock to be stable */ - SDHC_DEV->CCR.reg |= SDHC_CCR_SDCLKEN; /* enable clock to card */ + SDHC_DEV->CCR.bit.SDCLKEN = 1; /* enable clock to card */ } /** @@ -503,7 +564,6 @@ static bool _test_capacity(sdhc_state_t *state) { alignas(uint32_t) uint8_t csd[CSD_REG_BSIZE]; - uint32_t transfer_speed; if (!sdhc_send_cmd(state, SDMMC_MCI_CMD9_SEND_CSD, (uint32_t)state->rca << 16)) { return false; @@ -512,9 +572,7 @@ static bool _test_capacity(sdhc_state_t *state) uint32_t *csd32 = (void *)csd; csd32[i] = __builtin_bswap32(SDHC_DEV->RR[3 - i].reg); } - transfer_speed = CSD_TRAN_SPEED(&csd[1]); - state->clock = transfer_units[transfer_speed & 0x7] * - transfer_multiplier[(transfer_speed >> 3) & 0xF] * 1000; + /* * Card Capacity. * ---------------------------------------------------- @@ -569,10 +627,24 @@ static bool _test_version(sdhc_state_t *state) return false; } + /* wait until buffer read ready */ + if (!_wait_for_event(state, SDHC_NISTR_BRDRDY, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + return false; + } + for (int words = 0; words < (SD_SCR_REG_BSIZE / 4); words++) { *p++ = SDHC_DEV->BDPR.reg; } + /* wait until transfer is complete */ + if (!_wait_for_event(state, SDHC_NISTR_TRFC, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + return false; + } + /* Get SD Memory Card - Spec. Version */ switch (SD_SCR_SD_SPEC(scr)) { case SD_SCR_SD_SPEC_1_0_01: @@ -606,17 +678,16 @@ static bool _init_transfer(sdhc_state_t *state, uint32_t cmd, uint32_t arg, uint uint32_t tmr; uint32_t command; uint32_t eis; + uint32_t timeout = 0xFFFFFFFF; /* wait if card is busy */ while (SDHC_DEV->PSR.reg & (SDHC_PSR_CMDINHC | SDHC_PSR_CMDINHD)) {} if (cmd & MCI_CMD_WRITE) { tmr = SDHC_TMR_DTDSEL_WRITE; - SDHC_DEV->NISIER.reg = SDHC_NISTR_BWRRDY | NISTR_CARD_DETECT; } else { tmr = SDHC_TMR_DTDSEL_READ; - SDHC_DEV->NISIER.reg = SDHC_NISTR_BRDRDY | NISTR_CARD_DETECT; } if (cmd & MCI_CMD_SDIO_BYTE) { @@ -672,17 +743,24 @@ static bool _init_transfer(sdhc_state_t *state, uint32_t cmd, uint32_t arg, uint DEBUG("sdhc: send cmd %lx\n", command); - SDHC_DEV->EISIER.reg = eis; SDHC_DEV->SSAR.reg = num_blocks; /* Setup block size for Auto CMD23 */ SDHC_DEV->ARG1R.reg = arg; /* setup the argument register */ SDHC_DEV->CR.reg = command; /* send command */ - if (_wait_for_event(state)) { - DEBUG("sdhc error: %x, reset all\n", state->error); - _reset_all(state); + if (!_wait_for_event(state, SDHC_NISTR_CMDC, eis, SDHC_SRR_SWRSTCMD)) { return false; } + if (cmd & MCI_RESP_BUSY) { + do { + if (--timeout == 0) { + SDHC_DEV->SRR.reg = SDHC_SRR_SWRSTCMD; /* reset command */ + while (SDHC_DEV->SRR.bit.SWRSTCMD) {} + return false; + } + } while (!(SDHC_DEV->PSR.reg & SDHC_PSR_DATLL(1))); /* DAT[0] is busy bit */ + } + return true; } @@ -707,6 +785,7 @@ int sdhc_read_blocks(sdhc_state_t *state, uint32_t address, void *dst, uint16_t } mutex_lock(&state->lock); + _clock_sdcard(state, 1); if (state->need_init) { res = sdhc_init(state); @@ -741,13 +820,30 @@ int sdhc_read_blocks(sdhc_state_t *state, uint32_t address, void *dst, uint16_t goto out; } + /* wait until buffer read ready */ + if (!_wait_for_event(state, SDHC_NISTR_BRDRDY, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + res = -EIO; + goto out; + } + int num_words = (num_blocks * SD_MMC_BLOCK_SIZE) / 4; for (int words = 0; words < num_words; words++) { while (!SDHC_DEV->PSR.bit.BUFRDEN) {} *p++ = SDHC_DEV->BDPR.reg; } + /* wait until transfer is complete */ + if (!_wait_for_event(state, SDHC_NISTR_TRFC, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + res = -EIO; + goto out; + } + out: + _clock_sdcard(state, 0); mutex_unlock(&state->lock); return res; } @@ -772,6 +868,7 @@ int sdhc_write_blocks(sdhc_state_t *state, uint32_t address, const void *src, } mutex_lock(&state->lock); + _clock_sdcard(state, 1); if (state->need_init) { res = sdhc_init(state); @@ -808,6 +905,14 @@ int sdhc_write_blocks(sdhc_state_t *state, uint32_t address, const void *src, goto out; } + /* wait until buffer write ready */ + if (!_wait_for_event(state, SDHC_NISTR_BWRRDY, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + res = -EIO; + goto out; + } + /* Write data */ int num_words = (num_blocks * SD_MMC_BLOCK_SIZE) / 4; for (int words = 0; words < num_words; words++) { @@ -815,7 +920,17 @@ int sdhc_write_blocks(sdhc_state_t *state, uint32_t address, const void *src, SDHC_DEV->BDPR.reg = *p++; } + /* wait until transfer is complete */ + if (!_wait_for_event(state, SDHC_NISTR_TRFC, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + res = -EIO; + goto out; + } + out: + _wait_not_busy(state); + _clock_sdcard(state, 0); mutex_unlock(&state->lock); return res; } @@ -830,6 +945,7 @@ int sdhc_erase_blocks(sdhc_state_t *state, uint32_t start, uint16_t num_blocks) } mutex_lock(&state->lock); + _clock_sdcard(state, 1); if (state->need_init) { res = sdhc_init(state); @@ -859,6 +975,8 @@ int sdhc_erase_blocks(sdhc_state_t *state, uint32_t start, uint16_t num_blocks) } out: + _wait_not_busy(state); + _clock_sdcard(state, 0); mutex_unlock(&state->lock); return res; } @@ -966,10 +1084,24 @@ static bool _test_high_speed(sdhc_state_t *state) return false; } + /* wait until buffer read ready */ + if (!_wait_for_event(state, SDHC_NISTR_BRDRDY, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + return false; + } + for (int words = 0; words < (SD_SW_STATUS_BSIZE / 4); words++) { *p++ = SDHC_DEV->BDPR.reg; } + /* wait until transfer is complete */ + if (!_wait_for_event(state, SDHC_NISTR_TRFC, + SDHC_EISTR_DATTEO | SDHC_EISTR_DATCRC | SDHC_EISTR_DATEND, + SDHC_SRR_SWRSTALL)) { + return false; + } + if (SDHC_DEV->RR[0].reg & CARD_STATUS_SWITCH_ERROR) { return false; } @@ -1016,32 +1148,13 @@ static bool _test_bus_width(sdhc_state_t *state) static void _isr(sdhc_state_t *state) { - uint16_t events = (SDHC_DEV->NISIER.reg & ~NISTR_CARD_DETECT) - | SDHC_NISTR_ERRINT; - - if (SDHC_DEV->EISTR.reg) { - state->error = SDHC_DEV->EISTR.reg; - } - - DEBUG("NISTR: %x\n", SDHC_DEV->NISTR.reg); - DEBUG("EISTR: %x\n", SDHC_DEV->EISTR.reg); - DEBUG("ACESR: %x\n", SDHC_DEV->ACESR.reg); - - /* we got the awaited event */ - if (SDHC_DEV->NISTR.reg & events) { - DEBUG_PUTS("unlock"); - mutex_unlock(&state->sync); - } - /* if card got inserted we need to re-init */ if (SDHC_DEV->NISTR.reg & NISTR_CARD_DETECT) { + SDHC_DEV->NISTR.reg = NISTR_CARD_DETECT; DEBUG_PUTS("card presence changed"); state->need_init = true; } - SDHC_DEV->EISTR.reg = SDHC_EISTR_MASK; - SDHC_DEV->NISTR.reg = SDHC_NISTR_MASK; - cortexm_isr_end(); } diff --git a/cpu/samd5x/cpu.c b/cpu/samd5x/cpu.c index 0daf4665a3..0001b2e867 100644 --- a/cpu/samd5x/cpu.c +++ b/cpu/samd5x/cpu.c @@ -255,9 +255,9 @@ void sam0_gclk_enable(uint8_t id) gclk_connect(SAM0_GCLK_PERIPH, GCLK_SOURCE_ACTIVE_XOSC, 0); } break; - case SAM0_GCLK_200MHZ: - fdpll_init_nolock(1, MHZ(200), OSCCTRL_DPLLCTRLA_ONDEMAND); - gclk_connect(SAM0_GCLK_200MHZ, GCLK_SOURCE_DPLL1, 0); + case SAM0_GCLK_100MHZ: + fdpll_init_nolock(1, MHZ(100), 0 /* OSCCTRL_DPLLCTRLA_ONDEMAND */); + gclk_connect(SAM0_GCLK_100MHZ, GCLK_SOURCE_DPLL1, 0); fdpll_lock(1); break; } @@ -281,8 +281,8 @@ uint32_t sam0_gclk_freq(uint8_t id) assert(0); return 0; } - case SAM0_GCLK_200MHZ: - return MHZ(200); + case SAM0_GCLK_100MHZ: + return MHZ(100); default: return 0; } diff --git a/cpu/samd5x/include/periph_cpu.h b/cpu/samd5x/include/periph_cpu.h index 09d0493de9..ba0f343067 100644 --- a/cpu/samd5x/include/periph_cpu.h +++ b/cpu/samd5x/include/periph_cpu.h @@ -65,7 +65,7 @@ enum { SAM0_GCLK_32KHZ, /**< 32 kHz clock */ SAM0_GCLK_TIMER, /**< 4-8 MHz clock for xTimer */ SAM0_GCLK_PERIPH, /**< 12-48 MHz (DFLL) clock */ - SAM0_GCLK_200MHZ, /**< 200MHz FDPLL clock */ + SAM0_GCLK_100MHZ, /**< 100MHz FDPLL clock */ }; /** @} */ diff --git a/drivers/Kconfig b/drivers/Kconfig index 13ffedf107..ef979f5b26 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -161,6 +161,7 @@ rsource "mtd_sdcard/Kconfig" rsource "nvram/Kconfig" rsource "nvram_spi/Kconfig" rsource "sdcard_spi/Kconfig" +rsource "sdmmc/Kconfig" endmenu # Storage Device Drivers endmenu # Drivers diff --git a/drivers/ft5x06/ft5x06.c b/drivers/ft5x06/ft5x06.c index 95fd046f59..baa5b5385e 100644 --- a/drivers/ft5x06/ft5x06.c +++ b/drivers/ft5x06/ft5x06.c @@ -54,17 +54,17 @@ int ft5x06_init(ft5x06_t *dev, const ft5x06_params_t *params, ft5x06_event_cb_t return -EPROTO; } - uint8_t expected_id; if (dev->params.type == FT5X06_TYPE_FT6X06 || dev->params.type == FT5X06_TYPE_FT6X36) { - expected_id = FT6XX6_VENDOR_ID; + if ((vendor_id != FT5X06_VENDOR_ID_2) && (vendor_id != FT5X06_VENDOR_ID_3)) { + DEBUG("[ft5x06] init: invalid vendor ID: '0x%02x' (expected: 0x%02x or 0x%02x)\n", + vendor_id, FT5X06_VENDOR_ID_2, FT5X06_VENDOR_ID_3); + i2c_release(FT5X06_BUS); + return -ENODEV; + } } - else { - expected_id = FT5X06_VENDOR_ID; - } - - if (expected_id != vendor_id) { + else if (vendor_id != FT5X06_VENDOR_ID_1) { DEBUG("[ft5x06] init: invalid vendor ID: '0x%02x' (expected: 0x%02x)\n", - vendor_id, expected_id); + vendor_id, FT5X06_VENDOR_ID_1); i2c_release(FT5X06_BUS); return -ENODEV; } diff --git a/drivers/ft5x06/include/ft5x06_constants.h b/drivers/ft5x06/include/ft5x06_constants.h index 76d80eb575..8663dfe6f7 100644 --- a/drivers/ft5x06/include/ft5x06_constants.h +++ b/drivers/ft5x06/include/ft5x06_constants.h @@ -31,15 +31,20 @@ extern "C" { */ #define FT5X06_I2C_DEFAULT_ADDRESS (0x38) -/** - * @brief Vendor ID for FT6X06 and FT6X36 models. - */ -#define FT6XX6_VENDOR_ID (0xcd) - /** * @brief Vendor ID for FT5606, FT5X16, FT5X06I, FT5336, FT3316, FT5436I, FT5336I, FT5X46 models. */ -#define FT5X06_VENDOR_ID (0x51) +#define FT5X06_VENDOR_ID_1 (0x51) + +/** + * @brief Vendor ID used for most FT6X06 and FT6X36 as well as FT3X67 models. + */ +#define FT5X06_VENDOR_ID_2 (0x11) + +/** + * @brief Vendor ID used for some FT6X06 and FT6X36 models. + */ +#define FT5X06_VENDOR_ID_3 (0xcd) /** * @brief Maximum touches count for FT6X06 and FT6X36 models. @@ -81,9 +86,9 @@ extern "C" { #define FT5X06_TOUCH5_YH_REG (0x1D) #define FT5X06_TOUCH5_YL_REG (0x1E) #define FT5X06_G_AUTO_CLB_MODE_REG (0xA0) -#define FT5X06_G_CIPHER_REG (0xA1) -#define FT5X06_G_LIB_VERSION_H_REG (0xA2) -#define FT5X06_G_LIB_VERSION_L_REG (0xA3) +#define FT5X06_G_LIB_VERSION_H_REG (0xA1) +#define FT5X06_G_LIB_VERSION_L_REG (0xA2) +#define FT5X06_G_CIPHER_REG (0xA3) #define FT5X06_G_MODE_REG (0xA4) #define FT5X06_G_PMODE_REG (0xA5) #define FT5X06_G_FIRMID_REG (0xA6) diff --git a/drivers/include/mtd_sdmmc.h b/drivers/include/mtd_sdmmc.h new file mode 100644 index 0000000000..4143813177 --- /dev/null +++ b/drivers/include/mtd_sdmmc.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_mtd_sdmmc mtd wrapper for sdmmc + * @ingroup drivers_storage + * @brief Driver for SD Memory Cards and MMCs/eMMCs using mtd interface + * + * @{ + * + * @file + * @brief Interface definition for mtd_sdmmc driver + * + * @author Michel Rottleuthner + * @author Gunar Schorcht + */ + +#ifndef MTD_SDMMC_H +#define MTD_SDMMC_H + +#include + +#include "mtd.h" +#include "sdmmc/sdmmc.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Device descriptor for a mtd_sdmmc device + * + * This is an extension of the @c mtd_dev_t struct + */ +typedef struct { + mtd_dev_t base; /**< inherit mtd_dev_t object */ + sdmmc_dev_t *sdmmc; /**< SD/MMC device descriptor */ + uint8_t sdmmc_idx; /**< SD/MMC peripheral index */ +} mtd_sdmmc_t; + +/** + * @defgroup drivers_mtd_sdmmc_config SD Memory Card driver compile configuration + * @ingroup config_drivers_storage + * @{ + */ +/** + * @brief Enable SD Memory Card Erase + * + * SD Memory Cards and MMCs/eMMCs handle sector erase internally + * so it's possible to directly write to the card without erasing + * the sector first. + * + * @note An erase call will NOT touch the content if `CONFIG_MTD_SDMMC_ERASE` + * is not set, so enable this feature to ensure overriding the data. + * + * @pre This feature requires the `mtd_write_page` module. + */ +#ifdef DOXYGEN +#define CONFIG_MTD_SDMMC_ERASE +#endif +/** @} */ + +/** + * @brief sdcard device operations table for mtd + */ +extern const mtd_desc_t mtd_sdmmc_driver; + +#ifdef __cplusplus +} +#endif + +#endif /* MTD_SDMMC_H */ +/** @} */ diff --git a/drivers/include/sdmmc/sdmmc.h b/drivers/include/sdmmc/sdmmc.h new file mode 100644 index 0000000000..f1ea8dbeb5 --- /dev/null +++ b/drivers/include/sdmmc/sdmmc.h @@ -0,0 +1,1909 @@ +/* + * Copyright (C) 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup drivers_sdmmc SDIO/SD/MMC Device API (SDMMC) + * @ingroup drivers_periph + * @ingroup drivers_storage + * + * @experimental This API is experimental and in an early state - expect + * changes! + * + * # Overview + * + * The SDIO/SD/MMC Device API (SDMMC) implements an SD host controller driver + * that provides a high-level API using a low-level SDIO/SD/MMC peripheral + * driver for accessing + * + * - MultiMediaCards (MMC) and Embedded MultiMediaCards (eMMC) + * - SD Memory Cards (SD Cards) with Standard Capacity (SDSC), + * High Capacity (SDHC) or Extended Capacity (SDXC). + * + * In the context of this API, the term _Card_ refers to either + * - a removable card such as SD Memory Card, SDIO Card, Combo Card + * or MMC Card or + * - an embedded device such as Embedded SD Memory Device, Embedded SDIO device, + * or eMMC device. + * + * The term _slot_ refers to the interface for a removable card. + * + * The SDIO/SD/MMC device API (SDMMC) is divided into two parts: + * + * 1. The high-level API that implements the SD Host Controller driver and + * allows + * - to inititialize and identify different types of cards, + * - to access them either blockwise or bytewise, + * - to get information about the used card, and + * - to send single commands or application specific commands to the card. + * + * 2. The low-level SDIO/SD/MMC peripheral driver of type @ref sdmmc_driver_t + * implements the low-level functions required by the high-level device API. + * It has to be implemented for each MCU. The support of the low-level + * SDIO/SD/MMC peripheral driver is indicated by the MCU or board by + * the `periph_sdmmc` feature. + * + * Currently the SDIO/SD/MMC Device API supports the following card types: + * + *
+ * | Card Type | Support | Remark | + * |:--------------------------------|:---:|------------| + * | MMC/eMMC in MultiMediaCard mode | yes | | + * | MMC/eMMC in SPI mode | no | | + * | SD Memory Card in SD mode | yes | SDSC, SDHC and SDXC with Default or High Speed | + * | SD Memory Card in SPI mode | no | | + * | SDIO in SD mode | no | | + * | SDIO in SPI mode | no | | + *
+ * + * # Limitations: + * + * - Only one card per SDIO/SD/MMC device is supported. + * - eMMCs specific features are not supported. + * - UHS-I, UHS-II and UHS-III are not supported. + * + * # Features and Modules + * + * A board that uses an MCU with an SDIO/SD/MMC peripheral and has a card + * slot or device connected has to indicate this as feature `periph_sdmmc` + * in order to use the SDIO/SD/MMC API or drivers based on it. + * Furthermore, specific features of the SDIO/SD/MMC peripheral or + * configurations of the board have to be defined by corresponding features. + * + * These are in detail: + * + * - `periph_sdmmc` (`HAS_PERIPH_SDMMC` in Kconfig) + * indicates that an SDIO/SD/MMC peripheral is present and used by the + * board. This feature shall be provided by the board configuration. + * + * - `periph_sdmmc_8bit` (`HAS_PERIPH_SDMMC_8BIT` in Kconfig) + * indicates that the SDIO/SD/MMC peripheral supports the 8-bit bus width + * and at least one component of the board is connected with 8 data lines. + * This feature shall be provided by the board configuration, if available. + * + * - `periph_sdmmc_auto_clk` (`HAS_PERIPH_SDMMC_AUTO_CLK` in Kconfig) + * indicates that the SDIO/SD/MMC peripheral supports the Auto-CLK + * feature, i.e. the automatic activation and deactivation of the SD CLK + * signal when required to save power. This function shall be provided by + * the MCU if supported. + * + * - `periph_sdmmc_auto_cmd12` (`HAS_PERIPH_SDMMC_AUTO_CMD12` in Kconfig) + * indicates that the SDIO/SD/MMC peripheral supports the Auto-CMD12 + * feature, i.e. CMD12 is sent automatically to stop the transmission in + * multiple block operations. This feature shall be provided by the MCU + * if supported. + * + * - `periph_sdmmc_hs` (`HAS_PERIPH_SDMMC_HS` in Kconfig) + * indicates that the SDIO/SD/MMC peripheral supports the high speed + * access, that is 50 MHz for SD and 52 MHz for MMC. This feature shall be + * provided by the MCU if supported. + + * - `periph_sdmmc_mmc` (`HAS_PERIPH_SDMMC_MMC` in Kconfig) + * indicates that the SDIO/SD/MMC peripheral supports MMC/eMMCs. This + * feature shall be provided by the MCU if supported. + * + * Some functionalities of the SDIO/SD/MMC Device API must be explicitly + * enabled via modules: + * + * - `sdmmc_mmc` (`MODULE_SDMMC_MMC` in Kconfig) + * enables the support for MMCs/eMMCs. + * + * - `periph_sdmmc_8bit` (`MODULE_PERIPH_SDMMC_8BIT` in Kconfig) + * enables the 8-bit bus width support. It requires the corresponding + * feature of the board. + * + * @{ + * + * @file + * @brief SDIO/SD/MMC device API using a low-level peripheral driver + * + * @author Gunar Schorcht + */ + +#ifndef SDMMC_SDMMC_H +#define SDMMC_SDMMC_H + +#include + +#include "assert.h" +#include "byteorder.h" +#include "macros/units.h" +#include "periph_conf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CPU-specific requirements for SDIO/SD/MMC buffers + * + * Can be overridden by periph_cpu if needed by the SDMMC/SDIO peripheral. + * Example usage: + * ``` + * SDMMC_CPU_DMA_REQUIREMENTS uint8_t buffer[64]; + * ``` + */ +#ifndef SDMMC_CPU_DMA_REQUIREMENTS +#define SDMMC_CPU_DMA_REQUIREMENTS +#endif + +/** + * @brief Instantiation type for SDIO/SD/MMC buffers + * + * Example usage: + * ``` + * sdmmc_buf_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ``` + */ +#define sdmmc_buf_t SDMMC_CPU_DMA_REQUIREMENTS uint8_t + +/** + * @brief Size of a single data block on SDHC/SDXC Cards in bytes + * + * The size of a data block depend on type and mode of the card. SDSC/SDXC + * use a fix size of 512 bytes. + */ +#define SDMMC_SDHC_BLOCK_SIZE (512) + +/** + * @name Command and Response related definitions + * @{ + */ + +/** + * @brief Command index + */ +#define SDMMC_CMD(n) (n) + +/** + * @brief Application specific command index prefix (Bit is 7 used) + */ +#define SDMMC_ACMD_PREFIX (1 << 7) + +/** + * @brief Application specific command index + */ +#define SDMMC_ACMD(n) (SDMMC_ACMD_PREFIX | SDMMC_CMD(n)) + +/** + * @brief SDIO/SD/MMC Commands + * + * Commands used by the driver. Command indices are in range [0..63]. + * Application specific command indices are ORed with + * @ref SDMMC_ACMD_PREFIX (0x80) and thus marked as application specific. + * @ref SDMMC_ACMD_PREFIX can be used to check whether a given command index + * specifies an application specific command. + */ +typedef enum { + SDMMC_CMD0 = SDMMC_CMD(0), /**< GO_IDLE_STATE */ + SDMMC_CMD1 = SDMMC_CMD(1), /**< SEND_OP_COND */ + SDMMC_CMD2 = SDMMC_CMD(2), /**< ALL_SEND_CID */ + SDMMC_CMD3 = SDMMC_CMD(3), /**< SET_RELATIVE_ADDR */ + SDMMC_CMD4 = SDMMC_CMD(4), /**< SET_DSR */ + SDMMC_CMD5 = SDMMC_CMD(5), /**< SD_APP_OP_COND (SDIO only) */ + SDMMC_CMD6 = SDMMC_CMD(6), /**< SWITCH */ + SDMMC_CMD7 = SDMMC_CMD(7), /**< SELECT/DESELECT_CARD */ + SDMMC_CMD8 = SDMMC_CMD(8), /**< SEND_IF_COND (SD), SEND_EXT_CSD (MMC) */ + SDMMC_CMD9 = SDMMC_CMD(9), /**< SEND_CSD */ + SDMMC_CMD10 = SDMMC_CMD(10), /**< SEND_CID */ + SDMMC_CMD12 = SDMMC_CMD(12), /**< STOP_TRANSMISSION */ + SDMMC_CMD13 = SDMMC_CMD(13), /**< SEND_STATUS */ + SDMMC_CMD16 = SDMMC_CMD(16), /**< SET_BLOCKLEN */ + SDMMC_CMD17 = SDMMC_CMD(17), /**< READ_SINGLE_BLOCK */ + SDMMC_CMD18 = SDMMC_CMD(18), /**< READ_MULTIPLE_BLOCK */ + SDMMC_CMD23 = SDMMC_CMD(23), /**< SET_BLOCK_COUNT */ + SDMMC_CMD24 = SDMMC_CMD(24), /**< WRITE_BLOCK */ + SDMMC_CMD25 = SDMMC_CMD(25), /**< WRITE_MULTIPLE_BLOCK */ + SDMMC_CMD32 = SDMMC_CMD(32), /**< ERASE_WR_BLK_START */ + SDMMC_CMD33 = SDMMC_CMD(33), /**< ERASE_WR_BLK_END */ + SDMMC_CMD38 = SDMMC_CMD(38), /**< ERASE */ + SDMMC_CMD52 = SDMMC_CMD(52), /**< IO_RW_DIRECT (SDIO only) */ + SDMMC_CMD53 = SDMMC_CMD(53), /**< IO_RW_EXTENDED (SDIO only) */ + SDMMC_CMD55 = SDMMC_CMD(55), /**< APP_CMD */ + SDMMC_CMD58 = SDMMC_CMD(58), /**< READ_OCR (SPI mode only) */ + SDMMC_CMD59 = SDMMC_CMD(59), /**< CRC_ON_OFF (SPI mode only) */ + SDMMC_ACMD6 = SDMMC_ACMD(6), /**< SET_BUS_WIDTH */ + SDMMC_ACMD13 = SDMMC_ACMD(13), /**< SD_STATUS */ + SDMMC_ACMD23 = SDMMC_ACMD(23), /**< SET_WR_BLK_ERASE_COUNT */ + SDMMC_ACMD41 = SDMMC_ACMD(41), /**< SD_APP_OP_COND */ + SDMMC_ACMD51 = SDMMC_ACMD(51), /**< SEND_SCR */ +} sdmmc_cmd_t; + +/** Command argument if no argument is required*/ +#define SDMMC_CMD_NO_ARG (0x00000000UL) + +/** Command argument if RCA is used in addressed commands */ +#define SDMMC_CMD_ARG_RCA(n) ((uint32_t)n << 16) + +/** Mask to check whether the response type uses CRC7 */ +#define SDMMC_RESP_CRC (1UL << 4) +/** Mask to check whether the response includes busy status from card */ +#define SDMMC_RESP_BUSY (1UL << 5) +/** Mask of response index */ +#define SDMMC_RESP_IDX (0xf) + +/** + * @brief SDIO/SD/MMC Response types + * + * @see Physical Layer Simplified Specification Version 9.00, Section 9.00 + * [[sdcard.org](https://www.sdcard.org)] and \n + * JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 7.10 for R4 and R5 + * [[jedec.org](https://www.jedec.org)] + */ +typedef enum { + SDMMC_NO_R = 0, /**< No response expected */ + SDMMC_R1 = 1 | SDMMC_RESP_CRC, /**< Normal Response [48 bit (32 bit card status)]*/ + SDMMC_R1B = 1 | SDMMC_RESP_CRC + | SDMMC_RESP_BUSY, /**< Normal Response + [48 bit (R1 with optional busy signal on DAT0)] */ + SDMMC_R2 = 2 | SDMMC_RESP_CRC, /**< CID/CSD [136 bit (128 bit CID or CSD)] */ + SDMMC_R3 = 3, /**< OCR Resister [48 bit (32 bit OCR)] */ + SDMMC_R4 = 4, /**< Fast I/O [48 bit (16 bit RCA, 1 bit status, + 7 bit addr, 8 bit reg)] */ + SDMMC_R5 = 5 | SDMMC_RESP_CRC, /**< Interrupt Request + [48 bit (16 bit RCA, 16 bit not defined) */ + SDMMC_R6 = 6 | SDMMC_RESP_CRC, /**< Published RCA Response [48 bit (16 bit RCA, + 16 bit card status) */ + SDMMC_R7 = 7 | SDMMC_RESP_CRC, /**< Card Interface Condition [48 bit] (32 bit data, + see section 4.9.6) */ +} sdmmc_resp_t; +/** @} */ + +/** + * @name Definitions used for CMD8 - SEND_IF_COND (SD only) + * + * @see Physical Layer Simplified Specification Version 9.00, Section 4.3.13 + * [[sdcard.org](https://www.sdcard.org)] + * @{ + */ + +#define SDMMC_CMD8_CHECK_PATTERN (0xaa) /**< Check pattern, 0xAA recommended */ +#define SDMMC_CMD8_VHS_27_36V (0b0001 << 8) /**< Voltage Supplied by host 2.7-3.6V */ +#define SDMMC_CMD8_PCIE_AVAIL (1 << 12) /**< PCIe Availability (not yet used) */ +#define SDMMC_CMD8_PCIE_12V (1 << 13) /**< PCIe 1.2V Support (not yet used) */ + +/** Command argument used in CMD8 */ +#define SDMMC_CMD8_CHECK (SDMMC_CMD8_VHS_27_36V | SDMMC_CMD8_CHECK_PATTERN) +/** @} */ + +/** + * @name OCR Register Definition + * + * @see Physical Layer Simplified Specification Version 9.00, Section 5.1, + * Table 5-1 [[sdcard.org](https://www.sdcard.org)] + * @{ + */ +#define SDMMC_OCR_18V (1UL << 7) /**< Low Voltage Range */ +#define SDMMC_OCR_27_28V (1UL << 15) /**< 2.7V to 2.8V Range */ +#define SDMMC_OCR_28_29V (1UL << 16) /**< 2.8V to 2.9V Range */ +#define SDMMC_OCR_29_30V (1UL << 17) /**< 2.9V to 3.0V Range */ +#define SDMMC_OCR_30_31V (1UL << 18) /**< 3.0V to 3.1V Range */ +#define SDMMC_OCR_31_32V (1UL << 19) /**< 3.1V to 3.2V Range */ +#define SDMMC_OCR_32_33V (1UL << 20) /**< 3.2V to 3.3V Range */ +#define SDMMC_OCR_33_34V (1UL << 21) /**< 3.3V to 3.4V Range */ +#define SDMMC_OCR_34_35V (1UL << 22) /**< 3.4V to 3.5V Range */ +#define SDMMC_OCR_35_36V (1UL << 23) /**< 3.5V to 3.6V Range */ + +#define SDMMC_OCR_S18A (1UL << 24) /**< Switching to 1.8V Accepted */ +#define SDMMC_OCR_OVER_2TB (1UL << 27) /**< Over 2TB support status (CO2T) */ +#define SDMMC_OCR_UHS_II (1UL << 29) /**< UHS-II Card Status */ +#define SDMMC_OCR_CCS (1UL << 30) /**< Card Capacity Status (CCS) */ +#define SDMMC_OCR_POWER_UP (1UL << 31) /**< Card power up status bit (busy) */ + +/** + * @brief Voltage profile for the range 2.7-3.6V as defined for MMC + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8, Table 29 + * [[jedec.org](https://www.jedec.org)] + */ +#define SDMMC_OCR_ALL_VOLTAGES (SDMMC_OCR_27_28V | SDMMC_OCR_28_29V | \ + SDMMC_OCR_29_30V | SDMMC_OCR_30_31V | \ + SDMMC_OCR_31_32V | SDMMC_OCR_32_33V | \ + SDMMC_OCR_33_34V | SDMMC_OCR_34_35V | \ + SDMMC_OCR_35_36V) +/** @} */ + +/** + * @brief SDIO/SD/MMC Card types + * + * To be able to specify a Combo card (combined SD Memory and SDIO card), + * the enumeration values are defined bitwise so that they can be ORed to + * represent a Combo card with different versions of the SD Memory part. For + * example, `SDMMC_CARD_TYPE_SDIO | SDMMC_CARD_TYPE_SDSC_V2_V3` represents a + * Combo card with SDIO function and SD Memory Card Standard Capacity (SDSC) + * Version 2.x+. + */ +typedef enum { + SDMMC_CARD_TYPE_UNKNOWN = 0x00, /**< Card type unknown */ + SDMMC_CARD_TYPE_SDSC_V1 = 0x01, /**< SD Memory Card Standard Capacity (SDSC) Version 1.x */ + SDMMC_CARD_TYPE_SDSC_V2_V3 = 0x02, /**< SD Memory Card Standard Capacity (SDSC) Version 2.x+ */ + SDMMC_CARD_TYPE_SDHC_SDXC = 0x04, /**< SD Memory Card High or Extended Capacity (SDHC/SDXC) */ + /* bits 3..5 are left unused for later extensions of SD Memory Card types */ + SDMMC_CARD_TYPE_SDIO = 0x40, /**< SDIO Card */ + SDMMC_CARD_TYPE_MMC = 0x80, /**< MultiMedia Card */ +} sdmmc_card_type_t; + +/** + * @brief Mask for any type of SD Memory card + */ +#define SDMMC_CARD_TYPE_SD (SDMMC_CARD_TYPE_SDSC_V1 | \ + SDMMC_CARD_TYPE_SDSC_V2_V3 | \ + SDMMC_CARD_TYPE_SDHC_SDXC) + +/** + * @brief SDIO/SD/MMC Card data bus widths + * + * Possible bus widths are given as integer values as defined in SD Status + * register and the SCR register for SD Cards. + * + * @warning The values differ from the bus widths used in ACMD6 for SD Cards + * and the CCC register in SDIO Cards. + * + * @see Physical Layer Simplified Specification Version 9.00, 4.10.2, + * Table 4-44, SD Status [[sdcard.org](https://www.sdcard.org)] \n + */ +typedef enum { + SDMMC_BUS_WIDTH_1BIT = 1, /**< Data bus width is 1 bit (default) */ + SDMMC_BUS_WIDTH_4BIT = 4, /**< Data bus width is 4 bit */ + SDMMC_BUS_WIDTH_8BIT = 8, /**< Data bus width is 8 bit */ +} sdmmc_bus_width_t; + +/** + * @brief SDIO/SD/MMC Card clock rate types + * + * Identifies the clock frequency to be used. The clock frequency in + * identification mode f_OD (Open Drain mode) is fixed and is f_OD = 400 kHz. + * The actual clock frequency in data transfer mode f_PP (Push-Pull mode) + * depends on the SDIO/SD/MMC device and the card used. + * The low-level SDIO/SD/MMC peripheral driver sets the actual clock rate + * in function sdmmc_driver_t::set_clock_rate. + */ +typedef enum { + SDMMC_CLK_400K = KHZ(400), /**< Identification Mode f_OD (400 kHz) */ + SDMMC_CLK_20M = MHZ(20), /**< MMC Card in Data Transfer Mode (Backward Compatibility) */ + SDMMC_CLK_25M = MHZ(25), /**< SD/SDIO Card in Data Transfer Mode (Default Speed) */ + SDMMC_CLK_26M = MHZ(26), /**< MMC/eMMC Card in Data Transfer Mode (Default Speed) */ + SDMMC_CLK_50M = MHZ(50), /**< SD/SDIO Card in Data Transfer Mode (High Speed) */ + SDMMC_CLK_52M = MHZ(52), /**< MMC/eMMC in Data Transfer Mode (High Speed) */ +} sdmmc_clock_rate_t; + +/** + * @name SDIO/SD/MMC Card status as returned in responses R1 to CMD13 with b[15]=0 + * + * @see Physical Layer Simplified Specification Version 9.00, Section 4.10.1, + * Table 4-42 [[sdcard.org](https://www.sdcard.org)] \n + * SDIO Simplified Specification Version 3.00, 4.10.8, Table 4-7, + * [[sdcard.org](https://www.sdcard.org)] \n + * JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 7.11, Table 25 + * [[jedec.org](https://www.jedec.org)] + * @{ + */ +#define SDMMC_CARD_STATUS_OUT_OF_RANGE (1UL << 31) /**< SD/SDIO and MMC */ +#define SDMMC_CARD_STATUS_ADDRESS_ERROR (1UL << 30) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_BLOCK_LEN_ERROR (1UL << 29) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_ERASE_SEQ_ERROR (1UL << 28) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_ERASE_PARAM (1UL << 27) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_WP_VIOLATION (1UL << 26) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_CARD_IS_LOCKED (1UL << 25) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_LOCK_UNLOCK_FAILED (1UL << 24) /**< SD/SDIO and MMC */ +#define SDMMC_CARD_STATUS_COM_CRC_ERROR (1UL << 23) /**< SD/SDIO and MMC */ +#define SDMMC_CARD_STATUS_ILLEGAL_COMMAND (1UL << 22) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_CARD_ECC_FAILED (1UL << 21) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_CC_ERROR (1UL << 20) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_ERROR (1UL << 19) /**< SD/SDIO and MMC */ +#define SDMMC_CARD_STATUS_UNDERRUN (1UL << 18) /**< MMC only */ +#define SDMMC_CARD_STATUS_OVERRUN (1UL << 17) /**< MMC only */ +#define SDMMC_CARD_STATUS_CSD_OVERWRITE (1UL << 16) /**< SD (CSD), MMC (CSD and CID) */ +#define SDMMC_CARD_STATUS_WP_ERASE_SKIP (1UL << 15) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_CARD_ECC_DISABLED (1UL << 14) /**< SD only */ +#define SDMMC_CARD_STATUS_ERASE_RESET (1UL << 13) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_READY_FOR_DATA (1UL << 8) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_SWITCH_ERROR (1UL << 7) /**< MMC only */ +#define SDMMC_CARD_STATUS_FX_EVENT (1UL << 6) /**< SD only */ +#define SDMMC_CARD_STATUS_APP_CMD (1UL << 5) /**< SD and MMC */ +#define SDMMC_CARD_STATUS_AKE_SEQ_ERROR (1UL << 3) /**< SD only */ + +/** SD/MMC Card status mask for error flags */ +#define SDMMC_CARD_STATUS_ERRORS (SDMMC_CARD_STATUS_OUT_OF_RANGE | \ + SDMMC_CARD_STATUS_ADDRESS_ERROR | \ + SDMMC_CARD_STATUS_BLOCK_LEN_ERROR | \ + SDMMC_CARD_STATUS_ERASE_SEQ_ERROR | \ + SDMMC_CARD_STATUS_ERASE_PARAM | \ + SDMMC_CARD_STATUS_WP_VIOLATION | \ + SDMMC_CARD_STATUS_LOCK_UNLOCK_FAILED | \ + SDMMC_CARD_STATUS_COM_CRC_ERROR | \ + SDMMC_CARD_STATUS_ILLEGAL_COMMAND | \ + SDMMC_CARD_STATUS_CARD_ECC_FAILED | \ + SDMMC_CARD_STATUS_CC_ERROR | \ + SDMMC_CARD_STATUS_ERROR | \ + SDMMC_CARD_STATUS_UNDERRUN | \ + SDMMC_CARD_STATUS_OVERRUN | \ + SDMMC_CARD_STATUS_CSD_OVERWRITE | \ + SDMMC_CARD_STATUS_WP_ERASE_SKIP | \ + SDMMC_CARD_STATUS_SWITCH_ERROR | \ + SDMMC_CARD_STATUS_AKE_SEQ_ERROR) + +/** SD/MMC Card status mask for current state */ +#define SDMMC_CARD_STATUS_CURRENT_STATE(n) (((n) >> SDMMC_CARD_STATUS_CURRENT_STATE_Pos) & 0x0f) +#define SDMMC_CARD_STATUS_CURRENT_STATE_Pos (9) /**< CURRENT_STATE position */ + +/** + * @brief SD/MMC Card states + */ +enum { + SDMMC_CARD_STATE_IDLE = 0, /**< Idle */ + SDMMC_CARD_STATE_READY = 1, /**< Ready */ + SDMMC_CARD_STATE_IDENT = 2, /**< Identification */ + SDMMC_CARD_STATE_STBY = 3, /**< Stand-by */ + SDMMC_CARD_STATE_TRAN = 4, /**< Transfer */ + SDMMC_CARD_STATE_DATA = 5, /**< Data */ + SDMMC_CARD_STATE_RCV = 6, /**< Receive */ + SDMMC_CARD_STATE_PRG = 7, /**< Programming */ + SDMMC_CARD_STATE_DIS = 8, /**< Disconnect */ + SDMMC_CARD_STATE_BTST = 9, /**< Bus Test */ +}; + +/** + * @brief SD/MMC Card status as structure + */ +typedef union { + struct { + uint32_t reserved0 :2; /**< [1:0] Reserved for appl. specific commands */ + uint32_t reserved2 :1; /**< [2] Reserved for appl. specific commands */ + uint32_t AKE_SEQ_ERROR :1; /**< [3] SD only, Reserved in MMC */ + uint32_t reserved4 :1; /**< [4] Reserved */ + uint32_t APP_CMD :1; /**< [5] SD and MMC */ + uint32_t FX_EVENT :1; /**< [6] SD only, Reserved in MMC */ + uint32_t SWITCH_ERROR :1; /**< [7] MMC only, reserved in SD/SDIO */ + uint32_t READY_FOR_DATA :1; /**< [8] SD and MMC */ + uint32_t CURRENT_STATE :4; /**< [12:9] SD and MMC */ + uint32_t ERASE_RESET :1; /**< [13] SD and MMC */ + uint32_t CARD_ECC_DISABLED :1; /**< [14] SD only, reserved in MMC */ + uint32_t WP_ERASE_SKIP :1; /**< [15] SD and MMC */ + uint32_t CSD_OVERWRITE :1; /**< [16] SD (CSD), MMC (CSD and CID) */ + uint32_t OVERRUN :1; /**< [17] MMC only, DEFERRED_RESPONSE in SD */ + uint32_t UNDERRUN :1; /**< [18] MMC only, reserved in SD */ + uint32_t ERROR :1; /**< [19] SD/SDIO and MMC */ + uint32_t CC_ERROR :1; /**< [20] SD and MMC */ + uint32_t CARD_ECC_FAILED :1; /**< [21] SD and MMC */ + uint32_t ILLEGAL_COMMAND :1; /**< [22] SD/SDIO and MMC */ + uint32_t COM_CRC_ERROR :1; /**< [23] SD/SDIO and MMC */ + uint32_t LOCK_UNLOCK_FAILED:1; /**< [24] SD and MMC */ + uint32_t CARD_IS_LOCKED :1; /**< [25] SD and MMC */ + uint32_t WP_VIOLATION :1; /**< [26] SD and MMC */ + uint32_t ERASE_PARAM :1; /**< [27] SD and MMC */ + uint32_t ERASE_SEQ_ERROR :1; /**< [28] SD and MMC */ + uint32_t BLOCK_LEN_ERROR :1; /**< [29] SD and MMC */ + uint32_t ADDRESS_ERROR :1; /**< [30] SD and MMC */ + uint32_t OUT_OF_RANGE :1; /**< [31] SD/SDIO and MMC */ + }; + uint32_t value; /**< SD/MMC Card status as 32 bit value */ +} sdmmc_card_status_t; + +/** @} */ + +/** + * @name SD Status register (SD Memory Cards) + * @{ + */ + +/** SD status register size */ +#define SDMMC_SD_STATUS_SIZE (64) + +/** + * @brief SD Status (SD Memory Card only) + * + * SD Status has a size of 512 bits and contains the status of SD Memory Card + * features. It is not supported by MMCs and SDIO Cards. In Combo Cards, it + * reflects the status of the SD Memory Card portion. + * + * Members are sorted by types to reduce the required RAM size. + * + * @see Physical Layer Simplified Specification Version 9.00, Section 4.10.2, + * Table 4-44 [[sdcard.org](https://www.sdcard.org)] + */ +typedef struct __attribute__((packed)) { + uint32_t SIZE_OF_PROTECTED_AREA:32; /**< [479:448] Section 4.10.2.1 */ + uint32_t SUS_ADDR:22; /**< [367:346] Section 4.10.2.12, Table 4-57 */ + uint32_t VSC_AU_SIZE:10; /**< [377:368] Section 4.10.2.11, Table 4-56 */ + uint16_t SD_CARD_TYPE:16; /**< [495:480] Section 4.10.2, Table 4-44 */ + uint16_t ERASE_SIZE:16; /**< [423:408] Section 4.10.2.5, Table 4-49 */ + uint8_t SPEED_CLASS:8; /**< [447:440] Section 4.10.2.2, Table 4-45 */ + uint8_t PERFORMANCE_MOVE:8; /**< [439:432] Section 4.10.2.3, Table 4-46 */ + uint8_t VIDEO_SPEED_CLASS:8; /**< [391:384] Section 4.10.2.10, Table 4-54 */ + uint8_t ERASE_TIMEOUT:6; /**< [407:402] Section 4.10.2.6, Table 4-50 */ + uint8_t ERASE_OFFSET:2; /**< [401:400] Section 4.10.2.7, Table 4-51 */ + uint8_t UHS_SPEED_GRADE:4; /**< [399:396] Section 4.10.2.8, Table 4-52 */ + uint8_t UHS_AU_SIZE:4; /**< [395:392] Section 4.10.2.9, Table 4-3 */ + uint8_t AU_SIZE:4; /**< [431:428] Section 4.10.2.4, Table 4-47 */ + uint8_t DAT_BUS_WIDTH:2; /**< [511:510] Section 4.10.2, Table 4-44 */ + uint8_t SECURED_MODE:1; /**< [509] Section 4.10.2, Table 4-44 */ +} sdmmc_sd_status_t; + +/** @} */ + +/** + * @name CID register (SD Memory and MMC Cards) + * @{ + */ + +/** CID register size in bytes (CID is 128 bit) */ +#define SDMMC_CID_REG_SIZE (16) + +#define SDMMC_CID_OID_SIZE_SD (2) /**< OID (OEM/Application ID) size in byte (SD) */ +#define SDMMC_CID_PNM_SIZE_SD (5) /**< PNM (Product name) size in byte (SD) */ +#define SDMMC_CID_PNM_SIZE_MMC (6) /**< PNM (Product name) size in byte (MMC) */ + +/** + * @brief CID register structure (SD Memory Cards) + * + * @see Physical Layer Simplified Specification Version 9.00, Section 5.2, + * Table 5.2 [[sdcard.org](https://www.sdcard.org)] + */ +typedef struct __attribute__((packed)) { + uint8_t MID; /**< Manufacturer ID */ + char OID[SDMMC_CID_OID_SIZE_SD]; /**< OEM/Application ID */ + char PNM[SDMMC_CID_PNM_SIZE_SD]; /**< Product name */ + uint8_t PRV; /**< Product revision */ + be_uint32_t PSN; /**< Product serial number in big-endian order */ + be_uint16_t MDT; /**< Manufacturing date in big-endian order */ + uint8_t CID_CRC; /**< CRC7 checksum including bit 0 */ +} sdmmc_cid_sd_t; + +/** + * @brief CID register structure (MMC) + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.2, Table 30 + * [[jedec.org](https://www.jedec.org)] + */ +typedef struct __attribute__((packed)) { + uint8_t MID; /**< Manufacturer ID */ + be_uint16_t OID; /**< OEM/Application ID in big-endian order */ + char PNM[SDMMC_CID_PNM_SIZE_MMC]; /**< Product name */ + uint8_t PRV; /**< Product revision */ + be_uint32_t PSN; /**< Product serial number in big-endian order */ + uint8_t MDT; /**< Manufacturing date */ + uint8_t CID_CRC; /**< CRC7 checksum including bit 0 */ +} sdmmc_cid_mmc_t; + +/** + * @brief CID register structure (SD Memory and MMC Cards) + */ +typedef union { + sdmmc_cid_sd_t sd; /**< CID register of SD Memory Cards */ + sdmmc_cid_mmc_t mmc; /**< CID register of MMC */ +} sdmmc_cid_t; + +/** @} */ + +/** + * @name CSD Register Definitions + * + * @see Physical Layer Simplified Specification Version 9.00, Section 5.3, + * [[sdcard.org](https://www.sdcard.org)] \n + * JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.3, Table 35 + * [[jedec.org](https://www.jedec.org)] + * @{ + */ + +/** + * @brief CSD Register Versions (SD Memory Card and MMC) + */ +typedef enum { + SDMMC_CSD_V1 = 0, /**< CSD Version 1.0 (SDSC and MMC) */ + SDMMC_CSD_V2 = 1, /**< CSD Version 2.0 (SDHC/SDXC) or Version 1.1 (MMC) */ + SDMMC_CSD_V3 = 2, /**< CSD Version 3.0 (SDUC) or Version 1.2 (MMC) */ + SDMMC_CSD_Vx = 3, /**< Reserved (SD) or Version in EXT_CSD (MMC) */ +} sdmmc_csd_version_t; + +/** CSD register size in byte (CSD is 128 bit) */ +#define SDMMC_CSD_REG_SIZE (16) + +/** + * @brief CSD register structure Version 1.0 + * + * @see Physical Layer Simplified Specification Version 9.00, Section 5.3.2, + * Table 5.4 [[sdcard.org](https://www.sdcard.org)] + */ +typedef struct __attribute__((packed)) { + uint32_t CSD_CRC:8; /**< CRC including End bit 1b [7:0] */ + uint32_t reserved5:1; /**< reserved [8] */ + uint32_t WP_UPC:1; /**< write protection until power cycle [9] */ + uint32_t FILE_FORMAT:2; /**< File format [11:10] */ + uint32_t TMP_WRITE_PROTECT:1; /**< temporary write protection [12] */ + uint32_t PERM_WRITE_PROTECT:1; /**< permanent write protection [13] */ + uint32_t COPY:1; /**< copy flag [14] */ + uint32_t FILE_FORMAT_GRP:1; /**< File format group [15] */ + uint32_t reserved4:5; /**< reserved [20:16] */ + uint32_t WRITE_BL_PARTIAL:1; /**< partial blocks for write allowed [21] */ + uint32_t WRITE_BL_LEN:4; /**< max. write data block length [25:22] */ + uint32_t R2W_FACTOR:3; /**< write speed factor [28:26] */ + uint32_t reserved3:2; /**< reserved [30:29] */ + uint32_t WP_GRP_ENABLE:1; /**< write protect group enable [31] */ + uint32_t WP_GRP_SIZE:7; /**< write protect group size [38:32] */ + uint32_t SECTOR_SIZE:7; /**< erase sector size [45:39] */ + uint32_t ERASE_BLK_EN:1; /**< erase single block enable [46] */ + uint32_t C_SIZE_MULT:3; /**< device size multiplier [49:47] */ + uint32_t VDD_W_CURR_MAX:3; /**< max. write current VDD max [52:50] */ + uint32_t VDD_W_CURR_MIN:3; /**< max. write current VDD min [55:53] */ + uint32_t VDD_R_CURR_MAX:3; /**< max. read current VDD max [58:56] */ + uint32_t VDD_R_CURR_MIN:3; /**< max. read current VDD min [61:59] */ + uint32_t C_SIZE:12; /**< device size [73:62] */ + uint32_t reserved2:2; /**< reserved [75:74] */ + uint32_t DSR_IMP:1; /**< DSR implemented [76] */ + uint32_t READ_BLK_MISALIGN:1; /**< read block misalignment [77] */ + uint32_t WRITE_BLK_MISALIGN:1; /**< write block misalignment [78] */ + uint32_t READ_BL_PARTIAL:1; /**< partial blocks for read allowed [79] */ + uint32_t READ_BL_LEN:4; /**< max. read data block length [83:80] */ + uint32_t CCC:12; /**< card command classes [95:84] */ + uint32_t TRAN_SPEED:8; /**< max. data transfer rate [103:96] */ + uint32_t NSAC:8; /**< data read access-time-2 in CLK cycles [111:104] */ + uint32_t TAAC:8; /**< data read access-time-1 [119:112] */ + uint32_t reserved1:6; /**< reserved [125:120] */ + uint32_t CSD_STRUCTURE:2; /**< CSD structure [127:126] */ +} sdmmc_csd_v1_t; + +/** + * @brief CSD register structure Version 2.0 and Version 3.0 + * + * A combined format is used vor CSD Version 2.0 and 3.0 to reduce the code + * size. The only difference is the bit length of `C_SIZE`. + * + * @see Physical Layer Simplified Specification Version 9.00 + * [[sdcard.org](https://www.sdcard.org)] \n + * - Version 2.0: Section 5.3.3, Table 5-16 \n + * - Version 3.0: Section 5.3.4, Table 5.3.4-1 + */ +typedef struct __attribute__((packed)) { + uint32_t CSD_CRC:8; /**< CRC including End bit 1b [7:0] */ + uint32_t reserved5:1; /**< reserved [8] */ + uint32_t WP_UPC:1; /**< write protection until power cycle [9] */ + uint32_t FILE_FORMAT:2; /**< File format [11:10] */ + uint32_t TMP_WRITE_PROTECT:1; /**< temporary write protection [12] */ + uint32_t PERM_WRITE_PROTECT:1; /**< permanent write protection [13] */ + uint32_t COPY:1; /**< copy flag [14] */ + uint32_t FILE_FORMAT_GRP:1; /**< File format group [15] */ + uint32_t reserved4:5; /**< reserved [20:16] */ + uint32_t WRITE_BL_PARTIAL:1; /**< partial blocks for write allowed [21] */ + uint32_t WRITE_BL_LEN:4; /**< max. write data block length [25:22] */ + uint32_t R2W_FACTOR:3; /**< write speed factor [28:26] */ + uint32_t reserved3:2; /**< reserved [30:29] */ + uint32_t WP_GRP_ENABLE:1; /**< write protect group enable [31] */ + uint32_t WP_GRP_SIZE:7; /**< write protect group size [38:32] */ + uint32_t SECTOR_SIZE:7; /**< erase sector size [45:39] */ + uint32_t ERASE_BLK_EN:1; /**< erase single block enable [46] */ + uint32_t reserved2:1; /**< reserved [47] */ + uint32_t C_SIZE:28; /**< device size v2.0 [69:48], v3.0 [75:48] */ + uint32_t DSR_IMP:1; /**< DSR implemented [76] */ + uint32_t READ_BLK_MISALIGN:1; /**< read block misalignment [77] */ + uint32_t WRITE_BLK_MISALIGN:1; /**< write block misalignment [78] */ + uint32_t READ_BL_PARTIAL:1; /**< partial blocks for read allowed [79] */ + uint32_t READ_BL_LEN:4; /**< max. read data block length [83:80] */ + uint32_t CCC:12; /**< card command classes [95:84] */ + uint32_t TRAN_SPEED:8; /**< max. data transfer rate [103:96] */ + uint32_t NSAC:8; /**< data read access-time-2 in CLK cycles [111:104] */ + uint32_t TAAC:8; /**< data read access-time-1 [119:112] */ + uint32_t reserved1:6; /**< reserved [125:120] */ + uint32_t CSD_STRUCTURE:2; /**< CSD structure [127:126] */ +} sdmmc_csd_v2_t; + +/** + * @brief CSD register structure for MMC + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.3, Table 35 + * [[jedec.org](https://www.jedec.org)] + */ +typedef struct __attribute__((packed)) { + uint32_t CSD_CRC:8; /**< CRC including End bit 1b [7:0] */ + uint32_t ECC:2; /**< ECC code [9:8] */ + uint32_t FILE_FORMAT:2; /**< File format [11:10] */ + uint32_t TMP_WRITE_PROTECT:1; /**< Temporary write protection [12] */ + uint32_t PERM_WRITE_PROTECT:1; /**< Permanent write protection [13] */ + uint32_t COPY:1; /**< Copy flag [14] */ + uint32_t FILE_FORMAT_GRP:1; /**< File format group [15] */ + uint32_t CONTENT_PROT_APP:1; /**< Content protection application [16] */ + uint32_t reserved4:4; /**< reserved [20:17] */ + uint32_t WRITE_BL_PARTIAL:1; /**< partial blocks for write allowed [21] */ + uint32_t WRITE_BL_LEN:4; /**< Max. write data block length [25:22] */ + uint32_t R2W_FACTOR:3; /**< Write speed factor [28:26] */ + uint32_t DEFAULT_ECC:2; /**< Manufacturer default ECC [30:29] */ + uint32_t WP_GRP_ENABLE:1; /**< write protect group enable [31] */ + uint32_t WP_GRP_SIZE:5; /**< Write protect group size [36:32] */ + uint32_t ERASE_GRP_MULT:5; /**< Erase group size multiplier [41:37] */ + uint32_t ERASE_GRP_SIZE:5; /**< Erase group size [46:42] */ + uint32_t C_SIZE_MULT:3; /**< Device size multiplier [49:47] */ + uint32_t VDD_W_CURR_MAX:3; /**< Max. write current VDD max [52:50] */ + uint32_t VDD_W_CURR_MIN:3; /**< Max. write current VDD min [55:53] */ + uint32_t VDD_R_CURR_MAX:3; /**< Max. read current VDD max [58:56] */ + uint32_t VDD_R_CURR_MIN:3; /**< Max. read current VDD min [61:59] */ + uint16_t C_SIZE:12; /**< Device size [73:62] */ + uint32_t reserved2:2; /**< reserved [75:74] */ + uint32_t DSR_IMP:1; /**< DSR implemented [76] */ + uint32_t READ_BLK_MISALIGN:1; /**< Read block misalignment [77] */ + uint32_t WRITE_BLK_MISALIGN:1; /**< Write block misalignment [78] */ + uint32_t READ_BL_PARTIAL:1; /**< Partial blocks for read allowed [79] */ + uint32_t READ_BL_LEN:4; /**< Max. read data block length [83:80] */ + uint32_t CCC:12; /**< Card command classes [95:84] */ + uint32_t TRAN_SPEED:8; /**< Max. bus clock frequency [103:96] */ + uint32_t NSAC:8; /**< Data read access-time-2 in CLK cycles [111:104] */ + uint32_t TAAC:8; /**< Data read access-time-1 [119:112] */ + uint32_t reserved1:2; /**< reserved [121:120] */ + uint32_t SPEC_VERS:4; /**< Specification version [125:122] */ + uint32_t CSD_STRUCTURE:2; /**< CSD structure [127:126] */ +} sdmmc_csd_mmc_t; + +/** + * @brief CSD register (SD Memory Card and MMC) + */ +typedef union { + sdmmc_csd_v1_t v1; /**< CSD Version 1.0 */ + sdmmc_csd_v2_t v2; /**< CSD Version 2.0 and 3.0 */ + sdmmc_csd_mmc_t mmc; /**< CSD Version for MMC */ +} sdmmc_csd_t; + +/** Extended CSD (EXT_CSD) register size in byte (EXT_CSD is 512 byte) */ +#define SDMMC_EXT_CSD_REG_SIZE (512) + +/** + * @brief Extended CSD (EXT_CSD) register structure (MMC only) + * + * Since the Extended CSD register is a complete 512 byte block which is only + * sparsely used, only the elements that are absolutely needed are stored in + * the extended CSD register structure. + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.4, Table 46 + * [[jedec.org](https://www.jedec.org)] + */ +typedef struct { + uint32_t SEC_COUNT; /**< Sector Count [215:212] */ + uint8_t CARD_TYPE; /**< Card Type [196] */ + uint8_t CSD_STRUCTURE; /**< CSD Structure Version [194] */ + uint8_t BUS_WIDTH; /**< Bus Width Mode [183] */ +} sdmmc_ext_csd_t; + +/** @} */ + +/** + * @name SCR Register Definitions (SD Memory Card only) + * + * @see Physical Layer Simplified Specification Version 9.00, Section 5.6, + * Table 5-17 [[sdcard.org](https://www.sdcard.org)] + * @{ + */ + +/** SCR register size in byte (SCR is 64 bit) */ +#define SDMMC_SCR_REG_SIZE (8) + +/** + * @brief SCR register structure (SD Memory Card only) + * + * @see Physical Layer Simplified Specification Version 9.00, Section 5.6, + * Table 5-17 [[sdcard.org](https://www.sdcard.org)] + */ +typedef struct { + /* first 32-bit word */ + uint32_t reserved0; /**< Reserved for manufacturer usage [31:0] */ + /* second 32-bit word */ + union { + struct { + uint32_t CMD_SUPPORT:5; /**< Command Support bits [36:32] */ + uint32_t reserved37:1; /**< Reserved [37] */ + uint32_t SD_SPECX:4; /**< Spec. Version 5.00 or higher [41:38] */ + uint32_t SD_SPEC4:1; /**< Spec. Version 4.00 or higher [42] */ + uint32_t EX_SECURITY:4; /**< Extended Security Support [46:43] */ + uint32_t SD_SPEC3:1; /**< Spec. Version 3.00 or higher [47] */ + uint32_t SD_BUS_WIDTHS:4; /**< DAT Bus widths supported [51:48] */ + uint32_t SD_SECURITY:3; /**< CPRM Security Support [54:52] */ + uint32_t DATA_STAT_AFTER_ERASE:1; /**< Data status after erases [55] */ + uint32_t SD_SPEC:4; /**< SD Memory Card - Spec. Version [59:56] */ + uint32_t SCR_STRUCTURE:4; /**< SCR Structure [63:60] */ + }; + uint32_t value; + }; +} sdmmc_scr_t; + +#define SDMMC_SCR_ACMD_53_54_SUPPORT (0b10000) /**< @ref sdmmc_scr_t::CMD_SUPPORT + Secure Receive/Send supported */ +#define SDMMC_SCR_ACMD_58_59_SUPPORT (0b01000) /**< @ref sdmmc_scr_t::CMD_SUPPORT + Extension Register Multi-Block */ +#define SDMMC_SCR_ACMD_48_49_SUPPORT (0b00100) /**< @ref sdmmc_scr_t::CMD_SUPPORT + Extension Register Single-Block */ +#define SDMMC_SCR_ACMD_23_SUPPORT (0b00010) /**< @ref sdmmc_scr_t::CMD_SUPPORT + Set Block Count */ +#define SDMMC_SCR_ACMD_20_SUPPORT (0b00001) /**< @ref sdmmc_scr_t::CMD_SUPPORT + Speed Class Control */ + +/** + * @brief Get Physical Layer Specification Version value from SCR value + * + * @param scr SCR register value of type sdmmc_scr_t + * + * | Value | Physical Layer Specification Version Number | + * |---|:---------------------------------| + * | 0 | Version 1.0 and 1.01 | + * | 1 | Version 1.10 | + * | 2 | Version 2.00 | + * | 3 | Version 3.0X | + * | 4 | Version 4.XX | + * | 5 | Version 5.XX | + * | 6 | Version 6.XX | + * | 7 | Version 7.XX | + * | 8 | Version 8.XX | + * | 9 | Version 9.XX | + */ +#define SDMMC_SCR_SD_SPEC(scr) \ + (scr.SD_SPEC + scr.SD_SPEC3 + (scr.SD_SPECX ? scr.SD_SPECX + 1 : scr.SD_SPEC4)) + +/** @} */ + +/** + * @brief SD/MMC device access macro + * @{ + */ +#define SDMMC_DEV(x) (sdmmc_get_dev(x)) +/** @} */ + +/** + * @brief sdmmc_dev_t forward declaration + */ +typedef struct sdmmc_dev sdmmc_dev_t; + +/** + * @brief Data transfer types + */ +typedef enum { + SDMMC_MULTIBYTE, /**< Transfer 1 to READ_BL_LEN/WRITE_BL_LEN bytes */ + SDMMC_BLOCK, /**< Transfer block with size of READ_BL_LEN/WRITE_BL_LEN */ + SDMMC_STREAM, /**< Transfer until CMD12 is sent (MMC and 1-bit bus only) */ +} sdmmc_xfer_type_t; + +/** + * @brief Transfer descriptor + * + * The transfer descriptor hold all information about a transfer such as the + * block size and the number of block. + */ +typedef struct { + sdmmc_xfer_type_t type; /**< Type of the transfer */ + bool write; /**< Indicate a write transfer */ + sdmmc_cmd_t cmd_idx; /**< Command index used for the transfer */ + sdmmc_resp_t resp_type; /**< Type of expected response for the transfer */ + uint32_t arg; /**< Command argument used for the transfer */ + uint16_t block_size; /**< Size of a block or number of bytes for Byte transfer */ + uint16_t block_num; /**< Number of blocks to be transferred, 1 for Byte transfer */ +} sdmmc_xfer_desc_t; + +/** + * @brief Auto Command features supported by the SDIO/SD/MMC peripheral + * + * Most SDIO/SD/MMC peripherals support the Auto-Command feature for + * CMD12 and CMD23. The low-level SDIO/SD/MMC peripheral driver defines + * in @ref sdmmc_driver_t::init which Auto-Command features are supported + * by the peripheral. To be able to specify a combination of these + * features, the enumeration values are defined bitwise. + */ +typedef enum { + SDMMC_AUTO_CMD_NONE = 0x00, + SDMMC_AUTO_CMD12 = 0x01, + SDMMC_AUTO_CMD23 = 0x02, + SDMMC_AUTO_CMD_BOTH = 0x03, +} sdmmc_auto_cmd_t; + +/** + * @brief Events generated by SDIO/SD/MMC high level API + */ +typedef enum { + SDMMC_EVENT_CARD_INSERTED, + SDMMC_EVENT_CARD_REMOVED, +} sdmmc_event_t; + +/** + * @brief Event callback function type + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] event Event + * + * @warning The function is called in the ISR context. Do not do anything + * comprehensive or time-consuming. Instead, use `thread_flags`, + * `event_queue` or `msg` mechanism to inform a thread about the event, + * which then handles the event asynchronously in thread context. + */ +typedef void (*sdmmc_event_cb_t)(sdmmc_dev_t *dev, sdmmc_event_t event); + +/** + * @brief Low-level SDIO/SD/MMC peripheral driver + * + * The low-level SDIO/SD/MMC peripheral driver interface as used by + * high-level SDIO/SD/MMC device API functions. It has to be implemented + * for a specific CPU. + * + * @note High-level functions such as @ref sdmmc_read_blocks are provided + * by the SDIO/SD/MMC API and should be used instead. Directly calling + * these functions is not recommended. + */ +typedef struct { + /** + * @brief Basic initialization of the given SDIO/SD/MMC device + * + * Low-level SDIO/SD/MMC peripheral driver function for basic initialization + * of the peripheral including pin configuration of used pins. It is + * called by @ref sdmmc_init during the board initialization to prepare + * the SDIO/SD/MMC peripheral for further usage. Specific initialization + * parameters required for this initialization are defined in the board's + * `periph_conf.h`. + * + * Errors like configuration parameter problems are not signaled by + * return values, but by using the `assert()`. + * + * @see @ref sdmmc_init + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to initialize + */ + void (*init)(sdmmc_dev_t *dev); + + /** + * @brief Send command to SDIO/SD/MMC Card and optionally wait for response + * + * Low-level SDIO/SD/MMC peripheral driver function to send command + * @p cmd_idx with argument @p arg to the SDIO/SD/MMC card. @p resp_type + * specifies the type of the response expected. + * + * @warning This function must not be used for application specific + * commands. The high-level SDIO/SD/MMC device API function + * @ref sdmmc_send_acmd is used instead. + * + * To ensure that the @ref sdmmc_send_acmd function is used for + * application specific commands, the function must fail if the + * command @p cmd_idx is an application specific command (ACMDx). + * Use + * ``` + * assert((cmd_idx & SDMMC_ACMD_PREFIX) == 0); + * ``` + * in the implementation of this function for that purpose. + * + * The response has to be stored word-wise in host byte order in the buffer + * provided by @p resp as follows: + * + * - @p resp_type = SDMMC_NO_R (No Response): @p resp can be NULL + * - @p resp_tpye = SDMMC_R2 (Long Response): + * @p resp = { R[127:96], R[95:64], R[63:32], R[31:0] } + * - @p resp_type = anything else (Short Response): @p resp = { R[39:8] } + * + * The buffer provided by @p resp can be NULL if the response is not + * needed. However, the low-level SDIO/SD/MMC peripheral driver must + * receive the expected response, but does not store it in @p resp + * in that case. + * + * @note R3 does not use CRC7 in CRC field but a fixed value of `0b111111`. + * The driver must check the CRC field for this response. + * + * @see @ref sdmmc_send_cmd and sdmmc_send_acmd + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] cmd_idx Command index + * @param[in] arg Command argument + * @param[in] resp_type Type of response expected + * @param[out] resp Buffer of 32-bit words to store the response if + * needed, otherwise NULL + * + * @retval 0 on success + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support the command or is in wrong state + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EBADMSG on CRC7 error in response + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*send_cmd)(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp); + + /** + * @brief Card Initialization and Identification + * + * @warning Usually the standard procedure implemented by the high-level + * SDIO/SD/MMC device API function @ref sdmmc_card_init is used + * for card initialization and identification (recommended). + * The low-level SDIO/SD/MMC peripheral driver should implement + * its own @ref sdmmc_driver_t::card_init function only in very + * special cases, e.g. when special hardware handling is required. + * Therefore, this driver function should be set to NULL, that + * is the card initialization and identification function is + * not implemented by low-level SDIO/SD/MMC peripheral driver + * + * @see @ref sdmmc_card_init + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * + * @retval 0 on success + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card is not supported or can't operate under supplied voltage + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on card initialization and identification timeout + * @retval -EBADMSG on CRC7 error + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*card_init)(sdmmc_dev_t *dev); + + /** + * @brief Set data bus width + * + * Set the data bus width used by the SDIO/SD/MMC peripheral. The + * function is called by the high-level SDIO/SD/MMC device function + * @ref sdmmc_card_init at the end of the card initialization and + * identification procedure to change the data bus width used by the + * SDIO/SD/MMC peripheral. The data bus width of the card has been + * already changed at that time by sending the ACMD6 (`SET_BUS_WIDTH`) + * command. + * + * Supported data bus width depend on + * + * - the card type, + * - the mode in which the card operates and + * - the SDIO/SD/MMC peripheral. + * + * In identification mode, always 1-bit data bus width is used. When + * switching from the identification mode to the data transfer mode, + * the data bus width is changed. In data transfer mode, + * the data bus width depends on the type of the used card: + * + * - MMCs V3.x support 4-bit data bus width + * - MMCs V4.x support 8-bit data bus width + * - SD Memory Cards support 4-bit data bus width + * - SDIO Cards support 4-bit data bus width + * - Embedded SDIO devices support 8-bit data bus width + * + * The data bus width @p width is the minimum of the data bus width + * supported by the identified card and the data bus width + * @ref sdmmc_dev_t::bus_width configured for the SDIO/SD/MMC + * peripheral. The low-level SDIO/SD/MMC peripheral driver is + * responsible for correctly setting @ref sdmmc_dev_t::bus_width in the + * @ref sdmmc_driver_t::init function. + * + * @note The @ref set_bus_width function must not change + * @ref sdmmc_dev_t::bus_width because it is the configured data + * bus width which may be larger than the supported data bus width + * of the identified card. + * + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] width Data bus width to be set + * + * @retval 0 on success + * @retval -ENOTSUP if SDIO/SD/MMC peripheral does not support the width + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*set_bus_width)(sdmmc_dev_t *dev, sdmmc_bus_width_t width); + + /** + * @brief Set SD CLK signal rate + * + * Set the SD CLK rate used by the SDIO/SD/MMC peripheral. The clock + * frequency in identification mode f_OD is fixed and is 400 kHz. The + * actual clock frequency in data transfer mode f_PP depends on the + * SDIO/SD/MMC device and the card used. + * + * The function is called by the high-level SDIO/SD/MMC device API function + * @ref sdmmc_card_init at the end of the card identification procedure + * with @p rate set to any of the clock rates defined in + * @ref sdmmc_clock_rate_t. The low-level SDIO/SD/MMC peripheral + * is responsible to set the clock rate to the highest value it supports + * for the identified card type. + * + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] rate Clock rate to be set + * + * @retval 0 on success + * @retval -ENOTSUP if SDIO/SD/MMC peripheral does not support the clock rate + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*set_clock_rate)(sdmmc_dev_t *dev, sdmmc_clock_rate_t rate); + +#if !IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK) || DOXYGEN + /** + * @brief Enable or disable the SD CLK signal + * + * The function is used by the SDIO/SD/MMC device API functions to enable + * or disable the SD CLK signal if the SDIO/SD/MMC peripheral driver does + * not support the Auto CLK feature (periph_sdmmc_auto_clk). It can be + * left NULL if the SDIO/SD/MMC peripheral driver does not support + * enabling or disabling the SD CLK signal. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] enable enable SD CLK signal on true or disable the + * SD CLK signal on false + * + * @retval 0 on success + * @retval -EIO if the SD CLK signal could not be enabled or disabled + */ + int (*enable_clock)(sdmmc_dev_t *dev, bool enable); +#endif + + /** + * @brief Prepare a data transfer + * + * Low-level SDIO/SD/MMC peripheral driver function to prepare a data + * transfer (read or write) in the SDIO/SD/MMC peripheral. + * + * It is called by the high-level SDIO/SD/MMC device API function + * @ref sdmmc_xfer before it sends a block-oriented, stream or + * byte/multi-byte command to the card to start the transfer from or + * to the card. + * + * A typical activity of this function is the configuration of + * the DMA transfer. + * + * @see @ref sdmmc_xfer + * @pre @p dev and @p xfer must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] xfer Transfer descriptor of type @ref sdmmc_xfer_desc_t + * + * @retval 0 on success + * @retval -EINVAL on invalid transfer parameters + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*xfer_prepare)(sdmmc_dev_t *dev, sdmmc_xfer_desc_t *xfer); + + /** + * @brief Execute the data transfer + * + * Low-level SDIO/SD/MMC peripheral driver function to transfer the data + * (read or write) by the SDIO/SD/MMC peripheral. + * + * It is called by the high-level SDIO/SD/MMC device API function + * @ref sdmmc_xfer after it sent the block-oriented, stream or + * byte/multi-byte command to start the transfer from or to the card. + * + * The function returns the number of transferred blocks in @p done. + * Most SDIO/SD/MMC peripherals use a block or byte counter when + * transferring data, which can be used for this purpose. + * + * @note If the SDIO/SD/MMC peripheral does not allow to determine + * the number of transferred blocks, the function must return `0` + * in @p done in case of an error or @ref sdmmc_xfer_desc_t::block_num + * on success. + * + * @see @ref sdmmc_xfer + * @pre @p dev and @p xfer must not be `NULL`. \n + * @p data_rd must not be `NULL` for read transfers and \n + * @p data_wr must not be `NULL` for write transfers. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] xfer Transfer descriptor of type @ref sdmmc_xfer_desc_t + * @param[in] data_wr Buffer with data to write in write transfers, NULL otherwise + * @param[out] data_rd Buffer for data to read in read transfers, NULL otherwise + * @param[out] done Number of blocks transferred, can be `NULL` + * + * @retval 0 on success + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support a used command or is in wrong state + * @retval -EBUSY if card is busy + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EINVAL on invalid transfer parameters + * @retval -EBADMSG on CRC7 error in data + * @retval -ENOMEM on RX FIFO overflow or TX FIFO underrun error + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*xfer_execute)(sdmmc_dev_t *dev, sdmmc_xfer_desc_t *xfer, + const void *data_wr, void *data_rd, + uint16_t *done); + + /** + * @brief Finish the data transfer + * + * Low-level SDIO/SD/MMC peripheral driver function to terminate a data + * transfer (read or write) in the SDIO/SD/MMC peripheral. + * + * It is called by the high-level SDIO/SD/MMC device API function + * @ref sdmmc_xfer after the data transfer has been executed and the + * stop command (CMD12) has been sent if necessary. + * + * @see @ref sdmmc_xfer + * @pre @p dev and @p xfer must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] xfer Transfer descriptor of type @ref sdmmc_xfer_desc_t + * + * @retval 0 on success + * @retval -EIO on not further specified error incl. hardware errors + */ + int (*xfer_finish)(sdmmc_dev_t *dev, sdmmc_xfer_desc_t *xfer); + +} sdmmc_driver_t; + +/** + * @brief SDIO/SD/MMC device descriptor + * + * The device descriptor holds all required information about the SDIO/SD/MMC + * device and the card that is used by this decive. + * + * @note Most of the information is determined or collected during the card + * initialization and identification procedure by the @ref sdmmc_card_init + * function. However, some information must be determined and collected + * by the low-level SDIO/SD/MMC peripheral driver. These are + * - @ref sdmmc_dev_t::driver + * - @ref sdmmc_dev_t::present + * - @ref sdmmc_dev_t::status + * - @ref sdmmc_dev_t::bus_width + * - @ref sdmmc_dev_t::spi_mode + */ +typedef struct sdmmc_dev { + /** + * Low-level SDIO/SD/MMC peripheral driver. It has to be set by the + * low-level SDIO/SD/MMC driver during the initialization. + */ + const sdmmc_driver_t *driver; + + /** + * The application can register an event callback function of type + * @ref sdmmc_event_cb_t which is called when one of the defined + * events (@ref sdmmc_event_t) is generated by the SDIO/SD/MMC driver. + * + * @warning The function is called in the ISR context. Do not do anything + * comprehensive or time-consuming. Instead, use `thread_flags`, + * `event_queue` or `msg` mechanism to inform a thread about + * the event, which then handles the event asynchronously in + * thread context. + */ + sdmmc_event_cb_t event_cb; + + /** + * CID register of the SD/MMC Card, read during the initialization and + * identification procedure in the @ref sdmmc_card_init function. It is not + * supported by SDIO cards and reflects the CID of the memory portion in + * case of Combo cards. + */ + sdmmc_cid_t cid; + + /** + * SCR register of the SD Card, read during the initialization and + * identification procedure in the @ref sdmmc_card_init function. It is not + * supported by SDIO and MMC cards. It reflects the SCR of the memory + * portion in case of Combo cards. + */ + sdmmc_scr_t scr; + + /** + * CSD register of the SD/MMC Card, read during the initialization and + * identification procedure in the @ref sdmmc_card_init function. It is not + * supported by SDIO cards. It reflects the CSD of the memory portion in + * case of Combo cards. + */ + sdmmc_csd_t csd; + +#if IS_USED(MODULE_SDMMC_MMC) + /** + * EXT_CSD register of the MMC Card, read during the initialization and + * identification procedure int the @ref sdmmc_card_init function. + */ + sdmmc_ext_csd_t ext_csd; +#endif + + /** + * Last SDIO/SD/MMC Card status reported in R1 response. It is + * set by the low-level SDIO/SD/MMC peripheral driver function + * @ref sdmmc_driver_t::send_cmd. + */ + uint32_t status; + + /** + * Relative Card Address (RCA) of the SDIO/SD/MMC Card as determined + * during the initialization and identification procedure in the + * @ref sdmmc_card_init function. + */ + uint16_t rca; + + /** + * Type of the SDIO/SD/MMC Card as identified during the initialization and + * identification procedure in the @ref sdmmc_card_init function + * (default @ref SDMMC_CARD_TYPE_UNKNOWN). + */ + sdmmc_card_type_t type; + + /** + * Data bus width supported by the SDIO/SD/MMC device + * (default @ref SDMMC_BUS_WIDTH_1BIT). It has to be set by the low-level + * SDIO/SD/MMC peripheral driver function @ref sdmmc_driver_t::init. + * It is either hard-configured for the SDIO/SD/MMC device or detected + * during its initialization. + */ + sdmmc_bus_width_t bus_width; + + /** + * Indicates whether a card is present. It has to be set by the low-level + * SDIO/SD/MMC peripheral driver in function @ref sdmmc_driver_t::init and + * if the card is removed or inserted. + * Either the CD signal is used if available, or it must be set to + * `true` by default. + */ + bool present; + + /** + * Indicates whether the card is initialized (default `false`). + * It is set by the initialization and identification procedure in + * function @ref sdmmc_card_init. + */ + bool init_done; + + /** + * Indicates whether the SDIO/SD/MMC peripheral supports the switching + * to 1.8V (default `false`). It has to be set by the low-level SDIO/SD/MMC + * peripheral driver function @ref sdmmc_driver_t::init if supported. + */ + bool s18v_support; + + /** + * Indicates whether the card supports the switching to 1.8V (default + * `false`). It is set during the initialization and identification + * procedure in function @ref sdmmc_card_init. + */ + bool s18v_allowed; + + /** + * Indicates whether SPI mode is used by the SDIO/SD/MMC device (default + * `false`). It has to be set by the low-level SDIO/SD/MMC peripheral driver + * function @ref sdmmc_driver_t::init. + */ + bool spi_mode; + +} sdmmc_dev_t; + +/** + * @brief Retrieve SDIO/SD/MMC device descriptor from the peripheral index + * + * The function converts the peripheral index to the corresponding SDIO/SD/MMC + * device descriptor. + * + * @note This function has to be implemented by the low-level SDIO/SD/MMC + * peripheral driver to allow to convert a SDIO/SD/MMC peripheral index + * to the corresponding SDIO/SD/MMC device descriptor. + * + * @param[in] num SDIO/SD/MMC peripheral index + * + * @retval pointer to the SDIO/SD/MMC device descriptor at index @p num on success + * @retval NULL if @p num is greater than the number of SDIO/SD/MMC device + * descriptors + */ +sdmmc_dev_t *sdmmc_get_dev(unsigned num); + +/** + * @brief Basic initialization of the given SDIO/SD/MMC device + * + * The function calls the low-level SDIO/SD/MMC peripheral driver function + * @ref sdmmc_driver_t::init for the basic initialization of the SDIO/SD/MMC + * peripheral including the configuration of used pins. + * + * If the `auto_init` module is enabled, it is called automatically during the + * startup. Otherwise, it has to be called before the SDIO/SD/MMC device is + * used for the first time. + * + * Errors like configuration parameter problems are not signaled by + * return values, but by using the `assert()`. + * + * @see @ref sdmmc_driver_t::init + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to initialize + */ +static inline void sdmmc_init(sdmmc_dev_t *dev) +{ + assert(dev); + assert(dev->driver); + + dev->driver->init(dev); +} + +/** + * @brief Send command to SDIO/SD/MMC Card and optionally wait for response + * + * Send the command @p cmd_idx with argument @p arg to the SDIO/SD/MMC + * card. @p resp_type specifies the type of the response expected. Addressed + * (point-to-point) commands are sent to the card that is currently selected. + * + * This function just calls the low-level SDIO/SD/MMC peripheral driver function + * @ref sdmmc_driver_t::send_cmd. + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @warning For application specific commands, the @ref sdmmc_send_acmd function + * MUST to be used. + * + * The response is stored word-wise in host byte order in the buffer + * provided by @p resp as follows: + * + * - @p resp_type = SDMMC_NO_R (No Response): @p resp can be NULL + * - @p resp_tpye = SDMMC_R2 (Long Response): + * @p resp = { R[127:96], R[95:64], R[63:32], R[31:0] } + * - @p resp_type = anything else (Short Response): @p resp = { R[39:8] } + * + * The buffer provided by @p resp can be NULL if the response is not + * needed. + * + * @note R3 does not use CRC7 in CRC field but a fixed value of `0b111111`. + * The low-level SDIO/SD/MMC peripheral driver must not check the CRC + * field for this response. + * + * @see @ref sdmmc_driver_t::send_cmd + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] cmd_idx Command index + * @param[in] arg Command argument + * @param[in] resp_type Type of response expected + * @param[out] resp Buffer of 32-bit words to store the response if + * needed, otherwise NULL + * + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support the command or is in wrong state + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EBADMSG on CRC7 error in response + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_send_cmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp); + +/** + * @brief Send application specific command optionally wait for response + * + * Send an application specific command @p cmd_idx with argument @p arg + * to the SDIO/SD/MMC Card. This function is a convenience function that + * uses the low-level SDIO/SD/MMC peripheral driver function + * @ref sdmmc_driver_t::send_cmd. It just sends CMD55 before sending the + * command with index @p cmd_idx to the card. Addressed (point-to-point) + * commands are sent to the card that is currently selected. + * + * Instead of using this function, the following could also be used: + * ```c + * sdmmc_send_cmd(dev, SDMMC_CMD55, RCA, SDMMC_R1, NULL); + * sdmmc_send_cmd(dev, cmd_idx & ~SDMMC_ACMD_PREFIX, arg, resp_type, resp); + * ``` + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @warning The command index must be an ACMD command index, i.e. the command + * index ORed with @ref SDMMC_ACMD_PREFIX. Otherwise the function + * fails because of an assertion. + * + * @p resp_type specifies the response expected. + * + * The response is stored word-wise in host byte order in the buffer + * provided by @p resp as follows: + * + * - @p resp_type = SDMMC_NO_R (No Response): @p resp can be NULL + * - @p resp_tpye = SDMMC_R2 (Long Response): + * @p resp = { R[127:96], R[95:64], R[63:32], R[31:0] } + * - @p resp_type = anything else (Short Response): @p resp = { R[39:8] } + * + * The buffer provided by @p resp can be NULL if the response is not + * needed. However, the low-level SDIO/SD/MMC peripheral driver must + * receive the expected response, but does not store it in @p resp + * in that case. + * + * @note R3 does not use CRC7 in CRC field but a fixed value of `0b111111`. + * The low-level SDIO/SD/MMC peripheral driver must not check the CRC + * field for this response. + * + * @pre @p dev must not be `NULL`. @p cmd_idx must be in range 0 to 63. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] cmd_idx Command index of an application specific command + * @param[in] arg Command argument + * @param[in] resp_type Type of response expected + * @param[out] resp Buffer of 32-bit words to store the response + * + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support the command or is in wrong state + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EBADMSG on CRC7 error in response + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_send_acmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp); + +/** + * @brief Card Initialization and Identification + * + * This function identifies the type of used SDIO/SD/MMC Cards and + * initializes them according to the standardized procedure specified in: + * + * - Physical Layer Simplified Specification Version 9.00, + * Section 4.2.3 Card Initialization and Identification Process, pp. 43 + * [[sdcard.org](https://www.sdcard.org)] + * - SDIO Simplified Specification Version 3.00, + * Section 3.1.2 Initialization by I/O Aware Host, pp. 17 + * [[sdcard.org](https://www.sdcard.org)] + * - SD Host Controller Simplified Specification Version 4.20, + * Section 3.6 Card Initialization and Identification (for SD I/F), pp. 160 + * [[sdcard.org](https://www.sdcard.org)] + * - JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), + * Section 7.2 Card Identification Mode, p. 24 and Figure 16 on p. 16 + * [[jedec.org](https://www.jedec.org)] + * + * Identified card types are: + * - SD Memory Cards with Standard Capacity (SDSC) Version 1.x, Version 2.x+ + * - SD Memory Cards with High or Extended Capacity (SDHC/SDXC) + * - SDIO Cards, not supported yet + * - Combined SDIO/SD Memory Cards (Combo Cards), SDIO part not supported yet + * - MultiMedia Cards (MMC) and Embedded Multimedia Cards (eMMC) + * + * @warning If the low-level SDIO/SD/MMC peripheral driver defines its own + * @ref sdmmc_driver_t::card_init function, this function is used + * instead. \n + * However, the low-level SDIO/SD/MMC peripheral driver should + * define its own @ref sdmmc_driver_t::card_init function only in + * very special cases, e.g. when special hardware handling is + * required. Otherwise it is strongly recommended to set + * sdmmc_driver_t::card_init to `NULL` and to use the default + * procedure implemented by @ref sdmmc_send_acmd. + * + * @see @ref sdmmc_driver_t::card_init + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * + * @retval 0 on success + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card is not supported or can't operate under supplied voltage + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on card initialization and identification timeout + * @retval -EBADMSG on CRC7 error + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_card_init(sdmmc_dev_t *dev); + +/** + * @brief Perform a data transfer with the selected card or embedded device + * + * This high-level SDIO/SD/MMC device API function performs a read or write data + * transfer either + * + * - block-oriented with a number of blocks of the same size (SD and MMC) + * - as stream until the stop command (CMD12) is sent by the host (MMC only) + * - in byte or multi-byte mode (SDIO only) + * + * The possible block sizes @p block_size depend on the card and mode + * used. For block-oriented data transfers, the block size is usually + * 512 bytes, but may differ for MMCs and SD Cards with Standard Capacity + * Version 1.x (SDSC): + * + * - MMC: block size can be 1, 2, 4, ..., 4096 bytes (as power of 2) + * - SDSC V1.x: block size can be 512, 1024 or 2048 bytes + * - SDSC V2.x and later, SDSC/SDHC: block size is 512 bytes + * + * The block size for SDIO transfers in byte or multi-byte mode, if + * supported, can be in the range of 1 to 512 bytes. The number of + * blocks @p block_num MUST be 1 in this case. + * + * For a data transfer as a stream, the block size @p block_size and the + * number of blocks @p block_num are 0. + * + * @note Some block-oriented data transfers such as reading the SD Card + * Configuration register (SCR) with ACMD51 may use the + * @ref sdmmc_xfer function with smaller block size, for example + * only 8 bytes. The low-level SDIO/SD/MMC peripheral driver MUST + * support block sizes smaller than the usual block size used + * for block-oriented operations. + * + * The @ref sdmmc_xfer function uses the low-level SDIO/SD/MMC peripheral + * driver functions @ref sdmmc_driver_t::xfer_prepare to prepare the data + * transfer, @ref sdmmc_driver_t::xfer_execute to perform the data transfer, + * and @ref sdmmc_driver_t::xfer_finish to complete the data transfer. + * In detail: + * ```c + * _wait_for_ready(dev); + * if (cmd_idx & SDMMC_ACMD_PREFIX) { + * sdmmc_send_cmd(dev, SDMMC_CMD55, ...); + * } + * dev->driver->xfer_prepare(dev, xfer); + * sdmmc_send_cmd(dev, cmd_idx, ...); + * dev->driver->xfer_execute(dev, xfer, ...); + * if (block_num > 1) { + * sdmmc_send_cmd(dev, SDMMC_CMD12, ...); + * } + * dev->driver->xfer_finish(dev, xfer); + * ``` + * @p xfer is the transfer descriptor of type @ref sdmmc_xfer_desc_t that + * contains all transfer parameters. + * + * If the parameter @p done is not `NULL`, the function returns the number of + * transferred blocks. + * + * @warning + * The buffers specified by @p data_wr and @p data_rd may need to be + * word-aligned depending on CPU-specific requirements. @ref sdmmc_buf_t + * or @ref SDMMC_CPU_DMA_REQUIREMENTS have to be used to define such buffers: + * ```c + * sdmmc_buf_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ... + * SDMMC_CPU_DMA_REQUIREMENTS uint8_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ``` + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @see @ref sdmmc_driver_t::xfer_prepare, @ref sdmmc_driver_t::xfer_execute + * and @ref sdmmc_driver_t::xfer_finish + * @pre @p dev must not be `NULL`. \n + * @p data_rd must not be `NULL` for read transfers and + * @p data_wr must not be `NULL` for write transfers. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] cmd_idx Command index or application specific command index + * @param[in] arg Command argument + * @param[in] block_size Block size dependent on card and mode used + * - 512 bytes for block-oriented transfers (SDSC V2.x/SDHC/SDXC) + * - 1, 2, 4, ... 4096 for block-oriented transfers (MMC) + * - 512, 1024, 2048 for block-oriented transfers (SDSC V1.x) + * - 1...512 bytes in byte/multibyte mode (SDIO) + * - 0 in stream mode (SDIO) + * @param[in] block_num Number of blocks: + * - 1, ... for block-oriented transfers (SD Card/MMC) + * - 1 in byte/multibyte mode (SDIO) + * - 0 in stream mode (MMC) + * @param[in] data_wr Buffer with data to write in write transfers, NULL otherwise + * @param[out] data_rd Buffer for data to read in read transfers, NULL otherwise + * @param[out] done Number of transferred blocks, can be `NULL` + * + * @retval 0 on success + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support a used command or is in wrong state + * @retval -EBUSY if card is busy + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EINVAL on invalid transfer parameters + * @retval -EBADMSG on CRC7 error + * @retval -ENOMEM on RX FIFO overflow or TX FIFO underrun error + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_xfer(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + uint16_t block_size, uint16_t block_num, + const void *data_wr, void *data_rd, uint16_t *done); + +/** + * @brief Read a number of blocks + * + * Read @p block_num blocks with size @p block_size from @p dev starting at + * block address @p block_addr to buffer @p data. + * + * The starting block address is always specified as block address independent + * on the actual addressing scheme of used card. The driver takes care of + * mapping to byte addressing if needed. + * + * If the parameter @p done is not `NULL`, the function returns the number of + * read blocks. + * + * @warning + * The buffer @p data may need to be word-aligned depending on CPU-specific + * requirements. @ref sdmmc_buf_t or @ref SDMMC_CPU_DMA_REQUIREMENTS have to + * be used to define the buffer: + * ```c + * sdmmc_buf_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ... + * SDMMC_CPU_DMA_REQUIREMENTS uint8_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ``` + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @pre @p dev and @p data must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] block_addr Start address to read from given as block address + * @param[in] block_size Block size dependent on card and mode used + * - 512 bytes for block-oriented transfers (SDSC V2.x/SDHC/SDXC) + * - 1, 2, 4, ... 4096 for block-oriented transfers (MMC) + * - 512, 1024, 2048 for block-oriented transfers (SDSC V1.x) + * - 1...512 bytes in byte/multibyte mode (SDIO only) + * - 0 in stream mode (MMC only) + * @param[in] block_num Number of blocks: + * - 1, ... for block-oriented transfers (SD Card/MMC) + * - 1 in byte/multibyte mode (SDIO only) + * - 0 in stream mode (MMC only) + * @param[out] data Buffer for read data + * @param[out] done Number of read blocks, can be `NULL` + * + * @retval 0 on success, @p block_num blocks were read successfully + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support a used command or is in wrong state + * @retval -EBUSY if card is busy + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EINVAL on invalid transfer parameters + * @retval -EBADMSG on CRC7 error + * @retval -ENOMEM on RX FIFO overflow error + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_read_blocks(sdmmc_dev_t *dev, + uint32_t block_addr, uint16_t block_size, + uint16_t block_num, void *data, uint16_t *done); + +/** + * @brief Write a number of blocks + * + * Write @p block_num blocks with size @p block_size to @p dev starting at + * block address @p block_addr from buffer @p data. + * + * The starting block address is always specified as block address independent + * on the actual addressing scheme of used card. The driver takes care of + * mapping to byte addressing if needed. + * + * If the parameter @p done is not `NULL`, the function returns the number of + * written blocks. + * + * @warning + * The buffer @p data may need to be word-aligned depending on CPU-specific + * requirements. @ref sdmmc_buf_t or @ref SDMMC_CPU_DMA_REQUIREMENTS have to + * be used to define the buffer: + * ```c + * sdmmc_buf_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ... + * SDMMC_CPU_DMA_REQUIREMENTS uint8_t buffer[SDMMC_SDHC_BLOCK_SIZE]; + * ``` + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @pre @p dev and @p data must not be `NULL`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] block_addr Start address to write to given as block address + * @param[in] block_size Block size dependent on card and mode used + * - 512 bytes for block-oriented transfers (SDSC V2.x/SDHC/SDXC) + * - 1, 2, 4, ... 4096 for block-oriented transfers (MMC) + * - 512, 1024, 2048 for block-oriented transfers (SDSC V1.x) + * - 1...512 bytes in byte/multibyte mode (SDIO only) + * - 0 in stream mode (MMC only) + * @param[in] block_num Number of blocks: + * - 1, ... for block-oriented transfers (SD and MMC) + * - 1 in byte/multibyte mode (SDIO only) + * - 0 in stream mode (MMC only) + * @param[in] data Buffer with data to write + * @param[out] done Number of blocks written, can be `NULL` + * + * @retval 0 on success, @p block_num blocks were read successfully + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support a used command or is in wrong state + * @retval -EBUSY if card is busy + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EINVAL on invalid transfer parameters + * @retval -EBADMSG on CRC7 error + * @retval -ENOMEM on TX FIFO underrun error + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_write_blocks(sdmmc_dev_t *dev, uint32_t block_addr, + uint16_t block_size, uint16_t block_num, + const void *data, uint16_t *done); + +/** + * @brief Erase a number of blocks + * + * Erase @p block_num blocks starting at block address @p block_addr on + * SD/MMC Card device. + * + * The starting block address is always specified as block address independent + * on the actual addressing scheme of used card. The driver takes care of + * mapping to byte addressing if needed. + * + * @note This function is only available for SD Memory Cards, MMC Cards + * or the SD Memory Card portion of a combined SDIO/SD Memory Card + * (Combo Card). Calling this function for a SDIO only card returns + * the `-ENOTSUP` error. + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @pre @p dev must not be `NULL`. @p block_num has to be greater than 0. + * + * @param[in] dev SD/MMC device to be used + * @param[in] block_addr Start address for erase given as block address + * @param[in] block_num Number of blocks to be erased + * + * @retval 0 on success, @p block_num blocks were erased successfully + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support erase operation or a command used + * @retval -EBUSY if card is busy + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EBADMSG on CRC7 error in response + * @retval -EINVAL on invalid erase parameters + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_erase_blocks(sdmmc_dev_t *dev, + uint32_t block_addr, uint16_t block_num); + +/** + * @brief Read SD Status Register + * + * Read the SD Status register of a SD Memory Card using ACMD13 and stores + * the results in the @p sds of type @ref sdmmc_sd_status_t. + * + * @note This function is only available for SD Memory Cards + * or the SD Memory Card portion of a combined SDIO/SD Memory Card + * (Combo Card). Calling this function for a SDIO only card returns + * the `-ENOTSUP` error. + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @pre @p dev and @p sds must not be `NULL`. + * + * @param[in] dev SD device to be used + * @param[out] sds SD Status register content + * + * @retval 0 on success + * @retval -ENODEV if card is not present or not usable + * @retval -ENOTSUP if card does not support the operation + * @retval -EBUSY if card is busy + * @retval -EFAULT on card status error + * @retval -ETIMEDOUT on timeout condition + * @retval -EINVAL on invalid transfer parameters + * @retval -EBADMSG on CRC7 error + * @retval -ENOMEM on RX FIFO overflow error + * @retval -EIO on not further specified error incl. hardware errors + */ +int sdmmc_read_sds(sdmmc_dev_t *dev, sdmmc_sd_status_t *sds); + +/** + * @brief Get Capacity of SD/MMC Card + * + * Get the capacity of a SD/MMC Card device. + * + * @note This function is only available for SD Memory Cards, MMC Cards + * or the SD Memory Card portion of a combined SDIO/SD Memory Card + * (Combo Card). Calling this function for a SDIO only card returns + * the `-ENOTSUP` error. + * + * @note @ref sdmmc_card_init is called implicitly if necessary. + * + * @pre @p dev must not be `NULL`. + * + * @param[in] dev SD/MMC device to be used + * + * @return the capacity in in byte or 0 on error + */ +uint64_t sdmmc_get_capacity(sdmmc_dev_t *dev); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SDMMC_SDMMC_H */ diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index 23aa76002a..a7b3d1fafd 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -38,6 +38,21 @@ config HAVE_MTD_SDCARD help Indicates that a sdcard MTD is present +config HAVE_MTD_SDMMC_DEFAULT + bool + depends on HAS_PERIPH_SDMMC + imply MODULE_MTD_SDMMC if MODULE_MTD + imply MODULE_MTD_SDMMC_DEFAULT if MODULE_MTD + help + Indicates that a SD/MMC MTD is present with generic configuration + +config HAVE_MTD_SDMMC + bool + depends on HAS_PERIPH_SDMMC + imply MODULE_MTD_SDMMC if MODULE_MTD + help + Indicates that a SD/MMC MTD is present + config HAVE_MTD_SPI_NOR bool imply MODULE_MTD_SPI_NOR if MODULE_MTD @@ -46,13 +61,13 @@ config HAVE_MTD_SPI_NOR config HAVE_SAM0_SDHC bool - imply MODULE_SAM0_SDHC if MODULE_MTD + imply MODULE_SAM0_SDHC if MODULE_MTD && !MODULE_MTD_SDMMC help Indicates that a SAM0 SD Host Controller MTD is present config HAVE_MTD_SPI_MCI bool - imply MODULE_MTD_MCI if MODULE_MTD + imply MODULE_MTD_MCI if MODULE_MTD && !MODULE_MTD_SDMMC help Indicates that a Multimedia Card Interface (MCI) MTD is present @@ -62,7 +77,7 @@ menuconfig MODULE_MTD if MODULE_MTD -menu "MTD Interefaces" +menu "MTD Interfaces" config MODULE_MTD_SPI_NOR bool "MTD interface for SPI NOR Flash" @@ -95,22 +110,33 @@ config MODULE_MTD_MCI depends on CPU_FAM_LPC23XX select MODULE_MCI +config MODULE_MTD_SDCARD + bool "MTD interface for SPI SD-Card" + depends on MODULE_SDCARD_SPI + config MODULE_MTD_SDCARD_DEFAULT bool "Use Generic SD card configuration" depends on MODULE_MTD_SDCARD help Automatically create a MTD device and mount point for the SD card. -config MODULE_MTD_SDCARD - bool "MTD interface for SPI SD-Card" - depends on MODULE_SDCARD_SPI +config MODULE_MTD_SDMMC + bool "MTD interface for SD/MMC" + depends on HAS_PERIPH_SDMMC + select MODULE_PERIPH_SDMMC + +config MODULE_MTD_SDMMC_DEFAULT + bool "Use Generic SD/MMC card configuration" + depends on MODULE_MTD_SDMMC + help + Automatically create a MTD device and mount point for the SD/MMC card. config MODULE_MTD_EMULATED bool "MTD interface for MTD emulated in RAM" config MODULE_SAM0_SDHC bool "MTD interface for SAM0 SD Host Controller" - depends on CPU_COMMON_SAM0 + depends on CPU_COMMON_SAM0 && HAVE_SAM0_SDHC && !MODULE_PERIPH_SDMMC endmenu # MTD Interfacs diff --git a/drivers/mtd/Makefile.dep b/drivers/mtd/Makefile.dep index 6c0bce41d7..a951c62d03 100644 --- a/drivers/mtd/Makefile.dep +++ b/drivers/mtd/Makefile.dep @@ -13,3 +13,11 @@ endif ifneq (,$(filter mtd_sdcard,$(USEMODULE))) USEMODULE += sdcard_spi endif + +ifneq (,$(filter mtd_sdmmc_default,$(USEMODULE))) + USEMODULE += mtd_sdmmc +endif + +ifneq (,$(filter mtd_sdmmc,$(USEMODULE))) + USEMODULE += sdmmc +endif diff --git a/drivers/mtd_sdmmc/Kconfig b/drivers/mtd_sdmmc/Kconfig new file mode 100644 index 0000000000..ff0134a0c7 --- /dev/null +++ b/drivers/mtd_sdmmc/Kconfig @@ -0,0 +1,35 @@ +# Copyright (c) 2020 Freie Universitaet Berlin +# 2020 HAW Hamburg +# 2023 Gunar Schorcht +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# +menuconfig KCONFIG_USEMODULE_MTD_SDMMC + bool "Configure MTD_SDMMC driver" + depends on USEMODULE_MTD_SDMMC + help + Configure the MTD_SDMMC driver using Kconfig. + +if KCONFIG_USEMODULE_MTD_SDMMC + +config SDMMC_GENERIC_MTD_OFFSET + depends on MODULE_MTD_SDMMC_DEFAULT + int "Index of first auto-configured MTD SD/MMC device" + default 0 + help + If you have other MTD devices defined, set this number so that + the auto-configured SD Memory Card(s) or MMCs/eMMCs from + mtd_sdmmc_default will come after them. + +config MTD_SDMMC_ERASE + bool "Enable SD Memory Card erase" + help + Enable this to erase sector before a data write operation (SD Memory + Cards only). + SD Memory Cards and MMCs/eMMCs handle sector erase internally + so it's possible to directly write to the card without erasing + the sector first hence this feature is disabled by default. + +endif # KCONFIG_USEMODULE_MTD_SDMMC diff --git a/drivers/mtd_sdmmc/Makefile b/drivers/mtd_sdmmc/Makefile new file mode 100644 index 0000000000..e644f4a4c9 --- /dev/null +++ b/drivers/mtd_sdmmc/Makefile @@ -0,0 +1,3 @@ +MODULE = mtd_sdmmc + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/mtd_sdmmc/Makefile.include b/drivers/mtd_sdmmc/Makefile.include new file mode 100644 index 0000000000..a7a441e75a --- /dev/null +++ b/drivers/mtd_sdmmc/Makefile.include @@ -0,0 +1 @@ +PSEUDOMODULES += mtd_sdmmc_default diff --git a/drivers/mtd_sdmmc/mtd_sdmmc.c b/drivers/mtd_sdmmc/mtd_sdmmc.c new file mode 100644 index 0000000000..624c95f250 --- /dev/null +++ b/drivers/mtd_sdmmc/mtd_sdmmc.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2017 HAW-Hamburg + * 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + */ + +/** + * @ingroup drivers_mtd_sdmmc + * @{ + * + * @file + * @brief Driver for using sdmmc via mtd interface + * + * @author Michel Rottleuthner + * @author Gunar Schorcht + * + * @} + */ + +#include +#include +#include + +#define ENABLE_DEBUG 0 +#include "debug.h" +#include "kernel_defines.h" +#include "macros/utils.h" +#include "mtd.h" +#include "mtd_sdmmc.h" +#include "sdmmc/sdmmc.h" + +static int mtd_sdmmc_init(mtd_dev_t *dev) +{ + DEBUG("mtd_sdmmc_init\n"); + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + /* get the SDMMC device descriptor from SDMMC peripheral index */ + mtd_sd->sdmmc = sdmmc_get_dev(mtd_sd->sdmmc_idx); + + if ((mtd_sd->sdmmc->init_done == true) || + (sdmmc_card_init(mtd_sd->sdmmc) == 0)) { + /* erasing whole sectors is handled internally by the card so you can + delete single blocks (i.e. pages) */ + dev->pages_per_sector = 1; + dev->sector_count = (uint32_t)(sdmmc_get_capacity(mtd_sd->sdmmc) / + SDMMC_SDHC_BLOCK_SIZE); + + /* sdcard uses the fixed block size of SD-HC cards */ + dev->page_size = SDMMC_SDHC_BLOCK_SIZE; + dev->write_size = SDMMC_SDHC_BLOCK_SIZE; + return 0; + } + + return -EIO; +} + +static int mtd_sdmmc_read_page(mtd_dev_t *dev, void *buff, uint32_t page, + uint32_t offset, uint32_t size) +{ + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + DEBUG("mtd_sdmmc_read_page: page:%" PRIu32 " offset:%" PRIu32 " size:%" PRIu32 "\n", + page, offset, size); + + if (offset || size % SDMMC_SDHC_BLOCK_SIZE) { +#if IS_USED(MODULE_MTD_WRITE_PAGE) + if (dev->work_area == NULL) { + DEBUG("mtd_sdmmc_read_page: no work area\n"); + return -ENOTSUP; + } + + if (sdmmc_read_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } + size = MIN(size, SDMMC_SDHC_BLOCK_SIZE - offset); + DEBUG("mtd_sdmmc_read_page: read %" PRIu32 " bytes at offset %" PRIu32 "\n", + size, offset); + memcpy(buff, (uint8_t *)dev->work_area + offset, size); + return size; +#else + return -ENOTSUP; +#endif + } + + int err = sdmmc_read_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + size / SDMMC_SDHC_BLOCK_SIZE, buff, NULL); + if (err) { + DEBUG("mtd_sdmmc_read_page: error %d\n", err); + return -EIO; + } + return size; +} + +static int mtd_sdmmc_write_page(mtd_dev_t *dev, const void *buff, uint32_t page, + uint32_t offset, uint32_t size) +{ + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + DEBUG("mtd_sdmmc_write_page: page:%" PRIu32 " offset:%" PRIu32 " size:%" PRIu32 "\n", + page, offset, size); + + if (offset || size % SDMMC_SDHC_BLOCK_SIZE) { +#if IS_USED(MODULE_MTD_WRITE_PAGE) + if (dev->work_area == NULL) { + DEBUG("mtd_sdmmc_write_page: no work area\n"); + return -ENOTSUP; + } + + if (sdmmc_read_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } + + size = MIN(size, SDMMC_SDHC_BLOCK_SIZE - offset); + DEBUG("mtd_sdmmc_write_page: write %" PRIu32 " bytes at offset %" PRIu32 "\n", + size, offset); + memcpy((uint8_t *)dev->work_area + offset, buff, size); + if (sdmmc_write_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } +#else + return -ENOTSUP; +#endif + } + else { + int err = sdmmc_write_blocks(mtd_sd->sdmmc, page, SDMMC_SDHC_BLOCK_SIZE, + size / SDMMC_SDHC_BLOCK_SIZE, buff, NULL); + if (err) { + DEBUG("mtd_sdmmc_write_page: error %d\n", err); + return -EIO; + } + } + return size; +} + +static int mtd_sdmmc_erase_sector(mtd_dev_t *dev, uint32_t sector, uint32_t count) +{ +#if IS_ACTIVE(CONFIG_MTD_SDMMC_ERASE) && IS_USED(MODULE_MTD_WRITE_PAGE) + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + + DEBUG("mtd_sdmmc_erase_sector: sector: %" PRIu32 " count: %" PRIu32 "\n", + sector, count); + + if (dev->work_area == NULL) { + DEBUG("mtd_sdmmc_erase_sector: no work area\n"); + return -ENOTSUP; + } + memset(dev->work_area, 0, SDMMC_SDHC_BLOCK_SIZE); + while (count) { + if (sdmmc_write_blocks(mtd_sd->sdmmc, sector, SDMMC_SDHC_BLOCK_SIZE, + 1, dev->work_area, NULL)) { + return -EIO; + } + --count; + ++sector; + } +#else + (void)dev; + (void)sector; + (void)count; + mtd_sdmmc_t *mtd_sd = (mtd_sdmmc_t*)dev; + if (IS_ACTIVE(CONFIG_MTD_SDMMC_ERASE)) { + return sdmmc_erase_blocks(mtd_sd->sdmmc, sector, count); + } +#endif + return 0; +} + +static int mtd_sdmmc_power(mtd_dev_t *dev, enum mtd_power_state power) +{ + (void)dev; + (void)power; + + /* TODO: implement power down of sdcard in sdcard_spi + (make use of sdcard_spi_params_t.power pin) */ + return -ENOTSUP; /* currently not supported */ +} + +static int mtd_sdmmc_read(mtd_dev_t *dev, void *buff, uint32_t addr, + uint32_t size) +{ + int res = mtd_sdmmc_read_page(dev, buff, addr / SDMMC_SDHC_BLOCK_SIZE, + addr % SDMMC_SDHC_BLOCK_SIZE, size); + if (res < 0) { + return res; + } + if (res == (int)size) { + return 0; + } + return -EOVERFLOW; +} + +static int mtd_sdmmc_write(mtd_dev_t *dev, const void *buff, uint32_t addr, + uint32_t size) +{ + int res = mtd_sdmmc_write_page(dev, buff, addr / SDMMC_SDHC_BLOCK_SIZE, + addr % SDMMC_SDHC_BLOCK_SIZE, size); + if (res < 0) { + return res; + } + if (res == (int)size) { + return 0; + } + return -EOVERFLOW; +} + +const mtd_desc_t mtd_sdmmc_driver = { + .init = mtd_sdmmc_init, + .read = mtd_sdmmc_read, + .read_page = mtd_sdmmc_read_page, + .write = mtd_sdmmc_write, + .write_page = mtd_sdmmc_write_page, + .erase_sector = mtd_sdmmc_erase_sector, + .power = mtd_sdmmc_power, +}; + +#if IS_USED(MODULE_MTD_SDMMC_DEFAULT) +#include "vfs_default.h" + +#ifndef CONFIG_SDMMC_GENERIC_MTD_OFFSET +#define CONFIG_SDMMC_GENERIC_MTD_OFFSET 0 +#endif + +#define MTD_SDMMC_DEV(n, m) \ + mtd_sdmmc_t mtd_sdmmc_dev ##n = { \ + .base = { \ + .driver = &mtd_sdmmc_driver, \ + }, \ + .sdmmc_idx = n, \ + }; \ + \ + mtd_dev_t CONCAT(*mtd, m) = (mtd_dev_t *)&mtd_sdmmc_dev ##n + +#if IS_USED(MODULE_MTD_SDCARD_DEFAULT) +/* we use /sd1 as default mount point for coexistence with mtd_sdcard */ +#define MTD_SDMMC_DEV_FS(n, m, filesystem) \ + VFS_AUTO_MOUNT(filesystem, VFS_MTD(mtd_sdmmc_dev ##n), VFS_DEFAULT_SD(1), m) +#else +#define MTD_SDMMC_DEV_FS(n, m, filesystem) \ + VFS_AUTO_MOUNT(filesystem, VFS_MTD(mtd_sdmmc_dev ##n), VFS_DEFAULT_SD(n), m) +#endif + +MTD_SDMMC_DEV(0, CONFIG_SDMMC_GENERIC_MTD_OFFSET); +#ifdef MODULE_FATFS_VFS +MTD_SDMMC_DEV_FS(0, CONFIG_SDMMC_GENERIC_MTD_OFFSET, fatfs); +#endif + +#endif diff --git a/drivers/periph_common/Kconfig b/drivers/periph_common/Kconfig index 5b5c53d64f..4db0969b8e 100644 --- a/drivers/periph_common/Kconfig +++ b/drivers/periph_common/Kconfig @@ -153,6 +153,7 @@ config MODULE_PERIPH_INIT_RTT default y if MODULE_PERIPH_INIT depends on MODULE_PERIPH_RTT +rsource "Kconfig.sdmmc" rsource "Kconfig.spi" config MODULE_PERIPH_TEMPERATURE diff --git a/drivers/periph_common/Kconfig.sdmmc b/drivers/periph_common/Kconfig.sdmmc new file mode 100644 index 0000000000..a40f65f5fc --- /dev/null +++ b/drivers/periph_common/Kconfig.sdmmc @@ -0,0 +1,62 @@ +# Copyright (c) 2020 HAW Hamburg +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +menuconfig MODULE_PERIPH_SDMMC + bool "SDIO/SD/MMC peripheral driver" + depends on HAS_PERIPH_SDMMC + select MODULE_PERIPH_COMMON + +if MODULE_PERIPH_SDMMC + +config MODULE_PERIPH_INIT_SDMMC + bool "Auto initialize SDIO/SD/MMC peripheral" + default y if MODULE_PERIPH_INIT + +config MODULE_PERIPH_SDMMC_8BIT + bool "8 Bit data bus support" + depends on HAS_PERIPH_SDMMC_8BIT + default y + help + If the SDIO/SD/MMC peripheral supports the 8-bit bus width, it can be + used by enabling this option. If the option is disabled, the driver + requires less RAM and ROM. + +config MODULE_PERIPH_INIT_SDMMC_8BIT + bool + depends on MODULE_PERIPH_SDMMC_8BIT + default y if MODULE_PERIPH_INIT + +config MODULE_PERIPH_SDMMC_HS + bool "High speed access" + depends on HAS_PERIPH_SDMMC_HS + default y + help + If the SDIO/SD/MMC peripheral supports the high speed access, i.e. 50 + MHz for SD and 52 MHz for MMC, it can be used by enabling this option. + If the option is disabled, the driver requires less RAM and ROM. + +config MODULE_PERIPH_INIT_SDMMC_HS + bool + depends on MODULE_PERIPH_SDMMC_HS + default y if MODULE_PERIPH_INIT + +config MODULE_PERIPH_SDMMC_AUTO_CLK + bool + depends on HAS_PERIPH_SDMMC_AUTO_CLK + default y + help + If the SDIO/SD/MMC peripheral supports the Auto-CLK feature, i.e. + the automatic activation and deactivation of the SD CLK signal, + it is enabled automatically by this option. Otherwise, the activation + and deactivation is controlled by SDIO/SD/MMC high-level functions. + +config MODULE_PERIPH_INIT_SDMMC_AUTO_CLK + bool + depends on MODULE_PERIPH_SDMMC_AUTO_CLK + default y if MODULE_PERIPH_INIT + +endif # MODULE_PERIPH_SDMMC diff --git a/drivers/periph_common/init.c b/drivers/periph_common/init.c index a92a4cf2e4..834ca3d672 100644 --- a/drivers/periph_common/init.c +++ b/drivers/periph_common/init.c @@ -57,6 +57,9 @@ #ifdef MODULE_PERIPH_INIT_PIO #include "periph/pio.h" #endif +#ifdef MODULE_PERIPH_INIT_SDMMC +#include "sdmmc/sdmmc.h" +#endif #endif /* MODULE_PERIPH_INIT */ void periph_init(void) @@ -138,5 +141,11 @@ void periph_init(void) pio_start_programs(); #endif +#if defined(MODULE_PERIPH_INIT_SDMMC) + for (unsigned i = 0; i < SDMMC_NUMOF; i++) { + sdmmc_init(sdmmc_get_dev(i)); + } +#endif + #endif /* MODULE_PERIPH_INIT */ } diff --git a/drivers/sdmmc/Kconfig b/drivers/sdmmc/Kconfig new file mode 100644 index 0000000000..66bf2008b7 --- /dev/null +++ b/drivers/sdmmc/Kconfig @@ -0,0 +1,22 @@ +# Copyright (c) 2020 HAW Hamburg +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. +# + +config MODULE_SDMMC + bool "SDIO/SD/MMC interface" + depends on HAS_PERIPH_SDMMC + select MODULE_PERIPH_SDMMC + select MODULE_ZTIMER_MSEC if !ZTIMER_USEC + +if MODULE_SDMMC + +config MODULE_SDMMC_MMC + bool "MMC support" + depends on HAS_PERIPH_SDMMC_MMC + help + Say y to support MMCs/eMMCs. + +endif # MODULE_SDMMC diff --git a/drivers/sdmmc/Makefile b/drivers/sdmmc/Makefile new file mode 100644 index 0000000000..1952551650 --- /dev/null +++ b/drivers/sdmmc/Makefile @@ -0,0 +1,3 @@ +MODULE = sdmmc + +include $(RIOTBASE)/Makefile.base diff --git a/drivers/sdmmc/Makefile.dep b/drivers/sdmmc/Makefile.dep new file mode 100644 index 0000000000..fdd2a7834c --- /dev/null +++ b/drivers/sdmmc/Makefile.dep @@ -0,0 +1,13 @@ +FEATURES_REQUIRED += periph_sdmmc +FEATURES_OPTIONAL += periph_sdmmc_auto_clk +FEATURES_OPTIONAL += periph_sdmmc_auto_cmd12 +FEATURES_OPTIONAL += periph_sdmmc_hs + +ifneq (,$(filter sdmmc_mmc,$(USEMODULE))) + FEATURES_REQUIRED += periph_sdmmc_mmc +endif + +ifeq (,$(filter ztimer_usec,$(USEMODULE))) + # enable ztimer_msec backend if ztimer_usec is not enabled + USEMODULE += ztimer_msec +endif diff --git a/drivers/sdmmc/Makefile.include b/drivers/sdmmc/Makefile.include new file mode 100644 index 0000000000..5f19835817 --- /dev/null +++ b/drivers/sdmmc/Makefile.include @@ -0,0 +1 @@ +PSEUDOMODULES += sdmmc_mmc diff --git a/drivers/sdmmc/sdmmc.c b/drivers/sdmmc/sdmmc.c new file mode 100644 index 0000000000..4e2b4a8dd8 --- /dev/null +++ b/drivers/sdmmc/sdmmc.c @@ -0,0 +1,1612 @@ +/* + * Copyright (C) 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_sdmmc + * @{ + * + * @file + * @brief SDIO/SD/MMC device API (SDMMC) + * + * @author Gunar Schorcht + * + * @} + */ + +#include +#include +#include +#include + +#include "architecture.h" +#include "assert.h" +#include "bitarithm.h" +#include "byteorder.h" +#include "log.h" +#include "periph_cpu.h" +#include "time_units.h" +#include "ztimer.h" + +#include "sdmmc/sdmmc.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* millisecond timer definitions dependent on active ztimer backend */ +#if IS_USED(MODULE_ZTIMER_MSEC) +#define _ZTIMER_ACQUIRE() ztimer_acquire(ZTIMER_MSEC) +#define _ZTIMER_RELEASE() ztimer_release(ZTIMER_MSEC) +#define _ZTIMER_NOW() ztimer_now(ZTIMER_MSEC) +#define _ZTIMER_SLEEP_MS(n) ztimer_sleep(ZTIMER_MSEC, n) +#elif IS_USED(MODULE_ZTIMER_USEC) +#define _ZTIMER_ACQUIRE() ztimer_acquire(ZTIMER_USEC) +#define _ZTIMER_RELEASE() ztimer_release(ZTIMER_USEC) +#define _ZTIMER_NOW() ztimer_now(ZTIMER_USEC) / US_PER_MS +#define _ZTIMER_SLEEP_MS(n) ztimer_sleep(ZTIMER_USEC, n * US_PER_MS) +#else +#error "Either module ztimer_msec or ztimer_usec is needed" +#endif + +/** + * @brief SDIO/SD/MMC Card initialization timeout in msec + * + * Card initialization shall be completed within 1 second from the first + * ACMD41. + * + * @see Physical Layer Simplified Specification Version 9.00, Section 4.2.3 + * [[sdcard.org](https://www.sdcard.org)] + */ +#define SDMMC_INIT_TIMEOUT_MS (1000) + +/** + * @brief SD/MMC Timeout Conditions + * + * @see Physical Layer Simplified Specification Version 9.00, + * Section 4.6.2 Read, Write and Erase Timeout Conditions + * [[sdcard.org](https://www.sdcard.org)] + */ +#define SDMMC_DATA_R_TIMEOUT_MS (100) /**< Read timeout should be 100 ms */ +#define SDMMC_DATA_W_TIMEOUT_MS (500) /**< Write timeout should be 250 ms for, + SDSC and 500 ms for SDHC, SDXC */ +#define SDMMC_ERASE_TIMEOUT_MS (250) /**< Erase timeout per block 250 ms */ + +/** + * @brief Time to wait for a SDIO/SD/MMC card to become ready for a data transfer + * + * When writing the data in a block write transfer, the card uses the + * READY_FOR_DATA bit to indicate when it is ready to receive more data. + * The host must wait until the card is ready before making the next data + * transfer. The maximum wait time is the maximum write time. + * + * Physical Layer Simplified Specification Version 9.00, + * 4.6.2 Read, Write and Erase Timeout Conditions + */ +#define SDMMC_WAIT_FOR_CARD_READY_MS 250 + +/* bus widths as used in ACMD (power of 2) */ +#define SDMMC_ACMD6_BUS_WIDTH_1BIT 0 +#define SDMMC_ACMD6_BUS_WIDTH_4BIT 2 +#define SDMMC_ACMD6_BUS_WIDTH_8BIT 3 + +#define SDMMC_ACMD41_S18R (SDMMC_OCR_S18A) +#define SDMMC_ACMD41_HCS (SDMMC_OCR_CCS) +#define SDMMC_ACMD41_POWER_UP (SDMMC_OCR_POWER_UP) +#define SDMMC_ACMD41_VOLTAGES (SDMMC_OCR_ALL_VOLTAGES) + +#define SDMMC_R3_S18A (SDMMC_OCR_S18A) +#define SDMMC_R3_CCS (SDMMC_OCR_CCS) +#define SDMMC_R3_POWER_UP (SDMMC_OCR_POWER_UP) + +#define SDMMC_CMD6_ACCESS (24) /* CMD6 arg bit shift for EXT_CSD access mode */ +#define SDMMC_CMD6_INDEX (16) /* CMD6 arg bit shift for EXT_CSD byte index */ +#define SDMMC_CMD6_VALUE (8) /* CMD6 arg bit shift for EXT_CSD value */ +#define SDMMC_CMD6_CMD_SET (0) /* CMD6 arg bit shift for EXT_CSD command set */ + +/** + * @brief Extended CSD Bus Widths (MMC only) + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.4, Table 55 + * [[jedec.org](https://www.jedec.org)] + */ +enum { + SDMMC_EXT_CSD_BUS_WIDTH_1BIT = 0, /**< Bus with 1-bit */ + SDMMC_EXT_CSD_BUS_WIDTH_4BIT = 1, /**< Bus with 4-bit */ + SDMMC_EXT_CSD_BUS_WIDTH_8BIT = 2, /**< Bus with 8-bit */ +}; + +/** + * @brief Extended CSD High Speed Interface Timing (MMC only) + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.4, Table 46 + * [[jedec.org](https://www.jedec.org)] + */ +enum { + SDMMC_EXT_CSD_HS_TIMING_BACK = 0, /**< Backward compatible timing */ + SDMMC_EXT_CSD_HS_TIMING_HS = 1, /**< High speed */ + SDMMC_EXT_CSD_HS_TIMING_HS200 = 2, /**< HS200 */ + SDMMC_EXT_CSD_HS_TIMING_HS400 = 3, /**< HS400 */ +}; + +/** + * @brief Extended CSD Card Type (MMC only) + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.4, Table 50 + * [[jedec.org](https://www.jedec.org)] + */ +enum { + SDMMC_EXT_CSD_CARD_TYPE_HS26 = 0x01, /**< Highspeed MMC @ 26MHz */ + SDMMC_EXT_CSD_CARD_TYPE_HS52 = 0x02, /**< Highspeed MMC @ 52MHz */ +}; + +/** + * @name Indices in Extended CSD (MMC only) + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 8.4, Table 46 + * [[jedec.org](https://www.jedec.org)] + * @{ + */ +#define SDMMC_EXT_CSD_BUS_WIDTH (183) /**< Index of Bus width mode */ +#define SDMMC_EXT_CSD_HS_TIMING (185) /**< High Speed Interface Timing */ +#define SDMMC_EXT_CSD_CSD_STRUCTURE (194) /**< Card Type */ +#define SDMMC_EXT_CSD_CARD_TYPE (196) /**< CSD Structure Version */ +#define SDMMC_EXT_CSD_SEC_COUNT (212) /**< Card Type */ +/** @} */ + +/** + * @brief EXT_CSD access mode (MMC only) + * + * @see JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 7.4.1, Table 2 + * [[jedec.org](https://www.jedec.org)] + */ +typedef enum { + SDMMC_EXT_CSD_COMMAND_SET = 0b00, /**< command set changed */ + SDMMC_EXT_CSD_SET_BITS = 0b01, /**< bits in pointed byte in EXT_CSD are set */ + SDMMC_EXT_CSD_CLEAR_BITS = 0b01, /**< bits in pointed byte in EXT_CSD are cleared */ + SDMMC_EXT_CSD_WRITE_BYTE = 0b11, /**< value is written to pointed byte */ +} sdmmc_ext_csd_access_t; + +#define SDMMC_CCC_SUPPORT_CMD6 (1 << 10) /** CMD6 support flag in CSD.CCC */ + +#define SDMMC_CMD6_MODE_CHECK (0 << 31) +#define SDMMC_CMD6_MODE_SWITCH (1 << 31) + +#define SDMMC_CMD6_FUNC_GROUP1_SHIFT (0) /**< Group 1 in argument */ +#define SDMMC_CMD6_FUNC_GROUP1_MASK (0x0f) /**< No influence */ +#define SDMMC_CMD6_FUNC_GROUP1_DS (0x00) /**< Default Speed / SDR12 */ +#define SDMMC_CMD6_FUNC_GROUP1_SDR12 (0x00) /**< Default Speed / SDR12 */ +#define SDMMC_CMD6_FUNC_GROUP1_HS (0x01) /**< High Speed / SDR25 */ +#define SDMMC_CMD6_FUNC_GROUP1_SDR25 (0x01) /**< High Speed / SDR25 */ +#define SDMMC_CMD6_FUNC_GROUP1_SDR50 (0x02) /**< SDR50 */ +#define SDMMC_CMD6_FUNC_GROUP1_SDR104 (0x03) /**< SDR104 */ +#define SDMMC_CMD6_FUNC_GROUP1_DDR50 (0x04) /**< DDR50 */ + +#define SDMMC_CMD6_FUNC_GROUP2_SHIFT (4) /**< Group 2 in argument */ +#define SDMMC_CMD6_FUNC_GROUP2_MASK (0x0f) /**< No influence */ +#define SDMMC_CMD6_FUNC_GROUP2_DEFAULT (0x00) /**< Default */ +#define SDMMC_CMD6_FUNC_GROUP2_EC (0x01) /**< For eC */ +#define SDMMC_CMD6_FUNC_GROUP2_OTP (0x03) /**< OTP */ +#define SDMMC_CMD6_FUNC_GROUP2_ASSD (0x04) /**< ASSD */ +#define SDMMC_CMD6_FUNC_GROUP2_VENDOR (0x0e) /**< Vendor specific */ + +#define SDMMC_CMD6_FUNC_GROUP3_SHIFT (8) /**< Group 3 in argument */ +#define SDMMC_CMD6_FUNC_GROUP3_MASK (0x0f) /**< No influence */ +#define SDMMC_CMD6_FUNC_GROUP4_SHIFT (12) /**< Group 4 in argument */ +#define SDMMC_CMD6_FUNC_GROUP4_MASK (0x0f) /**< No influence */ +#define SDMMC_CMD6_FUNC_GROUP5_SHIFT (16) /**< Group 5 in argument */ +#define SDMMC_CMD6_FUNC_GROUP5_MASK (0x0f) /**< No influence */ +#define SDMMC_CMD6_FUNC_GROUP6_SHIFT (20) /**< Group 6 in argument */ +#define SDMMC_CMD6_FUNC_GROUP6_MASK (0x0f) /**< No influence */ + +#define SDMMC_CMD6_SW_STATUS_SIZE (64) /**< Switch Status Size */ + +/** + * @brief Switch Function Status + * + * @note Since the structure is only used to directly map its members to the + * switch state buffer returned by the card, the members are given in + * big-endian order with word-wise host byte order. + * + * @see Physical Layer Simplified Specification Version 9.00, Section 4.3.10.4, + * Table 4-13 [[sdcard.org](https://www.sdcard.org)] + */ +typedef struct __attribute__((packed)) { + /* word 15, byte 60...63 */ + uint32_t group6_supported:16; /**< Supported Functions in Group 6 [495:480] */ + uint32_t max_power:16; /**< Maximum Current/Power Consumption [511:496] */ + /* word 14, byte 56...59 */ + uint32_t group4_supported:16; /**< Supported Functions in Group 4 [463:448] */ + uint32_t group5_supported:16; /**< Supported Functions in Group 5 [479:464] */ + /* word 13, byte 52...55 */ + uint32_t group2_supported:16; /**< Supported Functions in Group 2 [431:416] */ + uint32_t group3_supported:16; /**< Supported Functions in Group 3 [447:432] */ + /* word 12, byte 48...51 */ + uint32_t group3_selected:4; /**< Selected Function in Group 3 [387:384] */ + uint32_t group4_selected:4; /**< Selected Function in Group 4 [391:388] */ + uint32_t group5_selected:4; /**< Selected Function in Group 5 [395:392] */ + uint32_t group6_selected:4; /**< Selected Function in Group 6 [399:396] */ + uint32_t group1_supported:16; /**< Supported Functions in Group 1 [415:400] */ + /* word 11, byte 44...47 */ + uint32_t group6_busy:16; /**< Busy Status of Group 6 Functions [367:352] */ + uint32_t data_struct_v:8; /**< Data Structure Version [375:368] */ + uint32_t group1_selected:4; /**< Selected Function in Group 1 [379:376] */ + uint32_t group2_selected:4; /**< Selected Function in Group 2 [383:380] */ + /* word 10, byte 41...43 */ + uint32_t group4_busy:16; /**< Busy Status of Group 4 Functions [335:320] */ + uint32_t group5_busy:16; /**< Busy Status of Group 5 Functions [351:336] */ + /* word 9, byte 36...40 */ + uint32_t group2_busy:16; /**< Busy Status of Group 2 Functions [303:288] */ + uint32_t group3_busy:16; /**< Busy Status of Group 3 Functions [319:304] */ + /* word 8, byte 32...35 */ + uint32_t reserved1:16; /**< reserved [271:256] */ + uint32_t group1_busy:16; /**< Busy Status of Group 1 Functions [287:272] */ + /* word 0...7, byte 0...31 */ + uint8_t reserved[32]; /**< reserved [255:0] */ +} sdmmc_sw_status_t; + +/* forward declaration of internal functions */ +static int _send_cmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp); +static int _send_acmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp); +static inline int _send_xcmd(sdmmc_dev_t *dev, + sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp); +static int _xfer(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + uint16_t block_size, uint16_t block_num, + const void *data_wr, void *data_rd, uint16_t *done); +static int _assert_card(sdmmc_dev_t *dev); +static int _select_deselect(sdmmc_dev_t *dev, bool select); +static int _get_status(sdmmc_dev_t *dev, sdmmc_card_status_t *cs); +static int _wait_for_ready(sdmmc_dev_t *dev, uint32_t timeout_ms); +static int _wait_while_prg(sdmmc_dev_t *dev, + uint32_t sleep_ms, uint32_t timeout_ms); +static int _read_cid(sdmmc_dev_t *dev, uint8_t cmd); +static int _read_csd(sdmmc_dev_t *dev); +static int _read_scr(sdmmc_dev_t *dev); +static inline int _enable_clock(sdmmc_dev_t *dev); +static inline int _disable_clock(sdmmc_dev_t *dev); + +#if IS_USED(MODULE_SDMMC_MMC) +static int _read_ext_csd(sdmmc_dev_t *dev); +static int _write_ext_csd(sdmmc_dev_t *dev, uint8_t index, uint8_t value); +#endif + +#if ENABLE_DEBUG +static uint8_t _crc_7(const uint8_t *data, unsigned n); +static void _print_raw_data_crc(const uint8_t *data, unsigned size, bool crc); +#endif + +int sdmmc_send_cmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp) +{ + assert(dev); + assert(dev->driver); + + int res = _assert_card(dev); + if (res) { + return res; + } + + return _send_cmd(dev, cmd_idx, arg, resp_type, resp); +} + +int sdmmc_send_acmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp) +{ + assert(dev); + assert(dev->driver); + assert(cmd_idx & SDMMC_ACMD_PREFIX); + + int res; + uint32_t response; + + if (cmd_idx == SDMMC_ACMD41) { + res = sdmmc_send_cmd(dev, SDMMC_CMD55, + SDMMC_CMD_NO_ARG, + SDMMC_R1, &response); + } + else { + assert(dev->rca); + res = sdmmc_send_cmd(dev, SDMMC_CMD55, + SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R1, &response); + } + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + return sdmmc_send_cmd(dev, cmd_idx & ~SDMMC_ACMD_PREFIX, arg, + resp_type, resp); +} + +int sdmmc_card_init(sdmmc_dev_t *dev) +{ + assert(dev); + assert(dev->driver); + + /* use driver's card_init function if it defines its own */ + if (dev->driver->card_init) { + return dev->driver->card_init(dev); + } + + /* set bus width to 1-bit and the peripheral clock to 400 kHz for init */ + dev->driver->set_bus_width(dev, SDMMC_BUS_WIDTH_1BIT); + dev->driver->set_clock_rate(dev, SDMMC_CLK_400K); + + _ZTIMER_ACQUIRE(); /* ztimer is needed for different timeouts */ + + uint32_t response[4]; /* long response requires four 32-bit words */ + int res; + + /* timeout handling */ + uint32_t t_start = _ZTIMER_NOW(); + + _enable_clock(dev); + + DEBUG("[sdmmc] send CMD0 to put all cards into 'idle' state\n"); + /* TODO: SPI mode uses CMD0 with R1 */ + res = _send_cmd(dev, SDMMC_CMD0, SDMMC_CMD_NO_ARG, SDMMC_NO_R, NULL); + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + /* a short delay is required here */ + _ZTIMER_SLEEP_MS(1); + + /* reset RCA and card type */ + dev->rca = 0; + dev->type = SDMMC_CARD_TYPE_UNKNOWN; + + /* Card identification process */ + + DEBUG("[sdmmc] send CMD8 to test for a SD/SDIO card\n"); + res = _send_cmd(dev, SDMMC_CMD8, SDMMC_CMD8_CHECK, SDMMC_R7, response); + + /* SD Card should now be in `idle` state */ + + /* TODO: SPI mode uses CMD59 with CRC_ON_OFF (mandatory) */ + + /* + * If command CMD8 succeeds, the card is either + * - a SD Card compliant to Version 2.00 or later, or + * - a Combo card compliant to Version 2.00 or later. + * Use + * - CMD5 with Voltage Window 0 to determine whether it is a Combo Card + * - ACMD41 with HCS=1 to determine the capacity. + * + * If the card didn't respond to CMD8 (-ETIMEDOUT), the card is either + * - a SD Card Version 2.00 or later SD Card but the voltage mismatches, or + * - a SD Card Version 1.x, or + * - a MMC, or + * - a SDIO card + * Use + * - CMD5 with Voltage Window 0 to determine whether it is a SDIO Card + * - ACMD41 with HCS=0 to check + * + * If the card answers with invalid response, the card is unusable and + * the card initialization and identification process is stopped. + */ + + if (res && (res != -ETIMEDOUT)) { + /* card answers with invalid response, card is unusable */ + LOG_ERROR("[sdmmc] command failed with error %d, card unusable\n", res); + res = -ENODEV; + goto out; + } + + if ((res == 0) && ((response[0] & SDMMC_CMD8_CHECK) != SDMMC_CMD8_CHECK)) { + /* card answers but received R7 doesn't match, card unusable */ + LOG_ERROR("[sdmmc] R7 mismatch %08"PRIx32"!=%08"PRIx32", " + "card unusable\n", + (uint32_t)SDMMC_CMD8_CHECK, response[0] & SDMMC_CMD8_CHECK); + res = -ENODEV; + goto out; + } + + bool flag_f8 = (res != -ETIMEDOUT); + + /* check whether it is a SDIO or Combo card using CMD5 with + * Voltage Window = 0 to test */ + DEBUG("[sdmmc] send CMD5 to test for a SDIO or Combo card\n"); + + /* If CMD5 succeeds, it is a SDIO or Combo Card. */ + if (_send_cmd(dev, SDMMC_CMD5, SDMMC_CMD_NO_ARG, SDMMC_R4, response) == 0) { + dev->type = SDMMC_CARD_TYPE_SDIO; + /* If CMD8 previously failed (flag_f8 = 0), it is a SDIO only card */ + if (!IS_USED(MODULE_SDMMC_SDIO) && (res == 0) && !flag_f8) { + /* TODO: SDIO support */ + LOG_ERROR("[sdmmc] card is a SDIO card, not supported yet\n"); + res = -ENOTSUP; + goto out; + } + } + + /* use ACMD41 with Voltage Window = 0 to test for a SD Card/Combo Card */ + res = _send_acmd(dev, SDMMC_ACMD41, SDMMC_CMD_NO_ARG, + SDMMC_R3, response); + + uint8_t cmd; + uint32_t arg; + + /* Note: for use in SPI mode + * CMD58 has to be used here in SPI mode to determine the CCS flag */ + + if (res == 0) { + /* if SD Card/Combo Card, then use ACMD41 with Voltage Window and + * HCS=1 if flag_f8 is true or HCS=0 otherwise */ + cmd = SDMMC_ACMD41; + arg = SDMMC_ACMD41_VOLTAGES | (flag_f8 ? SDMMC_ACMD41_HCS : 0); + } + else { + /* otherwise try CMD1 with Voltage Window for MMC */ + cmd = SDMMC_CMD1; + arg = SDMMC_OCR_ALL_VOLTAGES | SDMMC_OCR_18V | SDMMC_OCR_CCS; + } + + /* the card is either SD card, Combo card or MMC */ + do { + _ZTIMER_SLEEP_MS(100); + DEBUG("[sdmmc] send %s %s to test whether card is SDSC/SDXC or MMC card\n", + cmd == SDMMC_ACMD41 ? "ACMD41" : "CMD1", + arg & SDMMC_ACMD41_HCS ? "HCS=1" : "HCS=0"); + res = _send_xcmd(dev, cmd, arg, SDMMC_R3, response); + if (res == -ETIMEDOUT) { + LOG_ERROR("[sdmmc] no response, card not present or not a SD Card\n"); + goto out; + } + else if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + res = -EIO; + goto out; + } + } while (((response[0] & SDMMC_R3_POWER_UP) == 0) && + ((_ZTIMER_NOW() - t_start) < SDMMC_INIT_TIMEOUT_MS)); + + if ((_ZTIMER_NOW() - t_start) >= SDMMC_INIT_TIMEOUT_MS) { + /* if timed out, it is neither SD Card/Combo Card nor MMC card */ + LOG_ERROR("[sdmmc] Card could not be detected\n"); + res = -ENODEV; + goto out; + } + + /* On success the card can operate at 2.7-3.6V VDD here */ + + if (cmd == SDMMC_ACMD41) { + /* card is either SD Card or Combo card + * TODO: since SDIO is not yet supported, the type is simply + * overwritten, otherwise the type would have to be ORed here */ + if (!flag_f8) { + /* SDSC Card Version 1.x if it did not respond to CMD8 */ + dev->type = SDMMC_CARD_TYPE_SDSC_V1; + } + else if (response[0] & SDMMC_R3_CCS) { + /* SDHC/SDXC Card card if did react on CMD8 and set CCS */ + dev->type = SDMMC_CARD_TYPE_SDHC_SDXC; + } + else { + /* SDSC Card Version 2.x or later if it did respond to CMD8 but + * didn't set CCS */ + dev->type = SDMMC_CARD_TYPE_SDSC_V2_V3; + } + } + else { + /* card is a MultimediaCard */ + dev->type = SDMMC_CARD_TYPE_MMC; + /* TODO: MMC support */ + if (!IS_USED(MODULE_SDMMC_MMC)) { + LOG_ERROR("[sdmmc] MultimediaCard detected, not supported\n"); + res = -ENOTSUP; + goto out; + } + } + + /* SD Card or all MMC cards are now in `ready` state */ + + /* Card identification using CMD2 and CMD3 is done once per peripheral */ + DEBUG("[sdmmc] read CID to put the card in 'ident' state\n"); + + /* read CID and bring the card into the `ident` state */ + res = _read_cid(dev, SDMMC_CMD2); + if (res) { + /* no card found */ + goto out; + } + + /* SD Card or MMC card is now in `ident` state */ + + if (IS_USED(MODULE_SDMMC_MMC) && (dev->type == SDMMC_CARD_TYPE_MMC)) { + DEBUG("[sdmmc] send CMD3 to set RCA\n"); + /* for MMCs, RCA is selected and assigned by host using the device address */ + dev->rca = ((uint32_t)dev & 0x0000ffff); + res = _send_cmd(dev, SDMMC_CMD3, SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R1, response); + } + else { + DEBUG("[sdmmc] send CMD3 to get RCA\n"); + /* for SD cards, the card selects RCA and sends it back in R3 */ + res = _send_cmd(dev, SDMMC_CMD3, SDMMC_CMD_NO_ARG, SDMMC_R3, response); + dev->rca = response[0] >> 16; + } + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + /* SD Card or MMC is now in `stby` state */ + + DEBUG("[sdmmc] read CSD\n"); + res = _read_csd(dev); + if (res) { + goto out; + } + + /* select the card to put it in the `tran` state for further programming */ + res = _select_deselect(dev, true); + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + /* SD Memory Card or MMC card is now selected and in `tran` state */ + + if (dev->type != SDMMC_CARD_TYPE_MMC) { + res = _read_scr(dev); + if (res) { + goto out; + } + + DEBUG("[sdmmc] send CMD16 to set the block length to %d byte\n", + SDMMC_SDHC_BLOCK_SIZE); + res = _send_cmd(dev, SDMMC_CMD16, SDMMC_SDHC_BLOCK_SIZE, + SDMMC_R1, response); + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + /* change the bus width if the configured bus width is not 1-bit */ + if (dev->bus_width != SDMMC_BUS_WIDTH_1BIT) { + /* ensure that the sdmmc_bus_width_t is not changed accientially */ + static_assert(SDMMC_BUS_WIDTH_1BIT == 1, "SDMMC_BUS_WIDTH_1BIT != 1"); + static_assert(SDMMC_BUS_WIDTH_4BIT == 4, "SDMMC_BUS_WIDTH_4BIT != 4"); + static_assert(SDMMC_BUS_WIDTH_8BIT == 8, "SDMMC_BUS_WIDTH_8BIT != 8"); + + /* use the minimum of configured and supported bus width, + * supported bus widths are given as ORed integer numbers. */ + sdmmc_bus_width_t width = (dev->bus_width < dev->scr.SD_BUS_WIDTHS) + ? dev->bus_width + : bitarithm_msb(dev->scr.SD_BUS_WIDTHS); + DEBUG("[sdmmc] send ACMD6 to set %d-bit bus width\n", width); + res = _send_acmd(dev, SDMMC_ACMD6, + width == SDMMC_BUS_WIDTH_4BIT ? SDMMC_ACMD6_BUS_WIDTH_4BIT + : SDMMC_ACMD6_BUS_WIDTH_8BIT, + SDMMC_R1, response); + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + dev->driver->set_bus_width(dev, width); + } + + /* finally change the bus speed used by the peripheral to default speed */ + dev->driver->set_clock_rate(dev, SDMMC_CLK_25M); + + /* if the peripheral supports High Speed mode, try to switch into */ + if (IS_USED(MODULE_PERIPH_SDMMC_HS) && dev->scr.SD_SPEC) { + static_assert(sizeof(sdmmc_sw_status_t) == SDMMC_CMD6_SW_STATUS_SIZE, + "sizeof(sdmmc_sw_status_t) != SDMMC_CMD6_SW_STATUS_SIZE"); + /* try to switch to high speed for SD Memory Cards v1.10 or higher */ + uint32_t arg = (SDMMC_CMD6_FUNC_GROUP1_HS << SDMMC_CMD6_FUNC_GROUP1_SHIFT) | + (SDMMC_CMD6_FUNC_GROUP2_DEFAULT << SDMMC_CMD6_FUNC_GROUP2_SHIFT) | + (SDMMC_CMD6_FUNC_GROUP3_MASK << SDMMC_CMD6_FUNC_GROUP3_SHIFT) | + (SDMMC_CMD6_FUNC_GROUP4_MASK << SDMMC_CMD6_FUNC_GROUP4_SHIFT) | + (SDMMC_CMD6_FUNC_GROUP5_MASK << SDMMC_CMD6_FUNC_GROUP5_SHIFT) | + (SDMMC_CMD6_FUNC_GROUP6_MASK << SDMMC_CMD6_FUNC_GROUP6_SHIFT); + sdmmc_buf_t status[SDMMC_CMD6_SW_STATUS_SIZE]; + res = _xfer(dev, SDMMC_CMD6, SDMMC_CMD6_MODE_SWITCH | arg, + SDMMC_CMD6_SW_STATUS_SIZE, 1, NULL, status, NULL); +#if ENABLE_DEBUG + _print_raw_data_crc(status, SDMMC_CMD6_SW_STATUS_SIZE, false); +#endif + if (res) { + goto out; + } + + /* convert big endian to host byte order */ + uint32_t *raw_data = (void *)status; + for (unsigned i = 0; i < (SDMMC_CMD6_SW_STATUS_SIZE >> 2); i++) { + raw_data[i] = ntohl(raw_data[i]); + } + + sdmmc_sw_status_t *sw_status = (void *)status; + if (sw_status->group1_selected == SDMMC_CMD6_FUNC_GROUP1_HS) { + /* switching the card to high speed was successful */ + dev->driver->set_clock_rate(dev, SDMMC_CLK_50M); + } + } + } + else { + /* handle MMCs only if MMC support is enabled */ +#if IS_USED(MODULE_SDMMC_MMC) + /* read Extended CSD */ + res = _read_ext_csd(dev); + if (res) { + goto out; + } + + /* change the bus width if the configured bus width is not 1-bit */ + if (dev->bus_width != SDMMC_BUS_WIDTH_1BIT) { + /* TODO: the bus width for MMC would require testing the + * functional pins with CMD19, for simplicity the configured + * device bus width is used for the moment */ + sdmmc_bus_width_t width = dev->bus_width; + uint8_t value = (width == SDMMC_BUS_WIDTH_4BIT ? SDMMC_EXT_CSD_BUS_WIDTH_4BIT + : SDMMC_EXT_CSD_BUS_WIDTH_8BIT); + DEBUG("[sdmmc] set %d-bit bus width\n", dev->bus_width); + res = _write_ext_csd(dev, SDMMC_EXT_CSD_BUS_WIDTH, value); + if (res) { + goto out; + } + dev->driver->set_bus_width(dev, dev->bus_width); + } + + /* change HS_TIMING if the card supports high speed interface */ + if (IS_USED(MODULE_PERIPH_SDMMC_HS) && + (dev->csd.mmc.SPEC_VERS >= 4) && + (dev->ext_csd.CARD_TYPE & SDMMC_EXT_CSD_CARD_TYPE_HS52)) { + DEBUG("[sdmmc] set high speed interface timing\n"); + res = _write_ext_csd(dev, SDMMC_EXT_CSD_HS_TIMING, SDMMC_EXT_CSD_HS_TIMING_HS); + if (res) { + goto out; + } + dev->driver->set_clock_rate(dev, SDMMC_CLK_52M); + } + else { + dev->driver->set_clock_rate(dev, SDMMC_CLK_26M); + } +#endif /* IS_USED(MODULE_SDMMC_MMC) */ + } + + dev->init_done = true; + +out: + /* release ztimer */ + _ZTIMER_RELEASE(); + _disable_clock(dev); + + return res; +} + +int sdmmc_xfer(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + uint16_t block_size, uint16_t block_num, + const void *data_wr, void *data_rd, uint16_t *done) +{ + assert(dev); + assert(dev->driver); + + if (done) { + *done = 0; + } + + int res = _assert_card(dev); + if (res) { + return res; + } + + return _xfer(dev, cmd_idx, arg, block_size, block_num, + data_wr, data_rd, done); +} + +int sdmmc_read_blocks(sdmmc_dev_t *dev, + uint32_t block_addr, uint16_t block_size, + uint16_t block_num, void *data, uint16_t *done) +{ + DEBUG("[sdmmc] %s dev=%p block_addr=%"PRIu32" block_size=%u block_num=%u done=%p\n", + __func__, dev, block_addr, block_size, block_num, done); + + return sdmmc_xfer(dev, (block_num == 1) ? SDMMC_CMD17 : SDMMC_CMD18, + block_addr, block_size, block_num, NULL, data, done); +} + +int sdmmc_write_blocks(sdmmc_dev_t *dev, + uint32_t block_addr, uint16_t block_size, + uint16_t block_num, const void *data, uint16_t *done) +{ + DEBUG("[sdmmc] %s dev=%p block_addr=%"PRIu32" block_size=%u block_num=%u done=%p\n", + __func__, dev, block_addr, block_size, block_num, done); + + return sdmmc_xfer(dev, (block_num == 1) ? SDMMC_CMD24 : SDMMC_CMD25, + block_addr, block_size, block_num, data, NULL, done); +} + +int sdmmc_erase_blocks(sdmmc_dev_t *dev, + uint32_t block_addr, uint16_t block_num) +{ + DEBUG("[sdmmc] %s dev=%p addr=%"PRIu32" num=%u\n", + __func__, dev, block_addr, block_num); + + assert(dev); + assert(dev->driver); + assert(block_num); + + int res = _assert_card(dev); + if (res) { + return res; + } + + if (IS_USED(MODULE_SDMMC_MMC) && (dev->type == SDMMC_CARD_TYPE_MMC)) { + /* MMCs don't support the erase of a single block but only the erase of + * a group of blocks. Blocks are erased implicitly on write. */ + DEBUG("[sdmmc] MMCs don't support the erase of single blocks\n"); + return -ENOTSUP; + } + + uint32_t block_end = block_addr + block_num - 1; + + /* SDSC Cards use byte unit address */ + if ((dev->type == SDMMC_CARD_TYPE_SDSC_V1) || + (dev->type == SDMMC_CARD_TYPE_SDSC_V2_V3)) { + block_addr *= SDMMC_SDHC_BLOCK_SIZE; + block_end *= SDMMC_SDHC_BLOCK_SIZE; + } + + if (_enable_clock(dev)) { + return -EIO; + } + + /* wait until the card is ready */ + if ((res = _wait_for_ready(dev, SDMMC_WAIT_FOR_CARD_READY_MS))) { + /* reset the Card */ + dev->init_done = false; + goto out; + } + + uint32_t response; + + DEBUG("[sdmmc] send CMD32 and CMD33 to set erase parameters\n"); + if (((res = _send_cmd(dev, SDMMC_CMD32, block_addr, SDMMC_R1, &response)) != 0) || + ((res = _send_cmd(dev, SDMMC_CMD33, block_end, SDMMC_R1, &response)) != 0)) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + DEBUG("[sdmmc] send CMD38 to execute erase\n"); + if ((res = _send_cmd(dev, SDMMC_CMD38, 0, SDMMC_R1B, &response))) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + + /* wait until the card left the programming state */ + if ((res = _wait_while_prg(dev, 1, block_num * SDMMC_ERASE_TIMEOUT_MS))) { + goto out; + } + +out: + if (_disable_clock(dev)) { + return -EIO; + } + + return 0; +} + +uint64_t sdmmc_get_capacity(sdmmc_dev_t *dev) +{ + DEBUG("[sdmmc] %s dev=%p\n", __func__, dev); + + assert(dev); + assert(dev->driver); + + int res = _assert_card(dev); + if (res) { + return res; + } + + uint32_t block_len = 0; + uint32_t block_nr = 0; + + if (dev->type == SDMMC_CARD_TYPE_MMC) { +#if IS_USED(MODULE_SDMMC_MMC) + if (dev->csd.mmc.C_SIZE == 0xfff) { + /* memory capacity = SEC_COUNT * 512 Byte */ + block_nr = dev->ext_csd.SEC_COUNT; + block_len = SDMMC_SDHC_BLOCK_SIZE; + } + else { + /* memory capacity = BLOCKNR * BLOCK_LEN + * MULT = 2^(C_SIZE_MULT+2) + * BLOCKNR = (C_SIZE+1) * MULT + * BLOCK_LEN = 2^READ_BL_LEN */ + block_nr = (dev->csd.mmc.C_SIZE + 1) * + (1 << (dev->csd.mmc.C_SIZE_MULT + 2)); + block_len = 1 << dev->csd.mmc.READ_BL_LEN; + } +#endif + } + else if (dev->csd.v1.CSD_STRUCTURE == SDMMC_CSD_V1) { + /* memory capacity = BLOCKNR * BLOCK_LEN + * MULT = 2^(C_SIZE_MULT+2) + * BLOCKNR = (C_SIZE+1) * MULT + * BLOCK_LEN = 2^READ_BL_LEN */ + block_nr = (dev->csd.v1.C_SIZE + 1) * + (1 << (dev->csd.v1.C_SIZE_MULT + 2)); + block_len = 1 << dev->csd.v1.READ_BL_LEN; + } + else if (dev->csd.v2.CSD_STRUCTURE == SDMMC_CSD_V2) { + /* memory capacity = (C_SIZE+1) * 512 KByte */ + block_nr = dev->csd.v2.C_SIZE + 1; + block_len = SDMMC_SDHC_BLOCK_SIZE << 10; + } + return (uint64_t)block_nr * block_len; +} + +int sdmmc_read_sds(sdmmc_dev_t *dev, sdmmc_sd_status_t *sds) +{ + DEBUG("[sdmmc] %s dev=%p sds=%p\n", __func__, dev, sds); + + assert(dev); + assert(sds); + + /* card must have a valid RCA */ + assert(dev->rca); + + int res = _assert_card(dev); + if (res) { + return res; + } + + sdmmc_buf_t raw_data[SDMMC_SD_STATUS_SIZE]; + + if (IS_USED(MODULE_SDMMC_MMC) && (dev->type == SDMMC_CARD_TYPE_MMC)) { + LOG_ERROR("[sdmmc] MMC cards don't have a SD Status register\n"); + return -ENOTSUP; + } + + DEBUG("[sdmmc] read SD status raw data"); + res = _xfer(dev, SDMMC_ACMD13, SDMMC_CMD_NO_ARG, + SDMMC_SD_STATUS_SIZE, 1, NULL, raw_data, NULL); +#if ENABLE_DEBUG + _print_raw_data_crc(raw_data, SDMMC_SD_STATUS_SIZE, false); +#endif + + if (res) { + DEBUG("[sdmmc] reading SD status raw data failed"); + return res; + } + + sds->DAT_BUS_WIDTH = raw_data[0] >> 6; + sds->SECURED_MODE = (raw_data[0] & (1 << 5)) >> 5; + sds->SD_CARD_TYPE = (raw_data[2] << 8) | raw_data[3]; + sds->SIZE_OF_PROTECTED_AREA = (raw_data[4] << 24) | + (raw_data[5] << 16) | + (raw_data[6] << 8) | raw_data[7]; + sds->SPEED_CLASS = raw_data[8]; + sds->PERFORMANCE_MOVE = raw_data[9]; + sds->AU_SIZE = raw_data[10] >> 4; + sds->ERASE_SIZE = (raw_data[11] << 8) | raw_data[12]; + sds->ERASE_TIMEOUT = raw_data[13] >> 2; + sds->ERASE_OFFSET = raw_data[13] & 0x03; + sds->UHS_SPEED_GRADE = raw_data[14] >> 4; + sds->UHS_AU_SIZE = raw_data[14] & 0x0F; + sds->VIDEO_SPEED_CLASS = raw_data[15]; + sds->VSC_AU_SIZE = ((raw_data[16] & 0x03) << 8) | raw_data[17]; + sds->SUS_ADDR = (raw_data[18] << 14) | (raw_data[19] << 6) | (raw_data[20] >> 2); + + return 0; +} + +/* Internal functions */ + +static int _send_cmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp) +{ + DEBUG("[sdmmc] %s dev=%p cmd_idx=%u arg=%"PRIu32" resp_type=%u resp=%p\n", + __func__, dev, cmd_idx, arg, resp_type, resp); + +#if !IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK) + /* enable the SD CLK signal if the SDIO/SD/MMC peripheral driver does + * not support the Auto-CLK feature (periph_sdmmc_auto_clk) */ + if (dev->driver->enable_clock && dev->driver->enable_clock(dev, true)) { + return -EIO; + } +#endif + + return dev->driver->send_cmd(dev, cmd_idx, arg, resp_type, resp); +} + +static int _send_acmd(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp) +{ + uint32_t response; + int res; + + DEBUG("[sdmmc] %s dev=%p cmd_idx=%u arg=%"PRIu32" resp_type=%u resp=%p\n", + __func__, dev, cmd_idx, arg, resp_type, resp); + + assert(cmd_idx & SDMMC_ACMD_PREFIX); + + if (cmd_idx == SDMMC_ACMD41) { + res = _send_cmd(dev, SDMMC_CMD55, SDMMC_CMD_NO_ARG, + SDMMC_R1, &response); + } + else { + assert(dev->rca); + res = _send_cmd(dev, SDMMC_CMD55, SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R1, &response); + } + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + + return _send_cmd(dev, cmd_idx & ~SDMMC_ACMD_PREFIX, arg, resp_type, resp); +} + +static inline int _send_xcmd(sdmmc_dev_t *dev, + sdmmc_cmd_t cmd_idx, uint32_t arg, + sdmmc_resp_t resp_type, uint32_t *resp) +{ + assert(dev); + assert(dev->driver); + + if (cmd_idx & SDMMC_ACMD_PREFIX) { + return _send_acmd(dev, cmd_idx, arg, resp_type, resp); + } + else { + return _send_cmd(dev, cmd_idx, arg, resp_type, resp); + } +} + +static int _xfer(sdmmc_dev_t *dev, sdmmc_cmd_t cmd_idx, uint32_t arg, + uint16_t block_size, uint16_t block_num, + const void *data_wr, void *data_rd, uint16_t *done) +{ + DEBUG("[sdmmc] %s dev=%p cmd_idx=%u arg=%"PRIu32" block_size=%u " + "block_num=%u data_wr=%p data_rd=%p done=%p\n", + __func__, dev, cmd_idx, arg, block_size, block_num, + data_wr, data_rd, done); + + /* TODO: in SDIO multi-byte mode, the block_size must be between 1 and 512 + * and block_num must be 1 */ + /* TODO: in stream mode, the block_size and block_num must be 0 */ + assert(block_num); + + /* check for valid transfer commands */ + assert((cmd_idx == SDMMC_CMD6) || + (cmd_idx == SDMMC_CMD8) || (cmd_idx == SDMMC_CMD9) || + (cmd_idx == SDMMC_CMD17) || (cmd_idx == SDMMC_CMD18) || + (cmd_idx == SDMMC_CMD24) || (cmd_idx == SDMMC_CMD25) || + (cmd_idx == SDMMC_ACMD13) || (cmd_idx == SDMMC_ACMD51)); + + /* only CMD18 and CMD25 are multiple block transfers */ + if ((cmd_idx == SDMMC_CMD18) || (cmd_idx == SDMMC_CMD25)) { + assert(block_num > 1); + } + else { + assert(block_num == 1); + } + + uint32_t response; + int res; + + /* TODO: all transfer types (MMC Stream, SDIO Multibyte) and Auto-CDM23 feature */ + sdmmc_xfer_desc_t xfer = { + .type = SDMMC_BLOCK, /* at the moment only block transfer supported */ + .cmd_idx = cmd_idx, + .arg = arg, + .resp_type = SDMMC_R1, /* all supported transfer commands use R1 */ + .write = (cmd_idx == SDMMC_CMD24) || (cmd_idx == SDMMC_CMD25), + .block_size = block_size, + .block_num = block_num, + }; + + /* for write transfers `data_wr` must not be NULL, otherwise `data_rd` */ + assert((xfer.write && data_wr) || data_rd); + + /* enable the SD CLK signal */ + if (_enable_clock(dev)) { + return -EIO; + } + + /* wait until the card is ready */ + if ((res = _wait_for_ready(dev, SDMMC_WAIT_FOR_CARD_READY_MS))) { + /* reset the Card */ + dev->init_done = false; + goto out; + } + + /* send CMD55 for application specific commands before preparing the transfer */ + if (cmd_idx & SDMMC_ACMD_PREFIX) { + res = _send_cmd(dev, SDMMC_CMD55, SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R1, &response); + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + goto out; + } + } + + /* prepare the transfer in device driver */ + DEBUG("[sdmmc] prepare transfer @%08"PRIx32" for %u block(s) with %u bytes\n", + arg, block_num, block_size); + res = dev->driver->xfer_prepare(dev, &xfer); + if (res) { + res = -EINVAL; + goto out; + } + + /* send the command to trigger the transfer */ + DEBUG("[sdmmc] send %s%d\n", cmd_idx & SDMMC_ACMD_PREFIX ? "ACMD" : "CMD", + cmd_idx & ~SDMMC_ACMD_PREFIX); + res = _send_cmd(dev, cmd_idx & ~SDMMC_ACMD_PREFIX, arg, + SDMMC_R1, &response); + if (res) { + goto out; + } + + /* execute the transfer in device driver */ + DEBUG("[sdmmc] execute transfer\n"); + res = dev->driver->xfer_execute(dev, &xfer, xfer.write ? data_wr : NULL, + !xfer.write ? data_rd : NULL, done); + if (res) { + DEBUG("[sdmmc] transfer failed with error %d\n", res); + /* Don't return by intention to call _xfer_finish in any case */ + } + + /* finish the transfer in device driver */ + DEBUG("[sdmmc] stop transfer\n"); + dev->driver->xfer_finish(dev, &xfer); + + /* TODO: use CMD23 (SET_BLOCK_COUNT) instead of CMD12 (STOP_TRANSMISSION) + * in case of multiple block transfers if supported by card, requires + * that SCR.CMD_SUPPORT[1] (SCR bit 33) is set */ + if (block_num > 1) { + if (IS_USED(MODULE_PERIPH_SDMMC_AUTO_CMD12)) { + DEBUG("[sdmmc] Auto CMD12 used\n"); + } + else if (IS_USED(MODULE_SDMMC_MMC) && (dev->type == SDMMC_CARD_TYPE_MMC)) { + /* MMC cards use R1 in case of read and R1B in case of write */ + _send_cmd(dev, SDMMC_CMD12, SDMMC_CMD_NO_ARG, + xfer.write ? SDMMC_R1B : SDMMC_R1, &response); + } + else { + /* SD Cards always use R1B */ + _send_cmd(dev, SDMMC_CMD12, SDMMC_CMD_NO_ARG, SDMMC_R1B, &response); + } + } + + /* wait until the card left the programming state */ + if (xfer.write && (res = _wait_while_prg(dev, 1, SDMMC_DATA_W_TIMEOUT_MS))) { + /* reset the Card */ + dev->init_done = false; + goto out; + } + +out: + if (_disable_clock(dev)) { + return -EIO; + } + + return res; +} + +/** + * @brief Ensure that the card is present and initialized + */ +static int _assert_card(sdmmc_dev_t *dev) +{ + if (!dev->present) { + return -ENODEV; + } + + /* return if card is already initialized */ + if (dev->init_done) { + return 0; + } + + /* otherwise initialize the card */ + return sdmmc_card_init(dev); +} + +/** + * @brief Select or deselect a card (Internal Function) + * + * The function selects or deselects the given card. The card to be selected + * is addressed by its RCA and must be in the `stby` state. When the card is + * selected, it changes to the `tran` state. All other not addressed cards + * are deselected by this and go into the state `stby`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[in] card Card or embedded device to be selected or deselected + * + * @return 0 on success or negative error code on error + */ +static int _select_deselect(sdmmc_dev_t *dev, bool select) +{ + /* card must have a valid RCA */ + assert(dev->rca); + + uint32_t response; + int res; + + DEBUG("[sdmmc] send CMD7 to %s the card\n", select ? "select" : "deselect"); + if (select) { + res = _send_cmd(dev, SDMMC_CMD7, SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R1B, &response); + } + else { + res = _send_cmd(dev, SDMMC_CMD7, SDMMC_CMD_NO_ARG, SDMMC_NO_R, NULL); + } + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + + return 0; +} + +/** + * @brief Get card status word as returned in responses R1 (Internal Function) + * + * The function returns the status of the given card as defined in + * - Physical Layer Simplified Specification Version 9.00, Section 4.10.1, + * Table 4-42 [[sdcard.org](https://www.sdcard.org)](sdcard.org) + * - SDIO Simplified Specification Version 3.00, 4.10.8, Table 4-7, + * [[sdcard.org](https://www.sdcard.org)] + * - JEDEC Standard No. JESD84-B42, MultiMediaCard (MMC) Electrical + * Standard, High Capacity (MMCA, 4.2), Section 7.11, Table 25 + * [[jedec.org](https://www.jedec.org)] + * and returned in R1 response. + * + * CMD13 can be used for the addressed card in most states except `idle`, + * `ready`, `ident` and `ina`. + * + * @param[in] dev SDIO/SD/MMC device to be used + * @param[out] cs Card status + * + * @retval 0 on success + * @retval -ENODEV on error + */ +static int _get_status(sdmmc_dev_t *dev, sdmmc_card_status_t *cs) +{ + static_assert(sizeof(sdmmc_card_status_t) == sizeof(uint32_t), + "sizeof(sdmmc_card_status_t) != sizeof(uint32_t)"); + + /* card must have a valid RCA */ + assert(dev->rca); + + if (_send_cmd(dev, SDMMC_CMD13, SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R1, &cs->value)) { + /* we suppose that the device is not present on error */ + return -ENODEV; + } + return 0; +} + +/** + * @brief Wait until the given SD Memory Card or MMC is ready (Internal Function) + * + * The function waits until the given SD Memory Card or MMC is ready + * which is indicated by the `READY_FOR_DATA` bit in the card status word. + * + * @param[in] dev SD/MMC device to be used + * @param[in] timeout_ms Timeout in ms, must not be 0 + * + * @retval 0 on success + * @retval -ENODEV if _get_status returned with any error + * @retval -ETIMEDOUT if card was available but waiting for ready timed out + */ +static inline int _wait_for_ready(sdmmc_dev_t *dev, uint32_t timeout_ms) +{ + /* card must be any SD Memory Card or MMC */ + assert(dev->type & (SDMMC_CARD_TYPE_SD | SDMMC_CARD_TYPE_MMC)); + assert(timeout_ms); + + uint32_t t_start = _ZTIMER_NOW(); + sdmmc_card_status_t cs; + + do { + if (_get_status(dev, &cs)) { + LOG_ERROR("[sdmmc] Card not present\n"); + return -ENODEV; + } + if (cs.value & SDMMC_CARD_STATUS_READY_FOR_DATA) { + return 0; + } + /* check the state every 10 ms */ + _ZTIMER_SLEEP_MS(10); + } while ((_ZTIMER_NOW() - t_start) < timeout_ms); + + LOG_ERROR("[sdmmc] Card is busy\n"); + return -ETIMEDOUT; +} + +/** + * @brief Wait with sleep as long as the card is programming + * + * @param[in] dev SD/MMC device to be used + * @param[in] sleep_ms Sleep time in ms, busy wait if 0 + * @param[in] timeout_ms Timeout in ms, no timeout if 0 + * + * @return 0 on success or negative error code on error + */ +static int _wait_while_prg(sdmmc_dev_t *dev, + uint32_t sleep_ms, uint32_t timeout_ms) +{ + uint32_t t_start = _ZTIMER_NOW(); + + int res; + sdmmc_card_status_t cs; + + _ZTIMER_ACQUIRE(); + do { + res = _get_status(dev, &cs); + if (res || (cs.CURRENT_STATE != SDMMC_CARD_STATE_PRG)) { + /* return on error with error code in res or with res=0 on success */ + goto out; + } + if (sleep_ms) { + /* check the status every ms */ + _ZTIMER_SLEEP_MS(1); + } + } while (!timeout_ms || ((_ZTIMER_NOW() - t_start) < timeout_ms)); + + LOG_ERROR("[sdmmc] Card did not leave the programming state \n"); + res = -ETIMEDOUT; + +out: + _ZTIMER_RELEASE(); + return 0; +} + +/** + * @brief Read the CID register (Internal Function) + * + * The function reads the CID register of a SD Memory Card or MMC and stores + * it in the card descriptor. + * + * @pre The card has to be either in the `ready` state when CMD2 is used or in + * the `stby` state (deselected) when CMD10 is used to read the CID. + * + * @param[in] dev SD/MMC device to be used + * @param[in] card Card or embedded device + * @param[in] cmd Command to be used + * (CMD2 in state `ready` or CMD10 in state `stdby') + * + * @return 0 on success or negative error code on error + */ +static int _read_cid(sdmmc_dev_t *dev, uint8_t cmd) +{ + /* sanity checks for CID structure versions */ + static_assert(sizeof(sdmmc_cid_sd_t) == sizeof(sdmmc_cid_mmc_t), + "sizeof(sdmmc_cid_sd_t) != sizeof(sdmmc_cid_mmc_t)"); + static_assert(sizeof(sdmmc_cid_sd_t) == SDMMC_CID_REG_SIZE, + "sizeof(sdmmc_cid_sd_t) != SDMMC_CID_REG_SIZE"); + + uint32_t response[4]; /* long response requires four 32-bit words */ + int res; + + assert((cmd == SDMMC_CMD2) || (cmd == SDMMC_CMD10)); + + /* card must have a valid RCA in case of CMD10 */ + assert((cmd != SDMMC_CMD10) || dev->rca); + + DEBUG("[sdmmc] send CMD%d to read CID\n", cmd); + res = _send_cmd(dev, cmd, + SDMMC_CMD10 ? SDMMC_CMD_ARG_RCA(dev->rca) : SDMMC_CMD_NO_ARG, + SDMMC_R2, response); + + /* raw data that are used to fill CID struct have to be in big-endian order */ + uint8_t cid_raw[SDMMC_CID_REG_SIZE]; + byteorder_htobebufl(cid_raw, response[0]); + byteorder_htobebufl(cid_raw + 4, response[1]); + byteorder_htobebufl(cid_raw + 8, response[2]); + byteorder_htobebufl(cid_raw + 12, response[3]); + +#if ENABLE_DEBUG + _print_raw_data_crc(cid_raw, SDMMC_CID_REG_SIZE, true); +#endif + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + + memcpy(&dev->cid, cid_raw, sizeof(sdmmc_cid_t)); + + return 0; +} + +#define SDMMC_CSD_STRUCTURE(raw_data) ((raw_data)[0] >> 6) + +/** + * @brief Read the CSD register (Internal Function) + * + * The function reads the CSD of a SD Memory Card or MMC and stores it in + * the card descriptor because it contains information that are required + * when handling the card. + * + * @pre The card has to be addressable by its RCA and has to be in the `stby` + * state to read the CSD (deselected). + * + * @param[in] dev SD/MMC device to be used + * @param[in] card Card or embedded device + * + * @return 0 on success or negative error code on error + */ +static int _read_csd(sdmmc_dev_t *dev) +{ + /* sanity checks for CSD structure versions */ + static_assert(sizeof(sdmmc_csd_v1_t) == sizeof(sdmmc_csd_v2_t), + "sizeof(sdmmc_csd_v1_t) != sizeof(sdmmc_csd_v2_t)"); + static_assert(sizeof(sdmmc_csd_v1_t) == sizeof(sdmmc_csd_mmc_t), + "sizeof(sdmmc_csd_v1_t) != sizeof(sdmmc_csd_mmc_t)"); + static_assert(sizeof(sdmmc_csd_v1_t) == sizeof(sdmmc_csd_t), + "sizeof(sdmmc_csd_v1_t) != sizeof(sdmmc_csd_t)"); +#ifndef NDEBUG + /* ensure to be able to use csd.mmc.* members for all CSD versions */ + sdmmc_csd_t csd = {}; + csd.mmc.CSD_STRUCTURE = 0x3; + csd.mmc.TAAC = 0xaa; + csd.mmc.NSAC = 0x55; + csd.mmc.R2W_FACTOR = 0x5; + assert(csd.v1.CSD_STRUCTURE == csd.mmc.CSD_STRUCTURE); + assert(csd.v2.CSD_STRUCTURE == csd.mmc.CSD_STRUCTURE); + assert(csd.v1.TAAC == csd.mmc.TAAC); + assert(csd.v2.TAAC == csd.mmc.TAAC); + assert(csd.v1.NSAC == csd.mmc.NSAC); + assert(csd.v2.NSAC == csd.mmc.NSAC); + assert(csd.v1.R2W_FACTOR == csd.mmc.R2W_FACTOR); + assert(csd.v2.R2W_FACTOR == csd.mmc.R2W_FACTOR); +#endif + + /* card must have a valid RCA */ + assert(dev->rca); + + uint32_t response[4]; /* long response requires four 32-bit words */ + int res; + + DEBUG("[sdmmc] send CMD9 to read CSD\n"); + res = _send_cmd(dev, SDMMC_CMD9, SDMMC_CMD_ARG_RCA(dev->rca), + SDMMC_R2, response); +#if ENABLE_DEBUG + /* raw data that are used to fill CSD struct have to be big endian */ + uint8_t csd_data[SDMMC_CSD_REG_SIZE]; + + byteorder_htobebufl(csd_data, response[0]); + byteorder_htobebufl(csd_data + 4, response[1]); + byteorder_htobebufl(csd_data + 8, response[2]); + byteorder_htobebufl(csd_data + 12, response[3]); + + _print_raw_data_crc(csd_data, SDMMC_CSD_REG_SIZE, true); +#endif + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + + uint32_t csd_raw[SDMMC_CSD_REG_SIZE >> 2]; + + /* read data are word-wise in host byte order */ + csd_raw[0] = response[3]; /* response[3] contains CSD[31:0] */ + csd_raw[1] = response[2]; /* response[2] contains CSD[63:32] */ + csd_raw[2] = response[1]; /* response[1] contains CSD[95:64] */ + csd_raw[3] = response[0]; /* response[0] contains CSD[127:96] */ + + memcpy(&dev->csd, csd_raw, sizeof(sdmmc_csd_t)); + + return 0; +} + +/** + * @brief Read the SCR register of the selected SD Memory Card (Internal Function) + * + * The function reads the SCR register of the selected SD Memory Card and stores + * it in the card descriptor because it contains information that are required + * when handling the card. + * + * The SCR register is only availably on SD Memory cards + * + * @pre The card has to be selected to read the SCR, that is, the card is in + * the `tran` state. + * + * @param[in] dev SD device to be used + * + * @return 0 on success or negative error code on error + */ +static int _read_scr(sdmmc_dev_t *dev) +{ + int res = 0; + + /* selected card has to be any SD Memory Card */ + assert(dev->type & SDMMC_CARD_TYPE_SD); + + sdmmc_buf_t scr_raw[SDMMC_SCR_REG_SIZE]; + + DEBUG("[sdmmc] send ACMD51 to read SCR\n"); + + res = _xfer(dev, SDMMC_ACMD51, SDMMC_CMD_NO_ARG, + SDMMC_SCR_REG_SIZE, 1, NULL, scr_raw, NULL); +#if ENABLE_DEBUG + _print_raw_data_crc(scr_raw, SDMMC_SCR_REG_SIZE, false); +#endif + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + + dev->scr.value = byteorder_bebuftohl(scr_raw); + dev->scr.reserved0 = byteorder_bebuftohl(scr_raw + 4); + + return 0; +} + +static inline int _enable_clock(sdmmc_dev_t *dev) +{ + DEBUG("[sdmmc] %s dev=%p\n", __func__, dev); + +#if !IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK) + return (dev->driver->enable_clock) ? dev->driver->enable_clock(dev, true) + : 0; +#else + (void)dev; + return 0; +#endif +} + +static inline int _disable_clock(sdmmc_dev_t *dev) +{ + DEBUG("[sdmmc] %s dev=%p\n", __func__, dev); + +#if !IS_USED(MODULE_PERIPH_SDMMC_AUTO_CLK) + return (dev->driver->enable_clock) ? dev->driver->enable_clock(dev, false) + : 0; +#else + (void)dev; + return 0; +#endif +} + +#if IS_USED(MODULE_SDMMC_MMC) + +/* To avoid a 512 byte buffer on the stack, a static variable is used here. + * The exclusive access to this buffer must be guaranteed. */ +static sdmmc_buf_t _ext_csd_raw[SDMMC_EXT_CSD_REG_SIZE]; + +static mutex_t _ext_csd_access = MUTEX_INIT; + +/** + * @brief Read the EXT_CSD register of the selected MMC (Internal Function) + * + * The function reads the EXT_CSD register of the selected MMC and stores + * it in the card descriptor because it contains information that are required + * when handling the card. + * + * @pre The card has to be selected to read the EXT_CSD, that is, the card is in + * the `tran` state. + * + * @param[in] dev MMC device to be used + * + * @return 0 on success or negative error code on error + */ +static int _read_ext_csd(sdmmc_dev_t *dev) +{ + /* card must be a MMC */ + assert(dev->type == SDMMC_CARD_TYPE_MMC); + + DEBUG("[sdmmc] send CMD8 to read Extended CSD\n"); + + mutex_lock(&_ext_csd_access); + + int res = _xfer(dev, SDMMC_CMD8, SDMMC_CMD_NO_ARG, + SDMMC_EXT_CSD_REG_SIZE, 1, NULL, _ext_csd_raw, NULL); +#if ENABLE_DEBUG + _print_raw_data_crc(_ext_csd_raw, SDMMC_EXT_CSD_REG_SIZE, false); +#endif + + if (res) { + DEBUG("[sdmmc] command failed with error %d\n", res); + mutex_unlock(&_ext_csd_access); + return res; + } + + dev->ext_csd.SEC_COUNT = byteorder_lebuftohl(_ext_csd_raw + SDMMC_EXT_CSD_SEC_COUNT); + dev->ext_csd.CSD_STRUCTURE = _ext_csd_raw[SDMMC_EXT_CSD_CSD_STRUCTURE]; + dev->ext_csd.BUS_WIDTH = _ext_csd_raw[SDMMC_EXT_CSD_BUS_WIDTH]; + dev->ext_csd.CARD_TYPE = _ext_csd_raw[SDMMC_EXT_CSD_CARD_TYPE]; + + mutex_unlock(&_ext_csd_access); + return 0; +} + +/** + * @brief Write the EXT_CSD register of the selected MMC (Internal Function) + * + * The function writes @p value to the byte @p index of the EXT_CSD register + * of the selected MMC. + * + * @pre The card has to be selected to write the EXT_CSD, that is, the card + * is in the `tran` state. + * + * @param[in] dev MMC device to be used + * + * @return 0 on success or negative error code on error + */ +static int _write_ext_csd(sdmmc_dev_t *dev, uint8_t index, uint8_t value) +{ + /* card must be a MMC */ + assert(dev->type == SDMMC_CARD_TYPE_MMC); + + uint32_t response; + int res; + uint32_t arg = (SDMMC_EXT_CSD_WRITE_BYTE << SDMMC_CMD6_ACCESS) | + (index << SDMMC_CMD6_INDEX) | + (value << SDMMC_CMD6_VALUE); + + DEBUG("[sdmmc] send CMD6 to set EXT_CSD[%u]=%02x\n", index, value); + + if ((res = _send_cmd(dev, SDMMC_CMD6, arg, SDMMC_R1B, &response))) { + DEBUG("[sdmmc] command failed with error %d\n", res); + return res; + } + + if ((res = _wait_while_prg(dev, 1, SDMMC_INIT_TIMEOUT_MS))) { + /* reset the Card */ + dev->init_done = false; + return res; + } + + return 0; +} +#endif /* IS_USED(MODULE_SDMMC_MMC) */ + +#if ENABLE_DEBUG +static uint8_t _crc_7(const uint8_t *data, unsigned n) +{ + uint8_t crc = 0; + + for (unsigned i = 0; i < n; i++) { + uint8_t d = data[i]; + for (unsigned j = 0; j < 8; j++) { + crc <<= 1; + if ((d & 0x80) ^ (crc & 0x80)) { + crc ^= 0x09; + } + d <<= 1; + } + } + return crc & 0x7f; +} + +static void _print_raw_data_crc(const uint8_t *data, unsigned size, bool crc) +{ + DEBUG("[sdmmc] raw data: "); + for (unsigned i = 0; i < size; i++) { + if ((size > 16) && ((i % 16) == 0)) { + DEBUG("\n%04x: ", i); + } + DEBUG("0x%02x ", data[i]); + } + DEBUG("\n"); + if (crc) { + /* print CRC if data include the CRC in last byte */ + DEBUG("[sdmmc] CRC: 0x%02x (received 0x%02x)\n", + _crc_7(data, size - 1), data[size - 1] >> 1); + } +} +#endif diff --git a/kconfigs/Kconfig.features b/kconfigs/Kconfig.features index 23eead08d8..d45aa29074 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -415,6 +415,55 @@ config HAS_PERIPH_RTT_OVERFLOW help Indicates that the RTT provides an overflow callback. +config HAS_PERIPH_SDMMC + bool + help + Indicates that an SDIO/SD/MMC peripheral is present and used by the + board. This feature shall be provided by the board configuration, + if available. + +config HAS_PERIPH_SDMMC_8BIT + bool + help + Indicates that the SDIO/SD/MMC peripheral supports the 8-bit bus width + and at least one component of the board is connected with 8 data lines. + This feature shall be provided by the board configuration, if available. + +config HAS_PERIPH_SDMMC_AUTO_CLK + bool + help + Indicates that the SDIO/SD/MMC peripheral supports the Auto-CLK + feature, i.e. the automatic activation and deactivation of the SD CLK + signal when required. This feature shall be provided by the MCU + if supported. + +config HAS_PERIPH_SDMMC_AUTO_CMD12 + bool + help + Indicates that the SDIO/SD/MMC peripheral supports the Auto-CMD12 + feature, i.e. CMD12 is sent automatically to stop the transmission in + multiple block operations. This feature shall be provided by the MCU + if supported. + +config HAS_PERIPH_SDMMC_CLK + bool + help + Indicates that the SDIO/SD/MMC peripheral has special clock + functionality used by the peripheral driver. + +config HAS_PERIPH_SDMMC_HS + bool + help + Indicates that the SDIO/SD/MMC peripheral supports the high speed + access, that is 50 MHz for SD and 52 MHz for MMC. This feature shall be + provided by the MCU. + +config HAS_PERIPH_SDMMC_MMC + bool + help + Indicates that the SDIO/SD/MMC peripheral supports MMC/eMMCs. This + feature shall be provided by the MCU. + config HAS_PERIPH_SPI bool help diff --git a/sys/include/vfs_default.h b/sys/include/vfs_default.h index b34ed9d35a..f314b5b160 100644 --- a/sys/include/vfs_default.h +++ b/sys/include/vfs_default.h @@ -71,7 +71,8 @@ extern "C" { * This can be written to by applications */ #ifndef VFS_DEFAULT_DATA -#if IS_USED(MODULE_MTD_MCI) || IS_USED(MODULE_MTD_SDCARD) || IS_USED(MODULE_SAM0_SDHC) +#if IS_USED(MODULE_MTD_MCI) || IS_USED(MODULE_MTD_SDCARD) || \ + IS_USED(MODULE_SAM0_SDHC) || IS_USED(MODULE_MTD_SDMMC) #define VFS_DEFAULT_DATA VFS_DEFAULT_SD(0) #else #define VFS_DEFAULT_DATA VFS_DEFAULT_NVM(0) diff --git a/tests/drivers/sdmmc/Makefile b/tests/drivers/sdmmc/Makefile new file mode 100644 index 0000000000..29679507dc --- /dev/null +++ b/tests/drivers/sdmmc/Makefile @@ -0,0 +1,12 @@ +include ../Makefile.drivers_common + +USEMODULE += sdmmc + +USEMODULE += fmt +USEMODULE += shell + +OUTPUT ?= 1 + +CFLAGS += -DOUTPUT=$(OUTPUT) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/drivers/sdmmc/Makefile.ci b/tests/drivers/sdmmc/Makefile.ci new file mode 100644 index 0000000000..1e025a2333 --- /dev/null +++ b/tests/drivers/sdmmc/Makefile.ci @@ -0,0 +1,2 @@ +BOARD_INSUFFICIENT_MEMORY := \ + # diff --git a/tests/drivers/sdmmc/app.config.test b/tests/drivers/sdmmc/app.config.test new file mode 100644 index 0000000000..e58cdc3d1a --- /dev/null +++ b/tests/drivers/sdmmc/app.config.test @@ -0,0 +1,5 @@ +# this file enables modules defined in Kconfig. Do not use this file for +# application configuration. This is only needed during migration. +CONFIG_MODULE_SDMMC=y +CONFIG_MODULE_FMT=y +CONFIG_MODULE_SHELL=y diff --git a/tests/drivers/sdmmc/main.c b/tests/drivers/sdmmc/main.c new file mode 100644 index 0000000000..b21cd00227 --- /dev/null +++ b/tests/drivers/sdmmc/main.c @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2016 Michel Rottleuthner + * 2023 Gunar Schorcht + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the SDIO/SD/MMC driver + * + * @author Michel Rottleuthner + * @author Gunar Schorcht + * @} + */ + +#include +#include +#include +#include + +#include "byteorder.h" +#include "container.h" +#include "fmt.h" +#include "macros/units.h" +#include "shell.h" + +#include "sdmmc/sdmmc.h" + +/* independent of what you specify in a r/w cmd this is the maximum number of blocks read at once. + If you call read with a bigger blockcount the read is performed in chunks*/ +#define MAX_BLOCKS_IN_BUFFER 4 +#define BLOCK_PRINT_BYTES_PER_LINE 16 +#define FIRST_PRINTABLE_ASCII_CHAR 0x20 +#define ASCII_UNPRINTABLE_REPLACEMENT "." + +sdmmc_dev_t *dev = NULL; + +uint8_t buffer[SDMMC_SDHC_BLOCK_SIZE * MAX_BLOCKS_IN_BUFFER]; + +static int _card_assert(void) +{ + if (dev == NULL) { + printf("[Error] SD/MMC device not initialized, use init command\n"); + return -1; + } + + if (!dev->present) { + printf("[Error] Card not present\n"); + return -1; + } + + if (!dev->init_done) { + printf("[Error] Card not initialized, use init command\n"); + return -1; + } + + return 0; +} + +static int _init(int argc, char **argv) +{ + int device = 0; + + if ((argc == 2)) { + device = atoi(argv[1]); + } + + dev = sdmmc_get_dev(device); + + if (dev == NULL) { + printf("[Error] No device with index %i\n", device); + /* use the first SDMMC device by default */ + dev = sdmmc_get_dev(0); + return -1; + } + + if (!dev->present) { + printf("[Error] Card not present\n"); + return -1; + } + + printf("Initializing SD Card/MMC at SD/MMC device %i\n", device); + if (sdmmc_card_init(dev)) { + puts("[FAILED]"); + puts("enable debugging in sdmmc.c for more information!"); + return -1; + } + printf("card found [OK]\n"); + + return 0; +} + +static int _cid(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if (_card_assert()) { + return -1; + } + + sdmmc_cid_t *cid = &dev->cid; + + puts("----------------------------------------"); + if (IS_USED(MODULE_SDMMC_MMC) && (dev->type == SDMMC_CARD_TYPE_MMC)) { + printf("MID: %d\n", cid->mmc.MID); + printf("OID: 0x%04x\n", byteorder_ntohs(cid->mmc.OID)); + printf("PNM: %c%c%c%c%c%c\n", + cid->mmc.PNM[0], cid->mmc.PNM[1], cid->mmc.PNM[2], + cid->mmc.PNM[3], cid->mmc.PNM[4], cid->mmc.PNM[5]); + printf("PRV: %d.%d\n", cid->mmc.PRV >> 4, cid->mmc.PRV & 0x0f); + printf("PSN: %" PRIu32 "\n", byteorder_ntohl(cid->mmc.PSN)); + printf("MDT: %u/%u\n", 1997 + (cid->mmc.MDT >> 4), cid->mmc.MDT & 0x0f); + printf("CRC: 0x%02x\n", cid->mmc.CID_CRC >> 1); + } + else { + printf("MID: %d\n", cid->sd.MID); + printf("OID: %c%c (0x%04x)\n", + cid->sd.OID[0], cid->sd.OID[1], (cid->sd.OID[0] << 8) | cid->sd.OID[1]); + printf("PNM: %c%c%c%c%c\n", + cid->sd.PNM[0], cid->sd.PNM[1], cid->sd.PNM[2], + cid->sd.PNM[3], cid->sd.PNM[4]); + printf("PRV: %d.%d\n", cid->sd.PRV >> 4, cid->sd.PRV & 0x0f); + printf("PSN: %" PRIu32 "\n", byteorder_ntohl(cid->sd.PSN)); + printf("MDT: %u/%u\n", 2000 + (byteorder_ntohs(cid->sd.MDT) >> 4), + byteorder_ntohs(cid->sd.MDT) & 0x000f); + printf("CRC: 0x%02x\n", cid->sd.CID_CRC >> 1); + } + puts("----------------------------------------"); + return 0; +} + +static int _scr(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if (_card_assert()) { + return -1; + } + + sdmmc_scr_t *scr = &dev->scr; + uint8_t ver = SDMMC_SCR_SD_SPEC(dev->scr); + + printf("Physical Layer Specification Version "); + switch (ver) { + case 0: + printf("1.0 or 1.01\n"); + break; + case 1: + printf("1.1\n"); + break; + case 2: + printf("2.00\n"); + break; + case 3: + printf("3.0X\n"); + break; + default: + printf("%u.XX\n", ver); + } + + printf("Reserved for manufacurer: %08"PRIx32"\n", scr->reserved0); + printf("SCR_STRUCTURE: 0x%01x\n", scr->SCR_STRUCTURE); + printf("SD_SPEC: 0x%01x\n", scr->SD_SPEC); + printf("DATA_STAT_AFTER_ERASE: %d\n", scr->DATA_STAT_AFTER_ERASE); + printf("SD_SECURITY: 0x%01x\n", scr->SD_SECURITY); + printf("SD_BUS_WIDTHS: 0x%01x\n", scr->SD_BUS_WIDTHS); + printf("SD_SPEC3: %d\n", scr->SD_SPEC3); + printf("EX_SECURITY: 0x%02x\n", scr->EX_SECURITY); + printf("SD_SPEC4: %d\n", scr->SD_SPEC4); + printf("SD_SPECX: 0x%01x\n", scr->SD_SPECX); + printf("CMD_SUPPORT 0x%02x\n", scr->CMD_SUPPORT); + + return 0; +} + +static int _csd(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if (_card_assert()) { + return -1; + } + + sdmmc_csd_t *csd = &dev->csd; + + if (IS_USED(MODULE_SDMMC_MMC) && (dev->type == SDMMC_CARD_TYPE_MMC)) { + puts("CSD MMC\n---------------------------------------"); + printf("CSD_STRUCTURE: 0x%x\n", csd->mmc.CSD_STRUCTURE); + printf("SPEC_VERS: 0x%x\n", csd->mmc.SPEC_VERS); + printf("TAAC: 0x%x\n", csd->mmc.TAAC); + printf("NSAC: 0x%x\n", csd->mmc.NSAC); + printf("TRAN_SPEED: 0x%x\n", csd->mmc.TRAN_SPEED); + printf("CCC: 0x%x\n", csd->mmc.CCC); + printf("READ_BL_LEN: 0x%x\n", csd->mmc.READ_BL_LEN); + printf("READ_BL_PARTIAL: 0x%x\n", csd->mmc.READ_BL_PARTIAL); + printf("WRITE_BLK_MISALIGN: 0x%x\n", csd->mmc.WRITE_BLK_MISALIGN); + printf("READ_BLK_MISALIGN: 0x%x\n", csd->mmc.READ_BLK_MISALIGN); + printf("DSR_IMP: 0x%x\n", csd->mmc.DSR_IMP); + printf("C_SIZE: 0x%x\n", csd->mmc.C_SIZE); + printf("VDD_R_CURR_MIN: 0x%x\n", csd->mmc.VDD_R_CURR_MIN); + printf("VDD_R_CURR_MAX: 0x%x\n", csd->mmc.VDD_R_CURR_MAX); + printf("VDD_W_CURR_MIN: 0x%x\n", csd->mmc.VDD_W_CURR_MIN); + printf("VDD_W_CURR_MAX: 0x%x\n", csd->mmc.VDD_W_CURR_MAX); + printf("C_SIZE_MULT: 0x%x\n", csd->mmc.C_SIZE_MULT); + printf("ERASE_GRP_SIZE: 0x%x\n", csd->mmc.ERASE_GRP_SIZE); + printf("ERASE_GRP_MULT: 0x%x\n", csd->mmc.ERASE_GRP_MULT); + printf("WP_GRP_SIZE: 0x%x\n", csd->mmc.WP_GRP_SIZE); + printf("WP_GRP_ENABLE: 0x%x\n", csd->mmc.WP_GRP_ENABLE); + printf("R2W_FACTOR: 0x%x\n", csd->mmc.R2W_FACTOR); + printf("WRITE_BL_LEN: 0x%x\n", csd->mmc.WRITE_BL_LEN); + printf("WRITE_BL_PARTIAL: 0x%x\n", csd->mmc.WRITE_BL_PARTIAL); + printf("CONTENT_PROT_APP: 0x%x\n", csd->mmc.CONTENT_PROT_APP); + printf("FILE_FORMAT_GRP: 0x%x\n", csd->mmc.FILE_FORMAT_GRP); + printf("COPY: 0x%x\n", csd->mmc.COPY); + printf("PERM_WRITE_PROTECT: 0x%x\n", csd->mmc.PERM_WRITE_PROTECT); + printf("TMP_WRITE_PROTECT: 0x%x\n", csd->mmc.TMP_WRITE_PROTECT); + printf("FILE_FORMAT: 0x%x\n", csd->mmc.FILE_FORMAT); + printf("ECC: 0x%x\n", csd->mmc.ECC); + printf("CRC: 0x%x\n", csd->mmc.CSD_CRC); + +#if IS_USED(MODULE_SDMMC_MMC) + sdmmc_ext_csd_t *ext_csd = &dev->ext_csd; + + puts("\nEXT_CSD MMC\n---------------------------------------"); + printf("CSD_STRUCTURE: 0x%x\n", ext_csd->CSD_STRUCTURE); + printf("CARD_TYPE: 0x%02x\n", ext_csd->CARD_TYPE); + printf("BUS_WIDTH: %u\n", ext_csd->BUS_WIDTH); + printf("SEC_COUNT: %"PRIu32"\n", ext_csd->SEC_COUNT); +#endif + } + else if (dev->csd.v1.CSD_STRUCTURE == SDMMC_CSD_V1) { + puts("CSD V1\n----------------------------------------"); + printf("CSD_STRUCTURE: 0x%x\n", csd->v1.CSD_STRUCTURE); + printf("TAAC: 0x%x\n", csd->v1.TAAC); + printf("NSAC: 0x%x\n", csd->v1.NSAC); + printf("TRAN_SPEED: 0x%x\n", csd->v1.TRAN_SPEED); + printf("CCC: 0x%x\n", csd->v1.CCC); + printf("READ_BL_LEN: 0x%x\n", csd->v1.READ_BL_LEN); + printf("READ_BL_PARTIAL: 0x%x\n", csd->v1.READ_BL_PARTIAL); + printf("WRITE_BLK_MISALIGN: 0x%x\n", csd->v1.WRITE_BLK_MISALIGN); + printf("READ_BLK_MISALIGN: 0x%x\n", csd->v1.READ_BLK_MISALIGN); + printf("DSR_IMP: 0x%x\n", csd->v1.DSR_IMP); + printf("C_SIZE: 0x%x\n", csd->v1.C_SIZE); + printf("VDD_R_CURR_MIN: 0x%x\n", csd->v1.VDD_R_CURR_MIN); + printf("VDD_R_CURR_MAX: 0x%x\n", csd->v1.VDD_R_CURR_MAX); + printf("VDD_W_CURR_MIN: 0x%x\n", csd->v1.VDD_W_CURR_MIN); + printf("VDD_W_CURR_MAX: 0x%x\n", csd->v1.VDD_W_CURR_MAX); + printf("C_SIZE_MULT: 0x%x\n", csd->v1.C_SIZE_MULT); + printf("ERASE_BLK_EN: 0x%x\n", csd->v1.ERASE_BLK_EN); + printf("SECTOR_SIZE: 0x%x\n", csd->v1.SECTOR_SIZE); + printf("WP_GRP_SIZE: 0x%x\n", csd->v1.WP_GRP_SIZE); + printf("WP_GRP_ENABLE: 0x%x\n", csd->v1.WP_GRP_ENABLE); + printf("R2W_FACTOR: 0x%x\n", csd->v1.R2W_FACTOR); + printf("WRITE_BL_LEN: 0x%x\n", csd->v1.WRITE_BL_LEN); + printf("WRITE_BL_PARTIAL: 0x%x\n", csd->v1.WRITE_BL_PARTIAL); + printf("FILE_FORMAT_GRP: 0x%x\n", csd->v1.FILE_FORMAT_GRP); + printf("COPY: 0x%x\n", csd->v1.COPY); + printf("PERM_WRITE_PROTECT: 0x%x\n", csd->v1.PERM_WRITE_PROTECT); + printf("TMP_WRITE_PROTECT: 0x%x\n", csd->v1.TMP_WRITE_PROTECT); + printf("FILE_FORMAT: 0x%x\n", csd->v1.FILE_FORMAT); + printf("CRC: 0x%x\n", csd->v1.CSD_CRC); + } + else if (dev->csd.v2.CSD_STRUCTURE == SDMMC_CSD_V2) { + puts("CSD V2:\n----------------------------------------"); + printf("CSD_STRUCTURE: 0x%x\n", csd->v2.CSD_STRUCTURE); + printf("TAAC: 0x%x\n", csd->v2.TAAC); + printf("NSAC: 0x%x\n", csd->v2.NSAC); + printf("TRAN_SPEED: 0x%x\n", csd->v2.TRAN_SPEED); + printf("CCC: 0x%x\n", csd->v2.CCC); + printf("READ_BL_LEN: 0x%x\n", csd->v2.READ_BL_LEN); + printf("READ_BL_PARTIAL: 0x%x\n", csd->v2.READ_BL_PARTIAL); + printf("WRITE_BLK_MISALIGN: 0x%x\n", csd->v2.WRITE_BLK_MISALIGN); + printf("READ_BLK_MISALIGN: 0x%x\n", csd->v2.READ_BLK_MISALIGN); + printf("DSR_IMP: 0x%x\n", csd->v2.DSR_IMP); + printf("C_SIZE: 0x%"PRIu32"\n", (uint32_t)csd->v2.C_SIZE); + printf("ERASE_BLK_EN: 0x%x\n", csd->v2.ERASE_BLK_EN); + printf("SECTOR_SIZE: 0x%x\n", csd->v2.SECTOR_SIZE); + printf("WP_GRP_SIZE: 0x%x\n", csd->v2.WP_GRP_SIZE); + printf("WP_GRP_ENABLE: 0x%x\n", csd->v2.WP_GRP_ENABLE); + printf("R2W_FACTOR: 0x%x\n", csd->v2.R2W_FACTOR); + printf("WRITE_BL_LEN: 0x%x\n", csd->v2.WRITE_BL_LEN); + printf("WRITE_BL_PARTIAL: 0x%x\n", csd->v2.WRITE_BL_PARTIAL); + printf("FILE_FORMAT_GRP: 0x%x\n", csd->v2.FILE_FORMAT_GRP); + printf("COPY: 0x%x\n", csd->v2.COPY); + printf("PERM_WRITE_PROTECT: 0x%x\n", csd->v2.PERM_WRITE_PROTECT); + printf("TMP_WRITE_PROTECT: 0x%x\n", csd->v2.TMP_WRITE_PROTECT); + printf("FILE_FORMAT: 0x%x\n", csd->v2.FILE_FORMAT); + printf("CRC: 0x%x\n", csd->v2.CSD_CRC); + } + else { + printf("[FAILED] wrong CSD structure version %u\n", + dev->csd.v1.CSD_STRUCTURE); + return -1; + } + + puts("----------------------------------------"); + return 0; +} + +static int _sds(int argc, char **argv) +{ + (void)argc; + (void)argv; + + sdmmc_sd_status_t sds; + + if (sdmmc_read_sds(dev, &sds) == 0) { + puts("SD Status:\n----------------------------------------"); + printf("SIZE_OF_PROTECTED_AREA: 0x%0lx\n", (unsigned long)sds.SIZE_OF_PROTECTED_AREA); + printf("SUS_ADDR: 0x%0lx\n", (unsigned long)sds.SUS_ADDR); + printf("VSC_AU_SIZE: 0x%0lx\n", (unsigned long)sds.VSC_AU_SIZE); + printf("SD_CARD_TYPE: 0x%0lx\n", (unsigned long)sds.SD_CARD_TYPE); + printf("ERASE_SIZE: 0x%0lx\n", (unsigned long)sds.ERASE_SIZE); + printf("SPEED_CLASS: 0x%0lx\n", (unsigned long)sds.SPEED_CLASS); + printf("PERFORMANCE_MOVE: 0x%0lx\n", (unsigned long)sds.PERFORMANCE_MOVE); + printf("VIDEO_SPEED_CLASS: 0x%0lx\n", (unsigned long)sds.VIDEO_SPEED_CLASS); + printf("ERASE_TIMEOUT: 0x%0lx\n", (unsigned long)sds.ERASE_TIMEOUT); + printf("ERASE_OFFSET: 0x%0lx\n", (unsigned long)sds.ERASE_OFFSET); + printf("UHS_SPEED_GRADE: 0x%0lx\n", (unsigned long)sds.UHS_SPEED_GRADE); + printf("UHS_AU_SIZE: 0x%0lx\n", (unsigned long)sds.UHS_AU_SIZE); + printf("AU_SIZE: 0x%0lx\n", (unsigned long)sds.AU_SIZE); + printf("DAT_BUS_WIDTH: 0x%0lx\n", (unsigned long)sds.DAT_BUS_WIDTH); + printf("SECURED_MODE: 0x%0lx\n", (unsigned long)sds.SECURED_MODE); + puts("----------------------------------------"); + return 0; + } + return -1; +} + +#define KILO (1000UL) +#define MEGA (1000000UL) +#define GIGA (1000000000UL) + +static void _print_size(uint64_t bytes) +{ + /* gib_frac = (bytes - gib_int * GiB) / MiB * KILO / KiB; */ + uint32_t gib_int = bytes / GiB(1); + uint32_t gib_frac = (((bytes / MiB(1)) - (gib_int * KiB(1))) * KILO) / KiB(1); + + /* gb_frac = (bytes - gb_int * GIGA) / MEGA */ + uint32_t gb_int = bytes / GIGA; + uint32_t gb_frac = (bytes / MEGA) - (gb_int * KILO); + + print_u64_dec( bytes ); + printf(" bytes (%" PRIu32 ",%03" PRIu32 " GiB | %" PRIu32 ",%03" PRIu32 " GB)\n", gib_int, + gib_frac, gb_int, gb_frac); +} + +static int _size(int argc, char **argv) +{ + (void)argc; + (void)argv; + + puts("\nCard size: "); + _print_size(sdmmc_get_capacity(dev)); + + return 0; +} + +static int _read(int argc, char **argv) +{ + int blockaddr; + int cnt; + bool print_as_char = false; + + if (_card_assert()) { + return -1; + } + + if ((argc == 3) || (argc == 4)) { + blockaddr = atoi(argv[1]); + cnt = atoi(argv[2]); + if (argc == 4 && (strcmp("-c", argv[3]) == 0)) { + print_as_char = true; + } + } + else { + printf("usage: %s blockaddr cnt [-c]\n", argv[0]); + return -1; + } + + int total_read = 0; + + while (total_read < cnt) { + int chunk_blocks = cnt - total_read; + if (chunk_blocks > MAX_BLOCKS_IN_BUFFER) { + chunk_blocks = MAX_BLOCKS_IN_BUFFER; + } + + uint16_t chunks_read; + int res = sdmmc_read_blocks(dev, blockaddr + total_read, + SDMMC_SDHC_BLOCK_SIZE, + chunk_blocks, buffer, &chunks_read); + if (res) { + printf("read error %d (block %d/%d)\n", + res, total_read + chunks_read, cnt); + return -1; + } + + if (IS_USED(OUTPUT)) { + for (int i = 0; i < chunk_blocks * SDMMC_SDHC_BLOCK_SIZE; i++) { + + if ((i % SDMMC_SDHC_BLOCK_SIZE) == 0) { + printf("BLOCK %d:\n", + blockaddr + total_read + i / SDMMC_SDHC_BLOCK_SIZE); + } + + if (print_as_char) { + if (buffer[i] >= FIRST_PRINTABLE_ASCII_CHAR) { + printf("%c", buffer[i]); + } + else { + printf(ASCII_UNPRINTABLE_REPLACEMENT); + } + } + else { + printf("%02x ", buffer[i]); + } + + if ((i % BLOCK_PRINT_BYTES_PER_LINE) == (BLOCK_PRINT_BYTES_PER_LINE - 1)) { + puts(""); /* line break after BLOCK_PRINT_BYTES_PER_LINE bytes */ + } + + if ((i % SDMMC_SDHC_BLOCK_SIZE) == (SDMMC_SDHC_BLOCK_SIZE - 1)) { + puts(""); /* empty line after each printed block */ + } + } + } + total_read += chunks_read; + } + printf("read %d block(s) from %d [OK]\n", cnt, blockaddr); + + return 0; +} + +static int _write(int argc, char **argv) +{ + int bladdr; + char *data; + int size; + bool repeat_data = false; + + if (_card_assert()) { + return -1; + } + + if (argc == 3 || argc == 4) { + bladdr = atoi(argv[1]); + data = argv[2]; + size = strlen(argv[2]); + printf("will write '%s' (%d chars) at start of block %d\n", + data, size, bladdr); + if (argc == 4 && (strcmp("-r", argv[3]) == 0)) { + repeat_data = true; + puts("the rest of the block will be filled with copies of that string"); + } + else { + puts("the rest of the block will be filled with zeros"); + } + } + else { + printf("usage: %s blockaddr string [-r]\n", argv[0]); + return -1; + } + + if (size > SDMMC_SDHC_BLOCK_SIZE) { + printf("maximum stringsize to write at once is %d ...aborting\n", + SDMMC_SDHC_BLOCK_SIZE); + return -1; + } + + /* copy data to a full-block-sized buffer an fill remaining block space + * according to -r param*/ + uint8_t write_buffer[SDMMC_SDHC_BLOCK_SIZE]; + for (unsigned i = 0; i < sizeof(write_buffer); i++) { + if (repeat_data || ((int)i < size)) { + write_buffer[i] = data[i % size]; + } + else { + write_buffer[i] = 0; + } + } + + int res = sdmmc_write_blocks(dev, bladdr, SDMMC_SDHC_BLOCK_SIZE, 1, + write_buffer, NULL); + if (res) { + printf("write error %d (wrote 1/1 blocks)\n", res); + return -1; + } + + printf("write block %d [OK]\n", bladdr); + return 0; +} + +static int _writem(int argc, char **argv) +{ + int bladdr; + int cnt; + uint16_t done; + + if (_card_assert()) { + return -1; + } + + if (argc == 3) { + bladdr = atoi(argv[1]); + cnt = atoi(argv[2]); + } + else { + printf("usage: %s blockaddr num\n", argv[0]); + return -1; + } + + /* writing cnt blocks with data from stack */ + int res = sdmmc_write_blocks(dev, bladdr, SDMMC_SDHC_BLOCK_SIZE, cnt, + (void *)&bladdr, &done); + if (res) { + printf("write error %d (wrote %u/%d blocks)\n", res, done, cnt); + return -1; + } + + printf("write %d block(s) to %d [OK]\n", cnt, bladdr); + return 0; +} + +static int _erase(int argc, char **argv) +{ + int blockaddr; + int cnt; + + if (_card_assert()) { + return -1; + } + + if (argc == 3) { + blockaddr = atoi(argv[1]); + cnt = atoi(argv[2]); + } + else { + printf("usage: %s blockaddr cnt\n", argv[0]); + return -1; + } + + int res = sdmmc_erase_blocks(dev, blockaddr, cnt); + if (res) { + printf("erase error %d\n", res); + return -1; + } + + printf("erase %d block(s) from %d [OK]\n", cnt, blockaddr); + return 0; +} + +static int _copy(int argc, char **argv) +{ + int src_block; + int dst_block; + int num_block = 1; + uint8_t tmp_copy[SDMMC_SDHC_BLOCK_SIZE]; + + if (_card_assert()) { + return -1; + } + + if (argc < 3) { + printf("usage: %s src dst [num]\n", argv[0]); + return -1; + } + + src_block = atoi(argv[1]); + dst_block = atoi(argv[2]); + + if (argc == 4) { + num_block = atoi(argv[3]); + } + + for (int i = 0; i < num_block; i++) { + int res = sdmmc_read_blocks(dev, src_block + i, SDMMC_SDHC_BLOCK_SIZE, 1, + tmp_copy, NULL); + + if (res) { + printf("read error %d (block %d)\n", res, src_block + i); + return -1; + } + + res = sdmmc_write_blocks(dev, dst_block + i, SDMMC_SDHC_BLOCK_SIZE, 1, + tmp_copy, NULL); + + if (res) { + printf("write error %d (block %d)\n", res, dst_block + i); + return -1; + } + + if (IS_USED(OUTPUT) && (num_block > 1)) { + extern ssize_t stdio_write(const void *buffer, size_t len); + stdio_write(".", 1); //printf("."); + if ((num_block % 79) == 79) { + printf("\n"); + } + } + } + + if (IS_USED(OUTPUT)) { + printf("\n"); + } + + printf("copy %d block(s) from %d to %d [OK]\n", + num_block, src_block, dst_block); + return 0; +} + +static int _sector_count(int argc, char **argv) +{ + (void)argc; + (void)argv; + + printf("available sectors on card: %" PRIu32 "\n", + (uint32_t)(sdmmc_get_capacity(dev) / SDMMC_SDHC_BLOCK_SIZE)); + return 0; +} + +void _card_event_cb(sdmmc_dev_t *dev, sdmmc_event_t event) +{ + (void)dev; + switch (event) { + case SDMMC_EVENT_CARD_INSERTED: + puts("Event: Card inserted"); + break; + case SDMMC_EVENT_CARD_REMOVED: + puts("Event: Card removed"); + break; + default: + puts("Event: unknown"); + } +} + +static const shell_command_t shell_commands[] = { + { "init", "initializes default card", _init }, + { "cid", "print content of CID (Card IDentification) register", _cid }, + { "csd", "print content of CSD (Card-Specific Data) register", _csd }, + { "sds", "print SD Status", _sds }, + { "scr", "print content of SCR (SD Card Configuration Register)", _scr }, + { "size", "print card size", _size }, + { "sectors", "print sector count of card", _sector_count }, + { "read", "'read n m' reads m blocks beginning at block address n and prints the result. " + "Append -c option to print data readable chars", _read }, + { "write", "'write n data' writes data to block n. Append -r option to " + "repeatedly write data to complete block", _write }, + { "copy", "'copy src dst' copies block src to block dst", _copy }, + { "erase", "'erase n m' erases m blocks beginning at block address n", _erase }, + { "writem", "'write n m' writes m data blocks beginning at block address n.", + _writem }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + /* use the first SDMMC device by default */ + dev = sdmmc_get_dev(0); + dev->event_cb = _card_event_cb; + + puts("SDMMC driver test application"); + + puts("insert a SD Card/MMC and use 'init' command to set card to spi mode"); + puts("WARNING: using 'write' or 'copy' commands WILL overwrite data on your card and"); + puts("almost for sure corrupt existing filesystems, partitions and contained data!"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; +}