]> granicus.if.org Git - esp-idf/commitdiff
sdmmc: add SDIO support
authorIvan Grokhotkov <ivan@espressif.com>
Tue, 6 Mar 2018 09:57:52 +0000 (17:57 +0800)
committerIvan Grokhotkov <ivan@espressif.com>
Wed, 11 Apr 2018 03:07:13 +0000 (11:07 +0800)
- Add SDIO support at protocol layer (probing, data transfer, interrupts)
- Add SDIO interrupts support in SDMMC host
- Add test (communicate with ESP32 in SDIO download mode)

components/driver/include/driver/sdmmc_defs.h
components/driver/include/driver/sdmmc_host.h
components/driver/include/driver/sdmmc_types.h
components/driver/include/driver/sdspi_host.h
components/driver/sdmmc_host.c
components/sdmmc/include/sdmmc_cmd.h
components/sdmmc/sdmmc_cmd.c
components/sdmmc/test/test_sdio.c [new file with mode: 0644]
components/soc/esp32/include/soc/sdmmc_reg.h

index 96e0743d5fd568097b5dbf3d83c3b223d35f7aa8..b9b13680dd15d6786621f1695f24496a1f58b3a3 100644 (file)
 #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 */
 /* 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_
index c298889ed3c6275beaf8cfed070b8f2801fdd00f..aa3357a736cd851cedbf797a985db2550f26a2be 100644 (file)
@@ -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
  *
index cece4174ef7f67e8aa4c4140507c09e6654df6c1..99e9715762a5e503bdec643ec940964fadc1bb6c 100644 (file)
@@ -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_
index 54eba081e90ea44a228e1af9e4ba50e67323be3b..9dad6cf7a7acf933f932b6a2be404a309cc8619d 100644 (file)
@@ -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, \
 }
 
index e33da77501cda05ae50ac82fa250b22828760382..f09d94d58569d1219d01c8b5427856c07338eb18 100644 (file)
@@ -26,6 +26,7 @@
 #include "driver/sdmmc_host.h"
 #include "driver/periph_ctrl.h"
 #include "sdmmc_private.h"
+#include "freertos/semphr.h"
 
 #define SDMMC_EVENT_QUEUE_LENGTH 32
 
@@ -40,9 +41,11 @@ typedef struct {
     uint32_t d5;
     uint32_t d6;
     uint32_t d7;
+    uint8_t d1_gpio;
     uint8_t d3_gpio;
     uint8_t card_detect;
     uint8_t write_protect;
+    uint8_t card_int;
     uint8_t width;
 } sdmmc_slot_info_t;
 
@@ -58,6 +61,7 @@ static const sdmmc_slot_info_t s_slot_info[2]  = {
         .d1 = PERIPHS_IO_MUX_SD_DATA1_U,
         .d2 = PERIPHS_IO_MUX_SD_DATA2_U,
         .d3 = PERIPHS_IO_MUX_SD_DATA3_U,
+        .d1_gpio = 8,
         .d3_gpio = 10,
         .d4 = PERIPHS_IO_MUX_GPIO16_U,
         .d5 = PERIPHS_IO_MUX_GPIO17_U,
@@ -65,6 +69,7 @@ static const sdmmc_slot_info_t s_slot_info[2]  = {
         .d7 = PERIPHS_IO_MUX_GPIO18_U,
         .card_detect = HOST_CARD_DETECT_N_1_IDX,
         .write_protect = HOST_CARD_WRITE_PRT_1_IDX,
+        .card_int = HOST_CARD_INT_N_1_IDX,
         .width = 8
     },
     {
@@ -74,9 +79,11 @@ static const sdmmc_slot_info_t s_slot_info[2]  = {
         .d1 = PERIPHS_IO_MUX_GPIO4_U,
         .d2 = PERIPHS_IO_MUX_MTDI_U,
         .d3 = PERIPHS_IO_MUX_MTCK_U,
+        .d1_gpio = 4,
         .d3_gpio = 13,
         .card_detect = HOST_CARD_DETECT_N_2_IDX,
         .write_protect = HOST_CARD_WRITE_PRT_2_IDX,
+        .card_int = HOST_CARD_INT_N_2_IDX,
         .width = 4
     }
 };
@@ -84,6 +91,7 @@ static const sdmmc_slot_info_t s_slot_info[2]  = {
 static const char* TAG = "sdmmc_periph";
 static intr_handle_t s_intr_handle;
 static QueueHandle_t s_event_queue;
+static SemaphoreHandle_t s_io_intr_event;
 
 size_t s_slot_width[2] = {1,1};
 
@@ -282,11 +290,19 @@ esp_err_t sdmmc_host_init()
     if (!s_event_queue) {
         return ESP_ERR_NO_MEM;
     }
+    s_io_intr_event = xSemaphoreCreateBinary();
+    if (!s_io_intr_event) {
+        vQueueDelete(s_event_queue);
+        s_event_queue = NULL;
+        return ESP_ERR_NO_MEM;
+    }
     // Attach interrupt handler
     esp_err_t ret = esp_intr_alloc(ETS_SDIO_HOST_INTR_SOURCE, 0, &sdmmc_isr, s_event_queue, &s_intr_handle);
     if (ret != ESP_OK) {
         vQueueDelete(s_event_queue);
         s_event_queue = NULL;
+        vSemaphoreDelete(s_io_intr_event);
+        s_io_intr_event = NULL;
         return ret;
     }
     // Enable interrupts
@@ -297,7 +313,7 @@ esp_err_t sdmmc_host_init()
             SDMMC_INTMASK_RCRC | SDMMC_INTMASK_DCRC |
             SDMMC_INTMASK_RTO | SDMMC_INTMASK_DTO | SDMMC_INTMASK_HTO |
             SDMMC_INTMASK_SBE | SDMMC_INTMASK_EBE |
-            SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE;
+            SDMMC_INTMASK_RESP_ERR | SDMMC_INTMASK_HLE; //sdio is enabled only when use.
     SDMMC.ctrl.int_enable = 1;
 
     // Enable DMA
@@ -308,6 +324,8 @@ esp_err_t sdmmc_host_init()
     if (ret != ESP_OK) {
         vQueueDelete(s_event_queue);
         s_event_queue = NULL;
+        vSemaphoreDelete(s_io_intr_event);
+        s_io_intr_event = NULL;
         esp_intr_free(s_intr_handle);
         s_intr_handle = NULL;
         return ret;
@@ -376,10 +394,17 @@ esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config)
             configure_pin(pslot->d7);
         }
     }
+
+    // SDIO slave interrupt is edge sensitive to ~(int_n | card_int | card_detect)
+    // set this and card_detect to high to enable sdio interrupt
+    gpio_matrix_in(GPIO_FUNC_IN_HIGH, pslot->card_int, 0);
     if (gpio_cd != -1) {
         gpio_set_direction(gpio_cd, GPIO_MODE_INPUT);
         gpio_matrix_in(gpio_cd, pslot->card_detect, 0);
+    } else {
+        gpio_matrix_in(GPIO_FUNC_IN_HIGH, pslot->card_detect, 0);
     }
+
     if (gpio_wp != -1) {
         gpio_set_direction(gpio_wp, GPIO_MODE_INPUT);
         gpio_matrix_in(gpio_wp, pslot->write_protect, 0);
@@ -405,6 +430,8 @@ esp_err_t sdmmc_host_deinit()
     s_intr_handle = NULL;
     vQueueDelete(s_event_queue);
     s_event_queue = NULL;
+    vQueueDelete(s_io_intr_event);
+    s_io_intr_event = NULL;
     sdmmc_host_input_clk_disable();
     sdmmc_host_transaction_handler_deinit();
     periph_module_disable(PERIPH_SDMMC_MODULE);
@@ -497,24 +524,62 @@ void sdmmc_host_dma_resume()
     SDMMC.pldmnd = 1;
 }
 
+esp_err_t sdmmc_host_io_int_enable(int slot)
+{
+    configure_pin(s_slot_info[slot].d1);
+    return ESP_OK;
+}
+
+esp_err_t sdmmc_host_io_int_wait(int slot, TickType_t timeout_ticks)
+{   
+    /* SDIO interrupts are negedge sensitive ones: the status bit is only set
+     * when first interrupt triggered.
+     *
+     * If D1 GPIO is low when entering this function, we know that interrupt
+     * (in SDIO sense) has occurred and we don't need to use SDMMC peripheral
+     * interrupt.
+     */
+
+    SDMMC.intmask.sdio &= ~BIT(slot);   /* Disable SDIO interrupt */
+    SDMMC.rintsts.sdio = BIT(slot);
+    if (gpio_get_level(s_slot_info[slot].d1_gpio) == 0) {
+        return ESP_OK;
+    }
+    /* Otherwise, need to wait for an interrupt. Since D1 was high,
+     * SDMMC peripheral interrupt is guaranteed to trigger on negedge.
+     */
+    xSemaphoreTake(s_io_intr_event, 0);
+    SDMMC.intmask.sdio |= BIT(slot);    /* Re-enable SDIO interrupt */
+    
+    if (xSemaphoreTake(s_io_intr_event, timeout_ticks) == pdTRUE) {
+        return ESP_OK;
+    } else {
+        return ESP_ERR_TIMEOUT;
+    }
+}
+
 /**
  * @brief SDMMC interrupt handler
  *
- * Ignoring SDIO and streaming read/writes for now (and considering just SD memory cards),
- * all communication is driven by the master, and the hardware handles things like stop
- * commands automatically. So the interrupt handler doesn't need to do much, we just push
- * interrupt status into a queue, clear interrupt flags, and let the task currently doing
- * communication figure out what to do next.
+ * All communication in SD protocol is driven by the master, and the hardware
+ * handles things like stop commands automatically.
+ * So the interrupt handler doesn't need to do much, we just push interrupt
+ * status into a queue, clear interrupt flags, and let the task currently
+ * doing communication figure out what to do next.
+ * This also applies to SDIO interrupts which are generated by the slave.
  *
- * Card detect interrupts pose a small issue though, because if a card is plugged in and
- * out a few times, while there is no task to process the events, event queue can become
- * full and some card detect events may be dropped. We ignore this problem for now, since
- * the there are no other interesting events which can get lost due to this.
+ * Card detect interrupts pose a small issue though, because if a card is
+ * plugged in and out a few times, while there is no task to process
+ * the events, event queue can become full and some card detect events
+ * may be dropped. We ignore this problem for now, since the there are no other
+ * interesting events which can get lost due to this.
  */
 static void sdmmc_isr(void* arg) {
     QueueHandle_t queue = (QueueHandle_t) arg;
     sdmmc_event_t event;
-    uint32_t pending = SDMMC.mintsts.val;
+    int higher_priority_task_awoken = pdFALSE;
+
+    uint32_t pending = SDMMC.mintsts.val & 0xFFFF;
     SDMMC.rintsts.val = pending;
     event.sdmmc_status = pending;
 
@@ -522,8 +587,17 @@ static void sdmmc_isr(void* arg) {
     SDMMC.idsts.val = dma_pending;
     event.dma_status = dma_pending & 0x1f;
 
-    int higher_priority_task_awoken = pdFALSE;
-    xQueueSendFromISR(queue, &event, &higher_priority_task_awoken);
+    if (pending != 0 || dma_pending != 0) {
+        xQueueSendFromISR(queue, &event, &higher_priority_task_awoken);
+    }
+
+    uint32_t sdio_pending = SDMMC.mintsts.sdio;
+    if (sdio_pending) {
+        // disable the interrupt (no need to clear here, this is done in sdmmc_host_io_wait_int)
+        SDMMC.intmask.sdio &= ~sdio_pending;
+        xSemaphoreGiveFromISR(s_io_intr_event, &higher_priority_task_awoken);
+    }
+
     if (higher_priority_task_awoken == pdTRUE) {
         portYIELD_FROM_ISR();
     }
index 58b6f082cc096b2d53817cea23a5ae4b0f8249a5..7b68ed7c8ab627dad0d226f0378ee28d711855cf 100644 (file)
@@ -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
index 6123990833af366102890fd857be4b919eca6027..469bbc5dafa05e03c28418c4c5bd2630ce9b0af5 100644 (file)
@@ -26,7 +26,8 @@
 #include "sys/param.h"
 #include "soc/soc_memory_layout.h"
 
-#define SDMMC_GO_IDLE_DELAY_MS      20
+#define SDMMC_GO_IDLE_DELAY_MS              20
+#define SDMMC_IO_SEND_OP_COND_DELAY_MS      10
 
 /* These delay values are mostly useful for cases when CD pin is not used, and
  * the card is removed. In this case, SDMMC peripheral may not always return
@@ -53,13 +54,14 @@ static esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card,
         uint32_t mode, uint32_t group, uint32_t function,
         sdmmc_switch_func_rsp_t* resp);
 static esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card);
+static esp_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card);
+static esp_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card);
 static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
 static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd);
 static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card, uint32_t rca);
 static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr);
 static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr);
 static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width);
-static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status);
 static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status);
 static esp_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable);
 static uint32_t  get_host_ocr(float voltage);
@@ -68,7 +70,12 @@ static esp_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src,
         size_t start_block, size_t block_count);
 static esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
         size_t start_block, size_t block_count);
-
+static esp_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp);
+static esp_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int function,
+        uint32_t reg, uint32_t arg, uint8_t *byte);
+static esp_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int function,
+        uint32_t reg, int arg, void *data, size_t size);
+static void sdmmc_fix_host_flags(sdmmc_card_t* card);
 
 static bool host_is_spi(const sdmmc_card_t* card)
 {
@@ -77,28 +84,38 @@ static bool host_is_spi(const sdmmc_card_t* card)
 
 esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
 {
-    ESP_LOGD(TAG, "%s", __func__);
+    esp_err_t err;
     memset(card, 0, sizeof(*card));
     memcpy(&card->host, config, sizeof(*config));
     const bool is_spi = host_is_spi(card);
 
-    if ( !is_spi ) {
-        //check HOST flags compatible with slot configuration.
-        int slot_bit_width = config->get_bus_width(config->slot);        
-        if ( slot_bit_width == 1 && (config->flags & (SDMMC_HOST_FLAG_4BIT|SDMMC_HOST_FLAG_8BIT))) {
-            ESP_LOGW(TAG, "HOST slot only enables 1-bit.");
-            card->host.flags = ((card->host.flags&(~(SDMMC_HOST_FLAG_4BIT|SDMMC_HOST_FLAG_8BIT)))|SDMMC_HOST_FLAG_1BIT);
-        } else if ( slot_bit_width == 4  && (config->flags & SDMMC_HOST_FLAG_8BIT)){
-            ESP_LOGW(TAG, "HOST slot only enables 4-bit.");
-            card->host.flags = ((card->host.flags&(~(SDMMC_HOST_FLAG_1BIT|SDMMC_HOST_FLAG_8BIT)))|SDMMC_HOST_FLAG_4BIT);            
-        }
+    if (!is_spi) {
+        // Check if host flags are compatible with slot configuration.
+        sdmmc_fix_host_flags(card);
+    }
+    
+    /* ----------- standard initialization process starts here ---------- */
+
+    /* Reset SDIO (CMD52, RES) before re-initializing IO (CMD5).
+     * Non-IO cards are allowed to time out.
+     */
+    uint8_t sdio_reset = CCCR_CTL_RES;
+    err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CTL, SD_ARG_CMD52_WRITE, &sdio_reset);
+    if (err != ESP_OK && err != ESP_ERR_TIMEOUT) {
+        ESP_LOGE(TAG, "%s: sdio_reset: unexpected return: 0x%x", __func__, err );
+        return err;
     }
+
     /* GO_IDLE_STATE (CMD0) command resets the card */
-    esp_err_t err = sdmmc_send_cmd_go_idle_state(card);
+    err = sdmmc_send_cmd_go_idle_state(card);
     if (err != ESP_OK) {
         ESP_LOGE(TAG, "%s: go_idle_state (1) returned 0x%x", __func__, err);
         return err;
     }
+
+    /* FIXME: we should check card status to wait until it is out of idle
+     * state, instead of using a delay.
+     */
     vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS);
     sdmmc_send_cmd_go_idle_state(card);
     vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS);
