diff --git a/tests/driver_sdmmc/Makefile b/tests/driver_sdmmc/Makefile new file mode 100644 index 0000000000000..088a6046018c9 --- /dev/null +++ b/tests/driver_sdmmc/Makefile @@ -0,0 +1,7 @@ +include ../Makefile.tests_common + +USEMODULE += periph_sdmmc +USEMODULE += fmt +USEMODULE += shell + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_sdmmc/Makefile.ci b/tests/driver_sdmmc/Makefile.ci new file mode 100644 index 0000000000000..1e025a2333460 --- /dev/null +++ b/tests/driver_sdmmc/Makefile.ci @@ -0,0 +1,2 @@ +BOARD_INSUFFICIENT_MEMORY := \ + # diff --git a/tests/driver_sdmmc/app.config.test b/tests/driver_sdmmc/app.config.test new file mode 100644 index 0000000000000..e20828acf00fd --- /dev/null +++ b/tests/driver_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_PERIPH_SDMMC=y +CONFIG_MODULE_FMT=y +CONFIG_MODULE_SHELL=y diff --git a/tests/driver_sdmmc/main.c b/tests/driver_sdmmc/main.c new file mode 100644 index 0000000000000..7d347987044e7 --- /dev/null +++ b/tests/driver_sdmmc/main.c @@ -0,0 +1,531 @@ +/* + * 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 "container.h" +#include "fmt.h" +#include "macros/units.h" +#include "shell.h" + +#include "periph/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 *card = NULL; + +uint8_t buffer[SDMMC_SDHC_BLOCK_SIZE * MAX_BLOCKS_IN_BUFFER]; + +static int _init(int argc, char **argv) +{ + int device = 0; + + if ((argc == 2)) { + device = atoi(argv[1]); + } + + card = sdmmc_get_dev(device); + + if (card == NULL) { + printf("[Error] No device with index %i\n", device); + /* use the first SDMMC device by default */ + card = sdmmc_get_dev(0); + return -1; + } + + printf("Initializing SD Card at SDMMC_%i\n", device); + if (sdmmc_card_init(card)) { + puts("[FAILED]"); + puts("enable debugging in sdmmc.c for more information!"); + return -2; + } + puts("[OK]"); + + return 0; +} + +static int _cid(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + sdmmc_cid_t cid; + int res = sdmmc_read_cid(card, &cid); + if (res) { + printf("[Error] Could not read CID, error %d\n", res); + return -1; + } + + puts("----------------------------------------"); + printf("MID: %d\n", cid.MID); + printf("OID: %c%c\n", cid.OID[0], cid.OID[1]); + printf("PNM: %c%c%c%c%c\n", cid.PNM[0], cid.PNM[1], cid.PNM[2], + cid.PNM[3], cid.PNM[4]); + printf("PRV: %u\n", cid.PRV); + printf("PSN: %" PRIu32 "\n", cid.PSN); + printf("MDT: %u\n", cid.MDT); + printf("CRC: %u\n", cid.CID_CRC); + puts("----------------------------------------"); + return 0; +} + +static int _scr(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + uint8_t ver = SDMMC_SCR_SD_SPEC(card->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", card->scr.reserved0); + printf("SCR_STRUCTURE: 0x%01x\n", card->scr.SCR_STRUCTURE); + printf("SD_SPEC: 0x%01x\n", card->scr.SD_SPEC); + printf("DATA_STAT_AFTER_ERASE: %d\n", card->scr.DATA_STAT_AFTER_ERASE); + printf("SD_SECURITY: 0x%01x\n", card->scr.SD_SECURITY); + printf("SD_BUS_WIDTHS: 0x%01x\n", card->scr.SD_BUS_WIDTHS); + printf("SD_SPEC3: %d\n", card->scr.SD_SPEC3); + printf("EX_SECURITY: 0x%02x\n", card->scr.EX_SECURITY); + printf("SD_SPEC4: %d\n", card->scr.SD_SPEC4); + printf("SD_SPECX: 0x%01x\n", card->scr.SD_SPECX); + printf("CMD_SUPPORT 0x%02x\n", card->scr.CMD_SUPPORT); + + return 0; +} + +static int _csd(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + if (card->csd_version == SDMMC_CSD_V1) { + puts("CSD V1\n----------------------------------------"); + printf("CSD_STRUCTURE: 0x%0lx\n", (unsigned long)card->csd.v1.CSD_STRUCTURE); + printf("TAAC: 0x%0lx\n", (unsigned long)card->csd.v1.TAAC); + printf("NSAC: 0x%0lx\n", (unsigned long)card->csd.v1.NSAC); + printf("TRAN_SPEED: 0x%0lx\n", (unsigned long)card->csd.v1.TRAN_SPEED); + printf("CCC: 0x%0lx\n", (unsigned long)card->csd.v1.CCC); + printf("READ_BL_LEN: 0x%0lx\n", (unsigned long)card->csd.v1.READ_BL_LEN); + printf("READ_BL_PARTIAL: 0x%0lx\n", (unsigned long)card->csd.v1.READ_BL_PARTIAL); + printf("WRITE_BLK_MISALIGN: 0x%0lx\n", (unsigned long)card->csd.v1.WRITE_BLK_MISALIGN); + printf("READ_BLK_MISALIGN: 0x%0lx\n", (unsigned long)card->csd.v1.READ_BLK_MISALIGN); + printf("DSR_IMP: 0x%0lx\n", (unsigned long)card->csd.v1.DSR_IMP); + printf("C_SIZE: 0x%0lx\n", (unsigned long)card->csd.v1.C_SIZE); + printf("VDD_R_CURR_MIN: 0x%0lx\n", (unsigned long)card->csd.v1.VDD_R_CURR_MIN); + printf("VDD_R_CURR_MAX: 0x%0lx\n", (unsigned long)card->csd.v1.VDD_R_CURR_MAX); + printf("VDD_W_CURR_MIN: 0x%0lx\n", (unsigned long)card->csd.v1.VDD_W_CURR_MIN); + printf("VDD_W_CURR_MAX: 0x%0lx\n", (unsigned long)card->csd.v1.VDD_W_CURR_MAX); + printf("C_SIZE_MULT: 0x%0lx\n", (unsigned long)card->csd.v1.C_SIZE_MULT); + printf("ERASE_BLK_EN: 0x%0lx\n", (unsigned long)card->csd.v1.ERASE_BLK_EN); + printf("SECTOR_SIZE: 0x%0lx\n", (unsigned long)card->csd.v1.SECTOR_SIZE); + printf("WP_GRP_SIZE: 0x%0lx\n", (unsigned long)card->csd.v1.WP_GRP_SIZE); + printf("WP_GRP_ENABLE: 0x%0lx\n", (unsigned long)card->csd.v1.WP_GRP_ENABLE); + printf("R2W_FACTOR: 0x%0lx\n", (unsigned long)card->csd.v1.R2W_FACTOR); + printf("WRITE_BL_LEN: 0x%0lx\n", (unsigned long)card->csd.v1.WRITE_BL_LEN); + printf("WRITE_BL_PARTIAL: 0x%0lx\n", (unsigned long)card->csd.v1.WRITE_BL_PARTIAL); + printf("FILE_FORMAT_GRP: 0x%0lx\n", (unsigned long)card->csd.v1.FILE_FORMAT_GRP); + printf("COPY: 0x%0lx\n", (unsigned long)card->csd.v1.COPY); + printf("PERM_WRITE_PROTECT: 0x%0lx\n", (unsigned long)card->csd.v1.PERM_WRITE_PROTECT); + printf("TMP_WRITE_PROTECT: 0x%0lx\n", (unsigned long)card->csd.v1.TMP_WRITE_PROTECT); + printf("FILE_FORMAT: 0x%0lx\n", (unsigned long)card->csd.v1.FILE_FORMAT); + printf("CRC: 0x%0lx\n", (unsigned long)card->csd.v1.CSD_CRC); + } + else if (card->csd_version == SDMMC_CSD_V2) { + puts("CSD V2:\n----------------------------------------"); + printf("CSD_STRUCTURE: 0x%0lx\n", (unsigned long)card->csd.v2.CSD_STRUCTURE); + printf("TAAC: 0x%0lx\n", (unsigned long)card->csd.v2.TAAC); + printf("NSAC: 0x%0lx\n", (unsigned long)card->csd.v2.NSAC); + printf("TRAN_SPEED: 0x%0lx\n", (unsigned long)card->csd.v2.TRAN_SPEED); + printf("CCC: 0x%0lx\n", (unsigned long)card->csd.v2.CCC); + printf("READ_BL_LEN: 0x%0lx\n", (unsigned long)card->csd.v2.READ_BL_LEN); + printf("READ_BL_PARTIAL: 0x%0lx\n", (unsigned long)card->csd.v2.READ_BL_PARTIAL); + printf("WRITE_BLK_MISALIGN: 0x%0lx\n", (unsigned long)card->csd.v2.WRITE_BLK_MISALIGN); + printf("READ_BLK_MISALIGN: 0x%0lx\n", (unsigned long)card->csd.v2.READ_BLK_MISALIGN); + printf("DSR_IMP: 0x%0lx\n", (unsigned long)card->csd.v2.DSR_IMP); + printf("C_SIZE: 0x%0lx\n", (unsigned long)card->csd.v2.C_SIZE); + printf("ERASE_BLK_EN: 0x%0lx\n", (unsigned long)card->csd.v2.ERASE_BLK_EN); + printf("SECTOR_SIZE: 0x%0lx\n", (unsigned long)card->csd.v2.SECTOR_SIZE); + printf("WP_GRP_SIZE: 0x%0lx\n", (unsigned long)card->csd.v2.WP_GRP_SIZE); + printf("WP_GRP_ENABLE: 0x%0lx\n", (unsigned long)card->csd.v2.WP_GRP_ENABLE); + printf("R2W_FACTOR: 0x%0lx\n", (unsigned long)card->csd.v2.R2W_FACTOR); + printf("WRITE_BL_LEN: 0x%0lx\n", (unsigned long)card->csd.v2.WRITE_BL_LEN); + printf("WRITE_BL_PARTIAL: 0x%0lx\n", (unsigned long)card->csd.v2.WRITE_BL_PARTIAL); + printf("FILE_FORMAT_GRP: 0x%0lx\n", (unsigned long)card->csd.v2.FILE_FORMAT_GRP); + printf("COPY: 0x%0lx\n", (unsigned long)card->csd.v2.COPY); + printf("PERM_WRITE_PROTECT: 0x%0lx\n", (unsigned long)card->csd.v2.PERM_WRITE_PROTECT); + printf("TMP_WRITE_PROTECT: 0x%0lx\n", (unsigned long)card->csd.v2.TMP_WRITE_PROTECT); + printf("FILE_FORMAT: 0x%0lx\n", (unsigned long)card->csd.v2.FILE_FORMAT); + printf("CRC: 0x%0lx\n", (unsigned long)card->csd.v2.CSD_CRC); + } + else { + printf("[FAILED] wrong CSD structure version %u\n", card->csd_version); + return -1; + } + puts("----------------------------------------"); + return 0; +} + +static int _sds(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + sdmmc_sd_status_t sds; + + if (sdmmc_read_sds(card, &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 int _size(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + uint64_t bytes = sdmmc_get_capacity(card); + + /* 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); + + puts("\nCard size: "); + print_u64_dec( bytes ); + printf(" bytes (%" PRIu32 ",%03" PRIu32 " GiB | %" PRIu32 ",%03" PRIu32 " GB)\n", gib_int, + gib_frac, gb_int, gb_frac); + return 0; +} + +static int _read(int argc, char **argv) +{ + int blockaddr; + int cnt; + bool print_as_char = false; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + 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(card, 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; + } + + 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; + } + return 0; +} + +static int _write(int argc, char **argv) +{ + int bladdr; + char *data; + int size; + bool repeat_data = false; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + 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(card, 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 _erase(int argc, char **argv) +{ + int blockaddr; + int cnt; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + if (argc == 3) { + blockaddr = atoi(argv[1]); + cnt = atoi(argv[2]); + } + else { + printf("usage: %s blockaddr cnt\n", argv[0]); + return -1; + } + + sdmmc_erase_blocks(card, blockaddr, cnt); + return 0; +} + +static int _copy(int argc, char **argv) +{ + int src_block; + int dst_block; + uint8_t tmp_copy[SDMMC_SDHC_BLOCK_SIZE]; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + if (argc != 3) { + printf("usage: %s src_block dst_block\n", argv[0]); + return -1; + } + + src_block = atoi(argv[1]); + dst_block = atoi(argv[2]); + + int res = sdmmc_read_blocks(card, src_block, SDMMC_SDHC_BLOCK_SIZE, 1, + tmp_copy, NULL); + + if (res) { + printf("read error %d (block %d)\n", res, src_block); + return -1; + } + + res = sdmmc_write_blocks(card, dst_block, SDMMC_SDHC_BLOCK_SIZE, 1, + tmp_copy, NULL); + + if (res) { + printf("write error %d (block %d)\n", res, dst_block); + return -2; + } + + printf("copy block %d to %d [OK]\n", src_block, dst_block); + return 0; +} + +static int _sector_count(int argc, char **argv) +{ + (void)argc; + (void)argv; + + if ((card == NULL || !card->init_done)) { + printf("[Error] Card not initialized or not present, use init command\n"); + return -1; + } + + printf("available sectors on card: %" PRIu32 "\n", + (uint32_t)(sdmmc_get_capacity(card) / SDMMC_SDHC_BLOCK_SIZE)); + return 0; +} + +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 SCR", _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 }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + /* use the first SDMMC device by default */ + + card = sdmmc_get_dev(0); + + puts("SD Card SDMMC driver test application"); + + puts("insert SD Card and use 'init' command to set card to spi mode"); + puts("WARNING: using 'write' or 'copy' commands WILL overwrite data on your sd-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; +}