diff --git a/application.c b/application.c index f3daa62..04560e3 100644 --- a/application.c +++ b/application.c @@ -44,6 +44,7 @@ #include "busvoodoo_onewire.h" // BusVoodoo 1-wire mode #include "busvoodoo_rs232.h" // BusVoodoo RS-232 mode #include "busvoodoo_rs485.h" // BusVoodoo RS-485/422 mode +#include "busvoodoo_sdio.h" // BusVoodoo SDIO mode /** all supported BusVoodoo modes */ static const struct busvoodoo_mode_t* busvoodoo_modes[] = { @@ -54,6 +55,7 @@ static const struct busvoodoo_mode_t* busvoodoo_modes[] = { &busvoodoo_onewire_mode, &busvoodoo_rs232_mode, &busvoodoo_rs485_mode, + &busvoodoo_sdio_mode, }; /** current BusVoodoo mode */ diff --git a/lib/busvoodoo_sdio.c b/lib/busvoodoo_sdio.c new file mode 100644 index 0000000..799e334 --- /dev/null +++ b/lib/busvoodoo_sdio.c @@ -0,0 +1,1124 @@ +/* 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, +}; diff --git a/lib/busvoodoo_sdio.h b/lib/busvoodoo_sdio.h new file mode 100644 index 0000000..1008162 --- /dev/null +++ b/lib/busvoodoo_sdio.h @@ -0,0 +1,23 @@ +/* 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 + */ + +/** SDIO mode interface definition */ +extern const struct busvoodoo_mode_t busvoodoo_sdio_mode;