1122 lines
45 KiB
C
1122 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_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_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,
|
||
|
};
|