From ee600784c539346fce8bf400b178f3a422b8d705 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 6 Mar 2018 17:57:52 +0800 Subject: [PATCH] sdmmc: add SDIO support - Add SDIO support at protocol layer (probing, data transfer, interrupts) - Add SDIO interrupts support in SDMMC host - Add test (communicate with ESP32 in SDIO download mode) --- components/driver/include/driver/sdmmc_defs.h | 82 +++ components/driver/include/driver/sdmmc_host.h | 22 + .../driver/include/driver/sdmmc_types.h | 11 +- components/driver/include/driver/sdspi_host.h | 2 + components/driver/sdmmc_host.c | 100 ++- components/sdmmc/include/sdmmc_cmd.h | 158 ++++- components/sdmmc/sdmmc_cmd.c | 643 +++++++++++++----- components/sdmmc/test/test_sdio.c | 382 +++++++++++ components/soc/esp32/include/soc/sdmmc_reg.h | 2 + 9 files changed, 1217 insertions(+), 185 deletions(-) create mode 100644 components/sdmmc/test/test_sdio.c diff --git a/components/driver/include/driver/sdmmc_defs.h b/components/driver/include/driver/sdmmc_defs.h index 96e0743d5f..b9b13680dd 100644 --- a/components/driver/include/driver/sdmmc_defs.h +++ b/components/driver/include/driver/sdmmc_defs.h @@ -54,6 +54,12 @@ #define SD_APP_OP_COND 41 /* R3 */ #define SD_APP_SEND_SCR 51 /* R1 */ +/* SD IO commands */ +#define SD_IO_SEND_OP_COND 5 /* R4 */ +#define SD_IO_RW_DIRECT 52 /* R5 */ +#define SD_IO_RW_EXTENDED 53 /* R5 */ + + /* OCR bits */ #define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */ #define MMC_OCR_ACCESS_MODE_MASK 0x60000000 /* bits 30:29 */ @@ -97,6 +103,8 @@ /* 48-bit response decoding (32 bits w/o CRC) */ #define MMC_R1(resp) ((resp)[0]) #define MMC_R3(resp) ((resp)[0]) +#define MMC_R4(resp) ((resp)[0]) +#define MMC_R5(resp) ((resp)[0]) #define SD_R6(resp) ((resp)[0]) #define MMC_R1_CURRENT_STATE(resp) (((resp)[0] >> 9) & 0xf) @@ -364,4 +372,78 @@ static inline uint32_t MMC_RSP_BITS(uint32_t *src, int start, int len) return (left | right) & mask; } +/* SD R4 response (IO OCR) */ +#define SD_IO_OCR_MEM_READY (1<<31) +#define SD_IO_OCR_NUM_FUNCTIONS(ocr) (((ocr) >> 28) & 0x7) +#define SD_IO_OCR_MEM_PRESENT (1<<27) +#define SD_IO_OCR_MASK 0x00fffff0 + +/* CMD52 arguments */ +#define SD_ARG_CMD52_READ (0<<31) +#define SD_ARG_CMD52_WRITE (1<<31) +#define SD_ARG_CMD52_FUNC_SHIFT 28 +#define SD_ARG_CMD52_FUNC_MASK 0x7 +#define SD_ARG_CMD52_EXCHANGE (1<<27) +#define SD_ARG_CMD52_REG_SHIFT 9 +#define SD_ARG_CMD52_REG_MASK 0x1ffff +#define SD_ARG_CMD52_DATA_SHIFT 0 +#define SD_ARG_CMD52_DATA_MASK 0xff +#define SD_R5_DATA(resp) ((resp)[0] & 0xff) + +/* CMD53 arguments */ +#define SD_ARG_CMD53_READ (0<<31) +#define SD_ARG_CMD53_WRITE (1<<31) +#define SD_ARG_CMD53_FUNC_SHIFT 28 +#define SD_ARG_CMD53_FUNC_MASK 0x7 +#define SD_ARG_CMD53_BLOCK_MODE (1<<27) +#define SD_ARG_CMD53_INCREMENT (1<<26) +#define SD_ARG_CMD53_REG_SHIFT 9 +#define SD_ARG_CMD53_REG_MASK 0x1ffff +#define SD_ARG_CMD53_LENGTH_SHIFT 0 +#define SD_ARG_CMD53_LENGTH_MASK 0x1ff +#define SD_ARG_CMD53_LENGTH_MAX 512 + +/* Card Common Control Registers (CCCR) */ +#define SD_IO_CCCR_START 0x00000 +#define SD_IO_CCCR_SIZE 0x100 +#define SD_IO_CCCR_FN_ENABLE 0x02 +#define SD_IO_CCCR_FN_READY 0x03 +#define SD_IO_CCCR_INT_ENABLE 0x04 +#define SD_IO_CCCR_INT_PENDING 0x05 +#define SD_IO_CCCR_CTL 0x06 +#define CCCR_CTL_RES (1<<3) +#define SD_IO_CCCR_BUS_WIDTH 0x07 +#define CCCR_BUS_WIDTH_1 (0<<0) +#define CCCR_BUS_WIDTH_4 (2<<0) +#define CCCR_BUS_WIDTH_8 (3<<0) +#define SD_IO_CCCR_CARD_CAP 0x08 +#define CCCR_CARD_CAP_LSC BIT(6) +#define CCCR_CARD_CAP_4BLS BIT(7) +#define SD_IO_CCCR_CISPTR 0x09 +#define SD_IO_CCCR_BLKSIZEL 0x10 +#define SD_IO_CCCR_BLKSIZEH 0x11 +#define SD_IO_CCCR_HIGHSPEED 0x13 +#define CCCR_HIGHSPEED_SUPPORT BIT(0) +#define CCCR_HIGHSPEED_ENABLE BIT(1) + +/* Function Basic Registers (FBR) */ +#define SD_IO_FBR_START 0x00100 +#define SD_IO_FBR_SIZE 0x00700 + +/* Card Information Structure (CIS) */ +#define SD_IO_CIS_START 0x01000 +#define SD_IO_CIS_SIZE 0x17000 + +/* CIS tuple codes (based on PC Card 16) */ +#define SD_IO_CISTPL_NULL 0x00 +#define SD_IO_CISTPL_VERS_1 0x15 +#define SD_IO_CISTPL_MANFID 0x20 +#define SD_IO_CISTPL_FUNCID 0x21 +#define SD_IO_CISTPL_FUNCE 0x22 +#define SD_IO_CISTPL_END 0xff + +/* CISTPL_FUNCID codes */ +#define TPLFID_FUNCTION_SDIO 0x0c + + #endif //_SDMMC_DEFS_H_ diff --git a/components/driver/include/driver/sdmmc_host.h b/components/driver/include/driver/sdmmc_host.h index c298889ed3..aa3357a736 100644 --- a/components/driver/include/driver/sdmmc_host.h +++ b/components/driver/include/driver/sdmmc_host.h @@ -44,6 +44,8 @@ extern "C" { .do_transaction = &sdmmc_host_do_transaction, \ .deinit = &sdmmc_host_deinit, \ .command_timeout_ms = 0, \ + .io_int_enable = sdmmc_host_io_int_enable, \ + .io_int_wait = sdmmc_host_io_int_wait, \ } /** @@ -166,6 +168,26 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz); */ esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo); +/** + * @brief Enable IO interrupts + * + * This function configures the host to accept SDIO interrupts. + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @return returns ESP_OK, other errors possible in the future + */ +esp_err_t sdmmc_host_io_int_enable(int slot); + +/** + * @brief Block until an SDIO interrupt is received, or timeout occurs + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param timeout_ticks number of RTOS ticks to wait for the interrupt + * @return + * - ESP_OK on success (interrupt received) + * - ESP_ERR_TIMEOUT if the interrupt did not occur within timeout_ticks + */ +esp_err_t sdmmc_host_io_int_wait(int slot, TickType_t timeout_ticks); + /** * @brief Disable SDMMC host and release allocated resources * diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h index cece4174ef..99e9715762 100644 --- a/components/driver/include/driver/sdmmc_types.h +++ b/components/driver/include/driver/sdmmc_types.h @@ -21,6 +21,7 @@ #include #include #include "esp_err.h" +#include "freertos/FreeRTOS.h" /** * Decoded values from SD card Card Specific Data register @@ -78,6 +79,7 @@ typedef struct { size_t datalen; /*!< length of data buffer */ size_t blklen; /*!< block length */ int flags; /*!< see below */ +/** @cond */ #define SCF_ITSDONE 0x0001 /*!< command is complete */ #define SCF_CMD(flags) ((flags) & 0x00f0) #define SCF_CMD_AC 0x0000 @@ -101,6 +103,7 @@ typedef struct { #define SCF_RSP_R5B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY) #define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) #define SCF_RSP_R7 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +/** @endcond */ esp_err_t error; /*!< error returned from transfer */ int timeout_ms; /*!< response timeout, in milliseconds */ } sdmmc_command_t; @@ -129,6 +132,8 @@ typedef struct { esp_err_t (*set_card_clk)(int slot, uint32_t freq_khz); /*!< host function to set card clock frequency */ esp_err_t (*do_transaction)(int slot, sdmmc_command_t* cmdinfo); /*!< host function to do a transaction */ esp_err_t (*deinit)(void); /*!< host function to deinitialize the driver */ + esp_err_t (*io_int_enable)(int slot); /*!< Host function to enable SDIO interrupt line */ + esp_err_t (*io_int_wait)(int slot, TickType_t timeout_ticks); /*!< Host function to wait for SDIO interrupt line to be active */ int command_timeout_ms; /*!< timeout, in milliseconds, of a single command. Set to 0 to use the default value. */ } sdmmc_host_t; @@ -142,9 +147,11 @@ typedef struct { sdmmc_csd_t csd; /*!< decoded CSD (Card-Specific Data) register value */ sdmmc_scr_t scr; /*!< decoded SCR (SD card Configuration Register) value */ uint16_t rca; /*!< RCA (Relative Card Address) */ + uint32_t is_mem : 1; /*!< Bit indicates if the card is a memory card */ + uint32_t is_sdio : 1; /*!< Bit indicates if the card is an IO card */ + uint32_t num_io_functions : 3; /*!< If is_sdio is 1, contains the number of IO functions on the card */ + uint32_t reserved : 27; /*!< Reserved for future expansion */ } sdmmc_card_t; - - #endif // _SDMMC_TYPES_H_ diff --git a/components/driver/include/driver/sdspi_host.h b/components/driver/include/driver/sdspi_host.h index 54eba081e9..9dad6cf7a7 100644 --- a/components/driver/include/driver/sdspi_host.h +++ b/components/driver/include/driver/sdspi_host.h @@ -43,6 +43,8 @@ extern "C" { .set_card_clk = &sdspi_host_set_card_clk, \ .do_transaction = &sdspi_host_do_transaction, \ .deinit = &sdspi_host_deinit, \ + .io_int_enable = NULL, \ + .io_int_wait = NULL, \ .command_timeout_ms = 0, \ } diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c index e33da77501..f09d94d585 100644 --- a/components/driver/sdmmc_host.c +++ b/components/driver/sdmmc_host.c @@ -26,6 +26,7 @@ #include "driver/sdmmc_host.h" #include "driver/periph_ctrl.h" #include "sdmmc_private.h" +#include "freertos/semphr.h" #define SDMMC_EVENT_QUEUE_LENGTH 32 @@ -40,9 +41,11 @@ typedef struct { uint32_t d5; uint32_t d6; uint32_t d7; + uint8_t d1_gpio; uint8_t d3_gpio; uint8_t card_detect; uint8_t write_protect; + uint8_t card_int; uint8_t width; } sdmmc_slot_info_t; @@ -58,6 +61,7 @@ static const sdmmc_slot_info_t s_slot_info[2] = { .d1 = PERIPHS_IO_MUX_SD_DATA1_U, .d2 = PERIPHS_IO_MUX_SD_DATA2_U, .d3 = PERIPHS_IO_MUX_SD_DATA3_U, + .d1_gpio = 8, .d3_gpio = 10, .d4 = PERIPHS_IO_MUX_GPIO16_U, .d5 = PERIPHS_IO_MUX_GPIO17_U, @@ -65,6 +69,7 @@ static const sdmmc_slot_info_t s_slot_info[2] = { .d7 = PERIPHS_IO_MUX_GPIO18_U, .card_detect = HOST_CARD_DETECT_N_1_IDX, .write_protect = HOST_CARD_WRITE_PRT_1_IDX, + .card_int = HOST_CARD_INT_N_1_IDX, .width = 8 }, { @@ -74,9 +79,11 @@ static const sdmmc_slot_info_t s_slot_info[2] = { .d1 = PERIPHS_IO_MUX_GPIO4_U, .d2 = PERIPHS_IO_MUX_MTDI_U, .d3 = PERIPHS_IO_MUX_MTCK_U, + .d1_gpio = 4, .d3_gpio = 13, .card_detect = HOST_CARD_DETECT_N_2_IDX, .write_protect = HOST_CARD_WRITE_PRT_2_IDX, + .card_int = HOST_CARD_INT_N_2_IDX, .width = 4 } }; @@ -84,6 +91,7 @@ static const sdmmc_slot_info_t s_slot_info[2] = { static const char* TAG = "sdmmc_periph"; static intr_handle_t s_intr_handle; static QueueHandle_t s_event_queue; +static SemaphoreHandle_t s_io_intr_event; size_t s_slot_width[2] = {1,1}; @@ -282,11 +290,19 @@ esp_err_t sdmmc_host_init() if (!s_event_queue) { return ESP_ERR_NO_MEM; } + s_io_intr_event = xSemaphoreCreateBinary(); + if (!s_io_intr_event) { + vQueueDelete(s_event_queue); + s_event_queue = NULL; + return ESP_ERR_NO_MEM; + } // Attach interrupt handler esp_err_t ret = esp_intr_alloc(ETS_SDIO_HOST_INTR_SOURCE, 0, &sdmmc_isr, s_event_queue, &s_intr_handle); if (ret != ESP_OK) { vQueueDelete(s_event_queue); s_event_queue = NULL; + vSemaphoreDelete(s_io_intr_event); + s_io_intr_event = NULL; return ret; } // Enable interrupts @@ -297,7 +313,7 @@ esp_err_t sdmmc_host_init() SDMMC_INTMASK_RCRC | SDMMC_INTMASK_DCRC | SDMMC_INTMASK_RTO | SDMMC_INTMASK_DTO | SDMMC_INTMASK_HTO | SDMMC_INTMASK_SBE | SDMMC_INTMASK_EBE | - SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE; + SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE; //sdio is enabled only when use. SDMMC.ctrl.int_enable = 1; // Enable DMA @@ -308,6 +324,8 @@ esp_err_t sdmmc_host_init() if (ret != ESP_OK) { vQueueDelete(s_event_queue); s_event_queue = NULL; + vSemaphoreDelete(s_io_intr_event); + s_io_intr_event = NULL; esp_intr_free(s_intr_handle); s_intr_handle = NULL; return ret; @@ -376,10 +394,17 @@ esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config) configure_pin(pslot->d7); } } + + // SDIO slave interrupt is edge sensitive to ~(int_n | card_int | card_detect) + // set this and card_detect to high to enable sdio interrupt + gpio_matrix_in(GPIO_FUNC_IN_HIGH, pslot->card_int, 0); if (gpio_cd != -1) { gpio_set_direction(gpio_cd, GPIO_MODE_INPUT); gpio_matrix_in(gpio_cd, pslot->card_detect, 0); + } else { + gpio_matrix_in(GPIO_FUNC_IN_HIGH, pslot->card_detect, 0); } + if (gpio_wp != -1) { gpio_set_direction(gpio_wp, GPIO_MODE_INPUT); gpio_matrix_in(gpio_wp, pslot->write_protect, 0); @@ -405,6 +430,8 @@ esp_err_t sdmmc_host_deinit() s_intr_handle = NULL; vQueueDelete(s_event_queue); s_event_queue = NULL; + vQueueDelete(s_io_intr_event); + s_io_intr_event = NULL; sdmmc_host_input_clk_disable(); sdmmc_host_transaction_handler_deinit(); periph_module_disable(PERIPH_SDMMC_MODULE); @@ -497,24 +524,62 @@ void sdmmc_host_dma_resume() SDMMC.pldmnd = 1; } +esp_err_t sdmmc_host_io_int_enable(int slot) +{ + configure_pin(s_slot_info[slot].d1); + return ESP_OK; +} + +esp_err_t sdmmc_host_io_int_wait(int slot, TickType_t timeout_ticks) +{ + /* SDIO interrupts are negedge sensitive ones: the status bit is only set + * when first interrupt triggered. + * + * If D1 GPIO is low when entering this function, we know that interrupt + * (in SDIO sense) has occurred and we don't need to use SDMMC peripheral + * interrupt. + */ + + SDMMC.intmask.sdio &= ~BIT(slot); /* Disable SDIO interrupt */ + SDMMC.rintsts.sdio = BIT(slot); + if (gpio_get_level(s_slot_info[slot].d1_gpio) == 0) { + return ESP_OK; + } + /* Otherwise, need to wait for an interrupt. Since D1 was high, + * SDMMC peripheral interrupt is guaranteed to trigger on negedge. + */ + xSemaphoreTake(s_io_intr_event, 0); + SDMMC.intmask.sdio |= BIT(slot); /* Re-enable SDIO interrupt */ + + if (xSemaphoreTake(s_io_intr_event, timeout_ticks) == pdTRUE) { + return ESP_OK; + } else { + return ESP_ERR_TIMEOUT; + } +} + /** * @brief SDMMC interrupt handler * - * Ignoring SDIO and streaming read/writes for now (and considering just SD memory cards), - * all communication is driven by the master, and the hardware handles things like stop - * commands automatically. So the interrupt handler doesn't need to do much, we just push - * interrupt status into a queue, clear interrupt flags, and let the task currently doing - * communication figure out what to do next. + * All communication in SD protocol is driven by the master, and the hardware + * handles things like stop commands automatically. + * So the interrupt handler doesn't need to do much, we just push interrupt + * status into a queue, clear interrupt flags, and let the task currently + * doing communication figure out what to do next. + * This also applies to SDIO interrupts which are generated by the slave. * - * Card detect interrupts pose a small issue though, because if a card is plugged in and - * out a few times, while there is no task to process the events, event queue can become - * full and some card detect events may be dropped. We ignore this problem for now, since - * the there are no other interesting events which can get lost due to this. + * Card detect interrupts pose a small issue though, because if a card is + * plugged in and out a few times, while there is no task to process + * the events, event queue can become full and some card detect events + * may be dropped. We ignore this problem for now, since the there are no other + * interesting events which can get lost due to this. */ static void sdmmc_isr(void* arg) { QueueHandle_t queue = (QueueHandle_t) arg; sdmmc_event_t event; - uint32_t pending = SDMMC.mintsts.val; + int higher_priority_task_awoken = pdFALSE; + + uint32_t pending = SDMMC.mintsts.val & 0xFFFF; SDMMC.rintsts.val = pending; event.sdmmc_status = pending; @@ -522,8 +587,17 @@ static void sdmmc_isr(void* arg) { SDMMC.idsts.val = dma_pending; event.dma_status = dma_pending & 0x1f; - int higher_priority_task_awoken = pdFALSE; - xQueueSendFromISR(queue, &event, &higher_priority_task_awoken); + if (pending != 0 || dma_pending != 0) { + xQueueSendFromISR(queue, &event, &higher_priority_task_awoken); + } + + uint32_t sdio_pending = SDMMC.mintsts.sdio; + if (sdio_pending) { + // disable the interrupt (no need to clear here, this is done in sdmmc_host_io_wait_int) + SDMMC.intmask.sdio &= ~sdio_pending; + xSemaphoreGiveFromISR(s_io_intr_event, &higher_priority_task_awoken); + } + if (higher_priority_task_awoken == pdTRUE) { portYIELD_FROM_ISR(); } diff --git a/components/sdmmc/include/sdmmc_cmd.h b/components/sdmmc/include/sdmmc_cmd.h index 58b6f082cc..7b68ed7c8a 100644 --- a/components/sdmmc/include/sdmmc_cmd.h +++ b/components/sdmmc/include/sdmmc_cmd.h @@ -29,7 +29,8 @@ extern "C" { * Support for MMC/eMMC cards will be added later. * * @param host pointer to structure defining host controller - * @param out_card pointer to structure which will receive information about the card when the function completes + * @param out_card pointer to structure which will receive information + * about the card when the function completes * @return * - ESP_OK on success * - One of the error codes from SDMMC host controller @@ -47,8 +48,10 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card); /** * Write given number of sectors to SD/MMC card * - * @param card pointer to card information structure previously initialized using sdmmc_card_init - * @param src pointer to data buffer to read data from; data size must be equal to sector_count * card->csd.sector_size + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param src pointer to data buffer to read data from; data size must be + * equal to sector_count * card->csd.sector_size * @param start_sector sector where to start writing * @param sector_count number of sectors to write * @return @@ -61,8 +64,10 @@ esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, /** * Write given number of sectors to SD/MMC card * - * @param card pointer to card information structure previously initialized using sdmmc_card_init - * @param dst pointer to data buffer to write into; buffer size must be at least sector_count * card->csd.sector_size + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param dst pointer to data buffer to write into; buffer size must be + * at least sector_count * card->csd.sector_size * @param start_sector sector where to start reading * @param sector_count number of sectors to read * @return @@ -72,6 +77,149 @@ esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, size_t start_sector, size_t sector_count); +/** + * Read one byte from an SDIO card using IO_RW_DIRECT (CMD52) + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param reg byte address within IO function + * @param[out] out_byte output, receives the value read from the card + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function, + uint32_t reg, uint8_t *out_byte); + +/** + * Write one byte to an SDIO card using IO_RW_DIRECT (CMD52) + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param reg byte address within IO function + * @param in_byte value to be written + * @param[out] out_byte if not NULL, receives new byte value read + * from the card (read-after-write). + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function, + uint32_t reg, uint8_t in_byte, uint8_t* out_byte); + +/** + * Read multiple bytes from an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs read operation using CMD53 in byte mode. + * For block mode, see sdmmc_io_read_blocks. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where reading starts + * @param dst buffer which receives the data read from card + * @param size number of bytes to read + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_SIZE if size exceeds 512 bytes + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size); + +/** + * Write multiple bytes to an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs write operation using CMD53 in byte mode. + * For block mode, see sdmmc_io_write_blocks. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where writing starts + * @param src data to be written + * @param size number of bytes to write + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_SIZE if size exceeds 512 bytes + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_io_write_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size); + +/** + * Read blocks of data from an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs read operation using CMD53 in block mode. + * For byte mode, see sdmmc_io_read_bytes. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where writing starts + * @param dst buffer which receives the data read from card + * @param size number of bytes to read, must be divisible by the card block + * size. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_SIZE if size is not divisible by 512 bytes + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size); + +/** + * Write blocks of data to an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs write operation using CMD53 in block mode. + * For byte mode, see sdmmc_io_write_bytes. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where writing starts + * @param src data to be written + * @param size number of bytes to read, must be divisible by the card block + * size. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_SIZE if size is not divisible by 512 bytes + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size); + +/** + * Enable SDIO interrupt in the SDMMC host + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if the host controller does not support + * IO interrupts + */ +esp_err_t sdmmc_io_enable_int(sdmmc_card_t* card); + +/** + * Block until an SDIO interrupt is received + * + * Slave uses D1 line to signal interrupt condition to the host. + * This function can be used to wait for the interrupt. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param timeout_ticks time to wait for the interrupt, in RTOS ticks + * @return + * - ESP_OK if the interrupt is received + * - ESP_ERR_NOT_SUPPORTED if the host controller does not support + * IO interrupts + * - ESP_ERR_TIMEOUT if the interrupt does not happen in timeout_ticks + */ +esp_err_t sdmmc_io_wait_int(sdmmc_card_t* card, TickType_t timeout_ticks); + #ifdef __cplusplus } #endif diff --git a/components/sdmmc/sdmmc_cmd.c b/components/sdmmc/sdmmc_cmd.c index 6123990833..469bbc5daf 100644 --- a/components/sdmmc/sdmmc_cmd.c +++ b/components/sdmmc/sdmmc_cmd.c @@ -26,7 +26,8 @@ #include "sys/param.h" #include "soc/soc_memory_layout.h" -#define SDMMC_GO_IDLE_DELAY_MS 20 +#define SDMMC_GO_IDLE_DELAY_MS 20 +#define SDMMC_IO_SEND_OP_COND_DELAY_MS 10 /* These delay values are mostly useful for cases when CD pin is not used, and * the card is removed. In this case, SDMMC peripheral may not always return @@ -53,13 +54,14 @@ static esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card, uint32_t mode, uint32_t group, uint32_t function, sdmmc_switch_func_rsp_t* resp); static esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card); +static esp_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card); +static esp_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card); static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd); static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd); static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card, uint32_t rca); static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr); static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr); static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width); -static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status); static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status); static esp_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable); static uint32_t get_host_ocr(float voltage); @@ -68,7 +70,12 @@ static esp_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src, size_t start_block, size_t block_count); static esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst, size_t start_block, size_t block_count); - +static esp_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp); +static esp_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int function, + uint32_t reg, uint32_t arg, uint8_t *byte); +static esp_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int function, + uint32_t reg, int arg, void *data, size_t size); +static void sdmmc_fix_host_flags(sdmmc_card_t* card); static bool host_is_spi(const sdmmc_card_t* card) { @@ -77,28 +84,38 @@ static bool host_is_spi(const sdmmc_card_t* card) esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) { - ESP_LOGD(TAG, "%s", __func__); + esp_err_t err; memset(card, 0, sizeof(*card)); memcpy(&card->host, config, sizeof(*config)); const bool is_spi = host_is_spi(card); - if ( !is_spi ) { - //check HOST flags compatible with slot configuration. - int slot_bit_width = config->get_bus_width(config->slot); - if ( slot_bit_width == 1 && (config->flags & (SDMMC_HOST_FLAG_4BIT|SDMMC_HOST_FLAG_8BIT))) { - ESP_LOGW(TAG, "HOST slot only enables 1-bit."); - card->host.flags = ((card->host.flags&(~(SDMMC_HOST_FLAG_4BIT|SDMMC_HOST_FLAG_8BIT)))|SDMMC_HOST_FLAG_1BIT); - } else if ( slot_bit_width == 4 && (config->flags & SDMMC_HOST_FLAG_8BIT)){ - ESP_LOGW(TAG, "HOST slot only enables 4-bit."); - card->host.flags = ((card->host.flags&(~(SDMMC_HOST_FLAG_1BIT|SDMMC_HOST_FLAG_8BIT)))|SDMMC_HOST_FLAG_4BIT); - } + if (!is_spi) { + // Check if host flags are compatible with slot configuration. + sdmmc_fix_host_flags(card); + } + + /* ----------- standard initialization process starts here ---------- */ + + /* Reset SDIO (CMD52, RES) before re-initializing IO (CMD5). + * Non-IO cards are allowed to time out. + */ + uint8_t sdio_reset = CCCR_CTL_RES; + err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CTL, SD_ARG_CMD52_WRITE, &sdio_reset); + if (err != ESP_OK && err != ESP_ERR_TIMEOUT) { + ESP_LOGE(TAG, "%s: sdio_reset: unexpected return: 0x%x", __func__, err ); + return err; } + /* GO_IDLE_STATE (CMD0) command resets the card */ - esp_err_t err = sdmmc_send_cmd_go_idle_state(card); + err = sdmmc_send_cmd_go_idle_state(card); if (err != ESP_OK) { ESP_LOGE(TAG, "%s: go_idle_state (1) returned 0x%x", __func__, err); return err; } + + /* FIXME: we should check card status to wait until it is out of idle + * state, instead of using a delay. + */ vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS); sdmmc_send_cmd_go_idle_state(card); vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS); @@ -120,51 +137,84 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) return err; } - /* In SPI mode, READ_OCR (CMD58) command is used to figure out which voltage - * ranges the card can support. This step is skipped since 1.8V isn't - * supported on the ESP32. - */ + /* IO_SEND_OP_COND(CMD5), Determine if the card is an IO card. + * Non-IO cards will not respond to this command. + */ + err = sdmmc_io_send_op_cond(card, 0, &card->ocr); + if (err != ESP_OK) { + ESP_LOGD(TAG, "%s: io_send_op_cond (1) returned 0x%x; not IO card", __func__, err); + card->is_sdio = 0; + card->is_mem = 1; + } else { + card->is_sdio = 1; - /* In SD mode, CRC checks of data transfers are mandatory and performed - * by the hardware. In SPI mode, CRC16 of data transfers is optional and - * needs to be enabled. - */ - if (is_spi) { - err = sdmmc_send_cmd_crc_on_off(card, true); + if (card->ocr & SD_IO_OCR_MEM_PRESENT) { + ESP_LOGD(TAG, "%s: IO-only card", __func__); + card->is_mem = 0; + } + card->num_io_functions = SD_IO_OCR_NUM_FUNCTIONS(card->ocr); + ESP_LOGD(TAG, "%s: number of IO functions: %d", __func__, card->num_io_functions); + if (card->num_io_functions == 0) { + card->is_sdio = 0; + } + host_ocr &= card->ocr; + err = sdmmc_io_send_op_cond(card, host_ocr, &card->ocr); if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: sdmmc_send_cmd_crc_on_off returned 0x%x", __func__, err); + ESP_LOGE(TAG, "%s: sdmmc_io_send_op_cond (1) returned 0x%x", __func__, err); return err; } + sdmmc_io_enable_int(card); } - /* Send SEND_OP_COND (ACMD41) command to the card until it becomes ready. */ - err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err); - return err; - } - if (is_spi) { - err = sdmmc_send_cmd_read_ocr(card, &card->ocr); + if (card->is_mem) { + /* In SPI mode, READ_OCR (CMD58) command is used to figure out which voltage + * ranges the card can support. This step is skipped since 1.8V isn't + * supported on the ESP32. + */ + + /* In SD mode, CRC checks of data transfers are mandatory and performed + * by the hardware. In SPI mode, CRC16 of data transfers is optional and + * needs to be enabled. + */ + if (is_spi) { + err = sdmmc_send_cmd_crc_on_off(card, true); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd_crc_on_off returned 0x%x", __func__, err); + return err; + } + } + + /* Send SEND_OP_COND (ACMD41) command to the card until it becomes ready. */ + err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr); if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: read_ocr returned 0x%x", __func__, err); + ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err); return err; } - } - ESP_LOGD(TAG, "host_ocr=0x%x card_ocr=0x%x", host_ocr, card->ocr); + if (is_spi) { + err = sdmmc_send_cmd_read_ocr(card, &card->ocr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: read_ocr returned 0x%x", __func__, err); + return err; + } + } + ESP_LOGD(TAG, "host_ocr=0x%x card_ocr=0x%x", host_ocr, card->ocr); - /* Clear all voltage bits in host's OCR which the card doesn't support. - * Don't touch CCS bit because in SPI mode cards don't report CCS in ACMD41 - * response. - */ - host_ocr &= (card->ocr | (~SD_OCR_VOL_MASK)); - ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr); + /* Clear all voltage bits in host's OCR which the card doesn't support. + * Don't touch CCS bit because in SPI mode cards don't report CCS in ACMD41 + * response. + */ + host_ocr &= (card->ocr | (~SD_OCR_VOL_MASK)); + ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr); + } /* Read and decode the contents of CID register */ if (!is_spi) { - err = sddmc_send_cmd_all_send_cid(card, &card->cid); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err); - return err; + if (card->is_mem) { + err = sddmc_send_cmd_all_send_cid(card, &card->cid); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err); + return err; + } } err = sdmmc_send_cmd_set_relative_addr(card, &card->rca); if (err != ESP_OK) { @@ -178,20 +228,22 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) return err; } } - - /* Get and decode the contents of CSD register. Determine card capacity. */ - err = sdmmc_send_cmd_send_csd(card, &card->csd); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: send_csd (1) returned 0x%x", __func__, err); - return err; - } - const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1; - if (!(card->ocr & SD_OCR_SDHC_CAP) && - card->csd.capacity > max_sdsc_capacity) { - ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.", - __func__, card->csd.capacity, max_sdsc_capacity); - card->csd.capacity = max_sdsc_capacity; + if (card->is_mem) { + /* Get and decode the contents of CSD register. Determine card capacity. */ + err = sdmmc_send_cmd_send_csd(card, &card->csd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_csd (1) returned 0x%x", __func__, err); + return err; + } + const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1; + if (!(card->ocr & SD_OCR_SDHC_CAP) && + card->csd.capacity > max_sdsc_capacity) { + ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.", + __func__, card->csd.capacity, max_sdsc_capacity); + card->csd.capacity = max_sdsc_capacity; + } } + /* ----------- standard initialization process ends here ----------- */ /* Switch the card from stand-by mode to data transfer mode (not needed if * SPI interface is used). This is needed to issue SET_BLOCKLEN and @@ -205,112 +257,118 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) } } - /* SDSC cards support configurable data block lengths. - * We don't use this feature and set the block length to 512 bytes, - * same as the block length for SDHC cards. - */ - if ((card->ocr & SD_OCR_SDHC_CAP) == 0) { - err = sdmmc_send_cmd_set_blocklen(card, &card->csd); + if (card->is_mem) { + /* SDSC cards support configurable data block lengths. + * We don't use this feature and set the block length to 512 bytes, + * same as the block length for SDHC cards. + */ + if ((card->ocr & SD_OCR_SDHC_CAP) == 0) { + err = sdmmc_send_cmd_set_blocklen(card, &card->csd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err); + return err; + } + } + /* Get the contents of SCR register: bus width and the version of SD spec + * supported by the card. + * In SD mode, this is the first command which uses D0 line. Errors at + * this step usually indicate connection issue or lack of pull-up resistor. + */ + err = sdmmc_send_cmd_send_scr(card, &card->scr); if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err); + ESP_LOGE(TAG, "%s: send_scr (1) returned 0x%x", __func__, err); return err; } } - /* Get the contents of SCR register: bus width and the version of SD spec - * supported by the card. - * In SD mode, this is the first command which uses D0 line. Errors at - * this step usually indicate connection issue or lack of pull-up resistor. - */ - err = sdmmc_send_cmd_send_scr(card, &card->scr); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: send_scr (1) returned 0x%x", __func__, err); - return err; - } - - /* If the host has been initialized with 4-bit bus support, and the card - * supports 4-bit bus, switch to 4-bit bus now. - */ - if ((card->host.flags & SDMMC_HOST_FLAG_4BIT) && - (card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT)) { - ESP_LOGD(TAG, "switching to 4-bit bus mode"); - err = sdmmc_send_cmd_set_bus_width(card, 4); - if (err != ESP_OK) { - ESP_LOGE(TAG, "set_bus_width failed"); - return err; - } - err = (*config->set_bus_width)(config->slot, 4); - if (err != ESP_OK) { - ESP_LOGE(TAG, "slot->set_bus_width failed"); - return err; + if (card->is_mem) { + /* If the host has been initialized with 4-bit bus support, and the card + * supports 4-bit bus, switch to 4-bit bus now. + */ + if ((card->host.flags & SDMMC_HOST_FLAG_4BIT) && + (card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT)) { + ESP_LOGD(TAG, "switching to 4-bit bus mode"); + err = sdmmc_send_cmd_set_bus_width(card, 4); + if (err != ESP_OK) { + ESP_LOGE(TAG, "set_bus_width failed"); + return err; + } + err = (*config->set_bus_width)(config->slot, 4); + if (err != ESP_OK) { + ESP_LOGE(TAG, "slot->set_bus_width failed"); + return err; + } } - } - /* Wait for the card to be ready for data transfers */ - uint32_t status = 0; - while (!is_spi && !(status & MMC_R1_READY_FOR_DATA)) { - // TODO: add some timeout here - uint32_t count = 0; - err = sdmmc_send_cmd_send_status(card, &status); - if (err != ESP_OK) { - return err; + /* Wait for the card to be ready for data transfers */ + uint32_t status = 0; + while (!is_spi && !(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + uint32_t count = 0; + err = sdmmc_send_cmd_send_status(card, &status); + if (err != ESP_OK) { + return err; + } + if (++count % 16 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } } - if (++count % 10 == 0) { - ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } else { + /* IO card */ + if (config->flags & SDMMC_HOST_FLAG_4BIT) { + uint8_t card_cap; + err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CARD_CAP, + SD_ARG_CMD52_READ, &card_cap); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read SD_IO_CCCR_CARD_CAP) returned 0x%0x", __func__, err); + return err; + } + ESP_LOGD(TAG, "IO card capabilities byte: %02x", card_cap); + if (!(card_cap & CCCR_CARD_CAP_LSC) || + (card_cap & CCCR_CARD_CAP_4BLS)) { + // This card supports 4-bit bus mode + uint8_t bus_width = CCCR_BUS_WIDTH_4; + err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_BUS_WIDTH, + SD_ARG_CMD52_WRITE, &bus_width); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write SD_IO_CCCR_BUS_WIDTH) returned 0x%0x", __func__, err); + return err; + } + err = (*config->set_bus_width)(config->slot, 4); + if (err != ESP_OK) { + ESP_LOGE(TAG, "slot->set_bus_width failed"); + return err; + } + } } } - /* So far initialization has been done using 400kHz clock. Determine the * clock rate which both host and the card support, and switch to it. */ bool freq_switched = false; if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED && !is_spi /* SPI doesn't support >26MHz in some cases */) { - /* This will determine if the card supports SWITCH_FUNC command, - * and high speed mode. If the cards supports both, this will enable - * high speed mode at the card side. - */ - err = sdmmc_enable_hs_mode(card); + if (card->is_mem) { + err = sdmmc_enable_hs_mode_and_check(card); + } else { + err = sdmmc_io_enable_hs_mode(card); + } + if (err == ESP_ERR_NOT_SUPPORTED) { ESP_LOGD(TAG, "%s: host supports HS mode, but card doesn't", __func__); } else if (err != ESP_OK) { - /* some other error */ return err; - } else { /* ESP_OK */ - /* HS mode has been enabled on the card. - * Read CSD again, it should now indicate that the card supports - * 50MHz clock. - * Since SEND_CSD is allowed only in standby mode, and the card is - * currently in data transfer more, deselect the card first, then - * get the CSD, then select the card again. + } else { + ESP_LOGD(TAG, "%s: switching host to HS mode", __func__); + /* ESP_OK, HS mode has been enabled on the card side. + * Switch the host to HS mode. */ - err = sdmmc_send_cmd_select_card(card, 0); + err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED); if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: select_card (2) returned 0x%x", __func__, err); + ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode"); return err; } - err = sdmmc_send_cmd_send_csd(card, &card->csd); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: send_csd (2) returned 0x%x", __func__, err); - return err; - } - err = sdmmc_send_cmd_select_card(card, card->rca); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: select_card (3) returned 0x%x", __func__, err); - return err; - } - - if (card->csd.tr_speed != 50000000) { - ESP_LOGW(TAG, "unexpected: after enabling HS mode, tr_speed=%d", card->csd.tr_speed); - } else { - /* Finally can switch the host to HS mode */ - err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED); - if (err != ESP_OK) { - ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode"); - return err; - } - freq_switched = true; - } + freq_switched = true; } } /* All SD cards must support default speed mode (25MHz). @@ -331,15 +389,21 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) * an indicator of potential signal integrity issues. */ if (freq_switched) { - sdmmc_scr_t scr_tmp; - err = sdmmc_send_cmd_send_scr(card, &scr_tmp); - if (err != ESP_OK) { - ESP_LOGE(TAG, "%s: send_scr (2) returned 0x%x", __func__, err); - return err; - } - if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) { - ESP_LOGE(TAG, "got corrupted data after increasing clock frequency"); - return ESP_ERR_INVALID_RESPONSE; + if (card->is_mem) { + sdmmc_scr_t scr_tmp; + err = sdmmc_send_cmd_send_scr(card, &scr_tmp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_scr (2) returned 0x%x", __func__, err); + return err; + } + if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) { + ESP_LOGE(TAG, "got corrupted data after increasing clock frequency"); + return ESP_ERR_INVALID_RESPONSE; + } + } else { + /* TODO: For IO cards, read some data to see if frequency switch + * was successful. + */ } } return ESP_OK; @@ -357,6 +421,26 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card) fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width); } +static void sdmmc_fix_host_flags(sdmmc_card_t* card) +{ + const uint32_t width_1bit = SDMMC_HOST_FLAG_1BIT; + const uint32_t width_4bit = SDMMC_HOST_FLAG_4BIT; + const uint32_t width_8bit = SDMMC_HOST_FLAG_8BIT; + const uint32_t width_mask = width_1bit | width_4bit | width_8bit; + + int slot_bit_width = card->host.get_bus_width(card->host.slot); + if (slot_bit_width == 1 && + (card->host.flags & (width_4bit | width_8bit))) { + ESP_LOGW(TAG, "host slot is configured in 1-bit mode"); + card->host.flags &= ~width_mask; + card->host.flags |= ~(width_1bit); + } else if (slot_bit_width == 4 && (card->host.flags & width_8bit)){ + ESP_LOGW(TAG, "host slot is configured in 4-bit mode"); + card->host.flags &= ~width_mask; + card->host.flags |= width_4bit; + } +} + static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd) { if (card->host.command_timeout_ms != 0) { @@ -675,20 +759,6 @@ static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width) return sdmmc_send_app_cmd(card, &cmd); } -static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status) -{ - sdmmc_command_t cmd = { - .opcode = MMC_STOP_TRANSMISSION, - .arg = 0, - .flags = SCF_RSP_R1B | SCF_CMD_AC - }; - esp_err_t err = sdmmc_send_cmd(card, &cmd); - if (err == 0) { - *status = MMC_R1(cmd.response); - } - return err; -} - static esp_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable) { assert(host_is_spi(card) && "CRC_ON_OFF can only be used in SPI mode"); @@ -944,6 +1014,10 @@ static esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card, static esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card) { + /* This will determine if the card supports SWITCH_FUNC command, + * and high speed mode. If the cards supports both, this will enable + * high speed mode at the card side. + */ if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 || ((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) { return ESP_ERR_NOT_SUPPORTED; @@ -974,3 +1048,242 @@ out: free(response); return err; } + +static esp_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card) +{ + /* Try to enabled HS mode */ + esp_err_t err = sdmmc_enable_hs_mode(card); + if (err != ESP_OK) { + return err; + } + /* HS mode has been enabled on the card. + * Read CSD again, it should now indicate that the card supports + * 50MHz clock. + * Since SEND_CSD is allowed only in standby mode, and the card is + * currently in data transfer more, deselect the card first, then + * get the CSD, then select the card again. + */ + err = sdmmc_send_cmd_select_card(card, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: select_card (2) returned 0x%x", __func__, err); + return err; + } + err = sdmmc_send_cmd_send_csd(card, &card->csd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_csd (2) returned 0x%x", __func__, err); + return err; + } + err = sdmmc_send_cmd_select_card(card, card->rca); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: select_card (3) returned 0x%x", __func__, err); + return err; + } + + if (card->csd.tr_speed != 50000000) { + ESP_LOGW(TAG, "unexpected: after enabling HS mode, tr_speed=%d", card->csd.tr_speed); + return ESP_ERR_NOT_SUPPORTED; + } + + return ESP_OK; +} + +static esp_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card) +{ + /* For IO cards, do write + read operation on "High Speed" register, + * setting EHS bit. If both EHS and SHS read back as set, then HS mode + * has been enabled. + */ + uint8_t val = CCCR_HIGHSPEED_ENABLE; + esp_err_t err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_HIGHSPEED, + SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &val); + if (err != ESP_OK) { + ESP_LOGD(TAG, "%s: sdmmc_io_rw_direct returned 0x%x", __func__, err); + return err; + } + + ESP_LOGD(TAG, "%s: CCCR_HIGHSPEED=0x%02x", __func__, val); + const uint8_t hs_mask = CCCR_HIGHSPEED_ENABLE | CCCR_HIGHSPEED_SUPPORT; + if ((val & hs_mask) != hs_mask) { + return ESP_ERR_NOT_SUPPORTED; + } + return ESP_OK; +} + + +static esp_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp) +{ + esp_err_t err = ESP_OK; + sdmmc_command_t cmd = { + .flags = SCF_CMD_BCR | SCF_RSP_R4, + .arg = ocr, + .opcode = SD_IO_SEND_OP_COND + }; + for (size_t i = 0; i < 100; i++) { + err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + break; + } + if ((MMC_R4(cmd.response) & SD_IO_OCR_MEM_READY) || + ocr == 0) { + break; + } + err = ESP_ERR_TIMEOUT; + vTaskDelay(SDMMC_IO_SEND_OP_COND_DELAY_MS / portTICK_PERIOD_MS); + } + if (err == ESP_OK && ocrp != NULL) + *ocrp = MMC_R4(cmd.response); + + return err; +} + +static esp_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int func, + uint32_t reg, uint32_t arg, uint8_t *byte) +{ + esp_err_t err; + sdmmc_command_t cmd = { + .flags = SCF_CMD_AC | SCF_RSP_R5, + .arg = 0, + .opcode = SD_IO_RW_DIRECT + }; + + arg |= (func & SD_ARG_CMD52_FUNC_MASK) << SD_ARG_CMD52_FUNC_SHIFT; + arg |= (reg & SD_ARG_CMD52_REG_MASK) << SD_ARG_CMD52_REG_SHIFT; + arg |= (*byte & SD_ARG_CMD52_DATA_MASK) << SD_ARG_CMD52_DATA_SHIFT; + cmd.arg = arg; + + err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + ESP_LOGV(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + + *byte = SD_R5_DATA(cmd.response); + + return ESP_OK; +} + + +esp_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function, + uint32_t addr, uint8_t *out_byte) +{ + esp_err_t ret = sdmmc_io_rw_direct(card, function, addr, SD_ARG_CMD52_READ, out_byte); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read 0x%x) returned 0x%x", __func__, addr, ret); + } + return ret; +} + +esp_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function, + uint32_t addr, uint8_t in_byte, uint8_t* out_byte) +{ + uint8_t tmp_byte = in_byte; + esp_err_t ret = sdmmc_io_rw_direct(card, function, addr, + SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &tmp_byte); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write 0x%x) returned 0x%x", __func__, addr, ret); + return ret; + } + if (out_byte != NULL) { + *out_byte = tmp_byte; + } + return ESP_OK; +} + +static esp_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int func, + uint32_t reg, int arg, void *datap, size_t datalen) +{ + esp_err_t err; + const size_t max_byte_transfer_size = 512; + sdmmc_command_t cmd = { + .flags = SCF_CMD_AC | SCF_RSP_R5, + .arg = 0, + .opcode = SD_IO_RW_EXTENDED, + .data = datap, + .datalen = datalen, + .blklen = max_byte_transfer_size /* TODO: read max block size from CIS */ + }; + + uint32_t count; /* number of bytes or blocks, depending on transfer mode */ + if (arg & SD_ARG_CMD53_BLOCK_MODE) { + if (cmd.datalen % cmd.blklen != 0) { + return ESP_ERR_INVALID_SIZE; + } + count = cmd.datalen / cmd.blklen; + } else { + if (datalen > max_byte_transfer_size) { + /* TODO: split into multiple operations? */ + return ESP_ERR_INVALID_SIZE; + } + if (datalen == max_byte_transfer_size) { + count = 0; // See 5.3.1 SDIO simplifed spec + } else { + count = datalen; + } + cmd.blklen = datalen; + } + + arg |= (func & SD_ARG_CMD53_FUNC_MASK) << SD_ARG_CMD53_FUNC_SHIFT; + arg |= (reg & SD_ARG_CMD53_REG_MASK) << SD_ARG_CMD53_REG_SHIFT; + arg |= (count & SD_ARG_CMD53_LENGTH_MASK) << SD_ARG_CMD53_LENGTH_SHIFT; + cmd.arg = arg; + + if ((arg & SD_ARG_CMD53_WRITE) == 0) { + cmd.flags |= SCF_CMD_READ; + } + + err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + + return ESP_OK; +} + +esp_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size) +{ + return sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT, + dst, size); +} + +esp_err_t sdmmc_io_write_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size) +{ + return sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT, + (void*) src, size); +} + +esp_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size) +{ + return sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT | SD_ARG_CMD53_BLOCK_MODE, + dst, size); +} + +esp_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size) +{ + return sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT | SD_ARG_CMD53_BLOCK_MODE, + (void*) src, size); +} + +esp_err_t sdmmc_io_enable_int(sdmmc_card_t* card) +{ + if (card->host.io_int_enable == NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + return (*card->host.io_int_enable)(card->host.slot); +} + +esp_err_t sdmmc_io_wait_int(sdmmc_card_t* card, TickType_t timeout_ticks) +{ + if (card->host.io_int_wait == NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + return (*card->host.io_int_wait)(card->host.slot, timeout_ticks); +} diff --git a/components/sdmmc/test/test_sdio.c b/components/sdmmc/test/test_sdio.c new file mode 100644 index 0000000000..8b885baf55 --- /dev/null +++ b/components/sdmmc/test/test_sdio.c @@ -0,0 +1,382 @@ +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "soc/gpio_reg.h" +#include "unity.h" + +/* Second ESP32 board attached as follows: + * Master Slave + * IO18 EN + * IO19 IO0 + * IO14 SD_CLK + * IO15 SD_CMD + * IO2 SD_D0 + * IO4 SD_D1 + * IO12 SD_D2 + * IO13 SD_D3 + */ + + +/* TODO: add SDIO slave header files, remove these definitions */ + +#define DR_REG_SLC_BASE 0x3ff58000 +#define DR_REG_SLC_MASK 0xfffffc00 + +#define SLCCONF1 (DR_REG_SLC_BASE + 0x60) +#define SLC_SLC0_RX_STITCH_EN (BIT(6)) +#define SLC_SLC0_TX_STITCH_EN (BIT(5)) + +#define SLC0TX_LINK (DR_REG_SLC_BASE + 0x40) +#define SLC_SLC0_TXLINK_PARK (BIT(31)) +#define SLC_SLC0_TXLINK_RESTART (BIT(30)) +#define SLC_SLC0_TXLINK_START (BIT(29)) + +#define DR_REG_SLCHOST_BASE 0x3ff55000 +#define DR_REG_SLCHOST_MASK 0xfffffc00 +#define SLCHOST_STATE_W0 (DR_REG_SLCHOST_BASE + 0x64) +#define SLCHOST_CONF_W0 (DR_REG_SLCHOST_BASE + 0x6C) +#define SLCHOST_CONF_W5 (DR_REG_SLCHOST_BASE + 0x80) +#define SLCHOST_WIN_CMD (DR_REG_SLCHOST_BASE + 0x84) + +#define SLC_WIN_CMD_READ 0x80 +#define SLC_WIN_CMD_WRITE 0xC0 +#define SLC_WIN_CMD_S 8 + +#define SLC_THRESHOLD_ADDR 0x1f800 + +static const char* TAG = "sdio_test"; + +static esp_err_t slave_slchost_reg_read(sdmmc_card_t* card, uint32_t addr, uint32_t* out_val) +{ + if ((addr & DR_REG_SLCHOST_MASK) != DR_REG_SLCHOST_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + return sdmmc_io_read_bytes(card, 1, addr & (~DR_REG_SLCHOST_MASK), out_val, sizeof(*out_val)); +} + +static esp_err_t slave_slchost_reg_write(sdmmc_card_t* card, uint32_t addr, uint32_t val) +{ + if ((addr & DR_REG_SLCHOST_MASK) != DR_REG_SLCHOST_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + return sdmmc_io_write_bytes(card, 1, addr & (~DR_REG_SLCHOST_MASK), &val, sizeof(val)); +} + +static esp_err_t slave_slc_reg_read(sdmmc_card_t* card, uint32_t addr, uint32_t* val) +{ + if ((addr & DR_REG_SLC_MASK) != DR_REG_SLC_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + uint32_t word = (addr - DR_REG_SLC_BASE) / 4; + if (word > INT8_MAX) { + return ESP_ERR_INVALID_ARG; + } + + uint32_t window_command = word | (SLC_WIN_CMD_READ << SLC_WIN_CMD_S); + esp_err_t err = slave_slchost_reg_write(card, SLCHOST_WIN_CMD, window_command); + if (err != ESP_OK) { + return err; + } + + return slave_slchost_reg_read(card, SLCHOST_STATE_W0, val); +} + +static esp_err_t slave_slc_reg_write(sdmmc_card_t* card, uint32_t addr, uint32_t val) +{ + if ((addr & DR_REG_SLC_MASK) != DR_REG_SLC_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + uint32_t word = (addr - DR_REG_SLC_BASE) / 4; + if (word > INT8_MAX) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = slave_slchost_reg_write(card, SLCHOST_CONF_W5, val); + if (err != ESP_OK) { + return err; + } + + uint32_t window_command = word | (SLC_WIN_CMD_WRITE << SLC_WIN_CMD_S); + return slave_slchost_reg_write(card, SLCHOST_WIN_CMD, window_command); +} + +/** Reset and put slave into download mode */ +static void reset_slave() +{ + const int pin_en = 18; + const int pin_io0 = 19; + gpio_config_t gpio_cfg = { + .pin_bit_mask = BIT(pin_en) | BIT(pin_io0), + .mode = GPIO_MODE_OUTPUT_OD, + }; + TEST_ESP_OK(gpio_config(&gpio_cfg)); + gpio_set_level(pin_en, 0); + gpio_set_level(pin_io0, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(pin_en, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(pin_io0, 1); +} + +static void sdio_slave_common_init(sdmmc_card_t* card) +{ + uint8_t card_cap; + esp_err_t err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_CARD_CAP, &card_cap); + TEST_ESP_OK(err); + printf("CAP: 0x%02x\n", card_cap); + + uint8_t hs; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_HIGHSPEED, &hs); + TEST_ESP_OK(err); + printf("HS: 0x%02x\n", hs); + + +#define FUNC1_EN_MASK (BIT(1)) + + uint8_t ioe; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &ioe); + TEST_ESP_OK(err); + printf("IOE: 0x%02x\n", ioe); + + uint8_t ior = 0; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); + TEST_ESP_OK(err); + printf("IOR: 0x%02x\n", ior); + + // enable function 1 + ioe |= FUNC1_EN_MASK; + err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_FN_ENABLE, ioe, NULL); + TEST_ESP_OK(err); + + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &ioe); + TEST_ESP_OK(err); + printf("IOE: 0x%02x\n", ioe); + + // wait for the card to become ready + while ( (ior & FUNC1_EN_MASK) == 0 ) { + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); + TEST_ESP_OK(err); + printf("IOR: 0x%02x\n", ior); + } + + // get interrupt status + uint8_t ie; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_INT_ENABLE, &ie); + TEST_ESP_OK(err); + printf("IE: 0x%02x\n", ie); + + // enable interrupts for function 1&2 and master enable + ie |= BIT(0) | FUNC1_EN_MASK; + err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_INT_ENABLE, ie, NULL); + TEST_ESP_OK(err); + + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_INT_ENABLE, &ie); + TEST_ESP_OK(err); + printf("IE: 0x%02x\n", ie); +} + +/** Common for all SDIO devices, set block size for specific function */ +static void sdio_slave_set_blocksize(sdmmc_card_t* card, int function, uint16_t bs) +{ + const uint8_t* bs_u8 = (const uint8_t*) &bs; + uint16_t bs_read = 0; + uint8_t* bs_read_u8 = (uint8_t*) &bs_read; + uint32_t offset = SD_IO_FBR_START * function; + TEST_ESP_OK( sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, bs_u8[0], NULL)); + TEST_ESP_OK( sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, bs_u8[1], NULL)); + TEST_ESP_OK( sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0])); + TEST_ESP_OK( sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1])); + TEST_ASSERT_EQUAL_HEX16(bs, bs_read); +} + +/** + * ESP32 ROM code does not set some SDIO slave registers to the defaults + * we need, this function clears/sets some bits. + */ +static void esp32_slave_init_extra(sdmmc_card_t* card) +{ + printf("Initialize some ESP32 SDIO slave registers\n"); + + uint32_t reg_val; + TEST_ESP_OK( slave_slc_reg_read(card, SLCCONF1, ®_val) ); + reg_val &= ~(SLC_SLC0_RX_STITCH_EN | SLC_SLC0_TX_STITCH_EN); + TEST_ESP_OK( slave_slc_reg_write(card, SLCCONF1, reg_val) ); + + TEST_ESP_OK( slave_slc_reg_read(card, SLC0TX_LINK, ®_val) ); + reg_val |= SLC_SLC0_TXLINK_START; + TEST_ESP_OK( slave_slc_reg_write(card, SLC0TX_LINK, reg_val) ); +} + +/** + * ESP32 bootloader implements "SIP" protocol which can be used to exchange + * some commands, events, and data packets between the host and the slave. + * This function sends a SIP command, testing CMD53 block writes along the way. + */ +static void esp32_send_sip_command(sdmmc_card_t* card) +{ + printf("Test block write using CMD53\n"); + const size_t block_size = 512; + uint8_t* data = heap_caps_calloc(1, block_size, MALLOC_CAP_DMA); + struct sip_cmd_bootup { + uint32_t boot_addr; + uint32_t discard_link; + }; + struct sip_cmd_write_reg { + uint32_t addr; + uint32_t val; + }; + struct sip_hdr { + uint8_t fc[2]; + uint16_t len; + uint32_t cmdid; + uint32_t seq; + }; + + struct sip_hdr* hdr = (struct sip_hdr*) data; + size_t len; + +#define SEND_WRITE_REG_CMD + +#ifdef SEND_WRITE_REG_CMD + struct sip_cmd_write_reg *write_reg = (struct sip_cmd_write_reg*) (data + sizeof(*hdr)); + len = sizeof(*hdr) + sizeof(*write_reg); + hdr->cmdid = 3; /* SIP_CMD_WRITE_REG */ + write_reg->addr = GPIO_ENABLE_W1TS_REG; + write_reg->val = BIT(0) | BIT(2) | BIT(4); /* Turn of RGB LEDs on WROVER-KIT */ +#else + struct sip_cmd_bootup *bootup = (struct sip_cmd_bootup*) (data + sizeof(*hdr)); + len = sizeof(*hdr) + sizeof(*bootup); + hdr->cmdid = 5; /* SIP_CMD_BOOTUP */ + bootup->boot_addr = 0x4005a980; /* start_tb_console function in ROM */ + bootup->discard_link = 1; +#endif + hdr->len = len; + + TEST_ESP_OK( sdmmc_io_write_blocks(card, 1, SLC_THRESHOLD_ADDR - len, data, block_size) ); + free(data); +} + +static void test_cmd52_read_write_single_byte(sdmmc_card_t* card) +{ + esp_err_t err; + printf("Write bytes to slave's W0_REG using CMD52\n"); + const size_t scratch_area_reg = SLCHOST_CONF_W0 - DR_REG_SLCHOST_BASE; + + const uint8_t test_byte_1 = 0xa5; + const uint8_t test_byte_2 = 0xb6; + // used to check Read-After-Write + uint8_t test_byte_1_raw; + uint8_t test_byte_2_raw; + uint8_t val = 0; + err = sdmmc_io_write_byte(card, 1, scratch_area_reg, test_byte_1, &test_byte_1_raw); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_UINT8(test_byte_1, test_byte_1_raw); + err = sdmmc_io_write_byte(card, 1, scratch_area_reg + 1, test_byte_2, &test_byte_2_raw); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_UINT8(test_byte_2, test_byte_2_raw); + + printf("Read back bytes using CMD52\n"); + TEST_ESP_OK(sdmmc_io_read_byte(card, 1, scratch_area_reg, &val)); + TEST_ASSERT_EQUAL_UINT8(test_byte_1, val); + + TEST_ESP_OK(sdmmc_io_read_byte(card, 1, scratch_area_reg + 1, &val)); + TEST_ASSERT_EQUAL_UINT8(test_byte_2, val); +} + +static void test_cmd53_read_write_multiple_bytes(sdmmc_card_t* card) +{ + printf("Write multiple bytes using CMD53\n"); + const size_t scratch_area_reg = SLCHOST_CONF_W0 - DR_REG_SLCHOST_BASE; + + uint8_t* src = heap_caps_malloc(512, MALLOC_CAP_DMA); + uint32_t* src_32 = (uint32_t*) src; + const size_t n_words = 6; + srand(0); + for (size_t i = 0; i < n_words; ++i) { + src_32[i] = rand(); + } + size_t len = n_words * sizeof(uint32_t); + + TEST_ESP_OK(sdmmc_io_write_bytes(card, 1, scratch_area_reg, src, len)); + ESP_LOG_BUFFER_HEX(TAG, src, len); + + printf("Read back using CMD52\n"); + uint8_t* dst = heap_caps_malloc(512, MALLOC_CAP_DMA); + for (size_t i = 0; i < len; ++i) { + TEST_ESP_OK(sdmmc_io_read_byte(card, 1, scratch_area_reg + i, &dst[i])); + } + ESP_LOG_BUFFER_HEX(TAG, dst, len); + TEST_ASSERT_EQUAL_UINT8_ARRAY(src, dst, len); + + printf("Read back using CMD53\n"); + TEST_ESP_OK(sdmmc_io_read_bytes(card, 1, scratch_area_reg, dst, len)); + ESP_LOG_BUFFER_HEX(TAG, dst, len); + TEST_ASSERT_EQUAL_UINT8_ARRAY(src, dst, len); + + free(src); + free(dst); +} + + +TEST_CASE("can probe and talk to ESP32 SDIO slave", "[sdio][ignore]") +{ + reset_slave(); + + /* Probe */ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.flags = SDMMC_HOST_FLAG_1BIT; + config.max_freq_khz = SDMMC_FREQ_PROBING; + + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + (sdmmc_host_init()); + (sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config)); + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + TEST_ESP_OK(sdmmc_card_init(&config, card)); + sdmmc_card_print_info(stdout, card); + + /* Set up standard SDIO registers */ + sdio_slave_common_init(card); + + for (int repeat = 0; repeat < 10; ++repeat) { + test_cmd52_read_write_single_byte(card); + test_cmd53_read_write_multiple_bytes(card); + } + + sdio_slave_set_blocksize(card, 0, 512); + sdio_slave_set_blocksize(card, 1, 512); + + esp32_slave_init_extra(card); + + esp32_send_sip_command(card); + + TEST_ESP_OK(sdmmc_host_deinit()); + free(card); +} + diff --git a/components/soc/esp32/include/soc/sdmmc_reg.h b/components/soc/esp32/include/soc/sdmmc_reg.h index d1b452d1d1..2f9c68f80b 100644 --- a/components/soc/esp32/include/soc/sdmmc_reg.h +++ b/components/soc/esp32/include/soc/sdmmc_reg.h @@ -66,6 +66,8 @@ #define SDMMC_CLOCK_REG (DR_REG_SDMMC_BASE + 0x800) +#define SDMMC_INTMASK_IO_SLOT1 BIT(17) +#define SDMMC_INTMASK_IO_SLOT0 BIT(16) #define SDMMC_INTMASK_EBE BIT(15) #define SDMMC_INTMASK_ACD BIT(14) #define SDMMC_INTMASK_SBE BIT(13) -- 2.40.0