@@ -120,51 +137,84 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
         return err;
     }
 
-    /* In SPI mode, READ_OCR (CMD58) command is used to figure out which voltage
-     * ranges the card can support. This step is skipped since 1.8V isn't
-     * supported on the ESP32.
-     */
+    /* IO_SEND_OP_COND(CMD5), Determine if the card is an IO card.
+     * Non-IO cards will not respond to this command.
+     */ 
+    err = sdmmc_io_send_op_cond(card, 0, &card->ocr);
+    if (err != ESP_OK) {
+        ESP_LOGD(TAG, "%s: io_send_op_cond (1) returned 0x%x; not IO card", __func__, err);
+        card->is_sdio = 0;
+        card->is_mem = 1;
+    } else {
+        card->is_sdio = 1;
 
-    /* In SD mode, CRC checks of data transfers are mandatory and performed
-     * by the hardware. In SPI mode, CRC16 of data transfers is optional and
-     * needs to be enabled.
-     */
-    if (is_spi) {
-        err = sdmmc_send_cmd_crc_on_off(card, true);
+        if (card->ocr & SD_IO_OCR_MEM_PRESENT) {
+            ESP_LOGD(TAG, "%s: IO-only card", __func__);
+            card->is_mem = 0;
+        }
+        card->num_io_functions = SD_IO_OCR_NUM_FUNCTIONS(card->ocr);
+        ESP_LOGD(TAG, "%s: number of IO functions: %d", __func__, card->num_io_functions);
+        if (card->num_io_functions == 0) {
+            card->is_sdio = 0;
+        }
+        host_ocr &= card->ocr;
+        err = sdmmc_io_send_op_cond(card, host_ocr, &card->ocr);
         if (err != ESP_OK) {
-            ESP_LOGE(TAG, "%s: sdmmc_send_cmd_crc_on_off returned 0x%x", __func__, err);
+            ESP_LOGE(TAG, "%s: sdmmc_io_send_op_cond (1) returned 0x%x", __func__, err);
             return err;
         }
+        sdmmc_io_enable_int(card);
     }
 
-    /* Send SEND_OP_COND (ACMD41) command to the card until it becomes ready. */
-    err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr);
-    if (err != ESP_OK) {
-        ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err);
-        return err;
-    }
-    if (is_spi) {
-        err = sdmmc_send_cmd_read_ocr(card, &card->ocr);
+    if (card->is_mem) {
+        /* In SPI mode, READ_OCR (CMD58) command is used to figure out which voltage
+         * ranges the card can support. This step is skipped since 1.8V isn't
+         * supported on the ESP32.
+         */
+
+        /* In SD mode, CRC checks of data transfers are mandatory and performed
+         * by the hardware. In SPI mode, CRC16 of data transfers is optional and
+         * needs to be enabled.
+         */
+        if (is_spi) {
+            err = sdmmc_send_cmd_crc_on_off(card, true);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: sdmmc_send_cmd_crc_on_off returned 0x%x", __func__, err);
+                return err;
+            }
+        }
+
+        /* Send SEND_OP_COND (ACMD41) command to the card until it becomes ready. */
+        err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr);
         if (err != ESP_OK) {
-            ESP_LOGE(TAG, "%s: read_ocr returned 0x%x", __func__, err);
+            ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err);
             return err;
         }
