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;