2019-02-20 19:14:08 +01:00
/** BusVoodoo SDIO (MMC/eMMC) mode
* @ file
* @ author King Kévin < kingkevin @ cuvoodoo . info >
* @ date 2018
2021-03-31 14:06:10 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2019-02-20 19:14:08 +01:00
* @ 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 ,
} ;