-    }
-    ESP_LOGD(TAG, "host_ocr=0x%x card_ocr=0x%x", host_ocr, card->ocr);
+        if (is_spi) {
+            err = sdmmc_send_cmd_read_ocr(card, &card->ocr);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: read_ocr returned 0x%x", __func__, err);
+                return err;
+            }
+        }
+        ESP_LOGD(TAG, "host_ocr=0x%x card_ocr=0x%x", host_ocr, card->ocr);
 
-    /* Clear all voltage bits in host's OCR which the card doesn't support.
-     * Don't touch CCS bit because in SPI mode cards don't report CCS in ACMD41
-     * response.
-     */
-    host_ocr &= (card->ocr | (~SD_OCR_VOL_MASK));
-    ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr);
+        /* Clear all voltage bits in host's OCR which the card doesn't support.
+         * Don't touch CCS bit because in SPI mode cards don't report CCS in ACMD41
+         * response.
+         */
+        host_ocr &= (card->ocr | (~SD_OCR_VOL_MASK));
+        ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr);
+    }
 
     /* Read and decode the contents of CID register */
     if (!is_spi) {
-        err = sddmc_send_cmd_all_send_cid(card, &card->cid);
-        if (err != ESP_OK) {
-            ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err);
-            return err;
+        if (card->is_mem) {
+            err = sddmc_send_cmd_all_send_cid(card, &card->cid);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err);
+                return err;
+            }
         }
         err = sdmmc_send_cmd_set_relative_addr(card, &card->rca);
         if (err != ESP_OK) {
@@ -178,20 +228,22 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
             return err;
         }
     }
