From b859da84951ce5a533b2c953a1ae0c23fe54b260 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Thu, 15 Jun 2023 14:48:07 +0200 Subject: [PATCH 01/14] cpu/samd5x: change FDPLL1 frequency to 100 MHz The only peripheral that currently uses the FDPLL1 is SDHC. However, the SDHC IP can only be clocked at up to 150 MHz. Therefore, 100 MHz is currently used as the frequency of the FDPLL1. If another peripheral device requires 200 MHz in the future, this must be realized via different clock generators. --- cpu/samd5x/cpu.c | 10 +++++----- cpu/samd5x/include/periph_cpu.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) 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 */ }; /** @} */ From 7899e8002ea40b48e8a7382b93186edfc1f5b0c0 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Sun, 18 Jun 2023 16:27:15 +0200 Subject: [PATCH 02/14] cpu/sam0_common/periph/sdhc: busy wait implementation --- cpu/sam0_common/sam0_sdhc/sdhc.c | 215 +++++++++++++++++++++++-------- 1 file changed, 162 insertions(+), 53 deletions(-) diff --git a/cpu/sam0_common/sam0_sdhc/sdhc.c b/cpu/sam0_common/sam0_sdhc/sdhc.c index 95f04e67a9..f465653456 100644 --- a/cpu/sam0_common/sam0_sdhc/sdhc.c +++ b/cpu/sam0_common/sam0_sdhc/sdhc.c @@ -66,6 +66,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. @@ -126,26 +130,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) @@ -220,7 +271,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; @@ -300,7 +353,7 @@ int sdhc_init(sdhc_state_t *state) _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; } @@ -355,15 +408,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; } @@ -396,12 +444,26 @@ static void _set_speed(sdhc_state_t *state, uint32_t fsdhc) /* since both examples use divided clock rather than programmable - just use divided here */ SDHC_DEV->CCR.reg &= ~SDHC_CCR_CLKGSEL; /* divided clock */ - /* Fsdclk = Fsdhc_core/(2 * div) */ - div = (sam0_gclk_freq(SDHC_CLOCK) / fsdhc) / 2; + /* According to the data sheet the divided clock is given by + * + * Fsdclk = Fsdhc_core/(2 * div) + * + * Hovewer, this seems to be wrong since the SD CLK is always exactly the half. + * So it seems that the clock is given by + * + * Fsdclk = Fsdhc_core/(4 * div) + */ + if (SDHC_CLOCK == SAM0_GCLK_100MHZ) { + /* if the FDPLL1 with 100 MHz is used, we can use 25 MHz/50 MHz clocks */ + div = (sam0_gclk_freq(SDHC_CLOCK) / fsdhc) / 4; + } + else { + 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; + /* high speed div must not be 0 */ + if (SDHC_DEV->HC1R.bit.HSEN && (div == 0)) { + div = 1; + } } /* write the 10 bit clock divider */ @@ -569,10 +631,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 +682,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 +747,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; } @@ -741,12 +823,28 @@ 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: mutex_unlock(&state->lock); return res; @@ -808,6 +906,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,6 +921,14 @@ 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: mutex_unlock(&state->lock); return res; @@ -966,10 +1080,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 +1144,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(); } From bfe98a58850312971b813aa6fa707fc7757bad7e Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Mon, 19 Jun 2023 14:29:01 +0200 Subject: [PATCH 03/14] cpu/sam0_common/periph/sdhc: fix clock generation --- cpu/sam0_common/sam0_sdhc/sdhc.c | 38 +++++++------------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/cpu/sam0_common/sam0_sdhc/sdhc.c b/cpu/sam0_common/sam0_sdhc/sdhc.c index f465653456..86018d5664 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" @@ -432,46 +433,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; - /* According to the data sheet the divided clock is given by - * - * Fsdclk = Fsdhc_core/(2 * div) - * - * Hovewer, this seems to be wrong since the SD CLK is always exactly the half. - * So it seems that the clock is given by - * - * Fsdclk = Fsdhc_core/(4 * div) - */ - if (SDHC_CLOCK == SAM0_GCLK_100MHZ) { - /* if the FDPLL1 with 100 MHz is used, we can use 25 MHz/50 MHz clocks */ - div = (sam0_gclk_freq(SDHC_CLOCK) / fsdhc) / 4; - } - else { - 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 */ } /** From 0f555f061f4a214fe1b49d73f9398b98cdf333b7 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Mon, 19 Jun 2023 19:19:12 +0200 Subject: [PATCH 04/14] cpu/sam0_common/periph/sdhc: disable clock when SD card is idle --- cpu/sam0_common/sam0_sdhc/sdhc.c | 60 ++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/cpu/sam0_common/sam0_sdhc/sdhc.c b/cpu/sam0_common/sam0_sdhc/sdhc.c index 86018d5664..4d3cc1dccb 100644 --- a/cpu/sam0_common/sam0_sdhc/sdhc.c +++ b/cpu/sam0_common/sam0_sdhc/sdhc.c @@ -114,6 +114,13 @@ 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; @@ -259,6 +266,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; @@ -290,14 +298,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 { @@ -314,40 +324,49 @@ 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; } /* update the host controller to the detected changes in bus_width and clock */ @@ -356,7 +375,8 @@ int sdhc_init(sdhc_state_t *state) /* if it is high speed capable, (well it is) */ if (IS_USED(SDHC_ENABLE_HS) && SDHC_DEV->CA0R.bit.HSSUP) { if (!_test_high_speed(state)) { - return -EIO; + res = -EIO; + goto out; } } @@ -364,11 +384,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) @@ -767,6 +791,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); @@ -824,6 +849,7 @@ int sdhc_read_blocks(sdhc_state_t *state, uint32_t address, void *dst, uint16_t } out: + _clock_sdcard(state, 0); mutex_unlock(&state->lock); return res; } @@ -848,6 +874,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); @@ -908,6 +935,8 @@ int sdhc_write_blocks(sdhc_state_t *state, uint32_t address, const void *src, } out: + _wait_not_busy(state); + _clock_sdcard(state, 0); mutex_unlock(&state->lock); return res; } @@ -922,6 +951,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); @@ -951,6 +981,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; } From 84ceea33d0d156d5ab36b4171848cf5f11983d5a Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Tue, 20 Jun 2023 12:46:30 +0200 Subject: [PATCH 05/14] cpu/sam0_common/periph/sdhc: always use 25 MHz --- cpu/sam0_common/include/sdhc.h | 1 + cpu/sam0_common/sam0_sdhc/sdhc.c | 14 ++++---------- 2 files changed, 5 insertions(+), 10 deletions(-) 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 4d3cc1dccb..d4252a17a0 100644 --- a/cpu/sam0_common/sam0_sdhc/sdhc.c +++ b/cpu/sam0_common/sam0_sdhc/sdhc.c @@ -103,12 +103,6 @@ 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; @@ -369,6 +363,9 @@ int sdhc_init(sdhc_state_t *state) 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); @@ -567,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; @@ -576,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. * ---------------------------------------------------- From 0df9480126d1f71c5adfa28d03ce679a8b93df1c Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Sun, 6 Aug 2023 16:09:40 +0200 Subject: [PATCH 06/14] drivers/fx5x06: fix vendor ID for FT6xx6 The vendor ID of FT6xx6 touch panel driver ICs is `0x11` instead of `0xcd`. --- drivers/ft5x06/include/ft5x06_constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ft5x06/include/ft5x06_constants.h b/drivers/ft5x06/include/ft5x06_constants.h index 76d80eb575..8d9dbb588d 100644 --- a/drivers/ft5x06/include/ft5x06_constants.h +++ b/drivers/ft5x06/include/ft5x06_constants.h @@ -34,7 +34,7 @@ extern "C" { /** * @brief Vendor ID for FT6X06 and FT6X36 models. */ -#define FT6XX6_VENDOR_ID (0xcd) +#define FT6XX6_VENDOR_ID (0x11) /** * @brief Vendor ID for FT5606, FT5X16, FT5X06I, FT5336, FT3316, FT5436I, FT5336I, FT5X46 models. From 556ef5235c412da59cdbc6d01eefb2ec8ecc170f Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Thu, 8 Jun 2023 09:51:28 +0200 Subject: [PATCH 07/14] drivers: add periph_sdmmc support --- drivers/Kconfig | 1 + drivers/include/sdmmc/sdmmc.h | 1909 +++++++++++++++++++++++++++ drivers/mtd/Kconfig | 40 +- drivers/periph_common/Kconfig | 1 + drivers/periph_common/Kconfig.sdmmc | 62 + drivers/periph_common/init.c | 9 + drivers/sdmmc/Kconfig | 22 + drivers/sdmmc/Makefile | 3 + drivers/sdmmc/Makefile.dep | 13 + drivers/sdmmc/Makefile.include | 1 + drivers/sdmmc/sdmmc.c | 1612 ++++++++++++++++++++++ kconfigs/Kconfig.features | 49 + 12 files changed, 3715 insertions(+), 7 deletions(-) create mode 100644 drivers/include/sdmmc/sdmmc.h create mode 100644 drivers/periph_common/Kconfig.sdmmc create mode 100644 drivers/sdmmc/Kconfig create mode 100644 drivers/sdmmc/Makefile create mode 100644 drivers/sdmmc/Makefile.dep create mode 100644 drivers/sdmmc/Makefile.include create mode 100644 drivers/sdmmc/sdmmc.c 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/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/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 c8b4ea57b6..ebe09c3a61 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) @@ -133,5 +136,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 44c586f059..cace50b5c5 100644 --- a/kconfigs/Kconfig.features +++ b/kconfigs/Kconfig.features @@ -410,6 +410,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 From 00275326a8f54f72de71e77ff2a11cd35d274a4e Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Fri, 9 Jun 2023 13:41:48 +0200 Subject: [PATCH 08/14] drivers/mtd: add SDMMC support --- drivers/include/mtd_sdmmc.h | 80 +++++++++ drivers/mtd/Makefile.dep | 8 + drivers/mtd_sdmmc/Kconfig | 35 ++++ drivers/mtd_sdmmc/Makefile | 3 + drivers/mtd_sdmmc/Makefile.include | 1 + drivers/mtd_sdmmc/mtd_sdmmc.c | 254 +++++++++++++++++++++++++++++ sys/include/vfs_default.h | 3 +- 7 files changed, 383 insertions(+), 1 deletion(-) create mode 100644 drivers/include/mtd_sdmmc.h create mode 100644 drivers/mtd_sdmmc/Kconfig create mode 100644 drivers/mtd_sdmmc/Makefile create mode 100644 drivers/mtd_sdmmc/Makefile.include create mode 100644 drivers/mtd_sdmmc/mtd_sdmmc.c 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/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/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) From 6e03f2620a174be4c2854438fb9cbeed88d2bbc8 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Tue, 8 Aug 2023 10:01:25 +0200 Subject: [PATCH 09/14] tests: add test app for periph_sdmmc --- tests/drivers/sdmmc/Makefile | 12 + tests/drivers/sdmmc/Makefile.ci | 2 + tests/drivers/sdmmc/app.config.test | 5 + tests/drivers/sdmmc/main.c | 679 ++++++++++++++++++++++++++++ 4 files changed, 698 insertions(+) create mode 100644 tests/drivers/sdmmc/Makefile create mode 100644 tests/drivers/sdmmc/Makefile.ci create mode 100644 tests/drivers/sdmmc/app.config.test create mode 100644 tests/drivers/sdmmc/main.c 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; +} From a9f3ce121a199d650191724b87e23afc28d43ea8 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Tue, 15 Aug 2023 00:06:02 +0200 Subject: [PATCH 10/14] drivers/ft5x06: fix register addresses --- drivers/ft5x06/ft5x06.c | 16 ++++++++-------- drivers/ft5x06/include/ft5x06_constants.h | 23 ++++++++++++++--------- 2 files changed, 22 insertions(+), 17 deletions(-) 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 8d9dbb588d..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 (0x11) - /** * @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) From 817bb48843be688a76c52cab5f634a59e43769d8 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Tue, 15 Aug 2023 13:20:59 +0200 Subject: [PATCH 11/14] cpu/efm32: fix DAC reference voltage configuration The EFM32 MCU allows the reference voltage to be configured per DAC device, not per DAC channel. Also, the DAC reference voltage was defined in the configuration but not used anywhere. --- cpu/efm32/include/periph_cpu.h | 2 +- cpu/efm32/periph/dac.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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); From 059bcafd749f5f131bf30629f45205d2b458f2ab Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Tue, 15 Aug 2023 13:21:42 +0200 Subject: [PATCH 12/14] boards/slwstk6220a: fix DAC configuration --- boards/slwstk6220a/include/periph_conf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, } }; From 5796abd4ae1fdf1804c77c746dacc4a0651bf14e Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Mon, 21 Aug 2023 18:12:23 +0200 Subject: [PATCH 13/14] boards/stk3600: fix DAC configuration --- boards/stk3600/include/periph_conf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, } }; From 76bbae90ffbdaa094810cd4ea88b9cd39e516106 Mon Sep 17 00:00:00 2001 From: Gunar Schorcht Date: Mon, 21 Aug 2023 18:12:39 +0200 Subject: [PATCH 14/14] boards/stk3700: fix DAC configuration --- boards/stk3700/include/periph_conf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, } };