stm32f1/lib/busvoodoo_sdio.c

1125 lines
45 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*
*/
/** BusVoodoo SDIO (MMC/eMMC) mode
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @date 2018
* @note peripherals used: SDIO
* @implements JESD84-B50.1 Embedded Multi-Media Card (e•MMC) Electrical Standard (5.01)
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // standard utilities
#include <string.h> // string utilities
/* STM32 (including CM3) libraries */
#include <libopencm3/stm32/gpio.h> // general purpose input output library
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/sdio.h> // 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<LENGTH(pinout_io) && i<LENGTH(busvoodoo_global_pinout_io); i++) {
busvoodoo_global_pinout_io[i] = pinout_io[i]; // set pin names
}
if (busvoodoo_full) {
const char* pinout_rscan[5] = {"HV", NULL, NULL, NULL, NULL}; // HiZ mode RS/CAN pinout
for (uint8_t i=0; i<LENGTH(pinout_rscan) && i<LENGTH(busvoodoo_global_pinout_rscan); i++) {
busvoodoo_global_pinout_rscan[i] = pinout_rscan[i]; // set pin names
}
}
busvoodoo_oled_text_pinout(pinout_io, true); // set pinout on display
busvoodoo_oled_update(); // update display to show text and pinout
complete = true; // configuration is complete
}
break;
default: // unknown case
busvoodoo_sdio_setting = BUSVOODOO_SDIO_SETTING_NONE; // restart settings next time
break;
}
return complete;
}
/** exit SDIO mode
*/
static void busvoodoo_sdio_exit(void)
{
SDIO_POWER &= ~SDIO_POWER_PWRCTRL_MASK; // power off/disable clock
rcc_periph_reset_pulse(RCC_SDIO); // reset SDIO peripheral
rcc_periph_clock_disable(RCC_SDIO); // disable clock for SDIO peripheral
gpio_set_mode(GPIOD, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO2); // set CMD back to floating mode
gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO12); // set CK back to floating input
gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO8); // set DAT0 back to floating input
gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO9); // set DAT1 back to floating input
gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO10); // set DAT2 back to floating input
gpio_set_mode(GPIOC, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO11); // set DAT1 back to floating input
busvoodoo_embedded_pullup(false); // disable embedded pull-ups
}
/** issue command and read response
* @param[in] index command index
* @param[in] argument command argument
* @param[in] response_type response_type to expect (UNDEF to use known command responses)
* @return if command succeeded (false if error occurred)
* @note the response is stored in busvoodoo_sdio_response
*/
static bool busvoodoo_sdio_command_response(uint8_t index, uint32_t argument, enum busvoodoo_sdio_response_type_t response_type)
{
// find predefined command matching to index
if (index >= 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<LENGTH(busvoodoo_sdio_data))) { // read data while there is receive activity
while ((SDIO_STA & SDIO_STA_RXDAVL) && (fifo_word<LENGTH(busvoodoo_sdio_data))) { // read data if data has been received
busvoodoo_sdio_data[fifo_word++] = SDIO_FIFO; // save data
}
}
while(!(SDIO_STA & SDIO_STA_DATAEND) && !(SDIO_STA & SDIO_STA_DTIMEOUT)); // wait until transfer is finished (including CRC), or timeout occurs
if (!(SDIO_STA & SDIO_STA_DATAEND) || (SDIO_STA & SDIO_STA_DCRCFAIL) || !(SDIO_STA & SDIO_STA_DBCKEND)) { // data transfer failed (end not reached of CRC error)
printf("transfer failed\n");
return false;
}
return true;
}
/** perform (e)MMC identification
* @implements JESD84-B50.1 6.4 Device identification mode
*/
static void busvoodoo_emmc_identification_mode(void)
{
uint16_t freq_max = busvoodoo_sdio_freq; // maximum frequency supported by all cards
float lv_backup = 0; // backup voltage setting before pulling up
// in identification mode the lines are open-drain until all cards are identified
if (busvoodoo_sdio_drive) {
busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_WARNING);
printf("lines will be pulled up using LV for identification\n");
busvoodoo_text_style(BUSVOODOO_TEXT_STYLE_RESET);
busvoodoo_lv_set(3.3);
busvoodoo_embedded_pullup(true); // set embedded pull-ups
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
}
lv_backup = busvoodoo_vreg_get(BUSVOODOO_LV_CHANNEL);
}
// the max freq for identification is 400 kHz
if (busvoodoo_sdio_freq > 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,
};