-
-    /* Get and decode the contents of CSD register. Determine card capacity. */
-    err = sdmmc_send_cmd_send_csd(card, &card->csd);
-    if (err != ESP_OK) {
-        ESP_LOGE(TAG, "%s: send_csd (1) returned 0x%x", __func__, err);
-        return err;
-    }
-    const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1;
-    if (!(card->ocr & SD_OCR_SDHC_CAP) &&
-         card->csd.capacity > max_sdsc_capacity) {
-        ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.",
-                __func__, card->csd.capacity, max_sdsc_capacity);
-        card->csd.capacity = max_sdsc_capacity;
+    if (card->is_mem) {
+        /* Get and decode the contents of CSD register. Determine card capacity. */
+        err = sdmmc_send_cmd_send_csd(card, &card->csd);
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "%s: send_csd (1) returned 0x%x", __func__, err);
+            return err;
+        }
+        const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1;
+        if (!(card->ocr & SD_OCR_SDHC_CAP) &&
+             card->csd.capacity > max_sdsc_capacity) {
+            ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.",
+                    __func__, card->csd.capacity, max_sdsc_capacity);
+            card->csd.capacity = max_sdsc_capacity;
+        }
     }
+    /* ----------- standard initialization process ends here ----------- */
 
     /* Switch the card from stand-by mode to data transfer mode (not needed if
      * SPI interface is used). This is needed to issue SET_BLOCKLEN and
@@ -205,112 +257,118 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
         }
     }
 
-    /* SDSC cards support configurable data block lengths.
-     * We don't use this feature and set the block length to 512 bytes,
-     * same as the block length for SDHC cards.
-     */
-    if ((card->ocr & SD_OCR_SDHC_CAP) == 0) {
-        err = sdmmc_send_cmd_set_blocklen(card, &card->csd);
+    if (card->is_mem) {
+        /* SDSC cards support configurable data block lengths.
+         * We don't use this feature and set the block length to 512 bytes,
+         * same as the block length for SDHC cards.
+         */
+        if ((card->ocr & SD_OCR_SDHC_CAP) == 0) {
+            err = sdmmc_send_cmd_set_blocklen(card, &card->csd);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err);
+                return err;
+            }
+        }
+        /* Get the contents of SCR register: bus width and the version of SD spec
+         * supported by the card.
+         * In SD mode, this is the first command which uses D0 line. Errors at
+         * this step usually indicate connection issue or lack of pull-up resistor.
+         */
+        err = sdmmc_send_cmd_send_scr(card, &card->scr);
         if (err != ESP_OK) {
-            ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err);
+            ESP_LOGE(TAG, "%s: send_scr (1) returned 0x%x", __func__, err);
             return err;
         }
     }
 
