mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/sdio_master' into 'master'
SDIO master driver See merge request idf/esp-idf!2008
This commit is contained in:
commit
bae9709a79
@ -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_
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#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_
|
||||
|
@ -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, \
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
||||
@ -115,22 +123,30 @@ void sdmmc_host_reset()
|
||||
* field of card's CSD register.
|
||||
*
|
||||
* 50 MHz can not be obtained exactly, closest we can get is 53 MHz.
|
||||
* For now set the first stage divider to generate 40MHz, and then configure
|
||||
* the second stage dividers to generate the frequency requested.
|
||||
*
|
||||
* The first stage divider is set to the highest possible value for the given
|
||||
* frequency, and the the second stage dividers are used if division factor
|
||||
* is >16.
|
||||
*
|
||||
* Of the second stage dividers, div0 is used for card 0, and div1 is used
|
||||
* for card 1.
|
||||
*/
|
||||
|
||||
static void sdmmc_host_input_clk_enable()
|
||||
static void sdmmc_host_set_clk_div(int div)
|
||||
{
|
||||
// Set frequency to 160MHz / (p + 1) = 40MHz, duty cycle (h + 1)/(p + 1) = 1/2
|
||||
SDMMC.clock.div_factor_p = 3;
|
||||
SDMMC.clock.div_factor_h = 1;
|
||||
SDMMC.clock.div_factor_m = 3;
|
||||
// Set frequency to 160MHz / div
|
||||
// div = p + 1
|
||||
// duty cycle = (h + 1)/(p + 1) (should be = 1/2)
|
||||
assert (div > 1 && div <= 16);
|
||||
int p = div - 1;
|
||||
int h = div / 2 - 1;
|
||||
|
||||
SDMMC.clock.div_factor_p = p;
|
||||
SDMMC.clock.div_factor_h = h;
|
||||
SDMMC.clock.div_factor_m = p;
|
||||
// Set phases for in/out clocks
|
||||
SDMMC.clock.phase_dout = 4;
|
||||
SDMMC.clock.phase_din = 4;
|
||||
SDMMC.clock.phase_dout = 4; // 180 degree phase on the output clock
|
||||
SDMMC.clock.phase_din = 4; // 180 degree phase on the input clock
|
||||
SDMMC.clock.phase_core = 0;
|
||||
// Wait for the clock to propagate
|
||||
ets_delay_us(10);
|
||||
@ -181,26 +197,40 @@ esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz)
|
||||
SDMMC.clkena.cclk_enable &= ~BIT(slot);
|
||||
sdmmc_host_clock_update_command(slot);
|
||||
|
||||
int host_div = 0; /* clock divider of the host (SDMMC.clock) */
|
||||
int card_div = 0; /* 1/2 of card clock divider (SDMMC.clkdiv) */
|
||||
|
||||
// Calculate new dividers
|
||||
int div = 0;
|
||||
if (freq_khz < clk40m) {
|
||||
// round up; extra *2 is because clock divider divides by 2*n
|
||||
div = (clk40m + freq_khz * 2 - 1) / (freq_khz * 2);
|
||||
if (freq_khz >= SDMMC_FREQ_HIGHSPEED) {
|
||||
host_div = 4; // 160 MHz / 4 = 40 MHz
|
||||
card_div = 0;
|
||||
} else if (freq_khz == SDMMC_FREQ_DEFAULT) {
|
||||
host_div = 8; // 160 MHz / 8 = 20 MHz
|
||||
card_div = 0;
|
||||
} else if (freq_khz == SDMMC_FREQ_PROBING) {
|
||||
host_div = 10; // 160 MHz / 10 / (20 * 2) = 400 kHz
|
||||
card_div = 20;
|
||||
} else {
|
||||
host_div = 2;
|
||||
card_div = (clk40m + freq_khz * 2 - 1) / (freq_khz * 2); // round up
|
||||
}
|
||||
ESP_LOGD(TAG, "slot=%d div=%d freq=%dkHz", slot, div,
|
||||
(div == 0) ? clk40m : clk40m / (2 * div));
|
||||
|
||||
ESP_LOGD(TAG, "slot=%d host_div=%d card_div=%d freq=%dkHz",
|
||||
slot, host_div, card_div,
|
||||
2 * APB_CLK_FREQ / host_div / ((card_div == 0) ? 1 : card_div * 2) / 1000);
|
||||
|
||||
// Program CLKDIV and CLKSRC, send them to the CIU
|
||||
switch(slot) {
|
||||
case 0:
|
||||
SDMMC.clksrc.card0 = 0;
|
||||
SDMMC.clkdiv.div0 = div;
|
||||
SDMMC.clkdiv.div0 = card_div;
|
||||
break;
|
||||
case 1:
|
||||
SDMMC.clksrc.card1 = 1;
|
||||
SDMMC.clkdiv.div1 = div;
|
||||
SDMMC.clkdiv.div1 = card_div;
|
||||
break;
|
||||
}
|
||||
sdmmc_host_set_clk_div(host_div);
|
||||
sdmmc_host_clock_update_command(slot);
|
||||
|
||||
// Re-enable clocks
|
||||
@ -243,8 +273,8 @@ esp_err_t sdmmc_host_init()
|
||||
|
||||
periph_module_enable(PERIPH_SDMMC_MODULE);
|
||||
|
||||
// Enable clock to peripheral
|
||||
sdmmc_host_input_clk_enable();
|
||||
// Enable clock to peripheral. Use smallest divider first.
|
||||
sdmmc_host_set_clk_div(2);
|
||||
|
||||
// Reset
|
||||
sdmmc_host_reset();
|
||||
@ -260,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
|
||||
@ -275,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
|
||||
@ -286,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;
|
||||
@ -354,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);
|
||||
@ -383,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);
|
||||
@ -475,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;
|
||||
|
||||
@ -500,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();
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* pstate);
|
||||
static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate);
|
||||
static void process_command_response(uint32_t status, sdmmc_command_t* cmd);
|
||||
static void fill_dma_descriptors(size_t num_desc);
|
||||
static size_t get_free_descriptors_count();
|
||||
|
||||
esp_err_t sdmmc_host_transaction_handler_init()
|
||||
{
|
||||
@ -167,6 +168,28 @@ esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t get_free_descriptors_count()
|
||||
{
|
||||
const size_t next = s_cur_transfer.next_desc;
|
||||
size_t count = 0;
|
||||
/* Starting with the current DMA descriptor, count the number of
|
||||
* descriptors which have 'owned_by_idmac' set to 0. These are the
|
||||
* descriptors already processed by the DMA engine.
|
||||
*/
|
||||
for (size_t i = 0; i < SDMMC_DMA_DESC_CNT; ++i) {
|
||||
sdmmc_desc_t* desc = &s_dma_desc[(next + i) % SDMMC_DMA_DESC_CNT];
|
||||
if (desc->owned_by_idmac) {
|
||||
break;
|
||||
}
|
||||
++count;
|
||||
if (desc->next_desc_ptr == NULL) {
|
||||
/* final descriptor in the chain */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void fill_dma_descriptors(size_t num_desc)
|
||||
{
|
||||
for (size_t i = 0; i < num_desc; ++i) {
|
||||
@ -246,9 +269,8 @@ static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd)
|
||||
} else {
|
||||
res.wait_complete = 1;
|
||||
}
|
||||
if (s_is_app_cmd && cmd->opcode == SD_APP_SET_BUS_WIDTH) {
|
||||
res.send_auto_stop = 1;
|
||||
res.data_expected = 1;
|
||||
if (cmd->opcode == MMC_GO_IDLE_STATE) {
|
||||
res.send_init = 1;
|
||||
}
|
||||
if (cmd->flags & SCF_RSP_PRESENT) {
|
||||
res.response_expect = 1;
|
||||
@ -288,22 +310,22 @@ static void process_command_response(uint32_t status, sdmmc_command_t* cmd)
|
||||
cmd->response[3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((status & SDMMC_INTMASK_RTO) &&
|
||||
cmd->opcode != MMC_ALL_SEND_CID &&
|
||||
cmd->opcode != MMC_SELECT_CARD &&
|
||||
cmd->opcode != MMC_STOP_TRANSMISSION) {
|
||||
cmd->error = ESP_ERR_TIMEOUT;
|
||||
esp_err_t err = ESP_OK;
|
||||
if (status & SDMMC_INTMASK_RTO) {
|
||||
// response timeout is only possible when response is expected
|
||||
assert(cmd->flags & SCF_RSP_PRESENT);
|
||||
err = ESP_ERR_TIMEOUT;
|
||||
} else if ((cmd->flags & SCF_RSP_CRC) && (status & SDMMC_INTMASK_RCRC)) {
|
||||
cmd->error = ESP_ERR_INVALID_CRC;
|
||||
err = ESP_ERR_INVALID_CRC;
|
||||
} else if (status & SDMMC_INTMASK_RESP_ERR) {
|
||||
cmd->error = ESP_ERR_INVALID_RESPONSE;
|
||||
err = ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
if (cmd->error != 0) {
|
||||
if (err != ESP_OK) {
|
||||
cmd->error = err;
|
||||
if (cmd->data) {
|
||||
sdmmc_host_dma_stop();
|
||||
}
|
||||
ESP_LOGD(TAG, "%s: error 0x%x (status=%08x)", __func__, cmd->error, status);
|
||||
ESP_LOGD(TAG, "%s: error 0x%x (status=%08x)", __func__, err, status);
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,15 +380,7 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_r
|
||||
case SDMMC_SENDING_CMD:
|
||||
if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) {
|
||||
process_command_response(orig_evt.sdmmc_status, cmd);
|
||||
if (cmd->error != ESP_ERR_TIMEOUT) {
|
||||
// Unless this is a timeout error, we need to wait for the
|
||||
// CMD_DONE interrupt
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_CMD_DONE) &&
|
||||
cmd->error != ESP_ERR_TIMEOUT) {
|
||||
break;
|
||||
break; // Need to wait for the CMD_DONE interrupt
|
||||
}
|
||||
process_command_response(orig_evt.sdmmc_status, cmd);
|
||||
if (cmd->error != ESP_OK || cmd->data == NULL) {
|
||||
@ -385,7 +399,8 @@ static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_r
|
||||
if (mask_check_and_clear(&evt.dma_status, SDMMC_DMA_DONE_MASK)) {
|
||||
s_cur_transfer.desc_remaining--;
|
||||
if (s_cur_transfer.size_remaining) {
|
||||
fill_dma_descriptors(1);
|
||||
int desc_to_fill = get_free_descriptors_count();
|
||||
fill_dma_descriptors(desc_to_fill);
|
||||
sdmmc_host_dma_resume();
|
||||
}
|
||||
if (s_cur_transfer.desc_remaining == 0) {
|
||||
|
@ -394,7 +394,7 @@ esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
release_bus(slot);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s: cmd=%d error=0x%x", __func__, cmd_index, ret);
|
||||
ESP_LOGD(TAG, "%s: cmd=%d error=0x%x", __func__, cmd_index, ret);
|
||||
} else {
|
||||
// Update internal state when some commands are sent successfully
|
||||
if (cmd_index == SD_CRC_ON_OFF) {
|
||||
@ -408,7 +408,8 @@ esp_err_t sdspi_host_start_command(int slot, sdspi_hw_cmd_t *cmd, void *data,
|
||||
static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
{
|
||||
size_t cmd_size = SDSPI_CMD_R1_SIZE;
|
||||
if (flags & SDSPI_CMD_FLAG_RSP_R1) {
|
||||
if ((flags & SDSPI_CMD_FLAG_RSP_R1) ||
|
||||
(flags & SDSPI_CMD_FLAG_NORSP)) {
|
||||
cmd_size = SDSPI_CMD_R1_SIZE;
|
||||
} else if (flags & SDSPI_CMD_FLAG_RSP_R2) {
|
||||
cmd_size = SDSPI_CMD_R2_SIZE;
|
||||
@ -428,6 +429,11 @@ static esp_err_t start_command_default(int slot, int flags, sdspi_hw_cmd_t *cmd)
|
||||
/* response is a stuff byte from previous transfer, ignore it */
|
||||
cmd->r1 = 0xff;
|
||||
}
|
||||
if (flags & SDSPI_CMD_FLAG_NORSP) {
|
||||
/* no (correct) response expected from the card, so skip polling loop */
|
||||
ESP_LOGV(TAG, "%s: ignoring response byte", __func__);
|
||||
cmd->r1 = 0x00;
|
||||
}
|
||||
int response_delay_bytes = SDSPI_RESPONSE_MAX_DELAY;
|
||||
while ((cmd->r1 & SD_SPI_R1_NO_RESPONSE) != 0 && response_delay_bytes-- > 0) {
|
||||
spi_transaction_t* t = get_transaction(slot);
|
||||
|
@ -89,6 +89,7 @@ typedef struct {
|
||||
#define SDSPI_CMD_FLAG_RSP_R2 BIT(3) //!< Response format R2 (2 bytes)
|
||||
#define SDSPI_CMD_FLAG_RSP_R3 BIT(4) //!< Response format R3 (5 bytes)
|
||||
#define SDSPI_CMD_FLAG_RSP_R7 BIT(5) //!< Response format R7 (5 bytes)
|
||||
#define SDSPI_CMD_FLAG_NORSP BIT(6) //!< Don't expect response (used when sending CMD0 first time).
|
||||
|
||||
#define SDSPI_MAX_DATA_LEN 512 //!< Max size of single block transfer
|
||||
|
||||
|
@ -51,22 +51,22 @@ void make_hw_cmd(uint32_t opcode, uint32_t arg, int timeout_ms, sdspi_hw_cmd_t *
|
||||
hw_cmd->timeout_ms = timeout_ms;
|
||||
}
|
||||
|
||||
static void r1_response_to_err(uint8_t r1, esp_err_t *out_err)
|
||||
static void r1_response_to_err(uint8_t r1, int cmd, esp_err_t *out_err)
|
||||
{
|
||||
if (r1 & SD_SPI_R1_NO_RESPONSE) {
|
||||
ESP_LOGD(TAG, "R1 response not found");
|
||||
ESP_LOGD(TAG, "cmd=%d, R1 response not found", cmd);
|
||||
*out_err = ESP_ERR_TIMEOUT;
|
||||
} else if (r1 & SD_SPI_R1_CMD_CRC_ERR) {
|
||||
ESP_LOGD(TAG, "R1 response: command CRC error");
|
||||
ESP_LOGD(TAG, "cmd=%d, R1 response: command CRC error", cmd);
|
||||
*out_err = ESP_ERR_INVALID_CRC;
|
||||
} else if (r1 & SD_SPI_R1_ILLEGAL_CMD) {
|
||||
ESP_LOGD(TAG, "R1 response: command not supported");
|
||||
ESP_LOGD(TAG, "cmd=%d, R1 response: command not supported", cmd);
|
||||
*out_err = ESP_ERR_NOT_SUPPORTED;
|
||||
} else if (r1 & SD_SPI_R1_ADDR_ERR) {
|
||||
ESP_LOGD(TAG, "R1 response: alignment error");
|
||||
ESP_LOGD(TAG, "cmd=%d, R1 response: alignment error", cmd);
|
||||
*out_err = ESP_ERR_INVALID_ARG;
|
||||
} else if (r1 & SD_SPI_R1_PARAM_ERR) {
|
||||
ESP_LOGD(TAG, "R1 response: size error");
|
||||
ESP_LOGD(TAG, "cmd=%d, R1 response: size error", cmd);
|
||||
*out_err = ESP_ERR_INVALID_SIZE;
|
||||
} else if ((r1 & SD_SPI_R1_ERASE_RST) ||
|
||||
(r1 & SD_SPI_R1_ERASE_SEQ_ERR)) {
|
||||
@ -74,7 +74,7 @@ static void r1_response_to_err(uint8_t r1, esp_err_t *out_err)
|
||||
} else if (r1 & SD_SPI_R1_IDLE_STATE) {
|
||||
// Idle state is handled at command layer
|
||||
} else if (r1 != 0) {
|
||||
ESP_LOGD(TAG, "R1 response: unexpected value 0x%02x", r1);
|
||||
ESP_LOGD(TAG, "cmd=%d, R1 response: unexpected value 0x%02x", cmd, r1);
|
||||
*out_err = ESP_ERR_INVALID_RESPONSE;
|
||||
}
|
||||
}
|
||||
@ -107,6 +107,10 @@ esp_err_t sdspi_host_do_transaction(int slot, sdmmc_command_t *cmdinfo)
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R3;
|
||||
} else if (s_app_cmd && cmdinfo->opcode == SD_APP_SD_STATUS) {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R2;
|
||||
} else if (!s_app_cmd && cmdinfo->opcode == MMC_GO_IDLE_STATE &&
|
||||
!(cmdinfo->flags & SCF_RSP_R1)) {
|
||||
/* used to send CMD0 without expecting a response */
|
||||
flags |= SDSPI_CMD_FLAG_NORSP;
|
||||
} else {
|
||||
flags |= SDSPI_CMD_FLAG_RSP_R1;
|
||||
}
|
||||
@ -121,11 +125,11 @@ esp_err_t sdspi_host_do_transaction(int slot, sdmmc_command_t *cmdinfo)
|
||||
// Some errors should be reported using return code
|
||||
if (flags & SDSPI_CMD_FLAG_RSP_R1) {
|
||||
cmdinfo->response[0] = hw_cmd.r1;
|
||||
r1_response_to_err(hw_cmd.r1, &ret);
|
||||
r1_response_to_err(hw_cmd.r1, cmdinfo->opcode, &ret);
|
||||
} else if (flags & SDSPI_CMD_FLAG_RSP_R2) {
|
||||
cmdinfo->response[0] = (((uint32_t)hw_cmd.r1) << 8) | (hw_cmd.response[0] >> 24);
|
||||
} else if (flags & (SDSPI_CMD_FLAG_RSP_R3 | SDSPI_CMD_FLAG_RSP_R7)) {
|
||||
r1_response_to_err(hw_cmd.r1, &ret);
|
||||
r1_response_to_err(hw_cmd.r1, cmdinfo->opcode, &ret);
|
||||
cmdinfo->response[0] = __builtin_bswap32(hw_cmd.response[0]);
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,9 @@ extern "C" {
|
||||
#define GPIO_ID_PIN(n) (GPIO_ID_PIN0+(n))
|
||||
#define GPIO_PIN_ADDR(i) (GPIO_PIN0_REG + i*4)
|
||||
|
||||
#define GPIO_FUNC_IN_HIGH 0x38
|
||||
#define GPIO_FUNC_IN_LOW 0x30
|
||||
|
||||
#define GPIO_ID_IS_PIN_REGISTER(reg_id) \
|
||||
((reg_id >= GPIO_ID_PIN0) && (reg_id <= GPIO_ID_PIN(GPIO_PIN_COUNT-1)))
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -36,6 +37,12 @@
|
||||
#define SDMMC_DEFAULT_CMD_TIMEOUT_MS 1000 // Max timeout of ordinary commands
|
||||
#define SDMMC_WRITE_CMD_TIMEOUT_MS 5000 // Max timeout of write commands
|
||||
|
||||
/* Maximum retry/error count for SEND_OP_COND (CMD1).
|
||||
* These are somewhat arbitrary, values originate from OpenBSD driver.
|
||||
*/
|
||||
#define SDMMC_SEND_OP_COND_MAX_RETRIES 100
|
||||
#define SDMMC_SEND_OP_COND_MAX_ERRORS 3
|
||||
|
||||
static const char* TAG = "sdmmc_cmd";
|
||||
|
||||
static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
|
||||
@ -53,13 +60,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 +76,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,31 +90,37 @@ 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 (in SD mode) or
|
||||
* return "invalid command" error (in SPI mode).
|
||||
*/
|
||||
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
|
||||
&& !(is_spi && err == ESP_ERR_NOT_SUPPORTED)) {
|
||||
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;
|
||||
}
|
||||
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);
|
||||
|
||||
/* SEND_IF_COND (CMD8) command is used to identify SDHC/SDXC cards.
|
||||
* SD v1 and non-SD cards will not respond to this command.
|
||||
@ -120,51 +139,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.
|
||||
*/
|
||||
|
||||
/* 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);
|
||||
/* 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_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);
|
||||
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;
|
||||
|
||||
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: read_ocr 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);
|
||||
}
|
||||
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);
|
||||
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: send_op_cond (1) returned 0x%x", __func__, err);
|
||||
return err;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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 +230,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,118 +259,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 (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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
err = (*config->set_bus_width)(config->slot, 4);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "slot->set_bus_width failed");
|
||||
return err;
|
||||
}
|
||||
uint32_t status;
|
||||
err = sdmmc_send_cmd_stop_transmission(card, &status);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "stop_transmission failed (0x%x)", err);
|
||||
return err;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 % 10 == 0) {
|
||||
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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).
|
||||
@ -337,15 +391,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;
|
||||
@ -363,6 +423,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) {
|
||||
@ -376,7 +456,7 @@ static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd)
|
||||
slot, cmd->opcode, cmd->arg, cmd->flags, cmd->data, cmd->blklen, cmd->datalen, cmd->timeout_ms);
|
||||
esp_err_t err = (*card->host.do_transaction)(slot, cmd);
|
||||
if (err != 0) {
|
||||
ESP_LOGD(TAG, "sdmmc_req_run returned 0x%x", err);
|
||||
ESP_LOGD(TAG, "cmd=%d, sdmmc_req_run returned 0x%x", cmd->opcode, err);
|
||||
return err;
|
||||
}
|
||||
int state = MMC_R1_CURRENT_STATE(cmd->response);
|
||||
@ -416,7 +496,21 @@ static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card)
|
||||
.opcode = MMC_GO_IDLE_STATE,
|
||||
.flags = SCF_CMD_BC | SCF_RSP_R0,
|
||||
};
|
||||
return sdmmc_send_cmd(card, &cmd);
|
||||
esp_err_t err = sdmmc_send_cmd(card, &cmd);
|
||||
if (host_is_spi(card)) {
|
||||
/* To enter SPI mode, CMD0 needs to be sent twice (see figure 4-1 in
|
||||
* SD Simplified spec v4.10). Some cards enter SD mode on first CMD0,
|
||||
* so don't expect the above command to succeed.
|
||||
* SCF_RSP_R1 flag below tells the lower layer to expect correct R1
|
||||
* response (in SPI mode).
|
||||
*/
|
||||
(void) err;
|
||||
vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS);
|
||||
|
||||
cmd.flags |= SCF_RSP_R1;
|
||||
err = sdmmc_send_cmd(card, &cmd);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@ -447,11 +541,18 @@ static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, u
|
||||
.flags = SCF_CMD_BCR | SCF_RSP_R3,
|
||||
.opcode = SD_APP_OP_COND
|
||||
};
|
||||
int nretries = 100; // arbitrary, BSD driver uses this value
|
||||
int nretries = SDMMC_SEND_OP_COND_MAX_RETRIES;
|
||||
int err_cnt = SDMMC_SEND_OP_COND_MAX_ERRORS;
|
||||
for (; nretries != 0; --nretries) {
|
||||
esp_err_t err = sdmmc_send_app_cmd(card, &cmd);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
if (--err_cnt == 0) {
|
||||
ESP_LOGD(TAG, "%s: sdmmc_send_app_cmd err=0x%x", __func__, err);
|
||||
return err;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "%s: ignoring err=0x%x", __func__, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// In SD protocol, card sets MEM_READY bit in OCR when it is ready.
|
||||
// In SPI protocol, card clears IDLE_STATE bit in R1 response.
|
||||
@ -681,20 +782,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");
|
||||
@ -950,6 +1037,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;
|
||||
@ -980,3 +1071,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);
|
||||
}
|
||||
|
382
components/sdmmc/test/test_sdio.c
Normal file
382
components/sdmmc/test/test_sdio.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -101,7 +101,6 @@ INPUT = \
|
||||
## SPIFFS
|
||||
../../components/spiffs/include/esp_spiffs.h \
|
||||
## SD/MMC Card Host
|
||||
## NOTE: for three lines below header_file.inc is not used
|
||||
../../components/sdmmc/include/sdmmc_cmd.h \
|
||||
../../components/driver/include/driver/sdmmc_host.h \
|
||||
../../components/driver/include/driver/sdmmc_types.h \
|
||||
|
@ -13,7 +13,8 @@ Peripherals API
|
||||
MCPWM <mcpwm>
|
||||
Pulse Counter <pcnt>
|
||||
Remote Control <rmt>
|
||||
SD/MMC Card Host <../storage/sdmmc>
|
||||
SDMMC Host <sdmmc_host>
|
||||
SD SPI Host <sdspi_host>
|
||||
Sigma-delta Modulation <sigmadelta>
|
||||
SPI Master <spi_master>
|
||||
SPI Slave <spi_slave>
|
||||
|
66
docs/en/api-reference/peripherals/sdmmc_host.rst
Normal file
66
docs/en/api-reference/peripherals/sdmmc_host.rst
Normal file
@ -0,0 +1,66 @@
|
||||
SDMMC Host Driver
|
||||
=================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
On the ESP32, SDMMC host peripheral has two slots:
|
||||
|
||||
- Slot 0 (:c:macro:`SDMMC_HOST_SLOT_0`) is an 8-bit slot. It uses ``HS1_*`` signals in the PIN MUX.
|
||||
- Slot 1 (:c:macro:`SDMMC_HOST_SLOT_1`) is a 4-bit slot. It uses ``HS2_*`` signals in the PIN MUX.
|
||||
|
||||
Pin mappings of these slots are given in the following table:
|
||||
|
||||
+--------+-------------+-------------+
|
||||
| Signal | Slot 0 | Slot 1 |
|
||||
+========+=============+=============+
|
||||
| CMD | GPIO11 | GPIO15 |
|
||||
+--------+-------------+-------------+
|
||||
| CLK | GPIO6 | GPIO14 |
|
||||
+--------+-------------+-------------+
|
||||
| D0 | GPIO7 | GPIO2 |
|
||||
+--------+-------------+-------------+
|
||||
| D1 | GPIO8 | GPIO4 |
|
||||
+--------+-------------+-------------+
|
||||
| D2 | GPIO9 | GPIO12 |
|
||||
+--------+-------------+-------------+
|
||||
| D3 | GPIO10 | GPIO13 |
|
||||
+--------+-------------+-------------+
|
||||
| D4 | GPIO16 | |
|
||||
+--------+-------------+-------------+
|
||||
| D5 | GPIO17 | |
|
||||
+--------+-------------+-------------+
|
||||
| D6 | GPIO5 | |
|
||||
+--------+-------------+-------------+
|
||||
| D7 | GPIO18 | |
|
||||
+--------+-------------+-------------+
|
||||
| CD | any input via GPIO matrix |
|
||||
+--------+---------------------------+
|
||||
| WP | any input via GPIO matrix |
|
||||
+--------+---------------------------+
|
||||
|
||||
Card Detect and Write Protect signals can be routed to arbitrary pins using GPIO matrix. To use these pins, set ``gpio_cd`` and ``gpio_wp`` members of :cpp:class:`sdmmc_slot_config_t` structure when calling :cpp:func:`sdmmc_host_init_slot`. Note that it is not advised to specify Card Detect pin when working with SDIO cards, because in ESP32 card detect signal can also trigger SDIO slave interrupt.
|
||||
|
||||
.. warning::
|
||||
|
||||
Pins used by slot 0 (``HS1_*``) are also used to connect SPI flash chip in ESP-WROOM32 and ESP32-WROVER modules. These pins can not be shared between SD card and SPI flash. If you need to use Slot 0, connect SPI flash to different pins and set Efuses accordingly.
|
||||
|
||||
|
||||
Using the SDMMC Host driver
|
||||
---------------------------
|
||||
|
||||
Of all the funtions listed below, only :cpp:func:`sdmmc_host_init`, :cpp:func:`sdmmc_host_init_slot`, and :cpp:func:`sdmmc_host_deinit` will be used directly by most applications.
|
||||
|
||||
Other functions, such as :cpp:func:`sdmmc_host_set_bus_width`, :cpp:func:`sdmmc_host_set_card_clk`, and :cpp:func:`sdmmc_host_do_transaction` will be called by the SD/MMC protocol layer via function pointers in :cpp:class:`sdmmc_host_t` structure.
|
||||
|
||||
See also
|
||||
--------
|
||||
|
||||
See :doc:`SD/SDIO/MMC Driver <../storage/sdmmc>` for the higher level driver which implements the protocol layer.
|
||||
|
||||
See :doc:`SD SPI Host Driver <sdspi_host>` for a similar driver which uses SPI controller and is limited to SPI mode of SD protocol.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include:: /_build/inc/sdmmc_host.inc
|
25
docs/en/api-reference/peripherals/sdspi_host.rst
Normal file
25
docs/en/api-reference/peripherals/sdspi_host.rst
Normal file
@ -0,0 +1,25 @@
|
||||
SD SPI Host Driver
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
SPI controllers accessible via spi_master driver (HSPI, VSPI) can be used to work with SD cards. The driver which provides this capability is called "SD SPI Host", due to its similarity with the :doc:`SDMMC Host <sdmmc_host>` driver.
|
||||
|
||||
In SPI mode, SD driver has lower throughput than in 1-line SD mode. However SPI mode makes pin selection more flexible, as SPI peripheral can be connected to any ESP32 pins using GPIO Matrix. SD SPI driver uses software controlled CS signal. Currently SD SPI driver assumes that it can use the SPI controller exclusively, so applications which need to share SPI bus between SD cards and other peripherals need to make sure that SD card and other devices are not used at the same time from different tasks.
|
||||
|
||||
SD SPI driver is represented using an :cpp:class:`sdmmc_host_t` structure initialized using :c:macro:`SDSPI_HOST_DEFAULT` macro. For slot initialization, :c:macro:`SDSPI_SLOT_CONFIG_DEFAULT` can be used to fill in default pin mapping, which is the same as the pin mapping in SD mode.
|
||||
|
||||
SD SPI driver APIs are very similar to :doc:`SDMMC host APIs <sdmmc_host>`. As with the SDMMC host driver, only :cpp:func:`sdspi_host_init`, :cpp:func:`sdspi_host_init_slot`, and :cpp:func:`sdspi_host_deinit` functions are normally used by the applications. Other functions are called by the protocol level driver via function pointers in :cpp:class:`sdmmc_host_t` structure.
|
||||
|
||||
.. note:
|
||||
|
||||
SD over SPI does not support speeds above SDMMC_FREQ_DEFAULT due to a limitation of SPI driver.
|
||||
|
||||
See :doc:`SD/SDIO/MMC Driver <../storage/sdmmc>` for the higher level driver which implements the protocol layer.
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include:: /_build/inc/sdspi_host.inc
|
@ -5,7 +5,7 @@ Storage API
|
||||
:maxdepth: 1
|
||||
|
||||
SPI Flash and Partition APIs <spi_flash>
|
||||
SD/MMC Card Host <sdmmc>
|
||||
SD/SDIO/MMC Driver <sdmmc>
|
||||
Non-Volatile Storage <nvs_flash>
|
||||
Virtual Filesystem <vfs>
|
||||
FAT Filesystem <fatfs>
|
||||
|
@ -1,18 +1,19 @@
|
||||
SDMMC Host Peripheral
|
||||
=====================
|
||||
SD/SDIO/MMC Driver
|
||||
==================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
SDMMC peripheral supports SD and MMC memory cards and SDIO cards. SDMMC software builds on top of SDMMC driver and consists of the following parts:
|
||||
SD/SDIO/MMC driver currently supports SD memory and IO cards. Support for MCC/eMMC cards will be added in the future. This protocol level driver builds on top of SDMMC and SD SPI host drivers.
|
||||
|
||||
1. SDMMC host driver (``driver/sdmmc_host.h``) — this driver provides APIs to send commands to the slave device(s), send and receive data, and handling error conditions on the bus.
|
||||
SDMMC and SD SPI host drivers (``driver/sdmmc_host.h``) provide APIs to send commands to the slave device(s), send and receive data, and handle error conditions on the bus.
|
||||
|
||||
- See :doc:`SDMMC Host API <../peripherals/sdmmc_host>` for functions used to initialize and configure SDMMC host.
|
||||
- See :doc:`SD SPI Host API <../peripherals/sdspi_host>` for functions used to initialize and configure SD SPI host.
|
||||
|
||||
2. SDMMC protocol layer (``sdmmc_cmd.h``) — this component handles specifics of SD protocol such as card initialization and data transfer commands. Despite the name, only SD (SDSC/SDHC/SDXC) cards are supported at the moment. Support for MCC/eMMC cards can be added in the future.
|
||||
SDMMC protocol layer (``sdmmc_cmd.h``), described in this document, handles specifics of SD protocol such as card initialization and data transfer commands.
|
||||
|
||||
Protocol layer works with the host via ``sdmmc_host_t`` structure. This structure contains pointers to various functions of the host.
|
||||
|
||||
In addition to SDMMC Host peripheral, ESP32 has SPI peripherals which can also be used to work with SD cards. This is supported using a variant of the host driver, ``driver/sdspi_host.h``. This driver has the same interface as SDMMC host driver, and the protocol layer can use either of two.
|
||||
Protocol layer works with the host via :cpp:class:`sdmmc_host_t` structure. This structure contains pointers to various functions of the host.
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
@ -23,107 +24,54 @@ An example which combines SDMMC driver with FATFS library is provided in ``examp
|
||||
Protocol layer APIs
|
||||
-------------------
|
||||
|
||||
Protocol layer is given ``sdmmc_host_t`` structure which describes the SD/MMC host driver, lists its capabilites, and provides pointers to functions of the driver. Protocol layer stores card-specific information in ``sdmmc_card_t`` structure. When sending commands to the SD/MMC host driver, protocol layer uses ``sdmmc_command_t`` structure to describe the command, argument, expected return value, and data to transfer, if any.
|
||||
Protocol layer is given :cpp:class:`sdmmc_host_t` structure which describes the SD/MMC host driver, lists its capabilites, and provides pointers to functions of the driver. Protocol layer stores card-specific information in :cpp:class:`sdmmc_card_t` structure. When sending commands to the SD/MMC host driver, protocol layer uses :cpp:class:`sdmmc_command_t` structure to describe the command, argument, expected return value, and data to transfer, if any.
|
||||
|
||||
Normal usage of the protocol layer is as follows:
|
||||
Usage with SD memory cards
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. Call the host driver functions to initialize the host (e.g. ``sdmmc_host_init``, ``sdmmc_host_init_slot``).
|
||||
2. Call ``sdmmc_card_init`` to initialize the card, passing it host driver information (``host``) and a pointer to ``sdmmc_card_t`` structure which will be filled in (``card``).
|
||||
3. To read and write sectors of the card, use ``sdmmc_read_sectors`` and ``sdmmc_write_sectors``, passing the pointer to card information structure (``card``).
|
||||
4. When card is not used anymore, call the host driver function to disable SDMMC host peripheral and free resources allocated by the driver (e.g. ``sdmmc_host_deinit``).
|
||||
1. Call the host driver functions to initialize the host (e.g. :cpp:func:`sdmmc_host_init`, :cpp:func:`sdmmc_host_init_slot`).
|
||||
2. Call :cpp:func:`sdmmc_card_init` to initialize the card, passing it host driver information (``host``) and a pointer to :cpp:class:`sdmmc_card_t` structure which will be filled in (``card``).
|
||||
3. To read and write sectors of the card, use :cpp:func:`sdmmc_read_sectors` and :cpp:func:`sdmmc_write_sectors`, passing the pointer to card information structure (``card``).
|
||||
4. When card is not used anymore, call the host driver function to disable the host peripheral and free resources allocated by the driver (e.g. :cpp:func:`sdmmc_host_deinit`).
|
||||
|
||||
Most applications need to use the protocol layer only in one task; therefore the protocol layer doesn't implement any kind of locking on the ``sdmmc_card_t`` structure, or when accessing SDMMC host driver. Such locking has to be implemented in the higher layer, if necessary (e.g. in the filesystem driver).
|
||||
Usage with SDIO cards
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. doxygenstruct:: sdmmc_host_t
|
||||
:members:
|
||||
Initialization an probing process is the same as with SD memory cards. Only data transfer commands differ in SDIO mode.
|
||||
|
||||
.. doxygendefine:: SDMMC_HOST_FLAG_1BIT
|
||||
.. doxygendefine:: SDMMC_HOST_FLAG_4BIT
|
||||
.. doxygendefine:: SDMMC_HOST_FLAG_8BIT
|
||||
.. doxygendefine:: SDMMC_HOST_FLAG_SPI
|
||||
.. doxygendefine:: SDMMC_FREQ_DEFAULT
|
||||
.. doxygendefine:: SDMMC_FREQ_HIGHSPEED
|
||||
.. doxygendefine:: SDMMC_FREQ_PROBING
|
||||
During probing and card initialization (done by :cpp:func:`sdmmc_card_init`), the driver only configures the following registers of the IO card:
|
||||
|
||||
.. doxygenstruct:: sdmmc_command_t
|
||||
:members:
|
||||
1. The IO portion of the card is reset by setting RES bit in "I/O Abort" (0x06) register.
|
||||
2. If 4-line mode is enalbed in host and slot configuration, driver attempts to set "Bus width" field in "Bus Interface Control" (0x07) register. If that succeeds (which means that slave supports 4-line mode), host is also switched to 4-line mode.
|
||||
3. If high-speed mode is enabled in host configuration, SHS bit is set in "High Speed" (0x13) register.
|
||||
|
||||
.. doxygenstruct:: sdmmc_card_t
|
||||
:members:
|
||||
In particular, the driver does not set any of the bits in I/O Enable, Int Enable registers, IO block sizes, etc. Applications can set these by calling :cpp:func:`sdmmc_io_write_byte`.
|
||||
|
||||
.. doxygenstruct:: sdmmc_csd_t
|
||||
:members:
|
||||
For card configuration and data transfer, use one of the following functions:
|
||||
|
||||
.. doxygenstruct:: sdmmc_cid_t
|
||||
:members:
|
||||
- :cpp:func:`sdmmc_io_read_byte`, :cpp:func:`sdmmc_io_write_byte` — read and write single byte using IO_RW_DIRECT (CMD52).
|
||||
- :cpp:func:`sdmmc_io_read_bytes`, :cpp:func:`sdmmc_io_write_bytes` — read and write multiple bytes using IO_RW_EXTENDED (CMD53), in byte mode.
|
||||
- :cpp:func:`sdmmc_io_read_blocks`, :cpp:func:`sdmmc_io_write_blocks` — read and write blocks of data using IO_RW_EXTENDED (CMD53), in block mode.
|
||||
|
||||
.. doxygenstruct:: sdmmc_scr_t
|
||||
:members:
|
||||
SDIO interrupts can be enabled by the application using :cpp:func:`sdmmc_io_enable_int` function. When using SDIO in 1-line mode, D1 line also needs to be connected to use SDIO interrupts.
|
||||
|
||||
.. doxygenfunction:: sdmmc_card_init
|
||||
.. doxygenfunction:: sdmmc_write_sectors
|
||||
.. doxygenfunction:: sdmmc_read_sectors
|
||||
The application can wait for SDIO interrupt to occur using :cpp:func:`sdmmc_io_wait_int`.
|
||||
|
||||
SDMMC host driver APIs
|
||||
----------------------
|
||||
Combo (memory + IO) cards
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
On the ESP32, SDMMC host peripheral has two slots:
|
||||
|
||||
- Slot 0 (``SDMMC_HOST_SLOT_0``) is an 8-bit slot. It uses ``HS1_*`` signals in the PIN MUX.
|
||||
- Slot 1 (``SDMMC_HOST_SLOT_1``) is a 4-bit slot. It uses ``HS2_*`` signals in the PIN MUX.
|
||||
|
||||
Card Detect and Write Protect signals can be routed to arbitrary pins using GPIO matrix. To use these pins, set ``gpio_cd`` and ``gpio_wp`` members of ``sdmmc_slot_config_t`` structure when calling ``sdmmc_host_init_slot``.
|
||||
|
||||
Of all the funtions listed below, only ``sdmmc_host_init``, ``sdmmc_host_init_slot``, and ``sdmmc_host_deinit`` will be used directly by most applications. Other functions, such as ``sdmmc_host_set_bus_width``, ``sdmmc_host_set_card_clk``, and ``sdmmc_host_do_transaction`` will be called by the SD/MMC protocol layer via function pointers in ``sdmmc_host_t`` structure.
|
||||
|
||||
.. doxygenfunction:: sdmmc_host_init
|
||||
|
||||
.. doxygendefine:: SDMMC_HOST_SLOT_0
|
||||
.. doxygendefine:: SDMMC_HOST_SLOT_1
|
||||
.. doxygendefine:: SDMMC_HOST_DEFAULT
|
||||
.. doxygendefine:: SDMMC_SLOT_WIDTH_DEFAULT
|
||||
|
||||
.. doxygenfunction:: sdmmc_host_init_slot
|
||||
|
||||
.. doxygenstruct:: sdmmc_slot_config_t
|
||||
:members:
|
||||
|
||||
.. doxygendefine:: SDMMC_SLOT_NO_CD
|
||||
.. doxygendefine:: SDMMC_SLOT_NO_WP
|
||||
.. doxygendefine:: SDMMC_SLOT_CONFIG_DEFAULT
|
||||
|
||||
.. doxygenfunction:: sdmmc_host_set_bus_width
|
||||
.. doxygenfunction:: sdmmc_host_set_card_clk
|
||||
.. doxygenfunction:: sdmmc_host_do_transaction
|
||||
.. doxygenfunction:: sdmmc_host_deinit
|
||||
|
||||
SD SPI driver APIs
|
||||
------------------
|
||||
|
||||
SPI controllers accessible via spi_master driver (HSPI, VSPI) can be used to work with SD cards. In SPI mode, SD driver has lower throughput than in 1-line SD mode. However SPI mode makes pin selection more flexible, as SPI peripheral can be connected to any ESP32 pins using GPIO Matrix. SD SPI driver uses software controlled CS signal. Currently SD SPI driver assumes that it can use the SPI controller exclusively, so applications which need to share SPI bus between SD cards and other peripherals need to make sure that SD card and other devices are not used at the same time from different tasks.
|
||||
|
||||
SD SPI driver is represented using an ``sdmmc_host_t`` structure initialized using ``SDSPI_HOST_DEFAULT`` macro. For slot initialization, ``SDSPI_SLOT_CONFIG_DEFAULT`` can be used to fill in default pin mapping, which is the same as the pin mapping in SD mode.
|
||||
|
||||
SD SPI driver APIs are very similar to SDMMC host APIs. As with the SDMMC host driver, only ``sdspi_host_init``, ``sdspi_host_init_slot``, and ``sdspi_host_deinit`` functions are normally used by the applications. Other functions are called by the protocol level driver via function pointers in ``sdmmc_host_t`` structure.
|
||||
|
||||
.. note:
|
||||
|
||||
SD over SPI does not support speeds above SDMMC_FREQ_DEFAULT due to a limitation of SPI driver.
|
||||
The driver does not support SD combo cards. Combo cards will be treated as IO cards.
|
||||
|
||||
|
||||
.. doxygenfunction:: sdspi_host_init
|
||||
Thread safety
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
.. doxygendefine:: SDSPI_HOST_DEFAULT
|
||||
Most applications need to use the protocol layer only in one task; therefore the protocol layer doesn't implement any kind of locking on the :cpp:class:`sdmmc_card_t` structure, or when accessing SDMMC or SD SPI host drivers. Such locking is usually implemented in the higher layer (e.g. in the filesystem driver).
|
||||
|
||||
.. doxygenfunction:: sdspi_host_init_slot
|
||||
|
||||
.. doxygenstruct:: sdspi_slot_config_t
|
||||
:members:
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. doxygendefine:: SDSPI_SLOT_NO_CD
|
||||
.. doxygendefine:: SDSPI_SLOT_NO_WP
|
||||
.. doxygendefine:: SDSPI_SLOT_CONFIG_DEFAULT
|
||||
.. include:: /_build/inc/sdmmc_cmd.inc
|
||||
|
||||
.. doxygenfunction:: sdspi_host_set_card_clk
|
||||
.. doxygenfunction:: sdspi_host_do_transaction
|
||||
.. doxygenfunction:: sdspi_host_deinit
|
||||
.. include:: /_build/inc/sdmmc_types.inc
|
||||
|
1
docs/zh_CN/api-reference/peripherals/sdmmc_host.rst
Normal file
1
docs/zh_CN/api-reference/peripherals/sdmmc_host.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../../en/api-reference/peripherals/sdmmc_host.rst
|
1
docs/zh_CN/api-reference/peripherals/sdspi_host.rst
Normal file
1
docs/zh_CN/api-reference/peripherals/sdspi_host.rst
Normal file
@ -0,0 +1 @@
|
||||
.. include:: ../../../en/api-reference/peripherals/sdspi_host.rst
|
Loading…
Reference in New Issue
Block a user