/* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** BusVoodoo SDIO (MMC/eMMC) mode * @file * @author King Kévin * @date 2018 * @note peripherals used: SDIO * @implements JESD84-B50.1 Embedded Multi-Media Card (e•MMC) Electrical Standard (5.01) */ /* standard libraries */ #include // standard integer types #include // standard utilities #include // string utilities /* STM32 (including CM3) libraries */ #include // general purpose input output library #include // real-time control clock library #include // SDIO library /* own libraries */ #include "global.h" // board definitions #include "print.h" // printing utilities #include "menu.h" // menu definitions #include "busvoodoo_global.h" // BusVoodoo definitions #include "busvoodoo_oled.h" // OLED utilities #include "busvoodoo_sdio.h" // own definitions /** mode setup stage */ static enum busvoodoo_sdio_setting_t { BUSVOODOO_SDIO_SETTING_NONE, BUSVOODOO_SDIO_SETTING_PROTOCOL, BUSVOODOO_SDIO_SETTING_DATALINES, BUSVOODOO_SDIO_SETTING_FREQUENCY, BUSVOODOO_SDIO_SETTING_DRIVE, BUSVOODOO_SDIO_SETTING_DONE, } busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_NONE; /**< current mode setup stage */ /** SDIO protocols */ static enum busvoodoo_sdio_protocol_t { BUSVOODOO_SDIO_PROTOCOL_MMC = 1, BUSVOODOO_SDIO_PROTOCOL_SD = 2, } busvoodoo_sdio_protocol = BUSVOODOO_SDIO_PROTOCOL_MMC; /**< current SDIO protocol */ /** number of SDIO data lines (false = 1, true = 4) */ static bool busvoodoo_sdio_4dat = false; /** SDIO clock frequency * * the STM32F103 datasheet is not very clear on what the maximum CK frequency is. * the main features indicate that it can go up 48 MHz for the 8-bit mode. * SDIO_CK can only be set to 48 MHz when bypass is enabled and SDIOCLK drives directly SDIO_CK. * SDIOCLK is equal to HCLK and comes from AHB, which comes from the SYSCLK. * SYSCLK is max. 72 MHz, but since AHB is just an power of 2 prescaler, SYSCLK must be restricted to 48 MHz. * but if we look at the errata "2.21.6 No underrun detection with wrong data transmission", an underrun would not be detected if [3 x period(PCLK2) + 3 x period(SDIOCLK)] ≥ (32 / (BusWidth)) x period(SDIO_CK). * since PCLK2 is max AHB (i.e. SDIOCLK), 3/48 + 3/48 = 1/8 > 1/12 = 32/8 * 1/48, this issue would occur. * thus I have no idea how to have a 48 MHz for the 8-bit mode. * and how about the other modes? * * there is also the indication that "Frequenc(PCLK2) = 3/8 * Frequency(SDIO_CK)" * but having a fixed SDIO_CK makes little sense, particularly when it is settable and must be < 400 kHz during the identification phase. * * if the end I will only respect the errata advise and have "[3 x period(PCLK2) + 3 x period(SDIOCLK)] < (32 / (BusWidth)) x period(SDIO_CK)", with F_PCLK2 = 72 MHz, F_SDIOCLK = 72 MHz, BusWidth = 4, thus F_SDIO_CK < 24 MHz */ static uint16_t busvoodoo_sdio_freq = 280; /** pin drive mode (true = push-pull, false = open-drain) */ static bool busvoodoo_sdio_drive = true; /** if embedded pull-up resistors are used */ static bool busvoodoo_sdio_pullup = true; /** SDIO command types */ enum busvoodoo_sdio_command_type_t { BUSVOODOO_SDIO_COMMAND_TYPE_UNDEF, /**< undefined command type */ BUSVOODOO_SDIO_COMMAND_TYPE_BC, /**< broadcast command, no response */ BUSVOODOO_SDIO_COMMAND_TYPE_BCR, /**< broadcast command, with response */ BUSVOODOO_SDIO_COMMAND_TYPE_AC, /**< address command, no data transfer */ BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, /**< address data transfer command, with data transfer */ }; /** SDIO response types */ enum busvoodoo_sdio_response_type_t { BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF, /**< undefined response type */ BUSVOODOO_SDIO_RESPONSE_TYPE_NONE, /**< no response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R1, /**< R1 response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R1b, /**< R1b response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R2, /**< R2 response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R3, /**< R3 response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R4, /**< R4 response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R4b, /**< R4b response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R5, /**< R5 response */ BUSVOODOO_SDIO_RESPONSE_TYPE_R6, /**< R6 response */ }; /** SD/(e)MMC command description */ struct busvoodoo_sdio_command_desc_t { enum busvoodoo_sdio_command_type_t command_type; /**< command type */ enum busvoodoo_sdio_response_type_t response_type; /**< command reponse type */ const char* command_name; /**< command name/abbreviation */ }; /** (e)MMC response type expected from command index * @implements JESD84-B50.1 6.10.4 Detailed command description */ static const struct busvoodoo_sdio_command_desc_t busvoodoo_sdio_emmc_commands[] = { [0] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_BC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_NONE, .command_name = "GO_IDLE_STATE/GO_PRE_IDLE_STATE/BOOT_INITIATION", }, [1] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_BCR, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R3, .command_name = "SEND_OP_COND", }, [2] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_BCR, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R2, .command_name = "ALL_SEND_CID", }, [3] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SET_RELATIVE_ADDR", }, [4] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_BC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_NONE, .command_name = "SET_DSR", }, [5] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1b, .command_name = "SLEEP_AWAKE", }, [6] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1b, .command_name = "SWITCH", }, [7] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, // R1/R1b1 .command_name = "SELECT/DESELECT_CARD", }, [8] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SEND_EXT_CSD", }, [9] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R2, .command_name = "SEND_CSD", }, [10] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R2, .command_name = "SEND_CID", }, // CMD11 is obsolete [12] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, // R1/R1b4 .command_name = "STOP_TRANSMISSION", }, [13] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SEND_STATUS", }, [14] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "BUSTEST_R", }, [15] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_NONE, .command_name = "GO_INACTIVE_STATE", }, [16] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SET_BLOCKLEN", }, [17] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "READ_SINGLE_BLOCK", }, [18] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "READ_MULTIPLE_BLOCK", }, [19] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "BUSTEST_W", }, // CMD20 is obsolete [21] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SEND_TUNING_BLOCK", }, // CMD22 is reserved [23] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SET_BLOCK_COUNT", }, [24] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "WRITE_BLOCK", }, [25] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "WRITE_MULTIPLE_BLOCK", }, [26] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "PROGRAM_CID", }, [27] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "PROGRAM_CSD", }, [28] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1b, .command_name = "SET_WRITE_PROT", }, [29] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1b, .command_name = "CLR_WRITE_PROT", }, [30] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SEND_WRITE_PROT", }, [31] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SEND_WRITE_PROT_TYPE", }, // CMD32-CMD34 are reserved [35] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "ERASE_GROUP_START", }, [36] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "ERASE_GROUP_END", }, // CMD37 is reserved [38] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1b, .command_name = "ERASE", }, [39] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R4, .command_name = "FAST_IO", }, [40] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_BCR, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R5, .command_name = "GO_IRQ_STATE", }, // CMD41 is reserved [42] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "LOCK_UNLOCK", }, // CMD43-48 are reserved [49] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "SET_TIME", }, // CMD50-52 are reserved [53] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "PROTOCOL_RD", }, [54] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "PROTOCOL_WR", }, [55] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_AC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "APP_CMD", }, [56] = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_ADTC, .response_type = BUSVOODOO_SDIO_RESPONSE_TYPE_R1, .command_name = "GEN_CMD", }, // CMD57-59 are reserved // CMD60-63 are reserved for manufacturer }; /** to store the transferred 512-byte data block */ static uint32_t busvoodoo_sdio_data[512/sizeof(uint32_t)]; /** setup SDIO mode * @param[out] prefix terminal prompt prefix * @param[in] line terminal prompt line to configure mode * @return if setup is complete */ static bool busvoodoo_sdio_setup(char** prefix, const char* line) { bool complete = false; // is the setup complete if (NULL==line) { // first call busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_NONE; // re-start configuration } switch (busvoodoo_sdio_setting) { case BUSVOODOO_SDIO_SETTING_NONE: busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_PROTOCOL; printf("1) (e)MMC\n"); printf("2) SD\n"); snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "device type (1,2) [%u]", busvoodoo_sdio_protocol); *prefix = busvoodoo_global_string; // ask for setting break; case BUSVOODOO_SDIO_SETTING_PROTOCOL: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_DATALINES; // go to next setting } else if (1 == strlen(line)) { // setting provided uint8_t protocol = atoi(line); // parse setting if (BUSVOODOO_SDIO_PROTOCOL_MMC == protocol || BUSVOODOO_SDIO_PROTOCOL_SD == protocol) { // check setting busvoodoo_sdio_protocol = protocol; // remember setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_DATALINES; // go to next setting } } if (BUSVOODOO_SDIO_SETTING_DATALINES == busvoodoo_sdio_setting) { // if next setting snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "data lines (1,4) [%c]", busvoodoo_sdio_4dat ? '4' : '1'); *prefix = busvoodoo_global_string; // display next setting } break; case BUSVOODOO_SDIO_SETTING_DATALINES: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_FREQUENCY; // go to next setting } else if (1==strlen(line)) { // setting provided uint8_t datalines = atoi(line); // parse setting if (1 == datalines || 4 == datalines) { // check setting busvoodoo_sdio_4dat = (4 == datalines); // remember setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_FREQUENCY; // go to next setting } } if (BUSVOODOO_SDIO_SETTING_FREQUENCY == busvoodoo_sdio_setting) { // if next setting snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "maximum clock frequency (280-18000 kHz) [%u]", busvoodoo_sdio_freq); // prepare next setting *prefix = busvoodoo_global_string; // display next setting } break; case BUSVOODOO_SDIO_SETTING_FREQUENCY: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_DRIVE; // go to next setting } else { // setting provided uint16_t freq = atoi(line); // parse setting if (freq >= 280 && freq <= 18000) { // check setting busvoodoo_sdio_freq = freq; // remember setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_DRIVE; // go to next setting } } if (BUSVOODOO_SDIO_SETTING_DRIVE == busvoodoo_sdio_setting) { // if next setting printf("1) push-pull (3.3V)\n"); printf("2) open-drain, with embedded pull-up resistors (2kO)\n"); printf("3) open-drain, with external pull-up resistors\n"); snprintf(busvoodoo_global_string, LENGTH(busvoodoo_global_string), "drive mode (1,2,3) [%c]", busvoodoo_sdio_drive ? '1' : (busvoodoo_sdio_pullup ? '2' : '3')); // show drive mode *prefix = busvoodoo_global_string; // display next setting } break; case BUSVOODOO_SDIO_SETTING_DRIVE: if (NULL == line || 0 == strlen(line)) { // use default setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_DONE; // go to next setting } else if (1 == strlen(line)) { // setting provided uint8_t drive = atoi(line); // parse setting if (1 == drive || 2 == drive || 3 == drive) { // check setting busvoodoo_sdio_drive = (1 == drive); // remember setting busvoodoo_sdio_pullup = (2 == drive); // remember setting busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_DONE; // go to next setting } } if (BUSVOODOO_SDIO_SETTING_DONE == busvoodoo_sdio_setting) { // we have all settings, configure SPI // setup SDIO peripheral rcc_periph_clock_enable(RCC_AFIO); // enable clock for SDIO alternate function rcc_periph_reset_pulse(RCC_SDIO); // reset SDIO peripheral rcc_periph_clock_enable(RCC_SDIO); // enable clock for SDIO peripheral uint32_t clkcr = SDIO_CLKCR_CLKEN; // enable clock (disable clock when inactive doesn't work well) // set data bus width if (busvoodoo_sdio_4dat) { clkcr |= SDIO_CLKCR_WIDBUS_4; // use 4-wire bus mode } else { clkcr |= SDIO_CLKCR_WIDBUS_1; // use 1-wire bus mode } // set clock frequency, SDIOCLK = AHB clock (e.g. 72 MHz) uint32_t sdio_clock_div = (rcc_ahb_frequency / busvoodoo_sdio_freq) % 1000; // calculate remaining if (0 == sdio_clock_div) { // exact frequency match sdio_clock_div = (rcc_ahb_frequency / (busvoodoo_sdio_freq * 1000)) - 2; } else { // use closest lower frequency sdio_clock_div = (rcc_ahb_frequency / (busvoodoo_sdio_freq * 1000)) - 2 + 1; } if (sdio_clock_div > 255) { // enforce limit sdio_clock_div = 255; } clkcr |= sdio_clock_div & 0xff; SDIO_CLKCR = clkcr; // only write CLKCR once because it needs 7 HCLK cycles between writes // setup pins rcc_periph_clock_enable(RCC_GPIOC); // enable clock for port C domain rcc_periph_clock_enable(RCC_GPIOD); // enable clock for port D domain if (busvoodoo_sdio_drive) { // drive in push-pull mode // CMD and DATx lines are bi-directional, but start as output because host initiated, and changed by the SDIO when necessary gpio_set_mode(GPIOD, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2); // set CMD on PD2 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO12); // set CK on PC12 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO8); // set DAT0 on PC8 as output if (busvoodoo_sdio_4dat) { gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9); // set DAT1 on PC9 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10); // set DAT2 on PC10 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO11); // set DAT1 on PC11 as output } } else { // drive in open-drain mode // CMD and DATx lines are bi-directional, but start as output because host initiated, and changed by the SDIO when necessary gpio_set_mode(GPIOD, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO2); // set CMD on PD2 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO12); // set CK on PC12 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO8); // set DAT0 on PC8 as output if (busvoodoo_sdio_4dat) { gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO9); // set DAT1 on PC9 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO10); // set DAT2 on PC10 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO11); // set DAT1 on PC11 as output } } SDIO_POWER |= SDIO_POWER_PWRCTRL_PWRON; // power on, enable clock if (!busvoodoo_sdio_drive && busvoodoo_sdio_pullup) { busvoodoo_embedded_pullup(true); // set embedded pull-ups busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_WARNING); printf("use LV to set pull-up voltage\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); } busvoodoo_led_blue_off(); // disable blue LED because there is no activity busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_NONE; // restart settings next time *prefix = "SDIO"; // display mode busvoodoo_oled_text_left(*prefix); // set mode title on OLED display const char* pinout_io[10] = {"GND", "5V", "3V3", "LV", "D0", NULL, NULL, NULL, "CK", "CMD"}; // SDIO mode pinout if (busvoodoo_sdio_4dat) { // if all 4 data lines are used pinout_io[5] = "D1"; pinout_io[6] = "D2"; pinout_io[7] = "D3"; } for (uint8_t i=0; i= 64) { printf("command index %u too high\n", index); return false; } struct busvoodoo_sdio_command_desc_t command_desc = { .command_type = BUSVOODOO_SDIO_COMMAND_TYPE_UNDEF, .response_type = response_type, .command_name = NULL, }; if (index < LENGTH(busvoodoo_sdio_emmc_commands) && BUSVOODOO_SDIO_COMMAND_TYPE_UNDEF != busvoodoo_sdio_emmc_commands[index].command_type && BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF != busvoodoo_sdio_emmc_commands[index].response_type) { // predefined command exists if (BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF == command_desc.response_type) { command_desc.response_type = busvoodoo_sdio_emmc_commands[index].response_type; } if (busvoodoo_sdio_emmc_commands[index].response_type == command_desc.response_type) { command_desc.command_name = busvoodoo_sdio_emmc_commands[index].command_name; } } printf("CMD%u", index); if (command_desc.command_name) { printf(" (%s)", command_desc.command_name); } printf(" %+08x:", argument); SDIO_ICR = 0xc007ff; // clear all state flags (start clean) SDIO_ARG = argument; // write command argument uint16_t sdio_cmd = index; // build command, include index switch (command_desc.response_type) { case BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF: busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("response type must be defined\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; break; case BUSVOODOO_SDIO_RESPONSE_TYPE_NONE: // no response is expected break; case BUSVOODOO_SDIO_RESPONSE_TYPE_R1: case BUSVOODOO_SDIO_RESPONSE_TYPE_R1b: case BUSVOODOO_SDIO_RESPONSE_TYPE_R3: case BUSVOODOO_SDIO_RESPONSE_TYPE_R4: case BUSVOODOO_SDIO_RESPONSE_TYPE_R4b: case BUSVOODOO_SDIO_RESPONSE_TYPE_R5: case BUSVOODOO_SDIO_RESPONSE_TYPE_R6: sdio_cmd |= SDIO_CMD_WAITRESP_SHORT; // short response is expected break; case BUSVOODOO_SDIO_RESPONSE_TYPE_R2: sdio_cmd |= SDIO_CMD_WAITRESP_LONG; // long response is expected break; default: busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("unknown response type\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; break; } sdio_cmd |= SDIO_CMD_CPSMEN; // enable state machine printf(" rsp=%u, cmd=%04x ", command_desc.response_type, sdio_cmd); SDIO_CMD = sdio_cmd; // write index, this sends the command busvoodoo_led_blue_pulse(BUSVOODOO_LED_PULSE); // pulse blue LED to show we sent the command while ((SDIO_STA & SDIO_STA_CMDACT) || !(SDIO_STA & 0x7ff)); // wait until command is sent or error occurred if (SDIO_STA & (SDIO_STA_DCRCFAIL | SDIO_STA_CTIMEOUT | SDIO_STA_DTIMEOUT | SDIO_STA_TXUNDERR | SDIO_STA_RXOVERR | SDIO_STA_STBITERR)) { // error occurred busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); if (SDIO_STA & SDIO_STA_DCRCFAIL) { printf(" data block CRC check failed"); } if (SDIO_STA & SDIO_STA_CTIMEOUT) { printf(" command response timeout"); } if (SDIO_STA & SDIO_STA_DTIMEOUT) { printf(" data block timeout"); } if (SDIO_STA & SDIO_STA_TXUNDERR) { printf(" transmit FIFO underrun"); } if (SDIO_STA & SDIO_STA_RXOVERR) { printf(" receive FIFO overrun"); } if (SDIO_STA & SDIO_STA_STBITERR) { printf(" start bit not detected on data signals"); } printf("\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; } // check response if (BUSVOODOO_SDIO_RESPONSE_TYPE_NONE == command_desc.response_type) { if (SDIO_STA & SDIO_STA_CMDSENT) { printf(" sent\n"); } else { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf(" not sent\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; } } else { if (BUSVOODOO_SDIO_RESPONSE_TYPE_R3 || SDIO_STA & SDIO_STA_CMDREND) { // CMDREND is only set if the response is received and the CRC is ok, but R# for CMD1 has no CRC printf(" response received\n"); } else { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf(" no response received\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; } if ((BUSVOODOO_SDIO_RESPONSE_TYPE_R3 == command_desc.response_type)) { // R3 (for CMD1) uses a fixed pattern instead of CRC // the CRC is not saved in SDIO_RESP1 to check the pattern } else if (SDIO_STA & SDIO_STA_CCRCFAIL) { // all other responses include a CRC busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("response CRC check failed\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); //return false; } if ((BUSVOODOO_SDIO_RESPONSE_TYPE_R2 == command_desc.response_type) || (BUSVOODOO_SDIO_RESPONSE_TYPE_R3 == command_desc.response_type)) { // R2 and R3 don't include the command index but a fixed pattern if ((SDIO_RESPCMD & 0x3f) != 0x3f) { // verify check bits busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("response command index pattern malformed (expected 3f, got %02x)\n", (SDIO_RESPCMD & 0x3f)); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; } } else if ((SDIO_RESPCMD & 0x3f) != index) { // all other response should have a valid command index and CRC busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("response command index does not match command (expected %u, got %u)\n", index, (SDIO_RESPCMD & 0x3f)); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; } // display response printf("response: %08x", SDIO_RESP1); if (BUSVOODOO_SDIO_RESPONSE_TYPE_R2 == command_desc.response_type) { // long response received printf(" %08x %08x %08x", SDIO_RESP2, SDIO_RESP3, SDIO_RESP4); } printf("\n"); } return true; } /** issue command and read data * @param[in] index command index * @param[in] argument command argument * @return if command succeeded (false if error occurred) */ static bool busvoodoo_sdio_data_read(uint16_t index, uint32_t argument) { SDIO_DTIMER = 200000; // set data transfer timeout to 0.5 seconds (200000 clock cycles) SDIO_DLEN = 512; // set data length to one 512-byte block/sector (the default block size) SDIO_DCTRL = SDIO_DCTRL_DBLOCKSIZE_9 | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN; // start reading (block size: 2^9=512 bytes; direction: from card to controller; enable data transfer) if (!busvoodoo_sdio_command_response(index, argument, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send command to read data block return false; } uint32_t r1 = SDIO_RESP1; // R1 response if (r1 & 0x100) { // device is ready for data } else { return false; } if (r1 & 0xfff90000) { // an error occurred return false; } uint8_t fifo_word = 0; while ((SDIO_STA & SDIO_STA_RXACT) && (fifo_word 400) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_INFO); printf("lowering clock frequency for identification\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); SDIO_CLKCR &= ~SDIO_CLKCR_CLKEN; // disable clock for (volatile uint8_t i = 0; i < 7; i++); // wait for at least 7 HCLK cycles before rewriting SDIO_CLKCR SDIO_CLKCR |= (SDIO_CLKCR_CLKEN | 0xff); // set cock to 280 kHz } if (busvoodoo_sdio_command_response(0, 0, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send CMD0 (GO_IDLE_STATE) to put card to idle state } else { // could not send command busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("could not put device in idle state\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); goto end; } bool busy = true; // if card is in busy mode while (busy) { // TODO add max tries busy = false; if (busvoodoo_sdio_command_response(1, 0x40ff8000, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send CMD1 (SEND_OP_COND) with OCR (support sector and 3.3V) to put card to ready state and get OCR uint32_t ocr = SDIO_RESP1; if (0 == (ocr & 0x80000000)) { // card busy busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_INFO); printf("card busy\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); busy = true; } else if (0x00ff8000 != (ocr & 0x1fffff7f)) { // invalid OCR (voltage not supported) busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("voltage not supported\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); goto end; } else { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_INFO); printf("card ready\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); } } else { // could not send command goto end; } } uint16_t device_count = 0; // the device relative address to be set while (busvoodoo_sdio_command_response(2, 0, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send CMD2 (ALL_SEND_CID) to put card in identification state printf("Card IDentification (CID):\n"); uint8_t cid_mid = (SDIO_RESP1 >> 24) & 0xff; char* name = NULL; switch (cid_mid) { case 0x11: name = "Toshiba"; break; case 0x15: name = "Samsung"; break; case 0x45: name = "SanDisk"; break; case 0x9d: name = "ISSI"; break; case 0xfe: name = "Micron"; break; // find out list // if there is no list, find for Panasonic, BIG-INNO, Kingston, ... default: name = "unknown, please contact BusVoodoo author"; break; } printf("- manufacturer ID: %u (%s)\n", cid_mid, name); uint8_t cid_cbx = (SDIO_RESP1 >> 16) & 0x3; static const char* cbxs[] = {"removable", "BGA", "POP", "reserved"}; printf("- device type: %s\n", cbxs[cid_cbx]); uint8_t cid_oid = (SDIO_RESP1 >> 8) & 0xff; printf("- OEM/application ID: %u\n", cid_oid); char cid_pnm[] = {(SDIO_RESP1 >> 0), (SDIO_RESP2 >> 24), (SDIO_RESP2 >> 16), (SDIO_RESP2 >> 8), (SDIO_RESP2 >> 0), (SDIO_RESP3 >> 24), 0}; printf("- product name: %s\n", cid_pnm); uint8_t cid_prv = (SDIO_RESP3 >> 16) & 0xff; printf("- product version: %u\n", cid_prv); uint32_t cid_psn = ((SDIO_RESP3 >> 0) & 0xffff) | ((SDIO_RESP3 >> 16) & 0xffff); printf("- product serial number: %+08x\n", cid_psn); uint8_t cid_mdt = (SDIO_RESP3 >> 8) & 0xff; static const char* months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; printf("- manufacturing date: %s %u/%u\n", months[((cid_mdt >> 4) - 1) % LENGTH(months)], (cid_mdt & 0xf) + 1997, (cid_mdt & 0xf) + 2013); if (busvoodoo_sdio_command_response(3, (++device_count) << 16, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send CMD3 (SET_RELATIVE_ADDR) to set RCA address 0x0001 and switch to data transfer mode busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_INFO); printf("device RCA set to %u\n", device_count); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); } else { // could not complete command goto end; } } printf("found %u device(s)\n", device_count); if (0 == device_count) { goto end; } // identification is finished, switch back from open-drain to push-pull mode if (busvoodoo_sdio_drive) { // got back in push-pull mode busvoodoo_embedded_pullup(false); // disable embedded pull-ups gpio_set_mode(GPIOD, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2); // set CMD on PD2 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO12); // set CK on PC12 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO8); // set DAT0 on PC8 as output if (busvoodoo_sdio_4dat) { gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9); // set DAT1 on PC9 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10); // set DAT2 on PC10 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO11); // set DAT1 on PC11 as output } busvoodoo_lv_set(lv_backup); // restore output voltage } // read CSD from all devices for (uint16_t device = 0; device < device_count; device++) { // got through each device if (busvoodoo_sdio_command_response(9, (device+1) << 16, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send CMD9 (SEND_CSD) to get device specific CSD printf("device %u Card Specific Data (CSD):\n", device); uint8_t csd_structure = (SDIO_RESP1 >> 30) & 0x3; static const char* csd_structures[] = {"1.0", "1.1", "1.2", "see EXT_CSD"}; printf("- CSD structure: %s\n", csd_structures[csd_structure]); uint8_t csd_spec_vers = (SDIO_RESP1 >> 26) & 0xf; char* name = NULL; if (csd_spec_vers < 4) { name = "MMCA"; } else if (4 == csd_spec_vers) { name = "4.1-5.01"; } else { name = "reserved"; } printf("- specification version: %s\n", name); uint8_t csd_taac = (SDIO_RESP1 >> 16) & 0xff; static const float taac_units[] = {1E-6, 10E-6, 100E-6, 1E-3, 10E-3, 100E-3, 1E0, 10E0}; // in ms static const float taac_factors[] = {0.0, 1.0, 1.2, 1.3, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 7.0, 8.0}; printf("- data read access time 1 (TAAC): %.3f ms\n", taac_units[csd_taac & 0x7] * taac_factors[(csd_taac >> 3) & 0xf]); uint8_t csd_nsac = (SDIO_RESP1 >> 8) & 0xff; printf("- data read access time 2 (NSAC): %u clock cycles\n", csd_nsac * 100); uint8_t csd_tran_speed = (SDIO_RESP1 >> 0) & 0xff; static const uint32_t tran_speeds[] = {100, 1000 , 10000, 100000}; // in kHz uint32_t tran_speed = tran_speeds[csd_tran_speed & 0x3] * taac_factors[(csd_tran_speed >> 3) & 0xf]; // in kHz printf("- max. bus clock frequency: %.2f MHz\n", tran_speed/100.0); uint8_t csd_ccc = (SDIO_RESP2 >> 20) & 0xfff; printf("- supported command classes:"); for (uint8_t b = 0; b < 12; b++) { if (csd_ccc & (1 << b)) { printf(" %u", b); } } printf("\n"); uint8_t csd_read_bl_len = (SDIO_RESP2 >> 16) & 0xf; printf("- max. read data block length: %u bytes\n", 1 << csd_read_bl_len); uint16_t csd_c_size = (((SDIO_RESP2 >> 0) & 0x3ff) << 2) | ((SDIO_RESP3 >> 30) & 0x3); uint8_t csd_c_size_mult = (SDIO_RESP3 >> 15) & 0x7; printf("- device size: "); if (0xfff == csd_c_size) { printf("> 2 GB\n"); } else { printf("%u bytes\n", (csd_c_size + 1) * (1 << (csd_c_size_mult + 2)) * (1 << csd_read_bl_len)); } if (tran_speed < 280) { // set lowest supported limit tran_speed = 280; } if (freq_max > tran_speed) { freq_max = tran_speed; busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_INFO); printf("lowering clock frequency to %u kHz\n", freq_max); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); } } else { // could not complete command goto end; } } goto end; // select eMMC if (busvoodoo_sdio_command_response(7, 1 << 16, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF)) { // send CMD7 (SELECT_CARD) to put device from static to transfer state } else { // could not complete command goto end; } if (SDIO_RESP1 & 0x100) { // device is ready for data } else { goto end; } // get size of card (in extended CSD since it's > 2GB) if (busvoodoo_sdio_data_read(8, 0)) { // send CMD8 (SEND_EXT_CSD) } else { // could not complete command } if (SDIO_RESP1) { // device is ready for data } else { goto end; } uint32_t emmc_sec_count = busvoodoo_sdio_data[212 / sizeof(busvoodoo_sdio_data[0])]; // saved SEC_COUNT on bytes [215:212] printf("sector count: %lu (of 512-byte)\n", emmc_sec_count); printf("device density: %llu bytes\n", emmc_sec_count * 512ULL); // TODO set data width // TODO set speed/frequency end: if (busvoodoo_sdio_drive) { // got back in push-pull mode busvoodoo_embedded_pullup(false); // disable embedded pull-ups gpio_set_mode(GPIOD, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO2); // set CMD on PD2 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO12); // set CK on PC12 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO8); // set DAT0 on PC8 as output if (busvoodoo_sdio_4dat) { gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO9); // set DAT1 on PC9 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO10); // set DAT2 on PC10 as output gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO11); // set DAT1 on PC11 as output } busvoodoo_lv_set(lv_backup); // restore output voltage } // set max frequency if (freq_max < 280) { // set lower bound freq_max = 280; } if (freq_max < busvoodoo_sdio_freq) { SDIO_CLKCR &= ~SDIO_CLKCR_CLKEN; // disable clock for (volatile uint8_t i = 0; i < 7; i++); // wait for at least 7 HCLK cycles before rewriting SDIO_CLKCR uint32_t sdio_clock_div = (rcc_ahb_frequency / freq_max) % 1000; // calculate remaining if (0 == sdio_clock_div) { // exact frequency match sdio_clock_div = (rcc_ahb_frequency/(freq_max * 1000)) - 2; } else { // use closest lower frequency sdio_clock_div = (rcc_ahb_frequency/(freq_max * 1000)) - 2 + 1; } if (sdio_clock_div > 255) { // enforce limit sdio_clock_div = 255; } SDIO_CLKCR = (SDIO_CLKCR_CLKEN | (SDIO_CLKCR & ~0xff) | (sdio_clock_div & 0xff)); } } /** read data at address from (e)MMC * @param[in] address address or sector of data to read * @return if read succeeded */ static bool busvoodoo_sdio_emmc_data_read(uint32_t address) { return busvoodoo_sdio_data_read(17, address); // send CMD17 (READ_SINGLE_BLOCK) to read block } /** check if the lines are pulled up when applicable * @return if lines are pulled up */ static bool busvoodoo_sdio_check_pullup(void) { if (!busvoodoo_sdio_drive && busvoodoo_vreg_get(BUSVOODOO_LV_CHANNEL) < 1.6) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); if (busvoodoo_sdio_pullup) { printf("set LV voltage or apply external voltage to pull lines up\n"); } else { printf("lines are not pulled up\n"); } busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return false; } return true; } // command handlers /** command to perform identification phase of (e)MMC/SD * @param[in] argument argument not used */ static void busvoodoo_sdio_command_identify(void* argument) { (void)argument; if (!busvoodoo_sdio_check_pullup()) { return; } switch (busvoodoo_sdio_protocol) { case BUSVOODOO_SDIO_PROTOCOL_MMC: busvoodoo_emmc_identification_mode(); break; case BUSVOODOO_SDIO_PROTOCOL_SD: default: busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("initialisation not available for this protocol\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); break; } } /** command to read data from (e)MMC/SD * @param[in] argument address of data/sector to read */ static void busvoodoo_sdio_command_read(void* argument) { if (!argument) { printf("address required\n"); return; } switch (busvoodoo_sdio_protocol) { case BUSVOODOO_SDIO_PROTOCOL_MMC: busvoodoo_sdio_emmc_data_read(*(uint32_t*)argument); break; case BUSVOODOO_SDIO_PROTOCOL_SD: default: busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("read not available for this protocol\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); break; } } /** command to send a command * @param[in] argument command index and argument */ static void busvoodoo_sdio_command_command(void* argument) { if (!argument || 0 == strlen(argument)) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("command index and argument required\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return; } switch (busvoodoo_sdio_protocol) { case BUSVOODOO_SDIO_PROTOCOL_MMC: break; case BUSVOODOO_SDIO_PROTOCOL_SD: default: busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("read not available for this protocol\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return; } // get command index const char* delimiter = " "; char* word = strtok(argument, delimiter); if (!word) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("command index required\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return; } uint8_t cmd_index = strtol(word, NULL, 0); if (cmd_index > 63) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("index must be between 0 and 63\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return; } if (cmd_index >= LENGTH(busvoodoo_sdio_emmc_commands) || BUSVOODOO_SDIO_COMMAND_TYPE_UNDEF == busvoodoo_sdio_emmc_commands[cmd_index].command_type || BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF == busvoodoo_sdio_emmc_commands[cmd_index].response_type) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("unknown command index %u\n", cmd_index); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return; } // get command argument word = strtok(NULL, delimiter); if (!word) { busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_ERROR); printf("command argument required\n"); busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET); return; } uint32_t cmd_argument = strtol(word, NULL, 0); // send command busvoodoo_sdio_command_response(cmd_index, cmd_argument, BUSVOODOO_SDIO_RESPONSE_TYPE_UNDEF); } /** SDIO menu commands */ static const struct menu_command_t busvoodoo_sdio_commands[] = { { .shortcut = 'i', .name = "init", .command_description = "identify and initialize (e)MMC/SD", .argument = MENU_ARGUMENT_NONE, .argument_description = NULL, .command_handler = &busvoodoo_sdio_command_identify, }, { .shortcut = 'c', .name = "cmd", .command_description = "send command", .argument = MENU_ARGUMENT_STRING, .argument_description = "index argument", .command_handler = &busvoodoo_sdio_command_command, }, { .shortcut = 'r', .name = "r", .command_description = "read sector", .argument = MENU_ARGUMENT_UNSIGNED, .argument_description = "address", .command_handler = &busvoodoo_sdio_command_read, }, }; const struct busvoodoo_mode_t busvoodoo_sdio_mode = { .name = "sdio", .description = "(e)MMC/SD", .full_only = false, .setup = &busvoodoo_sdio_setup, .commands = busvoodoo_sdio_commands, .commands_nb = LENGTH(busvoodoo_sdio_commands), .exit = &busvoodoo_sdio_exit, };