-    /* Get the contents of SCR register: bus width and the version of SD spec
-     * supported by the card.
-     * In SD mode, this is the first command which uses D0 line. Errors at
-     * this step usually indicate connection issue or lack of pull-up resistor.
-     */
-    err = sdmmc_send_cmd_send_scr(card, &card->scr);
-    if (err != ESP_OK) {
-        ESP_LOGE(TAG, "%s: send_scr (1) returned 0x%x", __func__, err);
-        return err;
-    }
-
-    /* If the host has been initialized with 4-bit bus support, and the card
-     * supports 4-bit bus, switch to 4-bit bus now.
-     */
-    if ((card->host.flags & SDMMC_HOST_FLAG_4BIT) &&
-        (card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT)) {
-        ESP_LOGD(TAG, "switching to 4-bit bus mode");
-        err = sdmmc_send_cmd_set_bus_width(card, 4);
-        if (err != ESP_OK) {
-            ESP_LOGE(TAG, "set_bus_width failed");
-            return err;
-        }
-        err = (*config->set_bus_width)(config->slot, 4);
-        if (err != ESP_OK) {
-            ESP_LOGE(TAG, "slot->set_bus_width failed");
-            return err;
+    if (card->is_mem) {
+        /* If the host has been initialized with 4-bit bus support, and the card
+         * supports 4-bit bus, switch to 4-bit bus now.
+         */
+        if ((card->host.flags & SDMMC_HOST_FLAG_4BIT) &&
+            (card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT)) {
+            ESP_LOGD(TAG, "switching to 4-bit bus mode");
+            err = sdmmc_send_cmd_set_bus_width(card, 4);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "set_bus_width failed");
+                return err;
+            }
+            err = (*config->set_bus_width)(config->slot, 4);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "slot->set_bus_width failed");
+                return err;
+            }
         }
-    }
 
-    /* Wait for the card to be ready for data transfers */
-    uint32_t status = 0;
-    while (!is_spi && !(status & MMC_R1_READY_FOR_DATA)) {
-        // TODO: add some timeout here
-        uint32_t count = 0;
-        err = sdmmc_send_cmd_send_status(card, &status);
-        if (err != ESP_OK) {
-            return err;
+        /* Wait for the card to be ready for data transfers */
+        uint32_t status = 0;
+        while (!is_spi && !(status & MMC_R1_READY_FOR_DATA)) {
+            // TODO: add some timeout here
+            uint32_t count = 0;
+            err = sdmmc_send_cmd_send_status(card, &status);
+            if (err != ESP_OK) {
+                return err;
+            }
+            if (++count % 16 == 0) {
+                ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
+            }
         }
-        if (++count % 10 == 0) {
-            ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
+    } else {
+        /* IO card */
+        if (config->flags & SDMMC_HOST_FLAG_4BIT) {
+            uint8_t card_cap;
+            err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CARD_CAP,
+                    SD_ARG_CMD52_READ, &card_cap);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read SD_IO_CCCR_CARD_CAP) returned 0x%0x", __func__, err);
+                return err;
+            }
+            ESP_LOGD(TAG, "IO card capabilities byte: %02x", card_cap);
+            if (!(card_cap & CCCR_CARD_CAP_LSC) ||
+                    (card_cap & CCCR_CARD_CAP_4BLS)) {
+                // This card supports 4-bit bus mode
+                uint8_t bus_width = CCCR_BUS_WIDTH_4;
+                err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_BUS_WIDTH,
+                                    SD_ARG_CMD52_WRITE, &bus_width);
+                if (err != ESP_OK) {
+                    ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write SD_IO_CCCR_BUS_WIDTH) returned 0x%0x", __func__, err);
+                    return err;
+                }
+                err = (*config->set_bus_width)(config->slot, 4);
+                if (err != ESP_OK) {
+                    ESP_LOGE(TAG, "slot->set_bus_width failed");
+                    return err;
+                }                
+            }
         }
     }
-
     /* So far initialization has been done using 400kHz clock. Determine the
      * clock rate which both host and the card support, and switch to it.
      */
     bool freq_switched = false;
     if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED &&
             !is_spi /* SPI doesn't support >26MHz in some cases */) {
-        /* This will determine if the card supports SWITCH_FUNC command,
-         * and high speed mode. If the cards supports both, this will enable
-         * high speed mode at the card side.
-         */
-        err = sdmmc_enable_hs_mode(card);
+        if (card->is_mem) {
+            err = sdmmc_enable_hs_mode_and_check(card);
+        } else {
+            err = sdmmc_io_enable_hs_mode(card);
+        }
+
         if (err == ESP_ERR_NOT_SUPPORTED) {
             ESP_LOGD(TAG, "%s: host supports HS mode, but card doesn't", __func__);
         } else if (err != ESP_OK) {
-            /* some other error */
             return err;
-        } else { /* ESP_OK */
-            /* HS mode has been enabled on the card.
-             * Read CSD again, it should now indicate that the card supports
-             * 50MHz clock.
-             * Since SEND_CSD is allowed only in standby mode, and the card is
-             * currently in data transfer more, deselect the card first, then
-             * get the CSD, then select the card again.
+        } else {
+            ESP_LOGD(TAG, "%s: switching host to HS mode", __func__);
+            /* ESP_OK, HS mode has been enabled on the card side.
+             * Switch the host to HS mode.
              */
-            err = sdmmc_send_cmd_select_card(card, 0);
+            err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED);
             if (err != ESP_OK) {
-                ESP_LOGE(TAG, "%s: select_card (2) returned 0x%x", __func__, err);
+                ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
                 return err;
             }
-            err = sdmmc_send_cmd_send_csd(card, &card->csd);
-            if (err != ESP_OK) {
-                ESP_LOGE(TAG, "%s: send_csd (2) returned 0x%x", __func__, err);
-                return err;
-            }
-            err = sdmmc_send_cmd_select_card(card, card->rca);
-            if (err != ESP_OK) {
-                ESP_LOGE(TAG, "%s: select_card (3) returned 0x%x", __func__, err);
-                return err;
-            }
-
-            if (card->csd.tr_speed != 50000000) {
-                ESP_LOGW(TAG, "unexpected: after enabling HS mode, tr_speed=%d", card->csd.tr_speed);
-            } else {
-                /* Finally can switch the host to HS mode */
-                err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_HIGHSPEED);
-                if (err != ESP_OK) {
-                    ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode");
-                    return err;
-                }
-                freq_switched = true;
-            }
+            freq_switched = true;
         }
     }
     /* All SD cards must support default speed mode (25MHz).
@@ -331,15 +389,21 @@ esp_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card)
      * an indicator of potential signal integrity issues.
      */
     if (freq_switched) {
-        sdmmc_scr_t scr_tmp;
-        err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
-        if (err != ESP_OK) {
-            ESP_LOGE(TAG, "%s: send_scr (2) returned 0x%x", __func__, err);
-            return err;
-        }
-        if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) {
-            ESP_LOGE(TAG, "got corrupted data after increasing clock frequency");
-            return ESP_ERR_INVALID_RESPONSE;
+        if (card->is_mem) {
+            sdmmc_scr_t scr_tmp;
+            err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
+            if (err != ESP_OK) {
+                ESP_LOGE(TAG, "%s: send_scr (2) returned 0x%x", __func__, err);
+                return err;
+            }
+            if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) {
+                ESP_LOGE(TAG, "got corrupted data after increasing clock frequency");
+                return ESP_ERR_INVALID_RESPONSE;
+            }
+        } else {
+            /* TODO: For IO cards, read some data to see if frequency switch
+             * was successful.
+             */
         }
     }
     return ESP_OK;
@@ -357,6 +421,26 @@ void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card)
     fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width);
 }
 
+static void sdmmc_fix_host_flags(sdmmc_card_t* card)
+{
+    const uint32_t width_1bit = SDMMC_HOST_FLAG_1BIT;
+    const uint32_t width_4bit = SDMMC_HOST_FLAG_4BIT;
+    const uint32_t width_8bit = SDMMC_HOST_FLAG_8BIT;
+    const uint32_t width_mask = width_1bit | width_4bit | width_8bit;
+
+    int slot_bit_width = card->host.get_bus_width(card->host.slot);
+    if (slot_bit_width == 1 &&
+            (card->host.flags & (width_4bit | width_8bit))) {
+        ESP_LOGW(TAG, "host slot is configured in 1-bit mode");
+        card->host.flags &= ~width_mask;
+        card->host.flags |= ~(width_1bit);
+    } else if (slot_bit_width == 4 && (card->host.flags & width_8bit)){
+        ESP_LOGW(TAG, "host slot is configured in 4-bit mode");
+        card->host.flags &= ~width_mask;
+        card->host.flags |= width_4bit;
+    }
+}
+
 static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd)
 {
     if (card->host.command_timeout_ms != 0) {
@@ -675,20 +759,6 @@ static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width)
     return sdmmc_send_app_cmd(card, &cmd);
 }
 
-static esp_err_t sdmmc_send_cmd_stop_transmission(sdmmc_card_t* card, uint32_t* status)
-{
-    sdmmc_command_t cmd = {
-            .opcode = MMC_STOP_TRANSMISSION,
-            .arg = 0,
-            .flags = SCF_RSP_R1B | SCF_CMD_AC
-    };
-    esp_err_t err = sdmmc_send_cmd(card, &cmd);
-    if (err == 0) {
-        *status = MMC_R1(cmd.response);
-    }
-    return err;
-}
-
 static esp_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable)
 {
     assert(host_is_spi(card) && "CRC_ON_OFF can only be used in SPI mode");
@@ -944,6 +1014,10 @@ static esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card,
 
 static esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card)
 {
+    /* This will determine if the card supports SWITCH_FUNC command,
+     * and high speed mode. If the cards supports both, this will enable
+     * high speed mode at the card side.
+     */
     if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 ||
         ((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) {
             return ESP_ERR_NOT_SUPPORTED;
@@ -974,3 +1048,242 @@ out:
     free(response);
     return err;
 }
+
+static esp_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card)
+{
+    /* Try to enabled HS mode */
+    esp_err_t err = sdmmc_enable_hs_mode(card);
+    if (err != ESP_OK) {
+        return err;
+    }
+    /* HS mode has been enabled on the card.
+     * Read CSD again, it should now indicate that the card supports
+     * 50MHz clock.
+     * Since SEND_CSD is allowed only in standby mode, and the card is
+     * currently in data transfer more, deselect the card first, then
+     * get the CSD, then select the card again.
+     */
+    err = sdmmc_send_cmd_select_card(card, 0);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: select_card (2) returned 0x%x", __func__, err);
+        return err;
+    }
+    err = sdmmc_send_cmd_send_csd(card, &card->csd);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: send_csd (2) returned 0x%x", __func__, err);
+        return err;
+    }
+    err = sdmmc_send_cmd_select_card(card, card->rca);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: select_card (3) returned 0x%x", __func__, err);
+        return err;
+    }
+
+    if (card->csd.tr_speed != 50000000) {
+        ESP_LOGW(TAG, "unexpected: after enabling HS mode, tr_speed=%d", card->csd.tr_speed);
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    return ESP_OK;
+}
+
+static esp_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card)
+{
+    /* For IO cards, do write + read operation on "High Speed" register,
+     * setting EHS bit. If both EHS and SHS read back as set, then HS mode
+     * has been enabled.
+     */
+    uint8_t val = CCCR_HIGHSPEED_ENABLE;
+    esp_err_t err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_HIGHSPEED,
+            SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &val);
+    if (err != ESP_OK) {
+        ESP_LOGD(TAG, "%s: sdmmc_io_rw_direct returned 0x%x", __func__, err);
+        return err;
+    }
+
+    ESP_LOGD(TAG, "%s: CCCR_HIGHSPEED=0x%02x", __func__, val);
+    const uint8_t hs_mask = CCCR_HIGHSPEED_ENABLE | CCCR_HIGHSPEED_SUPPORT;
+    if ((val & hs_mask) != hs_mask) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+    return ESP_OK;
+}
+
+
+static esp_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp)
+{
+    esp_err_t err = ESP_OK;
+    sdmmc_command_t cmd = {
+        .flags = SCF_CMD_BCR | SCF_RSP_R4,
+        .arg = ocr,
+        .opcode = SD_IO_SEND_OP_COND
+    };
+    for (size_t i = 0; i < 100; i++) {
+        err = sdmmc_send_cmd(card, &cmd);
+        if (err != ESP_OK) {
+            break;
+        }
+        if ((MMC_R4(cmd.response) & SD_IO_OCR_MEM_READY) ||
+            ocr == 0) {
+            break;
+        }
+        err = ESP_ERR_TIMEOUT;
+        vTaskDelay(SDMMC_IO_SEND_OP_COND_DELAY_MS / portTICK_PERIOD_MS);
+    }
+    if (err == ESP_OK && ocrp != NULL)
+        *ocrp = MMC_R4(cmd.response);
+
+    return err;
+}
+
+static esp_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int func,
+        uint32_t reg, uint32_t arg, uint8_t *byte)
+{
+    esp_err_t err;
+    sdmmc_command_t cmd = {
+        .flags = SCF_CMD_AC | SCF_RSP_R5,
+        .arg = 0,
+        .opcode = SD_IO_RW_DIRECT
+    };
+
+    arg |= (func & SD_ARG_CMD52_FUNC_MASK) << SD_ARG_CMD52_FUNC_SHIFT;
+    arg |= (reg & SD_ARG_CMD52_REG_MASK) << SD_ARG_CMD52_REG_SHIFT;
+    arg |= (*byte & SD_ARG_CMD52_DATA_MASK) << SD_ARG_CMD52_DATA_SHIFT;
+    cmd.arg = arg;
+
+    err = sdmmc_send_cmd(card, &cmd);
+    if (err != ESP_OK) {
+        ESP_LOGV(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
+        return err;
+    }
+
+    *byte = SD_R5_DATA(cmd.response);
+
+    return ESP_OK;
+}
+
+
+esp_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function,
+        uint32_t addr, uint8_t *out_byte)
+{
+    esp_err_t ret = sdmmc_io_rw_direct(card, function, addr, SD_ARG_CMD52_READ, out_byte);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read 0x%x) returned 0x%x", __func__, addr, ret);
+    }
+    return ret;
+}
+
+esp_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function,
+        uint32_t addr, uint8_t in_byte, uint8_t* out_byte)
+{
+    uint8_t tmp_byte = in_byte;
+    esp_err_t ret = sdmmc_io_rw_direct(card, function, addr,
+            SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &tmp_byte);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write 0x%x) returned 0x%x", __func__, addr, ret);
+        return ret;
+    }
+    if (out_byte != NULL) {
+        *out_byte = tmp_byte;
+    }
+    return ESP_OK;
+}
+
+static esp_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int func,
+    uint32_t reg, int arg, void *datap, size_t datalen)
+{
+    esp_err_t err;
+    const size_t max_byte_transfer_size = 512;
+    sdmmc_command_t cmd = {
+        .flags = SCF_CMD_AC | SCF_RSP_R5,
+        .arg = 0,
+        .opcode = SD_IO_RW_EXTENDED,
+        .data = datap,
+        .datalen = datalen,
+        .blklen = max_byte_transfer_size /* TODO: read max block size from CIS */
+    };
+
+    uint32_t count; /* number of bytes or blocks, depending on transfer mode */
+    if (arg & SD_ARG_CMD53_BLOCK_MODE) {
+        if (cmd.datalen % cmd.blklen != 0) {
+            return ESP_ERR_INVALID_SIZE;
+        }
+        count = cmd.datalen / cmd.blklen;
+    } else {
+        if (datalen > max_byte_transfer_size) {
+            /* TODO: split into multiple operations? */
+            return ESP_ERR_INVALID_SIZE;
+        }
+        if (datalen == max_byte_transfer_size) {
+            count = 0;  // See 5.3.1 SDIO simplifed spec
+        } else {
+            count = datalen;
+        }
+        cmd.blklen = datalen;
+    }
+
+    arg |= (func & SD_ARG_CMD53_FUNC_MASK) << SD_ARG_CMD53_FUNC_SHIFT;
+    arg |= (reg & SD_ARG_CMD53_REG_MASK) << SD_ARG_CMD53_REG_SHIFT;
+    arg |= (count & SD_ARG_CMD53_LENGTH_MASK) << SD_ARG_CMD53_LENGTH_SHIFT;
+    cmd.arg = arg;
+
+    if ((arg & SD_ARG_CMD53_WRITE) == 0) {
+        cmd.flags |= SCF_CMD_READ;
+    }
+
+    err = sdmmc_send_cmd(card, &cmd);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
+        return err;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function,
+        uint32_t addr, void* dst, size_t size)
+{
+    return sdmmc_io_rw_extended(card, function, addr,
+            SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT,
+            dst, size);
+}
+
+esp_err_t sdmmc_io_write_bytes(sdmmc_card_t* card, uint32_t function,
+        uint32_t addr, const void* src, size_t size)
+{
+    return sdmmc_io_rw_extended(card, function, addr,
+            SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT,
+            (void*) src, size);
+}
+
+esp_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function,
+        uint32_t addr, void* dst, size_t size)
+{
+    return sdmmc_io_rw_extended(card, function, addr,
+            SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT | SD_ARG_CMD53_BLOCK_MODE,
+            dst, size);
+}
+
+esp_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function,
+        uint32_t addr, const void* src, size_t size)
+{
+    return sdmmc_io_rw_extended(card, function, addr,
+            SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT | SD_ARG_CMD53_BLOCK_MODE,
+            (void*) src, size);
+}
+
+esp_err_t sdmmc_io_enable_int(sdmmc_card_t* card)
+{
+    if (card->host.io_int_enable == NULL) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+    return (*card->host.io_int_enable)(card->host.slot);
+}
+
+esp_err_t sdmmc_io_wait_int(sdmmc_card_t* card, TickType_t timeout_ticks)
+{
+    if (card->host.io_int_wait == NULL) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+    return (*card->host.io_int_wait)(card->host.slot, timeout_ticks);
+}
diff --git a/components/sdmmc/test/test_sdio.c b/components/sdmmc/test/test_sdio.c
new file mode 100644 (file)
index 0000000..8b885ba
--- /dev/null
@@ -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, &reg_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, &reg_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);
+}
+
index d1b452d1d194468c63d099729531178ddefd8eda..2f9c68f80bbba2438adf1e1ed7312aa98c5c5c86 100644 (file)
@@ -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)