From edd924f2735449be266ae5e48f0cbb1ca486067c Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 19 Dec 2016 22:19:47 +0800 Subject: [PATCH] sdmmc: add peripheral driver and protocol layer --- components/driver/include/driver/sdmmc_defs.h | 303 ++++++++++ components/driver/include/driver/sdmmc_host.h | 165 +++++ .../driver/include/driver/sdmmc_types.h | 140 +++++ components/driver/sdmmc_host.c | 463 ++++++++++++++ components/driver/sdmmc_private.h | 44 ++ components/driver/sdmmc_transaction.c | 372 ++++++++++++ components/esp32/include/esp_err.h | 3 +- components/esp32/include/soc/sdmmc_reg.h | 94 +++ components/esp32/include/soc/sdmmc_struct.h | 371 ++++++++++++ components/esp32/include/soc/soc.h | 2 +- components/esp32/ld/esp32.peripherals.ld | 1 + components/sdmmc/component.mk | 0 components/sdmmc/include/sdmmc_cmd.h | 77 +++ components/sdmmc/sdmmc_cmd.c | 571 ++++++++++++++++++ components/sdmmc/test/component.mk | 1 + components/sdmmc/test/test_sd.c | 95 +++ examples/27_sd_card/Makefile | 9 + examples/27_sd_card/README.md | 81 +++ examples/27_sd_card/main/component.mk | 4 + examples/27_sd_card/main/sd_card.c | 107 ++++ 20 files changed, 2901 insertions(+), 2 deletions(-) create mode 100644 components/driver/include/driver/sdmmc_defs.h create mode 100644 components/driver/include/driver/sdmmc_host.h create mode 100644 components/driver/include/driver/sdmmc_types.h create mode 100644 components/driver/sdmmc_host.c create mode 100644 components/driver/sdmmc_private.h create mode 100644 components/driver/sdmmc_transaction.c create mode 100644 components/esp32/include/soc/sdmmc_reg.h create mode 100644 components/esp32/include/soc/sdmmc_struct.h create mode 100755 components/sdmmc/component.mk create mode 100644 components/sdmmc/include/sdmmc_cmd.h create mode 100644 components/sdmmc/sdmmc_cmd.c create mode 100644 components/sdmmc/test/component.mk create mode 100644 components/sdmmc/test/test_sd.c create mode 100644 examples/27_sd_card/Makefile create mode 100644 examples/27_sd_card/README.md create mode 100644 examples/27_sd_card/main/component.mk create mode 100644 examples/27_sd_card/main/sd_card.c diff --git a/components/driver/include/driver/sdmmc_defs.h b/components/driver/include/driver/sdmmc_defs.h new file mode 100644 index 000000000..9913bfd17 --- /dev/null +++ b/components/driver/include/driver/sdmmc_defs.h @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2006 Uwe Stuehler + * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SDMMC_DEFS_H_ +#define _SDMMC_DEFS_H_ + +#include +#include + +/* MMC commands */ /* response type */ +#define MMC_GO_IDLE_STATE 0 /* R0 */ +#define MMC_SEND_OP_COND 1 /* R3 */ +#define MMC_ALL_SEND_CID 2 /* R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* R1 */ +#define MMC_SWITCH 6 /* R1B */ +#define MMC_SELECT_CARD 7 /* R1 */ +#define MMC_SEND_EXT_CSD 8 /* R1 */ +#define MMC_SEND_CSD 9 /* R2 */ +#define MMC_STOP_TRANSMISSION 12 /* R1B */ +#define MMC_SEND_STATUS 13 /* R1 */ +#define MMC_SET_BLOCKLEN 16 /* R1 */ +#define MMC_READ_BLOCK_SINGLE 17 /* R1 */ +#define MMC_READ_BLOCK_MULTIPLE 18 /* R1 */ +#define MMC_SET_BLOCK_COUNT 23 /* R1 */ +#define MMC_WRITE_BLOCK_SINGLE 24 /* R1 */ +#define MMC_WRITE_BLOCK_MULTIPLE 25 /* R1 */ +#define MMC_APP_CMD 55 /* R1 */ + +/* SD commands */ /* response type */ +#define SD_SEND_RELATIVE_ADDR 3 /* R6 */ +#define SD_SEND_SWITCH_FUNC 6 /* R1 */ +#define SD_SEND_IF_COND 8 /* R7 */ + +/* SD application commands */ /* response type */ +#define SD_APP_SET_BUS_WIDTH 6 /* R1 */ +#define SD_APP_OP_COND 41 /* R3 */ +#define SD_APP_SEND_SCR 51 /* R1 */ + +/* OCR bits */ +#define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */ +#define MMC_OCR_ACCESS_MODE_MASK 0x60000000 /* bits 30:29 */ +#define MMC_OCR_SECTOR_MODE (1<<30) +#define MMC_OCR_BYTE_MODE (1<<29) +#define MMC_OCR_3_5V_3_6V (1<<23) +#define MMC_OCR_3_4V_3_5V (1<<22) +#define MMC_OCR_3_3V_3_4V (1<<21) +#define MMC_OCR_3_2V_3_3V (1<<20) +#define MMC_OCR_3_1V_3_2V (1<<19) +#define MMC_OCR_3_0V_3_1V (1<<18) +#define MMC_OCR_2_9V_3_0V (1<<17) +#define MMC_OCR_2_8V_2_9V (1<<16) +#define MMC_OCR_2_7V_2_8V (1<<15) +#define MMC_OCR_2_6V_2_7V (1<<14) +#define MMC_OCR_2_5V_2_6V (1<<13) +#define MMC_OCR_2_4V_2_5V (1<<12) +#define MMC_OCR_2_3V_2_4V (1<<11) +#define MMC_OCR_2_2V_2_3V (1<<10) +#define MMC_OCR_2_1V_2_2V (1<<9) +#define MMC_OCR_2_0V_2_1V (1<<8) +#define MMC_OCR_1_65V_1_95V (1<<7) + +#define SD_OCR_SDHC_CAP (1<<30) +#define SD_OCR_VOL_MASK 0xFF8000 /* bits 23:15 */ + +/* R1 response type bits */ +#define MMC_R1_READY_FOR_DATA (1<<8) /* ready for next transfer */ +#define MMC_R1_APP_CMD (1<<5) /* app. commands supported */ + +/* 48-bit response decoding (32 bits w/o CRC) */ +#define MMC_R1(resp) ((resp)[0]) +#define MMC_R3(resp) ((resp)[0]) +#define SD_R6(resp) ((resp)[0]) +#define MMC_R1_CURRENT_STATE(resp) (((resp)[0] >> 9) & 0xf) + +/* RCA argument and response */ +#define MMC_ARG_RCA(rca) ((rca) << 16) +#define SD_R6_RCA(resp) (SD_R6((resp)) >> 16) + +/* bus width argument */ +#define SD_ARG_BUS_WIDTH_1 0 +#define SD_ARG_BUS_WIDTH_4 2 + +/* EXT_CSD fields */ +#define EXT_CSD_BUS_WIDTH 183 /* WO */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_REV 192 /* RO */ +#define EXT_CSD_STRUCTURE 194 /* RO */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_SEC_COUNT 212 /* RO */ + +/* EXT_CSD field definitions */ +#define EXT_CSD_CMD_SET_NORMAL (1U << 0) +#define EXT_CSD_CMD_SET_SECURE (1U << 1) +#define EXT_CSD_CMD_SET_CPSECURE (1U << 2) + +/* EXT_CSD_HS_TIMING */ +#define EXT_CSD_HS_TIMING_BC 0 +#define EXT_CSD_HS_TIMING_HS 1 +#define EXT_CSD_HS_TIMING_HS200 2 +#define EXT_CSD_HS_TIMING_HS400 3 + +/* EXT_CSD_BUS_WIDTH */ +#define EXT_CSD_BUS_WIDTH_1 0 +#define EXT_CSD_BUS_WIDTH_4 1 +#define EXT_CSD_BUS_WIDTH_8 2 +#define EXT_CSD_BUS_WIDTH_4_DDR 5 +#define EXT_CSD_BUS_WIDTH_8_DDR 6 + +/* EXT_CSD_CARD_TYPE */ +/* The only currently valid values for this field are 0x01, 0x03, 0x07, + * 0x0B and 0x0F. */ +#define EXT_CSD_CARD_TYPE_F_26M (1 << 0) +#define EXT_CSD_CARD_TYPE_F_52M (1 << 1) +#define EXT_CSD_CARD_TYPE_F_52M_1_8V (1 << 2) +#define EXT_CSD_CARD_TYPE_F_52M_1_2V (1 << 3) +#define EXT_CSD_CARD_TYPE_26M 0x01 +#define EXT_CSD_CARD_TYPE_52M 0x03 +#define EXT_CSD_CARD_TYPE_52M_V18 0x07 +#define EXT_CSD_CARD_TYPE_52M_V12 0x0b +#define EXT_CSD_CARD_TYPE_52M_V12_18 0x0f + +/* MMC_SWITCH access mode */ +#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */ +#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits in value */ +#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits in value */ +#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ + +/* MMC R2 response (CSD) */ +#define MMC_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2) +#define MMC_CSD_CSDVER_1_0 1 +#define MMC_CSD_CSDVER_2_0 2 +#define MMC_CSD_CSDVER_EXT_CSD 3 +#define MMC_CSD_MMCVER(resp) MMC_RSP_BITS((resp), 122, 4) +#define MMC_CSD_MMCVER_1_0 0 /* MMC 1.0 - 1.2 */ +#define MMC_CSD_MMCVER_1_4 1 /* MMC 1.4 */ +#define MMC_CSD_MMCVER_2_0 2 /* MMC 2.0 - 2.2 */ +#define MMC_CSD_MMCVER_3_1 3 /* MMC 3.1 - 3.3 */ +#define MMC_CSD_MMCVER_4_0 4 /* MMC 4 */ +#define MMC_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4) +#define MMC_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12) +#define MMC_CSD_CAPACITY(resp) ((MMC_CSD_C_SIZE((resp))+1) << \ + (MMC_CSD_C_SIZE_MULT((resp))+2)) +#define MMC_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3) + +/* MMC v1 R2 response (CID) */ +#define MMC_CID_MID_V1(resp) MMC_RSP_BITS((resp), 104, 24) +#define MMC_CID_PNM_V1_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \ + (pnm)[6] = MMC_RSP_BITS((resp), 48, 8); \ + (pnm)[7] = '\0'; \ + } while (0) +#define MMC_CID_REV_V1(resp) MMC_RSP_BITS((resp), 40, 8) +#define MMC_CID_PSN_V1(resp) MMC_RSP_BITS((resp), 16, 24) +#define MMC_CID_MDT_V1(resp) MMC_RSP_BITS((resp), 8, 8) + +/* MMC v2 R2 response (CID) */ +#define MMC_CID_MID_V2(resp) MMC_RSP_BITS((resp), 120, 8) +#define MMC_CID_OID_V2(resp) MMC_RSP_BITS((resp), 104, 16) +#define MMC_CID_PNM_V2_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = MMC_RSP_BITS((resp), 56, 8); \ + (pnm)[6] = '\0'; \ + } while (0) +#define MMC_CID_PSN_V2(resp) MMC_RSP_BITS((resp), 16, 32) + +/* SD R2 response (CSD) */ +#define SD_CSD_CSDVER(resp) MMC_RSP_BITS((resp), 126, 2) +#define SD_CSD_CSDVER_1_0 0 +#define SD_CSD_CSDVER_2_0 1 +#define SD_CSD_TAAC(resp) MMC_RSP_BITS((resp), 112, 8) +#define SD_CSD_TAAC_1_5_MSEC 0x26 +#define SD_CSD_NSAC(resp) MMC_RSP_BITS((resp), 104, 8) +#define SD_CSD_SPEED(resp) MMC_RSP_BITS((resp), 96, 8) +#define SD_CSD_SPEED_25_MHZ 0x32 +#define SD_CSD_SPEED_50_MHZ 0x5a +#define SD_CSD_CCC(resp) MMC_RSP_BITS((resp), 84, 12) +#define SD_CSD_CCC_BASIC (1 << 0) /* basic */ +#define SD_CSD_CCC_BR (1 << 2) /* block read */ +#define SD_CSD_CCC_BW (1 << 4) /* block write */ +#define SD_CSD_CCC_ERASE (1 << 5) /* erase */ +#define SD_CSD_CCC_WP (1 << 6) /* write protection */ +#define SD_CSD_CCC_LC (1 << 7) /* lock card */ +#define SD_CSD_CCC_AS (1 << 8) /*application specific*/ +#define SD_CSD_CCC_IOM (1 << 9) /* I/O mode */ +#define SD_CSD_CCC_SWITCH (1 << 10) /* switch */ +#define SD_CSD_READ_BL_LEN(resp) MMC_RSP_BITS((resp), 80, 4) +#define SD_CSD_READ_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 79, 1) +#define SD_CSD_WRITE_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 78, 1) +#define SD_CSD_READ_BLK_MISALIGN(resp) MMC_RSP_BITS((resp), 77, 1) +#define SD_CSD_DSR_IMP(resp) MMC_RSP_BITS((resp), 76, 1) +#define SD_CSD_C_SIZE(resp) MMC_RSP_BITS((resp), 62, 12) +#define SD_CSD_CAPACITY(resp) ((SD_CSD_C_SIZE((resp))+1) << \ + (SD_CSD_C_SIZE_MULT((resp))+2)) +#define SD_CSD_V2_C_SIZE(resp) MMC_RSP_BITS((resp), 48, 22) +#define SD_CSD_V2_CAPACITY(resp) ((SD_CSD_V2_C_SIZE((resp))+1) << 10) +#define SD_CSD_V2_BL_LEN 0x9 /* 512 */ +#define SD_CSD_VDD_R_CURR_MIN(resp) MMC_RSP_BITS((resp), 59, 3) +#define SD_CSD_VDD_R_CURR_MAX(resp) MMC_RSP_BITS((resp), 56, 3) +#define SD_CSD_VDD_W_CURR_MIN(resp) MMC_RSP_BITS((resp), 53, 3) +#define SD_CSD_VDD_W_CURR_MAX(resp) MMC_RSP_BITS((resp), 50, 3) +#define SD_CSD_VDD_RW_CURR_100mA 0x7 +#define SD_CSD_VDD_RW_CURR_80mA 0x6 +#define SD_CSD_C_SIZE_MULT(resp) MMC_RSP_BITS((resp), 47, 3) +#define SD_CSD_ERASE_BLK_EN(resp) MMC_RSP_BITS((resp), 46, 1) +#define SD_CSD_SECTOR_SIZE(resp) MMC_RSP_BITS((resp), 39, 7) /* +1 */ +#define SD_CSD_WP_GRP_SIZE(resp) MMC_RSP_BITS((resp), 32, 7) /* +1 */ +#define SD_CSD_WP_GRP_ENABLE(resp) MMC_RSP_BITS((resp), 31, 1) +#define SD_CSD_R2W_FACTOR(resp) MMC_RSP_BITS((resp), 26, 3) +#define SD_CSD_WRITE_BL_LEN(resp) MMC_RSP_BITS((resp), 22, 4) +#define SD_CSD_RW_BL_LEN_2G 0xa +#define SD_CSD_RW_BL_LEN_1G 0x9 +#define SD_CSD_WRITE_BL_PARTIAL(resp) MMC_RSP_BITS((resp), 21, 1) +#define SD_CSD_FILE_FORMAT_GRP(resp) MMC_RSP_BITS((resp), 15, 1) +#define SD_CSD_COPY(resp) MMC_RSP_BITS((resp), 14, 1) +#define SD_CSD_PERM_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 13, 1) +#define SD_CSD_TMP_WRITE_PROTECT(resp) MMC_RSP_BITS((resp), 12, 1) +#define SD_CSD_FILE_FORMAT(resp) MMC_RSP_BITS((resp), 10, 2) + +/* SD R2 response (CID) */ +#define SD_CID_MID(resp) MMC_RSP_BITS((resp), 120, 8) +#define SD_CID_OID(resp) MMC_RSP_BITS((resp), 104, 16) +#define SD_CID_PNM_CPY(resp, pnm) \ + do { \ + (pnm)[0] = MMC_RSP_BITS((resp), 96, 8); \ + (pnm)[1] = MMC_RSP_BITS((resp), 88, 8); \ + (pnm)[2] = MMC_RSP_BITS((resp), 80, 8); \ + (pnm)[3] = MMC_RSP_BITS((resp), 72, 8); \ + (pnm)[4] = MMC_RSP_BITS((resp), 64, 8); \ + (pnm)[5] = '\0'; \ + } while (0) +#define SD_CID_REV(resp) MMC_RSP_BITS((resp), 56, 8) +#define SD_CID_PSN(resp) MMC_RSP_BITS((resp), 24, 32) +#define SD_CID_MDT(resp) MMC_RSP_BITS((resp), 8, 12) + +/* SCR (SD Configuration Register) */ +#define SCR_STRUCTURE(scr) MMC_RSP_BITS((scr), 60, 4) +#define SCR_STRUCTURE_VER_1_0 0 /* Version 1.0 */ +#define SCR_SD_SPEC(scr) MMC_RSP_BITS((scr), 56, 4) +#define SCR_SD_SPEC_VER_1_0 0 /* Version 1.0 and 1.01 */ +#define SCR_SD_SPEC_VER_1_10 1 /* Version 1.10 */ +#define SCR_SD_SPEC_VER_2 2 /* Version 2.00 or Version 3.0X */ +#define SCR_DATA_STAT_AFTER_ERASE(scr) MMC_RSP_BITS((scr), 55, 1) +#define SCR_SD_SECURITY(scr) MMC_RSP_BITS((scr), 52, 3) +#define SCR_SD_SECURITY_NONE 0 /* no security */ +#define SCR_SD_SECURITY_1_0 1 /* security protocol 1.0 */ +#define SCR_SD_SECURITY_1_0_2 2 /* security protocol 1.0 */ +#define SCR_SD_BUS_WIDTHS(scr) MMC_RSP_BITS((scr), 48, 4) +#define SCR_SD_BUS_WIDTHS_1BIT (1 << 0) /* 1bit (DAT0) */ +#define SCR_SD_BUS_WIDTHS_4BIT (1 << 2) /* 4bit (DAT0-3) */ +#define SCR_SD_SPEC3(scr) MMC_RSP_BITS((scr), 47, 1) +#define SCR_EX_SECURITY(scr) MMC_RSP_BITS((scr), 43, 4) +#define SCR_SD_SPEC4(scr) MMC_RSP_BITS((scr), 42, 1) +#define SCR_RESERVED(scr) MMC_RSP_BITS((scr), 34, 8) +#define SCR_CMD_SUPPORT_CMD23(scr) MMC_RSP_BITS((scr), 33, 1) +#define SCR_CMD_SUPPORT_CMD20(scr) MMC_RSP_BITS((scr), 32, 1) +#define SCR_RESERVED2(scr) MMC_RSP_BITS((scr), 0, 32) + +/* Status of Switch Function */ +#define SFUNC_STATUS_GROUP(status, group) \ + (__bitfield((uint32_t *)(status), 400 + (group - 1) * 16, 16)) + +#define SD_ACCESS_MODE_SDR12 0 +#define SD_ACCESS_MODE_SDR25 1 +#define SD_ACCESS_MODE_SDR50 2 +#define SD_ACCESS_MODE_SDR104 3 +#define SD_ACCESS_MODE_DDR50 4 + +static inline uint32_t MMC_RSP_BITS(uint32_t *src, int start, int len) +{ + uint32_t mask = (len % 32 == 0) ? UINT_MAX : UINT_MAX >> (32 - (len % 32)); + size_t word = 3 - start / 32; + size_t shift = start % 32; + uint32_t right = src[word] >> shift; + uint32_t left = (len + shift <= 32) ? 0 : src[word - 1] << ((32 - shift) % 32); + return (left | right) & mask; +} + +#endif //_SDMMC_DEFS_H_ diff --git a/components/driver/include/driver/sdmmc_host.h b/components/driver/include/driver/sdmmc_host.h new file mode 100644 index 000000000..0f56c266a --- /dev/null +++ b/components/driver/include/driver/sdmmc_host.h @@ -0,0 +1,165 @@ +// Copyright 2015-2016 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. + +#pragma once + +#include +#include +#include "esp_err.h" +#include "sdmmc_types.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDMMC_HOST_SLOT_0 0 ///< SDMMC slot 0 +#define SDMMC_HOST_SLOT_1 1 ///< SDMMC slot 1 + +/** + * @brief Default sdmmc_host_t structure initializer for SDMMC peripheral + * + * Uses SDMMC peripheral, with 4-bit mode enabled, and max frequency set to 20MHz + */ +#define SDMMC_HOST_DEFAULT() {\ + .flags = SDMMC_HOST_FLAG_4BIT, \ + .slot = SDMMC_HOST_SLOT_1, \ + .max_freq_khz = SDMMC_FREQ_DEFAULT, \ + .io_voltage = 3.3f, \ + .init = &sdmmc_host_init, \ + .set_bus_width = &sdmmc_host_set_bus_width, \ + .set_card_clk = &sdmmc_host_set_card_clk, \ + .do_transaction = &sdmmc_host_do_transaction, \ + .deinit = &sdmmc_host_deinit, \ +} + +/** + * Extra configuration for SDMMC peripheral slot + */ +typedef struct { + gpio_num_t gpio_cd; ///< GPIO number of card detect signal + gpio_num_t gpio_wp; ///< GPIO number of write protect signal +} sdmmc_slot_config_t; + +#define SDMMC_SLOT_NO_CD ((gpio_num_t) -1) ///< indicates that card detect line is not used +#define SDMMC_SLOT_NO_WP ((gpio_num_t) -1) ///< indicates that write protect line is not used + +/** + * Macro defining default configuration of SDMMC host slot + */ +#define SDMMC_SLOT_CONFIG_DEFAULT() {\ + .gpio_cd = SDMMC_SLOT_NO_CD, \ + .gpio_wp = SDMMC_SLOT_NO_WP, \ +} + +/** + * @brief Initialize SDMMC host peripheral + * + * @note This function is not thread safe + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if sdmmc_host_init was already called + * - ESP_ERR_NO_MEM if memory can not be allocated + */ +esp_err_t sdmmc_host_init(); + +/** + * @brief Initialize given slot of SDMMC peripheral + * + * On the ESP32, SDMMC peripheral has two slots: + * - Slot 0: 8-bit wide, maps to HS1_* signals in PIN MUX + * - Slot 1: 4-bit wide, maps to HS2_* signals in PIN MUX + * + * Card detect and write protect signals can be routed to + * arbitrary GPIOs using GPIO matrix. + * + * @note This function is not thread safe + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param slot_config additional configuration for the slot + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if host has not been initialized using sdmmc_host_init + */ +esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config); + +/** + * @brief Select bus width to be used for data transfer + * + * SD/MMC card must be initialized prior to this command, and a command to set + * bus width has to be sent to the card (e.g. SD_APP_SET_BUS_WIDTH) + * + * @note This function is not thread safe + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param width bus width (1, 4, or 8 for slot 0; 1 or 4 for slot 1) + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if slot number or width is not valid + */ +esp_err_t sdmmc_host_set_bus_width(int slot, size_t width); + +/** + * @brief Set card clock frequency + * + * Currently only integer fractions of 40MHz clock can be used. + * For High Speed cards, 40MHz can be used. + * For Default Speed cards, 20MHz can be used. + * + * @note This function is not thread safe + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param freq_khz card clock frequency, in kHz + * @return + * - ESP_OK on success + * - other error codes may be returned in the future + */ +esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz); + +/** + * @brief Send command to the card and get response + * + * This function returns when command is sent and response is received, + * or data is transferred, or timeout occurs. + * + * @note This function is not thread safe w.r.t. init/deinit functions, + * and bus width/clock speed configuration functions. Multiple tasks + * can call sdmmc_host_do_transaction as long as other sdmmc_host_* + * functions are not called. + * + * @param slot slot number (SDMMC_HOST_SLOT_0 or SDMMC_HOST_SLOT_1) + * @param cmdinfo pointer to structure describing command and data to transfer + * @return + * - ESP_OK on success + * - ESP_ERR_TIMEOUT if response or data transfer has timed out + * - ESP_ERR_INVALID_CRC if response or data transfer CRC check has failed + * - ESP_ERR_INVALID_RESPONSE if the card has sent an invalid response + */ +esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo); + +/** + * @brief Disable SDMMC host and release allocated resources + * + * @note This function is not thread safe + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if sdmmc_host_init function has not been called + */ +esp_err_t sdmmc_host_deinit(); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/sdmmc_types.h b/components/driver/include/driver/sdmmc_types.h new file mode 100644 index 000000000..dfbd2439c --- /dev/null +++ b/components/driver/include/driver/sdmmc_types.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2006 Uwe Stuehler + * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SDMMC_TYPES_H_ +#define _SDMMC_TYPES_H_ + +#include +#include +#include "esp_err.h" + +/** + * Decoded values from SD card Card Specific Data register + */ +typedef struct { + int csd_ver; /*!< CSD structure format */ + int mmc_ver; /*!< MMC version (for CID format) */ + int capacity; /*!< total number of sectors */ + int sector_size; /*!< sector size in bytes */ + int read_block_len; /*!< block length for reads */ + int card_command_class; /*!< Card Command Class for SD */ + int tr_speed; /*!< Max transfer speed */ +} sdmmc_csd_t; + +/** + * Decoded values from SD card Card IDentification register + */ +typedef struct { + int mfg_id; /*!< manufacturer identification number */ + int oem_id; /*!< OEM/product identification number */ + char name[8]; /*!< product name (MMC v1 has the longest) */ + int revision; /*!< product revision */ + int serial; /*!< product serial number */ + int date; /*!< manufacturing date */ +} sdmmc_cid_t; + +/** + * Decoded values from SD Configuration Register + */ +typedef struct { + int sd_spec; /*!< SD Physical layer specification version, reported by card */ + int bus_width; /*!< bus widths supported by card: BIT(0) — 1-bit bus, BIT(2) — 4-bit bus */ +} sdmmc_scr_t; + +/** + * SD/MMC command response buffer + */ +typedef uint32_t sdmmc_response_t[4]; + +/** + * SD/MMC command information + */ +typedef struct { + uint32_t opcode; /*!< SD or MMC command index */ + uint32_t arg; /*!< SD/MMC command argument */ + sdmmc_response_t response; /*!< response buffer */ + void* data; /*!< buffer to send or read into */ + size_t datalen; /*!< length of data buffer */ + size_t blklen; /*!< block length */ + int flags; /*!< see below */ +#define SCF_ITSDONE 0x0001 /*!< command is complete */ +#define SCF_CMD(flags) ((flags) & 0x00f0) +#define SCF_CMD_AC 0x0000 +#define SCF_CMD_ADTC 0x0010 +#define SCF_CMD_BC 0x0020 +#define SCF_CMD_BCR 0x0030 +#define SCF_CMD_READ 0x0040 /*!< read command (data expected) */ +#define SCF_RSP_BSY 0x0100 +#define SCF_RSP_136 0x0200 +#define SCF_RSP_CRC 0x0400 +#define SCF_RSP_IDX 0x0800 +#define SCF_RSP_PRESENT 0x1000 +/* response types */ +#define SCF_RSP_R0 0 /*!< none */ +#define SCF_RSP_R1 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +#define SCF_RSP_R1B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY) +#define SCF_RSP_R2 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_136) +#define SCF_RSP_R3 (SCF_RSP_PRESENT) +#define SCF_RSP_R4 (SCF_RSP_PRESENT) +#define SCF_RSP_R5 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) +#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) + esp_err_t error; /*!< error returned from transfer */ +} sdmmc_command_t; + +/** + * SD/MMC Host description + * + * This structure defines properties of SD/MMC host and functions + * of SD/MMC host which can be used by upper layers. + */ +typedef struct { + uint32_t flags; /*!< flags defining host properties */ +#define SDMMC_HOST_FLAG_1BIT BIT(0) /*!< host supports 1-line SD and MMC protocol */ +#define SDMMC_HOST_FLAG_4BIT BIT(1) /*!< host supports 4-line SD and MMC protocol */ +#define SDMMC_HOST_FLAG_8BIT BIT(2) /*!< host supports 8-line MMC protocol */ +#define SDMMC_HOST_FLAG_SPI BIT(3) /*!< host supports SPI protocol */ + int slot; /*!< slot number, to be passed to host functions */ + int max_freq_khz; /*!< max frequency supported by the host */ +#define SDMMC_FREQ_DEFAULT 20000 /*!< SD/MMC Default speed (limited by clock divider) */ +#define SDMMC_FREQ_HIGHSPEED 40000 /*!< SD High speed (limited by clock divider) */ +#define SDMMC_FREQ_PROBING 4000 /*!< SD/MMC probing speed */ + float io_voltage; /*!< I/O voltage used by the controller (voltage switching is not supported) */ + esp_err_t (*init)(void); /*!< Host function to initialize the driver */ + esp_err_t (*set_bus_width)(int slot, size_t width); /*!< host function to set bus width */ + 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 */ +} sdmmc_host_t; + +/** + * SD/MMC card information structure + */ +typedef struct { + sdmmc_host_t host; /*!< Host with which the card is associated */ + uint32_t ocr; /*!< OCR (Operation Conditions Register) value */ + sdmmc_cid_t cid; /*!< decoded CID (Card IDentification) register value */ + 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) */ +} sdmmc_card_t; + + + + +#endif // _SDMMC_TYPES_H_ diff --git a/components/driver/sdmmc_host.c b/components/driver/sdmmc_host.c new file mode 100644 index 000000000..400c85b88 --- /dev/null +++ b/components/driver/sdmmc_host.c @@ -0,0 +1,463 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "esp_log.h" +#include "esp_intr_alloc.h" +#include "soc/sdmmc_struct.h" +#include "soc/sdmmc_reg.h" +#include "soc/io_mux_reg.h" +#include "soc/gpio_sig_map.h" +#include "rom/gpio.h" +#include "driver/gpio.h" +#include "driver/sdmmc_host.h" +#include "sdmmc_private.h" + +#define SDMMC_EVENT_QUEUE_LENGTH 32 + +typedef struct { + uint32_t clk; + uint32_t cmd; + uint32_t d0; + uint32_t d1; + uint32_t d2; + uint32_t d3; + uint32_t d4; + uint32_t d5; + uint32_t d6; + uint32_t d7; + uint8_t card_detect; + uint8_t write_protect; + uint8_t width; +} sdmmc_slot_info_t; + + +static void sdmmc_isr(void* arg); +static void sdmmc_host_dma_init(); + +static const sdmmc_slot_info_t s_slot_info[2] = { + { + .clk = PERIPHS_IO_MUX_SD_CLK_U, + .cmd = PERIPHS_IO_MUX_SD_CMD_U, + .d0 = PERIPHS_IO_MUX_SD_DATA0_U, + .d1 = PERIPHS_IO_MUX_SD_DATA1_U, + .d2 = PERIPHS_IO_MUX_SD_DATA2_U, + .d3 = PERIPHS_IO_MUX_SD_DATA3_U, + .d4 = PERIPHS_IO_MUX_GPIO16_U, + .d5 = PERIPHS_IO_MUX_GPIO17_U, + .d6 = PERIPHS_IO_MUX_GPIO5_U, + .d7 = PERIPHS_IO_MUX_GPIO18_U, + .card_detect = HOST_CARD_DETECT_N_1_IDX, + .write_protect = HOST_CARD_WRITE_PRT_1_IDX, + .width = 8 + }, + { + .clk = PERIPHS_IO_MUX_MTMS_U, + .cmd = PERIPHS_IO_MUX_MTDO_U, + .d0 = PERIPHS_IO_MUX_GPIO2_U, + .d1 = PERIPHS_IO_MUX_GPIO4_U, + .d2 = PERIPHS_IO_MUX_MTDI_U, + .d3 = PERIPHS_IO_MUX_MTCK_U, + .card_detect = HOST_CARD_DETECT_N_2_IDX, + .write_protect = HOST_CARD_WRITE_PRT_2_IDX, + .width = 4 + } +}; + +static const char* TAG = "sdmmc_periph"; +static intr_handle_t s_intr_handle; +static QueueHandle_t s_event_queue; + + +void sdmmc_host_reset() +{ + // Set reset bits + SDMMC.ctrl.controller_reset = 1; + SDMMC.ctrl.dma_reset = 1; + SDMMC.ctrl.fifo_reset = 1; + // Wait for the reset bits to be cleared by hardware + while (SDMMC.ctrl.controller_reset || SDMMC.ctrl.fifo_reset || SDMMC.ctrl.dma_reset) { + ; + } +} + +/* We have two clock divider stages: + * - one is the clock generator which drives SDMMC peripheral, + * it can be configured using SDMMC.clock register. It can generate + * frequencies 160MHz/(N + 1), where 0 < N < 16, I.e. from 10 to 80 MHz. + * - 4 clock dividers inside SDMMC peripheral, which can divide clock + * from the first stage by 2 * M, where 0 < M < 255 + * (they can also be bypassed). + * + * For cards which aren't UHS-1 or UHS-2 cards, which we don't support, + * maximum bus frequency in high speed (HS) mode is 50 MHz. + * Note: for non-UHS-1 cards, HS mode is optional. + * Default speed (DS) mode is mandatory, it works up to 25 MHz. + * Whether the card supports HS or not can be determined using TRAN_SPEED + * 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. + * + * 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() +{ + // 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 phases for in/out clocks + SDMMC.clock.phase_dout = 4; + SDMMC.clock.phase_din = 4; + SDMMC.clock.phase_core = 0; + // Wait for the clock to propagate + ets_delay_us(10); +} + +static void sdmmc_host_input_clk_disable() +{ + SDMMC.clock.val = 0; +} + +static void sdmmc_host_clock_update_command(int slot) +{ + // Clock update command (not a real command; just updates CIU registers) + sdmmc_hw_cmd_t cmd_val = { + .card_num = slot, + .update_clk_reg = 1, + .wait_complete = 1 + }; + bool repeat = true; + while(repeat) { + sdmmc_host_start_command(slot, cmd_val, 0); + while (true) { + // Sending clock update command to the CIU can generate HLE error. + // According to the manual, this is okay and we must retry the command. + if (SDMMC.rintsts.hle) { + SDMMC.rintsts.hle = 1; + repeat = true; + break; + } + // When the command is accepted by CIU, start_command bit will be + // cleared in SDMMC.cmd register. + if (SDMMC.cmd.start_command == 0) { + repeat = false; + break; + } + } + } +} + +esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz) +{ + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + const int clk40m = 40000; + + // Disable clock first + SDMMC.clkena.cclk_enable &= ~BIT(slot); + sdmmc_host_clock_update_command(slot); + + // 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); + } + ESP_LOGD(TAG, "slot=%d div=%d freq=%dkHz", slot, div, + (div == 0) ? clk40m : clk40m / (2 * div)); + + // Program CLKDIV and CLKSRC, send them to the CIU + switch(slot) { + case 0: + SDMMC.clksrc.card0 = 0; + SDMMC.clkdiv.div0 = div; + break; + case 1: + SDMMC.clksrc.card1 = 1; + SDMMC.clkdiv.div1 = div; + break; + } + sdmmc_host_clock_update_command(slot); + + // Re-enable clocks + SDMMC.clkena.cclk_enable |= BIT(slot); + SDMMC.clkena.cclk_low_power |= BIT(slot); + sdmmc_host_clock_update_command(slot); + return ESP_OK; +} + +esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg) { + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + while (SDMMC.cmd.start_command == 1) { + ; + } + SDMMC.cmdarg = arg; + cmd.card_num = slot; + cmd.start_command = 1; + SDMMC.cmd = cmd; + return ESP_OK; +} + +esp_err_t sdmmc_host_init() +{ + if (s_intr_handle) { + return ESP_ERR_INVALID_STATE; + } + + // Enable clock to peripheral + sdmmc_host_input_clk_enable(); + + // Reset + sdmmc_host_reset(); + ESP_LOGD(TAG, "peripheral version %x, hardware config %08x", SDMMC.verid, SDMMC.hcon); + + // Clear interrupt status and set interrupt mask to known state + SDMMC.rintsts.val = 0xffffffff; + SDMMC.intmask.val = 0; + SDMMC.ctrl.int_enable = 0; + + // Allocate event queue + s_event_queue = xQueueCreate(SDMMC_EVENT_QUEUE_LENGTH, sizeof(sdmmc_event_t)); + if (!s_event_queue) { + 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; + return ret; + } + // Enable interrupts + SDMMC.intmask.val = + SDMMC_INTMASK_CD | + SDMMC_INTMASK_CMD_DONE | + SDMMC_INTMASK_DATA_OVER | + 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.ctrl.int_enable = 1; + + // Enable DMA + sdmmc_host_dma_init(); + + // Initialize transaction handler + ret = sdmmc_host_transaction_handler_init(); + if (ret != ESP_OK) { + vQueueDelete(s_event_queue); + s_event_queue = NULL; + esp_intr_free(s_intr_handle); + s_intr_handle = NULL; + return ret; + } + + return ESP_OK; +} + + +static inline void configure_pin(uint32_t io_mux_reg) +{ + const int sdmmc_func = 3; + const int drive_strength = 3; + PIN_INPUT_ENABLE(io_mux_reg); + PIN_FUNC_SELECT(io_mux_reg, sdmmc_func); + PIN_SET_DRV(io_mux_reg, drive_strength); +} + +esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t* slot_config) +{ + if (!s_intr_handle) { + return ESP_ERR_INVALID_STATE; + } + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + if (slot_config == NULL) { + return ESP_ERR_INVALID_ARG; + } + int gpio_cd = slot_config->gpio_cd; + int gpio_wp = slot_config->gpio_wp; + + // Configure pins + const sdmmc_slot_info_t* pslot = &s_slot_info[slot]; + configure_pin(pslot->clk); + configure_pin(pslot->cmd); + configure_pin(pslot->d0); + configure_pin(pslot->d1); + configure_pin(pslot->d2); + configure_pin(pslot->d3); + if (pslot->width == 8) { + configure_pin(pslot->d4); + configure_pin(pslot->d5); + configure_pin(pslot->d6); + configure_pin(pslot->d7); + } + if (gpio_cd != -1) { + gpio_set_direction(gpio_cd, GPIO_MODE_INPUT); + gpio_matrix_in(gpio_cd, 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); + } + // By default, set probing frequency (400kHz) and 1-bit bus + esp_err_t ret = sdmmc_host_set_card_clk(slot, 400); + if (ret != ESP_OK) { + return ret; + } + ret = sdmmc_host_set_bus_width(slot, 1); + if (ret != ESP_OK) { + return ret; + } + return ESP_OK; +} + +esp_err_t sdmmc_host_deinit() +{ + if (!s_intr_handle) { + return ESP_ERR_INVALID_STATE; + } + esp_intr_free(s_intr_handle); + s_intr_handle = NULL; + vQueueDelete(s_event_queue); + s_event_queue = NULL; + sdmmc_host_input_clk_disable(); + sdmmc_host_transaction_handler_deinit(); + return ESP_OK; +} + +esp_err_t sdmmc_host_wait_for_event(int tick_count, sdmmc_event_t* out_event) +{ + if (!out_event) { + return ESP_ERR_INVALID_ARG; + } + if (!s_event_queue) { + return ESP_ERR_INVALID_STATE; + } + int ret = xQueueReceive(s_event_queue, out_event, tick_count); + if (ret == pdFALSE) { + return ESP_ERR_TIMEOUT; + } + return ESP_OK; +} + +esp_err_t sdmmc_host_set_bus_width(int slot, size_t width) +{ + if (!(slot == 0 || slot == 1)) { + return ESP_ERR_INVALID_ARG; + } + if (s_slot_info[slot].width < width) { + return ESP_ERR_INVALID_ARG; + } + const uint16_t mask = BIT(slot); + if (width == 1) { + SDMMC.ctype.card_width_8 &= ~mask; + SDMMC.ctype.card_width &= ~mask; + } else if (width == 4) { + SDMMC.ctype.card_width_8 &= ~mask; + SDMMC.ctype.card_width |= mask; + } else if (width == 8){ + SDMMC.ctype.card_width_8 |= mask; + } else { + return ESP_ERR_INVALID_ARG; + } + ESP_LOGD(TAG, "slot=%d width=%d", slot, width); + return ESP_OK; +} + +static void sdmmc_host_dma_init() +{ + SDMMC.ctrl.dma_enable = 1; + SDMMC.bmod.val = 0; + SDMMC.bmod.sw_reset = 1; + SDMMC.idinten.ni = 1; + SDMMC.idinten.ri = 1; + SDMMC.idinten.ti = 1; +} + + +void sdmmc_host_dma_stop() +{ + SDMMC.ctrl.use_internal_dma = 0; + SDMMC.ctrl.dma_reset = 1; + SDMMC.bmod.fb = 0; + SDMMC.bmod.enable = 0; +} + +void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size) +{ + // TODO: set timeout depending on data size + SDMMC.tmout.val = 0xffffffff; + + // Set size of data and DMA descriptor pointer + SDMMC.bytcnt = data_size; + SDMMC.blksiz = block_size; + SDMMC.dbaddr = desc; + + // Enable everything needed to use DMA + SDMMC.ctrl.dma_enable = 1; + SDMMC.ctrl.use_internal_dma = 1; + SDMMC.bmod.enable = 1; + SDMMC.bmod.fb = 1; + sdmmc_host_dma_resume(); +} + +void sdmmc_host_dma_resume() +{ + SDMMC.pldmnd = 1; +} + +/** + * @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. + * + * 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; + SDMMC.rintsts.val = pending; + event.sdmmc_status = pending; + + uint32_t dma_pending = SDMMC.idsts.val; + 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 (higher_priority_task_awoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + diff --git a/components/driver/sdmmc_private.h b/components/driver/sdmmc_private.h new file mode 100644 index 000000000..5a10a3b67 --- /dev/null +++ b/components/driver/sdmmc_private.h @@ -0,0 +1,44 @@ +// Copyright 2015-2016 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. + +#pragma once + +#include +#include +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "soc/sdmmc_struct.h" + +typedef struct { + uint32_t sdmmc_status; ///< masked SDMMC interrupt status + uint32_t dma_status; ///< masked DMA interrupt status +} sdmmc_event_t; + +void sdmmc_host_reset(); + +esp_err_t sdmmc_host_start_command(int slot, sdmmc_hw_cmd_t cmd, uint32_t arg); + +esp_err_t sdmmc_host_wait_for_event(int tick_count, sdmmc_event_t* out_event); + +void sdmmc_host_dma_prepare(sdmmc_desc_t* desc, size_t block_size, size_t data_size); + +void sdmmc_host_dma_stop(); + +void sdmmc_host_dma_resume(); + +esp_err_t sdmmc_host_transaction_handler_init(); + +void sdmmc_host_transaction_handler_deinit(); + diff --git a/components/driver/sdmmc_transaction.c b/components/driver/sdmmc_transaction.c new file mode 100644 index 000000000..59b439eec --- /dev/null +++ b/components/driver/sdmmc_transaction.c @@ -0,0 +1,372 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "soc/sdmmc_reg.h" +#include "soc/sdmmc_struct.h" +#include "driver/sdmmc_types.h" +#include "driver/sdmmc_defs.h" +#include "driver/sdmmc_host.h" +#include "sdmmc_private.h" + + +/* Number of DMA descriptors used for transfer. + * Increasing this value above 4 doesn't improve performance for the usual case + * of SD memory cards (most data transfers are multiples of 512 bytes). + */ +#define SDMMC_DMA_DESC_CNT 4 + +static const char* TAG = "sdmmc_req"; + +typedef enum { + SDMMC_IDLE, + SDMMC_SENDING_CMD, + SDMMC_SENDING_DATA, + SDMMC_BUSY, +} sdmmc_req_state_t; + +typedef struct { + uint8_t* ptr; + size_t size_remaining; + size_t next_desc; + size_t desc_remaining; +} sdmmc_transfer_state_t; + +const uint32_t SDMMC_DATA_ERR_MASK = + SDMMC_INTMASK_DTO | SDMMC_INTMASK_DCRC | + SDMMC_INTMASK_HTO | SDMMC_INTMASK_SBE | + SDMMC_INTMASK_EBE; + +const uint32_t SDMMC_DMA_DONE_MASK = + SDMMC_IDMAC_INTMASK_RI | SDMMC_IDMAC_INTMASK_TI | + SDMMC_IDMAC_INTMASK_NI; + +const uint32_t SDMMC_CMD_ERR_MASK = + SDMMC_INTMASK_RTO | + SDMMC_INTMASK_RCRC | + SDMMC_INTMASK_RESP_ERR; + +static sdmmc_desc_t s_dma_desc[SDMMC_DMA_DESC_CNT]; +static sdmmc_transfer_state_t s_cur_transfer = { 0 }; +static QueueHandle_t s_request_mutex; + +static esp_err_t handle_idle_state_events(); +static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd); +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); + +esp_err_t sdmmc_host_transaction_handler_init() +{ + assert(s_request_mutex == NULL); + s_request_mutex = xSemaphoreCreateMutex(); + if (!s_request_mutex) { + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +void sdmmc_host_transaction_handler_deinit() +{ + assert(s_request_mutex); + vSemaphoreDelete(s_request_mutex); + s_request_mutex = NULL; +} + +esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t* cmdinfo) +{ + xSemaphoreTake(s_request_mutex, portMAX_DELAY); + // dispose of any events which happened asynchronously + handle_idle_state_events(); + // convert cmdinfo to hardware register value + sdmmc_hw_cmd_t hw_cmd = make_hw_cmd(cmdinfo); + if (cmdinfo->data) { + // these constraints should be handled by upper layer + assert(cmdinfo->datalen >= 4); + assert(cmdinfo->blklen % 4 == 0); + // this clears "owned by IDMAC" bits + memset(s_dma_desc, 0, sizeof(s_dma_desc)); + // initialize first descriptor + s_dma_desc[0].first_descriptor = 1; + // save transfer info + s_cur_transfer.ptr = (uint8_t*) cmdinfo->data; + s_cur_transfer.size_remaining = cmdinfo->datalen; + s_cur_transfer.next_desc = 0; + s_cur_transfer.desc_remaining = (cmdinfo->datalen + SDMMC_DMA_MAX_BUF_LEN - 1) / SDMMC_DMA_MAX_BUF_LEN; + // prepare descriptors + fill_dma_descriptors(SDMMC_DMA_DESC_CNT); + // write transfer info into hardware + sdmmc_host_dma_prepare(&s_dma_desc[0], cmdinfo->blklen, cmdinfo->datalen); + } + // write command into hardware, this also sends the command to the card + esp_err_t ret = sdmmc_host_start_command(slot, hw_cmd, cmdinfo->arg); + if (ret != ESP_OK) { + xSemaphoreGive(s_request_mutex); + return ret; + } + // process events until transfer is complete + cmdinfo->error = ESP_OK; + sdmmc_req_state_t state = SDMMC_SENDING_CMD; + while (state != SDMMC_IDLE) { + ret = handle_event(cmdinfo, &state); + if (ret != ESP_OK) { + break; + } + } + xSemaphoreGive(s_request_mutex); + return ret; +} + +static void fill_dma_descriptors(size_t num_desc) +{ + for (size_t i = 0; i < num_desc; ++i) { + if (s_cur_transfer.size_remaining == 0) { + return; + } + const size_t next = s_cur_transfer.next_desc; + sdmmc_desc_t* desc = &s_dma_desc[next]; + assert(!desc->owned_by_idmac); + size_t size_to_fill = + (s_cur_transfer.size_remaining < SDMMC_DMA_MAX_BUF_LEN) ? + s_cur_transfer.size_remaining : SDMMC_DMA_MAX_BUF_LEN; + bool last = size_to_fill == s_cur_transfer.size_remaining; + desc->last_descriptor = last; + desc->second_address_chained = 1; + desc->owned_by_idmac = 1; + desc->buffer1_ptr = s_cur_transfer.ptr; + desc->next_desc_ptr = (last) ? NULL : &s_dma_desc[(next + 1) % SDMMC_DMA_DESC_CNT]; + desc->buffer1_size = size_to_fill; + + s_cur_transfer.size_remaining -= size_to_fill; + s_cur_transfer.ptr += size_to_fill; + s_cur_transfer.next_desc = (s_cur_transfer.next_desc + 1) % SDMMC_DMA_DESC_CNT; + ESP_LOGV(TAG, "fill %d desc=%d rem=%d next=%d last=%d sz=%d", + num_desc, next, s_cur_transfer.size_remaining, + s_cur_transfer.next_desc, desc->last_descriptor, desc->buffer1_size); + } +} + +static esp_err_t handle_idle_state_events() +{ + /* Handle any events which have happened in between transfers. + * Under current assumptions (no SDIO support) only card detect events + * can happen in the idle state. + */ + sdmmc_event_t evt; + while (sdmmc_host_wait_for_event(0, &evt) == ESP_OK) { + if (evt.sdmmc_status & SDMMC_INTMASK_CD) { + ESP_LOGV(TAG, "card detect event"); + evt.sdmmc_status &= ~SDMMC_INTMASK_CD; + } + if (evt.sdmmc_status != 0 || evt.dma_status != 0) { + ESP_LOGE(TAG, "handle_idle_state_events unhandled: %08x %08x", + evt.sdmmc_status, evt.dma_status); + } + + } + return ESP_OK; +} + + +static esp_err_t handle_event(sdmmc_command_t* cmd, sdmmc_req_state_t* state) +{ + sdmmc_event_t evt; + esp_err_t err = sdmmc_host_wait_for_event(portMAX_DELAY, &evt); + if (err != ESP_OK) { + ESP_LOGE(TAG, "sdmmc_host_wait_for_event returned %d", err); + return err; + } + ESP_LOGV(TAG, "sdmmc_handle_event: evt %08x %08x", evt.sdmmc_status, evt.dma_status); + process_events(evt, cmd, state); + return ESP_OK; +} + +static sdmmc_hw_cmd_t make_hw_cmd(sdmmc_command_t* cmd) +{ + sdmmc_hw_cmd_t res = { 0 }; + + res.cmd_index = cmd->opcode; + if (cmd->opcode == MMC_STOP_TRANSMISSION) { + res.stop_abort_cmd = 1; + } else { + res.wait_complete = 1; + } + if (cmd->opcode == SD_APP_SET_BUS_WIDTH) { + res.send_auto_stop = 1; + res.data_expected = 1; + } + if (cmd->flags & SCF_RSP_PRESENT) { + res.response_expect = 1; + if (cmd->flags & SCF_RSP_136) { + res.response_long = 1; + } + } + if (cmd->flags & SCF_RSP_CRC) { + res.check_response_crc = 1; + } + res.use_hold_reg = 1; + if (cmd->data) { + res.data_expected = 1; + if ((cmd->flags & SCF_CMD_READ) == 0) { + res.rw = 1; + } + assert(cmd->datalen % cmd->blklen == 0); + if ((cmd->datalen / cmd->blklen) > 1) { + res.send_auto_stop = 1; + } + } + ESP_LOGV(TAG, "%s: opcode=%d, rexp=%d, crc=%d", __func__, + res.cmd_index, res.response_expect, res.check_response_crc); + return res; +} + +static void process_command_response(uint32_t status, sdmmc_command_t* cmd) +{ + if (cmd->flags & SCF_RSP_PRESENT) { + if (cmd->flags & SCF_RSP_136) { + cmd->response[3] = SDMMC.resp[0]; + cmd->response[2] = SDMMC.resp[1]; + cmd->response[1] = SDMMC.resp[2]; + cmd->response[0] = SDMMC.resp[3]; + + } else { + cmd->response[0] = SDMMC.resp[0]; + cmd->response[1] = 0; + cmd->response[2] = 0; + 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; + } else if ((cmd->flags & SCF_RSP_CRC) && (status & SDMMC_INTMASK_RCRC)) { + cmd->error = ESP_ERR_INVALID_CRC; + } else if (status & SDMMC_INTMASK_RESP_ERR) { + cmd->error = ESP_ERR_INVALID_RESPONSE; + } + if (cmd->error != 0) { + if (cmd->data) { + sdmmc_host_dma_stop(); + } + ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error); + } +} + +static void process_data_status(uint32_t status, sdmmc_command_t* cmd) +{ + if (status & SDMMC_DATA_ERR_MASK) { + if (status & SDMMC_INTMASK_DTO) { + cmd->error = ESP_ERR_TIMEOUT; + } else if (status & SDMMC_INTMASK_DCRC) { + cmd->error = ESP_ERR_INVALID_CRC; + } else if ((status & SDMMC_INTMASK_EBE) && + (cmd->flags & SCF_CMD_READ) == 0) { + cmd->error = ESP_ERR_TIMEOUT; + } else { + cmd->error = ESP_FAIL; + } + SDMMC.ctrl.fifo_reset = 1; + } + if (cmd->error != 0) { + if (cmd->data) { + sdmmc_host_dma_stop(); + } + ESP_LOGD(TAG, "%s: error %d", __func__, cmd->error); + } + +} + +static inline bool mask_check_and_clear(uint32_t* state, uint32_t mask) { + bool ret = ((*state) & mask) != 0; + *state &= ~mask; + return ret; +} + +static esp_err_t process_events(sdmmc_event_t evt, sdmmc_command_t* cmd, sdmmc_req_state_t* pstate) +{ + const char* const s_state_names[] __attribute__((unused)) = { + "IDLE", + "SENDING_CMD", + "SENDIND_DATA", + "BUSY" + }; + sdmmc_event_t orig_evt = evt; + ESP_LOGV(TAG, "%s: state=%s", __func__, s_state_names[*pstate]); + sdmmc_req_state_t next_state = *pstate; + sdmmc_req_state_t state = (sdmmc_req_state_t) -1; + while (next_state != state) { + state = next_state; + switch (state) { + case SDMMC_IDLE: + break; + + case SDMMC_SENDING_CMD: + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_CMD_ERR_MASK)) { + process_command_response(orig_evt.sdmmc_status, cmd); + break; + } + if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_CMD_DONE)) { + break; + } + process_command_response(orig_evt.sdmmc_status, cmd); + if (cmd->error != ESP_OK || cmd->data == NULL) { + next_state = SDMMC_IDLE; + break; + } + next_state = SDMMC_SENDING_DATA; + break; + + + case SDMMC_SENDING_DATA: + if (mask_check_and_clear(&evt.sdmmc_status, SDMMC_DATA_ERR_MASK)) { + process_data_status(orig_evt.sdmmc_status, cmd); + sdmmc_host_dma_stop(); + } + 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); + sdmmc_host_dma_resume(); + } + if (s_cur_transfer.desc_remaining == 0) { + next_state = SDMMC_BUSY; + } + } + break; + + case SDMMC_BUSY: + if (!mask_check_and_clear(&evt.sdmmc_status, SDMMC_INTMASK_DATA_OVER)) { + break; + } + process_data_status(orig_evt.sdmmc_status, cmd); + next_state = SDMMC_IDLE; + break; + } + ESP_LOGV(TAG, "%s state=%s next_state=%s", __func__, s_state_names[state], s_state_names[next_state]); + } + *pstate = state; + return ESP_OK; +} + + + diff --git a/components/esp32/include/esp_err.h b/components/esp32/include/esp_err.h index a1f4b8f35..b6a1e8b42 100644 --- a/components/esp32/include/esp_err.h +++ b/components/esp32/include/esp_err.h @@ -35,7 +35,8 @@ typedef int32_t esp_err_t; #define ESP_ERR_NOT_FOUND 0x105 #define ESP_ERR_NOT_SUPPORTED 0x106 #define ESP_ERR_TIMEOUT 0x107 - +#define ESP_ERR_INVALID_RESPONSE 0x108 +#define ESP_ERR_INVALID_CRC 0x109 #define ESP_ERR_WIFI_BASE 0x3000 /*!< Starting number of WiFi error codes */ diff --git a/components/esp32/include/soc/sdmmc_reg.h b/components/esp32/include/soc/sdmmc_reg.h new file mode 100644 index 000000000..d1b452d1d --- /dev/null +++ b/components/esp32/include/soc/sdmmc_reg.h @@ -0,0 +1,94 @@ +// Copyright 2015-2016 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. +#ifndef _SOC_SDMMC_REG_H_ +#define _SOC_SDMMC_REG_H_ +#include "soc.h" + +#define SDMMC_CTRL_REG (DR_REG_SDMMC_BASE + 0x00) +#define SDMMC_PWREN_REG (DR_REG_SDMMC_BASE + 0x04) +#define SDMMC_CLKDIV_REG (DR_REG_SDMMC_BASE + 0x08) +#define SDMMC_CLKSRC_REG (DR_REG_SDMMC_BASE + 0x0c) +#define SDMMC_CLKENA_REG (DR_REG_SDMMC_BASE + 0x10) +#define SDMMC_TMOUT_REG (DR_REG_SDMMC_BASE + 0x14) +#define SDMMC_CTYPE_REG (DR_REG_SDMMC_BASE + 0x18) +#define SDMMC_BLKSIZ_REG (DR_REG_SDMMC_BASE + 0x1c) +#define SDMMC_BYTCNT_REG (DR_REG_SDMMC_BASE + 0x20) +#define SDMMC_INTMASK_REG (DR_REG_SDMMC_BASE + 0x24) +#define SDMMC_CMDARG_REG (DR_REG_SDMMC_BASE + 0x28) +#define SDMMC_CMD_REG (DR_REG_SDMMC_BASE + 0x2c) +#define SDMMC_RESP0_REG (DR_REG_SDMMC_BASE + 0x30) +#define SDMMC_RESP1_REG (DR_REG_SDMMC_BASE + 0x34) +#define SDMMC_RESP2_REG (DR_REG_SDMMC_BASE + 0x38) +#define SDMMC_RESP3_REG (DR_REG_SDMMC_BASE + 0x3c) + +#define SDMMC_MINTSTS_REG (DR_REG_SDMMC_BASE + 0x40) +#define SDMMC_RINTSTS_REG (DR_REG_SDMMC_BASE + 0x44) +#define SDMMC_STATUS_REG (DR_REG_SDMMC_BASE + 0x48) +#define SDMMC_FIFOTH_REG (DR_REG_SDMMC_BASE + 0x4c) +#define SDMMC_CDETECT_REG (DR_REG_SDMMC_BASE + 0x50) +#define SDMMC_WRTPRT_REG (DR_REG_SDMMC_BASE + 0x54) +#define SDMMC_GPIO_REG (DR_REG_SDMMC_BASE + 0x58) +#define SDMMC_TCBCNT_REG (DR_REG_SDMMC_BASE + 0x5c) +#define SDMMC_TBBCNT_REG (DR_REG_SDMMC_BASE + 0x60) +#define SDMMC_DEBNCE_REG (DR_REG_SDMMC_BASE + 0x64) +#define SDMMC_USRID_REG (DR_REG_SDMMC_BASE + 0x68) +#define SDMMC_VERID_REG (DR_REG_SDMMC_BASE + 0x6c) +#define SDMMC_HCON_REG (DR_REG_SDMMC_BASE + 0x70) +#define SDMMC_UHS_REG_REG (DR_REG_SDMMC_BASE + 0x74) +#define SDMMC_RST_N_REG (DR_REG_SDMMC_BASE + 0x78) +#define SDMMC_BMOD_REG (DR_REG_SDMMC_BASE + 0x80) +#define SDMMC_PLDMND_REG (DR_REG_SDMMC_BASE + 0x84) +#define SDMMC_DBADDR_REG (DR_REG_SDMMC_BASE + 0x88) +#define SDMMC_DBADDRU_REG (DR_REG_SDMMC_BASE + 0x8c) +#define SDMMC_IDSTS_REG (DR_REG_SDMMC_BASE + 0x8c) +#define SDMMC_IDINTEN_REG (DR_REG_SDMMC_BASE + 0x90) +#define SDMMC_DSCADDR_REG (DR_REG_SDMMC_BASE + 0x94) +#define SDMMC_DSCADDRL_REG (DR_REG_SDMMC_BASE + 0x98) +#define SDMMC_DSCADDRU_REG (DR_REG_SDMMC_BASE + 0x9c) +#define SDMMC_BUFADDRL_REG (DR_REG_SDMMC_BASE + 0xa0) +#define SDMMC_BUFADDRU_REG (DR_REG_SDMMC_BASE + 0xa4) +#define SDMMC_CARDTHRCTL_REG (DR_REG_SDMMC_BASE + 0x100) +#define SDMMC_BACK_END_POWER_REG (DR_REG_SDMMC_BASE + 0x104) +#define SDMMC_UHS_REG_EXT_REG (DR_REG_SDMMC_BASE + 0x108) +#define SDMMC_EMMC_DDR_REG_REG (DR_REG_SDMMC_BASE + 0x10c) +#define SDMMC_ENABLE_SHIFT_REG (DR_REG_SDMMC_BASE + 0x110) + +#define SDMMC_CLOCK_REG (DR_REG_SDMMC_BASE + 0x800) + +#define SDMMC_INTMASK_EBE BIT(15) +#define SDMMC_INTMASK_ACD BIT(14) +#define SDMMC_INTMASK_SBE BIT(13) +#define SDMMC_INTMASK_HLE BIT(12) +#define SDMMC_INTMASK_FRUN BIT(11) +#define SDMMC_INTMASK_HTO BIT(10) +#define SDMMC_INTMASK_DTO BIT(9) +#define SDMMC_INTMASK_RTO BIT(8) +#define SDMMC_INTMASK_DCRC BIT(7) +#define SDMMC_INTMASK_RCRC BIT(6) +#define SDMMC_INTMASK_RXDR BIT(5) +#define SDMMC_INTMASK_TXDR BIT(4) +#define SDMMC_INTMASK_DATA_OVER BIT(3) +#define SDMMC_INTMASK_CMD_DONE BIT(2) +#define SDMMC_INTMASK_RESP_ERR BIT(1) +#define SDMMC_INTMASK_CD BIT(0) + +#define SDMMC_IDMAC_INTMASK_AI BIT(9) +#define SDMMC_IDMAC_INTMASK_NI BIT(8) +#define SDMMC_IDMAC_INTMASK_CES BIT(5) +#define SDMMC_IDMAC_INTMASK_DU BIT(4) +#define SDMMC_IDMAC_INTMASK_FBE BIT(2) +#define SDMMC_IDMAC_INTMASK_RI BIT(1) +#define SDMMC_IDMAC_INTMASK_TI BIT(0) + +#endif /* _SOC_SDMMC_REG_H_ */ diff --git a/components/esp32/include/soc/sdmmc_struct.h b/components/esp32/include/soc/sdmmc_struct.h new file mode 100644 index 000000000..20bb9d260 --- /dev/null +++ b/components/esp32/include/soc/sdmmc_struct.h @@ -0,0 +1,371 @@ +// Copyright 2015-2016 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. +#ifndef _SOC_SDMMC_STRUCT_H_ +#define _SOC_SDMMC_STRUCT_H_ + +#include + +typedef struct { + uint32_t reserved1: 1; + uint32_t disable_int_on_completion: 1; + uint32_t last_descriptor: 1; + uint32_t first_descriptor: 1; + uint32_t second_address_chained: 1; + uint32_t end_of_ring: 1; + uint32_t reserved2: 24; + uint32_t card_error_summary: 1; + uint32_t owned_by_idmac: 1; + uint32_t buffer1_size: 13; + uint32_t buffer2_size: 13; + uint32_t reserved3: 6; + void* buffer1_ptr; + union { + void* buffer2_ptr; + void* next_desc_ptr; + }; +} sdmmc_desc_t; + +#define SDMMC_DMA_MAX_BUF_LEN 4096 + +_Static_assert(sizeof(sdmmc_desc_t) == 16, "invalid size of sdmmc_desc_t structure"); + + +typedef struct { + uint32_t cmd_index: 6; ///< Command index + uint32_t response_expect: 1; ///< set if response is expected + uint32_t response_long: 1; ///< 0: short response expected, 1: long response expected + uint32_t check_response_crc: 1; ///< set if controller should check response CRC + uint32_t data_expected: 1; ///< 0: no data expected, 1: data expected + uint32_t rw: 1; ///< 0: read from card, 1: write to card (don't care if no data expected) + uint32_t stream_mode: 1; ///< 0: block transfer, 1: stream transfer (don't care if no data expected) + uint32_t send_auto_stop: 1; ///< set to send stop at the end of the transfer + uint32_t wait_complete: 1; ///< 0: send command at once, 1: wait for previous command to complete + uint32_t stop_abort_cmd: 1; ///< set if this is a stop or abort command intended to stop current transfer + uint32_t send_init: 1; ///< set to send init sequence (80 clocks of 1) + uint32_t card_num: 5; ///< card number + uint32_t update_clk_reg: 1; ///< 0: normal command, 1: don't send command, just update clock registers + uint32_t read_ceata: 1; ///< set if performing read from CE-ATA device + uint32_t ccs_expected: 1; ///< set if CCS is expected from CE-ATA device + uint32_t enable_boot: 1; ///< set for mandatory boot mode + uint32_t expect_boot_ack: 1; ///< when set along with enable_boot, controller expects boot ack pattern + uint32_t disable_boot: 1; ///< set to terminate boot operation (don't set along with enable_boot) + uint32_t boot_mode: 1; ///< 0: mandatory boot operation, 1: alternate boot operation + uint32_t volt_switch: 1; ///< set to enable voltage switching (for CMD11 only) + uint32_t use_hold_reg: 1; ///< clear to bypass HOLD register + uint32_t reserved: 1; + uint32_t start_command: 1; ///< Start command; once command is sent to the card, bit is cleared. +} sdmmc_hw_cmd_t; ///< command format used in cmd register; this structure is defined to make it easier to build command values + +_Static_assert(sizeof(sdmmc_hw_cmd_t) == 4, "invalid size of sdmmc_cmd_t structure"); + + +typedef volatile struct { + union { + struct { + uint32_t controller_reset: 1; + uint32_t fifo_reset: 1; + uint32_t dma_reset: 1; + uint32_t reserved1: 1; + uint32_t int_enable: 1; + uint32_t dma_enable: 1; + uint32_t read_wait: 1; + uint32_t send_irq_response: 1; + uint32_t abort_read_data: 1; + uint32_t send_ccsd: 1; + uint32_t send_auto_stop_ccsd: 1; + uint32_t ceata_device_interrupt_status: 1; + uint32_t reserved2: 4; + uint32_t card_voltage_a: 4; + uint32_t card_voltage_b: 4; + uint32_t enable_od_pullup: 1; + uint32_t use_internal_dma: 1; + uint32_t reserved3: 6; + }; + uint32_t val; + } ctrl; + + uint32_t pwren; ///< 1: enable power to card, 0: disable power to card + + union { + struct { + uint32_t div0: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + uint32_t div1: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + uint32_t div2: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + uint32_t div3: 8; ///< 0: bypass, 1-255: divide clock by (2*div0). + }; + uint32_t val; + } clkdiv; + + union { + struct { + uint32_t card0: 2; ///< 0-3: select clock divider for card 0 among div0-div3 + uint32_t card1: 2; ///< 0-3: select clock divider for card 1 among div0-div3 + uint32_t reserved: 28; + }; + uint32_t val; + } clksrc; + + union { + struct { + uint32_t cclk_enable: 16; ///< 1: enable clock to card, 0: disable clock + uint32_t cclk_low_power: 16; ///< 1: enable clock gating when card is idle, 0: disable clock gating + }; + uint32_t val; + } clkena; + + union { + struct { + uint32_t response: 8; ///< response timeout, in card output clock cycles + uint32_t data: 24; ///< data read timeout, in card output clock cycles + }; + uint32_t val; + } tmout; + + union { + struct { + uint32_t card_width: 16; ///< one bit for each card: 0: 1-bit mode, 1: 4-bit mode + uint32_t card_width_8: 16; ///< one bit for each card: 0: not 8-bit mode (corresponding card_width bit is used), 1: 8-bit mode (card_width bit is ignored) + }; + uint32_t val; + } ctype; + + uint32_t blksiz: 16; ///< block size, default 0x200 + uint32_t : 16; + + uint32_t bytcnt; ///< number of bytes to be transferred + + union { + struct { + uint32_t cd: 1; ///< Card detect interrupt enable + uint32_t re: 1; ///< Response error interrupt enable + uint32_t cmd_done: 1; ///< Command done interrupt enable + uint32_t dto: 1; ///< Data transfer over interrupt enable + uint32_t txdr: 1; ///< Transmit FIFO data request interrupt enable + uint32_t rxdr: 1; ///< Receive FIFO data request interrupt enable + uint32_t rcrc: 1; ///< Response CRC error interrupt enable + uint32_t dcrc: 1; ///< Data CRC error interrupt enable + uint32_t rto: 1; ///< Response timeout interrupt enable + uint32_t drto: 1; ///< Data read timeout interrupt enable + uint32_t hto: 1; ///< Data starvation-by-host timeout interrupt enable + uint32_t frun: 1; ///< FIFO underrun/overrun error interrupt enable + uint32_t hle: 1; ///< Hardware locked write error interrupt enable + uint32_t sbi_bci: 1; ///< Start bit error / busy clear interrupt enable + uint32_t acd: 1; ///< Auto command done interrupt enable + uint32_t ebe: 1; ///< End bit error / write no CRC interrupt enable + uint32_t sdio: 16; ///< SDIO interrupt enable + }; + uint32_t val; + } intmask; + + uint32_t cmdarg; ///< Command argument to be passed to card + + sdmmc_hw_cmd_t cmd; + + uint32_t resp[4]; ///< Response from card + + union { + struct { + uint32_t cd: 1; ///< Card detect interrupt masked status + uint32_t re: 1; ///< Response error interrupt masked status + uint32_t cmd_done: 1; ///< Command done interrupt masked status + uint32_t dto: 1; ///< Data transfer over interrupt masked status + uint32_t txdr: 1; ///< Transmit FIFO data request interrupt masked status + uint32_t rxdr: 1; ///< Receive FIFO data request interrupt masked status + uint32_t rcrc: 1; ///< Response CRC error interrupt masked status + uint32_t dcrc: 1; ///< Data CRC error interrupt masked status + uint32_t rto: 1; ///< Response timeout interrupt masked status + uint32_t drto: 1; ///< Data read timeout interrupt masked status + uint32_t hto: 1; ///< Data starvation-by-host timeout interrupt masked status + uint32_t frun: 1; ///< FIFO underrun/overrun error interrupt masked status + uint32_t hle: 1; ///< Hardware locked write error interrupt masked status + uint32_t sbi_bci: 1; ///< Start bit error / busy clear interrupt masked status + uint32_t acd: 1; ///< Auto command done interrupt masked status + uint32_t ebe: 1; ///< End bit error / write no CRC interrupt masked status + uint32_t sdio: 16; ///< SDIO interrupt masked status + }; + uint32_t val; + } mintsts; + + union { + struct { + uint32_t cd: 1; ///< Card detect raw interrupt status + uint32_t re: 1; ///< Response error raw interrupt status + uint32_t cmd_done: 1; ///< Command done raw interrupt status + uint32_t dto: 1; ///< Data transfer over raw interrupt status + uint32_t txdr: 1; ///< Transmit FIFO data request raw interrupt status + uint32_t rxdr: 1; ///< Receive FIFO data request raw interrupt status + uint32_t rcrc: 1; ///< Response CRC error raw interrupt status + uint32_t dcrc: 1; ///< Data CRC error raw interrupt status + uint32_t rto: 1; ///< Response timeout raw interrupt status + uint32_t drto: 1; ///< Data read timeout raw interrupt status + uint32_t hto: 1; ///< Data starvation-by-host timeout raw interrupt status + uint32_t frun: 1; ///< FIFO underrun/overrun error raw interrupt status + uint32_t hle: 1; ///< Hardware locked write error raw interrupt status + uint32_t sbi_bci: 1; ///< Start bit error / busy clear raw interrupt status + uint32_t acd: 1; ///< Auto command done raw interrupt status + uint32_t ebe: 1; ///< End bit error / write no CRC raw interrupt status + uint32_t sdio: 16; ///< SDIO raw interrupt status + }; + uint32_t val; + } rintsts; ///< interrupts can be cleared by writing this register + + union { + struct { + uint32_t fifo_rx_watermark: 1; ///< FIFO reached receive watermark level + uint32_t fifo_tx_watermark: 1; ///< FIFO reached transmit watermark level + uint32_t fifo_empty: 1; ///< FIFO is empty + uint32_t fifo_full: 1; ///< FIFO is full + uint32_t cmd_fsm_state: 4; ///< command FSM state + uint32_t data3_status: 1; ///< this bit reads 1 if card is present + uint32_t data_busy: 1; ///< this bit reads 1 if card is busy + uint32_t data_fsm_busy: 1; ///< this bit reads 1 if transmit/receive FSM is busy + uint32_t response_index: 6; ///< index of the previous response + uint32_t fifo_count: 13; ///< number of filled locations in the FIFO + uint32_t dma_ack: 1; ///< DMA acknowledge signal + uint32_t dma_req: 1; ///< DMA request signal + }; + uint32_t val; + } status; + + union { + struct { + uint32_t tx_watermark: 12; ///< FIFO TX watermark level + uint32_t reserved1: 4; + uint32_t rx_watermark: 12; ///< FIFO RX watermark level + uint32_t dw_dma_mts: 3; + uint32_t reserved2: 1; + }; + uint32_t val; + } fifoth; + + union { + struct { + uint32_t cards: 2; ///< bit N reads 1 if card N is present + uint32_t reserved: 30; + }; + uint32_t val; + } cdetect; + + union { + struct { + uint32_t card0: 2; ///< bit N reads 1 if card N is write protected + uint32_t reserved: 30; + }; + uint32_t val; + } wrtprt; + + uint32_t gpio; ///< unused + uint32_t tcbcnt; ///< transferred (to card) byte count + uint32_t tbbcnt; ///< transferred from host to FIFO byte count + + union { + struct { + uint32_t debounce_count: 24; ///< number of host cycles used by debounce filter, typical time should be 5-25ms + uint32_t reserved: 8; + }; + } debnce; + + uint32_t usrid; ///< user ID + uint32_t verid; ///< IP block version + uint32_t hcon; ///< compile-time IP configuration + uint32_t uhs; ///< TBD + + union { + struct { + uint32_t cards: 2; ///< bit N resets card N, active low + uint32_t reserved: 30; + }; + } rst_n; + + uint32_t reserved_7c; + + union { + struct { + uint32_t sw_reset: 1; ///< set to reset DMA controller + uint32_t fb: 1; ///< set if AHB master performs fixed burst transfers + uint32_t dsl: 5; ///< descriptor skip length: number of words to skip between two unchained descriptors + uint32_t enable: 1; ///< set to enable IDMAC + uint32_t pbl: 3; ///< programmable burst length + uint32_t reserved: 21; + }; + uint32_t val; + } bmod; + + uint32_t pldmnd; ///< set any bit to resume IDMAC FSM from suspended state + sdmmc_desc_t* dbaddr; ///< descriptor list base + + union { + struct { + uint32_t ti: 1; ///< transmit interrupt status + uint32_t ri: 1; ///< receive interrupt status + uint32_t fbe: 1; ///< fatal bus error + uint32_t reserved1: 1; + uint32_t du: 1; ///< descriptor unavailable + uint32_t ces: 1; ///< card error summary + uint32_t reserved2: 2; + uint32_t nis: 1; ///< normal interrupt summary + uint32_t fbe_code: 3; ///< code of fatal bus error + uint32_t fsm: 4; ///< DMAC FSM state + uint32_t reserved3: 15; + }; + uint32_t val; + } idsts; + + union { + struct { + uint32_t ti: 1; ///< transmit interrupt enable + uint32_t ri: 1; ///< receive interrupt enable + uint32_t fbe: 1; ///< fatal bus error interrupt enable + uint32_t reserved1: 1; + uint32_t du: 1; ///< descriptor unavailable interrupt enable + uint32_t ces: 1; ///< card error interrupt enable + uint32_t reserved2: 2; + uint32_t ni: 1; ///< normal interrupt interrupt enable + uint32_t ai: 1; ///< abnormal interrupt enable + uint32_t reserved3: 22; + }; + uint32_t val; + } idinten; + + uint32_t dscaddr; ///< current host descriptor address + uint32_t dscaddrl; ///< unused + uint32_t dscaddru; ///< unused + uint32_t bufaddrl; ///< unused + uint32_t bufaddru; ///< unused + uint32_t reserved_a8[22]; + uint32_t cardthrctl; + uint32_t back_end_power; + uint32_t uhs_reg_ext; + uint32_t emmc_ddr_reg; + uint32_t enable_shift; + uint32_t reserved_114[443]; + union { + struct { + uint32_t phase_dout: 3; ///< phase of data output clock (0x0: 0, 0x1: 90, 0x4: 180, 0x6: 270) + uint32_t phase_din: 3; ///< phase of data input clock + uint32_t phase_core: 3; ///< phase of the clock to SDMMC peripheral + uint32_t div_factor_p: 4; ///< controls clock period; it will be (div_factor_p + 1) / 160MHz + uint32_t div_factor_h: 4; ///< controls length of high pulse; it will be (div_factor_h + 1) / 160MHz + uint32_t div_factor_m: 4; ///< should be equal to div_factor_p + }; + uint32_t val; + } clock; +} sdmmc_dev_t; +extern sdmmc_dev_t SDMMC; + +_Static_assert(sizeof(sdmmc_dev_t) == 0x804, "invalid size of sdmmc_dev_t structure"); + + + +#endif //_SOC_SDMMC_STRUCT_H_ diff --git a/components/esp32/include/soc/soc.h b/components/esp32/include/soc/soc.h index b93bae729..1d447fadb 100755 --- a/components/esp32/include/soc/soc.h +++ b/components/esp32/include/soc/soc.h @@ -153,7 +153,7 @@ #define DR_REG_FRC_TIMER_BASE 0x3ff47000 #define DR_REG_RTCCNTL_BASE 0x3ff48000 #define DR_REG_RTCIO_BASE 0x3ff48400 -#define DR_REG_SENS_BASE 0x3ff48800 +#define DR_REG_SENS_BASE 0x3ff48800 #define DR_REG_IO_MUX_BASE 0x3ff49000 #define DR_REG_RTCMEM0_BASE 0x3ff61000 #define DR_REG_RTCMEM1_BASE 0x3ff62000 diff --git a/components/esp32/ld/esp32.peripherals.ld b/components/esp32/ld/esp32.peripherals.ld index 95aaadcbc..8d8d4e8b1 100644 --- a/components/esp32/ld/esp32.peripherals.ld +++ b/components/esp32/ld/esp32.peripherals.ld @@ -19,3 +19,4 @@ PROVIDE ( SPI3 = 0x3ff65000 ); PROVIDE ( I2C1 = 0x3ff67000 ); PROVIDE ( I2S1 = 0x3ff6D000 ); PROVIDE ( UART2 = 0x3ff6E000 ); +PROVIDE ( SDMMC = 0x3ff68000 ); diff --git a/components/sdmmc/component.mk b/components/sdmmc/component.mk new file mode 100755 index 000000000..e69de29bb diff --git a/components/sdmmc/include/sdmmc_cmd.h b/components/sdmmc/include/sdmmc_cmd.h new file mode 100644 index 000000000..58b6f082c --- /dev/null +++ b/components/sdmmc/include/sdmmc_cmd.h @@ -0,0 +1,77 @@ +// Copyright 2015-2016 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. + +#pragma once + +#include +#include "esp_err.h" +#include "driver/sdmmc_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Probe and initialize SD/MMC card using given host + * + * @note Only SD cards (SDSC and SDHC/SDXC) are supported now. + * 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 + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_card_init(const sdmmc_host_t* host, + sdmmc_card_t* out_card); + +/** + * @brief Print information about the card to a stream + * @param stream stream obtained using fopen or fdopen + * @param card card information structure initialized using sdmmc_card_init + */ +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 start_sector sector where to start writing + * @param sector_count number of sectors to write + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, + size_t start_sector, size_t sector_count); + +/** + * 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 start_sector sector where to start reading + * @param sector_count number of sectors to read + * @return + * - ESP_OK on success + * - One of the error codes from SDMMC host controller + */ +esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, + size_t start_sector, size_t sector_count); + +#ifdef __cplusplus +} +#endif diff --git a/components/sdmmc/sdmmc_cmd.c b/components/sdmmc/sdmmc_cmd.c new file mode 100644 index 000000000..659ff5dd6 --- /dev/null +++ b/components/sdmmc/sdmmc_cmd.c @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2006 Uwe Stuehler + * Adaptations to ESP-IDF Copyright (c) 2016 Espressif Systems (Shanghai) PTE LTD + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include "esp_log.h" +#include "esp_heap_alloc_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/sdmmc_defs.h" +#include "driver/sdmmc_types.h" +#include "sdmmc_cmd.h" + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +static const char* TAG = "sdmmc_cmd"; + +static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd); +static esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd); +static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card); +static esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr); +static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp); +static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid); +static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid); +static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca); +static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd); +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); +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 uint32_t get_host_ocr(float voltage); + + +esp_err_t sdmmc_card_init(const sdmmc_host_t* config, + sdmmc_card_t* card) +{ + ESP_LOGD(TAG, "%s", __func__); + memset(card, 0, sizeof(*card)); + memcpy(&card->host, config, sizeof(*config)); + esp_err_t 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; + } + ets_delay_us(10000); + uint32_t host_ocr = get_host_ocr(config->io_voltage); + err = sdmmc_send_cmd_send_if_cond(card, host_ocr); + if (err == ESP_OK) { + ESP_LOGD(TAG, "SDHC/SDXC card"); + host_ocr |= SD_OCR_SDHC_CAP; + } else if (err == ESP_ERR_TIMEOUT) { + ESP_LOGD(TAG, "CMD8 timeout; not an SDHC/SDXC card"); + } else { + ESP_LOGE(TAG, "%s: send_if_cond (1) returned 0x%x", __func__, err); + return err; + } + 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; + } + host_ocr &= card->ocr; + ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr); + 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) { + ESP_LOGE(TAG, "%s: set_relative_addr 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 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; + } + err = sdmmc_send_cmd_select_card(card); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err); + return err; + } + 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; + } + } + err = sdmmc_send_cmd_send_scr(card, &card->scr); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err); + return err; + } + if ((config->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; + } + 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; + } + } + uint32_t status = 0; + while (!(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); + } + } + if (config->max_freq_khz >= SDMMC_FREQ_HIGHSPEED && + card->csd.tr_speed / 1000 >= SDMMC_FREQ_HIGHSPEED) { + ESP_LOGD(TAG, "switching to HS bus 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; + } + } else if (config->max_freq_khz >= SDMMC_FREQ_DEFAULT && + card->csd.tr_speed / 1000 >= SDMMC_FREQ_DEFAULT) { + ESP_LOGD(TAG, "switching to DS bus mode"); + err = (*config->set_card_clk)(config->slot, SDMMC_FREQ_DEFAULT); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to switch peripheral to HS bus mode"); + return err; + } + } + sdmmc_scr_t scr_tmp; + err = sdmmc_send_cmd_send_scr(card, &scr_tmp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err); + return err; + } + if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) { + ESP_LOGE(TAG, "data check fail!"); + return ESP_ERR_INVALID_RESPONSE; + } + return ESP_OK; +} + +void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card) +{ + fprintf(stream, "Name: %s\n", card->cid.name); + fprintf(stream, "Type: %s\n", (card->ocr & SD_OCR_SDHC_CAP)?"SDHC/SDXC":"SDSC"); + fprintf(stream, "Speed: %s\n", (card->csd.tr_speed > 25000000)?"high speed":"default speed"); + fprintf(stream, "Size: %lluMB\n", ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024)); + fprintf(stream, "CSD: ver=%d, sector_size=%d, capacity=%d read_bl_len=%d\n", + card->csd.csd_ver, + card->csd.sector_size, card->csd.capacity, card->csd.read_block_len); + fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width); +} + +static esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd) +{ + int slot = card->host.slot; + ESP_LOGV(TAG, "sending cmd slot=%d op=%d arg=%x flags=%x data=%p blklen=%d datalen=%d", + slot, cmd->opcode, cmd->arg, cmd->flags, cmd->data, cmd->blklen, cmd->datalen); + esp_err_t err = (*card->host.do_transaction)(slot, cmd); + if (err != 0) { + ESP_LOGD(TAG, "sdmmc_req_run returned 0x%x", err); + return err; + } + int state = MMC_R1_CURRENT_STATE(cmd->response); + ESP_LOGV(TAG, "cmd response %08x %08x %08x %08x err=0x%x state=%d", + cmd->response[0], + cmd->response[1], + cmd->response[2], + cmd->response[3], + cmd->error, + state); + return cmd->error; +} + +static esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd) +{ + sdmmc_command_t app_cmd = { + .opcode = MMC_APP_CMD, + .flags = SCF_CMD_AC | SCF_RSP_R1, + .arg = MMC_ARG_RCA(card->rca), + }; + esp_err_t err = sdmmc_send_cmd(card, &app_cmd); + if (err != ESP_OK) { + return err; + } + if (!(MMC_R1(app_cmd.response) & MMC_R1_APP_CMD)) { + ESP_LOGW(TAG, "card doesn't support APP_CMD"); + return ESP_ERR_NOT_SUPPORTED; + } + return sdmmc_send_cmd(card, cmd); +} + + +static esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card) +{ + sdmmc_command_t cmd = { + .opcode = MMC_GO_IDLE_STATE, + .flags = SCF_CMD_BC | SCF_RSP_R0, + }; + return sdmmc_send_cmd(card, &cmd); +} + + +static esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr) +{ + const uint8_t pattern = 0xaa; /* any pattern will do here */ + sdmmc_command_t cmd = { + .opcode = SD_SEND_IF_COND, + .arg = (((ocr & SD_OCR_VOL_MASK) != 0) << 8) | pattern, + .flags = SCF_CMD_BCR | SCF_RSP_R7, + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + uint8_t response = cmd.response[0] & 0xff; + if (response != pattern) { + return ESP_ERR_INVALID_RESPONSE; + } + return ESP_OK; +} + +static esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp) +{ + sdmmc_command_t cmd = { + .arg = ocr, + .flags = SCF_CMD_BCR | SCF_RSP_R3, + .opcode = SD_APP_OP_COND + }; + int nretries = 100; // arbitrary, BSD driver uses this value + for (; nretries != 0; --nretries) { + esp_err_t err = sdmmc_send_app_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + if ((MMC_R3(cmd.response) & MMC_OCR_MEM_READY) || + ocr == 0) { + break; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } + if (nretries == 0) { + return ESP_ERR_TIMEOUT; + } + if (ocrp) { + *ocrp = MMC_R3(cmd.response); + } + return ESP_OK; +} + +static esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid) +{ + out_cid->mfg_id = SD_CID_MID(resp); + out_cid->oem_id = SD_CID_OID(resp); + SD_CID_PNM_CPY(resp, out_cid->name); + out_cid->revision = SD_CID_REV(resp); + out_cid->serial = SD_CID_PSN(resp); + out_cid->date = SD_CID_MDT(resp); + return ESP_OK; +} + +static esp_err_t sddmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_cid_t* out_cid) +{ + assert(out_cid); + sdmmc_command_t cmd = { + .opcode = MMC_ALL_SEND_CID, + .flags = SCF_CMD_BCR | SCF_RSP_R2 + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + return sdmmc_decode_cid(cmd.response, out_cid); +} + + +static esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca) +{ + assert(out_rca); + sdmmc_command_t cmd = { + .opcode = SD_SEND_RELATIVE_ADDR, + .flags = SCF_CMD_BCR | SCF_RSP_R6 + }; + + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + *out_rca = SD_R6_RCA(cmd.response); + return ESP_OK; +} + + +static esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SET_BLOCKLEN, + .arg = csd->sector_size, + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + return sdmmc_send_cmd(card, &cmd); +} + +static esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd) +{ + out_csd->csd_ver = SD_CSD_CSDVER(response); + switch (out_csd->csd_ver) { + case SD_CSD_CSDVER_2_0: + out_csd->capacity = SD_CSD_V2_CAPACITY(response); + out_csd->read_block_len = SD_CSD_V2_BL_LEN; + break; + case SD_CSD_CSDVER_1_0: + out_csd->capacity = SD_CSD_CAPACITY(response); + out_csd->read_block_len = SD_CSD_READ_BL_LEN(response); + break; + default: + ESP_LOGE(TAG, "unknown SD CSD structure version 0x%x", out_csd->csd_ver); + return ESP_ERR_NOT_SUPPORTED; + } + out_csd->card_command_class = SD_CSD_CCC(response); + int read_bl_size = 1 << out_csd->read_block_len; + out_csd->sector_size = MIN(read_bl_size, 512); + if (out_csd->sector_size < read_bl_size) { + out_csd->capacity *= read_bl_size / out_csd->sector_size; + } + int speed = SD_CSD_SPEED(response); + if (speed == SD_CSD_SPEED_50_MHZ) { + out_csd->tr_speed = 50000000; + } else { + out_csd->tr_speed = 25000000; + } + return ESP_OK; +} + +static esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SEND_CSD, + .arg = MMC_ARG_RCA(card->rca), + .flags = SCF_CMD_AC | SCF_RSP_R2 + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + return sdmmc_decode_csd(cmd.response, out_csd); +} + +static esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SELECT_CARD, + .arg = MMC_ARG_RCA(card->rca), + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + return sdmmc_send_cmd(card, &cmd); +} + +static esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr) +{ + sdmmc_response_t resp = {0xabababab, 0xabababab, 0x12345678, 0x09abcdef}; + resp[2] = __builtin_bswap32(raw_scr[0]); + resp[3] = __builtin_bswap32(raw_scr[1]); + int ver = SCR_STRUCTURE(resp); + if (ver != 0) { + return ESP_ERR_NOT_SUPPORTED; + } + out_scr->sd_spec = SCR_SD_SPEC(resp); + out_scr->bus_width = SCR_SD_BUS_WIDTHS(resp); + return ESP_OK; +} + +static esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr) +{ + size_t datalen = 8; + uint32_t* buf = (uint32_t*) pvPortMallocCaps(datalen, MALLOC_CAP_DMA); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + sdmmc_command_t cmd = { + .data = buf, + .datalen = datalen, + .blklen = datalen, + .flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1, + .opcode = SD_APP_SEND_SCR + }; + esp_err_t err = sdmmc_send_app_cmd(card, &cmd); + if (err == ESP_OK) { + buf[0] = (buf[0]); + buf[1] = (buf[1]); + err = sdmmc_decode_scr(buf, out_scr); + } + free(buf); + return err; +} + +static esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width) +{ + sdmmc_command_t cmd = { + .opcode = SD_APP_SET_BUS_WIDTH, + .flags = SCF_RSP_R1 | SCF_CMD_AC, + .arg = (width == 4) ? SD_ARG_BUS_WIDTH_4 : SD_ARG_BUS_WIDTH_1 + }; + + 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 uint32_t get_host_ocr(float voltage) +{ + // TODO: report exact voltage to the card + // For now tell that the host has 2.8-3.6V voltage range + (void) voltage; + return SD_OCR_VOL_MASK; +} + +static esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SEND_STATUS, + .arg = MMC_ARG_RCA(card->rca), + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + return err; + } + if (out_status) { + *out_status = MMC_R1(cmd.response); + } + return ESP_OK; +} + +esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, + size_t start_block, size_t block_count) +{ + if (start_block + block_count > card->csd.capacity) { + return ESP_ERR_INVALID_SIZE; + } + size_t block_size = card->csd.sector_size; + sdmmc_command_t cmd = { + .flags = SCF_CMD_ADTC | SCF_RSP_R1, + .blklen = block_size, + .data = (void*) src, + .datalen = block_count * block_size + }; + if (block_count == 1) { + cmd.opcode = MMC_WRITE_BLOCK_SINGLE; + } else { + cmd.opcode = MMC_WRITE_BLOCK_MULTIPLE; + } + if (card->ocr & SD_OCR_SDHC_CAP) { + cmd.arg = start_block; + } else { + cmd.arg = start_block * block_size; + } + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + uint32_t status = 0; + size_t count = 0; + while (!(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + 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); + } + } + return ESP_OK; +} + +esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, + size_t start_block, size_t block_count) +{ + if (start_block + block_count > card->csd.capacity) { + return ESP_ERR_INVALID_SIZE; + } + size_t block_size = card->csd.sector_size; + sdmmc_command_t cmd = { + .flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1, + .blklen = block_size, + .data = (void*) dst, + .datalen = block_count * block_size + }; + if (block_count == 1) { + cmd.opcode = MMC_READ_BLOCK_SINGLE; + } else { + cmd.opcode = MMC_READ_BLOCK_MULTIPLE; + } + if (card->ocr & SD_OCR_SDHC_CAP) { + cmd.arg = start_block; + } else { + cmd.arg = start_block * block_size; + } + esp_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != ESP_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + uint32_t status = 0; + size_t count = 0; + while (!(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + 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); + } + } + return ESP_OK; +} diff --git a/components/sdmmc/test/component.mk b/components/sdmmc/test/component.mk new file mode 100644 index 000000000..ce464a212 --- /dev/null +++ b/components/sdmmc/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/sdmmc/test/test_sd.c b/components/sdmmc/test/test_sd.c new file mode 100644 index 000000000..e8fbd2545 --- /dev/null +++ b/components/sdmmc/test/test_sd.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include "unity.h" +#include "driver/gpio.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" +#include "esp_heap_alloc_caps.h" +#include +#include + + +TEST_CASE("can probe SD", "[sd]") +{ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + 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); + sdmmc_host_deinit(); + free(card); +} + + +static void do_single_write_read_test(sdmmc_card_t* card, + size_t start_block, size_t block_count) +{ + size_t block_size = card->csd.sector_size; + size_t total_size = block_size * block_count; + printf(" %8d | %3d | %4.1f ", start_block, block_count, total_size / 1024.0f); + uint32_t* buffer = pvPortMallocCaps(total_size, MALLOC_CAP_DMA); + srand(start_block); + for (size_t i = 0; i < total_size / sizeof(buffer[0]); ++i) { + buffer[i] = rand(); + } + struct timeval t_start_wr; + gettimeofday(&t_start_wr, NULL); + TEST_ESP_OK(sdmmc_write_sectors(card, buffer, start_block, block_count)); + struct timeval t_stop_wr; + gettimeofday(&t_stop_wr, NULL); + float time_wr = 1e3f * (t_stop_wr.tv_sec - t_start_wr.tv_sec) + 1e-3f * (t_stop_wr.tv_usec - t_start_wr.tv_usec); + memset(buffer, 0xbb, total_size); + struct timeval t_start_rd; + gettimeofday(&t_start_rd, NULL); + TEST_ESP_OK(sdmmc_read_sectors(card, buffer, start_block, block_count)); + struct timeval t_stop_rd; + gettimeofday(&t_stop_rd, NULL); + float time_rd = 1e3f * (t_stop_rd.tv_sec - t_start_rd.tv_sec) + 1e-3f * (t_stop_rd.tv_usec - t_start_rd.tv_usec); + + printf(" | %6.2f | %.2f | %.2fs | %.2f\n", + time_wr, total_size / (time_wr / 1000) / (1024 * 1024), + time_rd, total_size / (time_rd / 1000) / (1024 * 1024)); + srand(start_block); + for (size_t i = 0; i < total_size / sizeof(buffer[0]); ++i) { + TEST_ASSERT_EQUAL_HEX32(rand(), buffer[i]); + } + free(buffer); +} + +TEST_CASE("can write and read back blocks", "[sd]") +{ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + sdmmc_host_init(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + 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); + printf(" sector | count | size(kB) | wr_time(ms) | wr_speed(MB/s) | rd_time(ms) | rd_speed(MB/s)\n"); + do_single_write_read_test(card, 0, 1); + do_single_write_read_test(card, 0, 4); + do_single_write_read_test(card, 1, 16); + do_single_write_read_test(card, 16, 32); + do_single_write_read_test(card, 48, 64); + do_single_write_read_test(card, 128, 128); + do_single_write_read_test(card, card->csd.capacity - 64, 32); + do_single_write_read_test(card, card->csd.capacity - 64, 64); + do_single_write_read_test(card, card->csd.capacity - 8, 1); + do_single_write_read_test(card, card->csd.capacity/2, 1); + do_single_write_read_test(card, card->csd.capacity/2, 4); + do_single_write_read_test(card, card->csd.capacity/2, 8); + do_single_write_read_test(card, card->csd.capacity/2, 16); + do_single_write_read_test(card, card->csd.capacity/2, 32); + do_single_write_read_test(card, card->csd.capacity/2, 64); + do_single_write_read_test(card, card->csd.capacity/2, 128); + free(card); + sdmmc_host_deinit(); +} diff --git a/examples/27_sd_card/Makefile b/examples/27_sd_card/Makefile new file mode 100644 index 000000000..512e40fa6 --- /dev/null +++ b/examples/27_sd_card/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := sd_card + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/27_sd_card/README.md b/examples/27_sd_card/README.md new file mode 100644 index 000000000..4053ad260 --- /dev/null +++ b/examples/27_sd_card/README.md @@ -0,0 +1,81 @@ +# SD Card example + +This example demonstrates how to use an SD card with ESP32. Example does the following steps: + +1. Use an "all-in-one" `esp_vfs_fat_sdmmc_mount` function to: + - initialize SDMMC peripheral, + - probe and initialize the card connected to SD/MMC slot 1 (HS2_CMD, HS2_CLK, HS2_D0, HS2_D1, HS2_D2, HS2_D3 lines), + - mount FAT filesystem using FATFS library (and format card, if the filesystem can not be mounted), + - register FAT filesystem in VFS, enabling C standard library and POSIX functions to be used. +2. Print information about the card, such as name, type, capacity, and maximum supported frequency. +3. Create a file using `fopen` and write to it using `fprintf`. +4. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function. +5. Open renamed file for reading, read back the line, and print it to the terminal. + +*Note:* despite the name, `sdmmc` component doesn't support MMC/eMMC cards yet. It is also possible to extend `sdmmc` component to support SPI mode with SD cards via SPI peripheral. + +## Hardware + +To run this example, ESP32 development board needs to be connected to SD card as follows: + +ESP32 pin | SD card pin | Notes +--------------|-------------|------------ +GPIO14 (MTMS) | CLK | 10k pullup +GPIO15 (MTDO) | CMD | 10k pullup +GPIO2 | D0 | 10k pullup, pull low to go into download mode +GPIO4 | D1 | 10k pullup; not used in 1-line mode +GPIO12 (MTDI) | D2 | otherwise 10k pullup (see note below!); not used in 1-line mode +GPIO13 (MTCK) | D3 | 10k pullup needed at card side, even in 1-line mode +N/C | CD | +N/C | WP | + +This example doesn't utilize card detect (CD) and write protect (WP) signals from SD card slot. + +### Note about GPIO2 +GPIO2 pin is used as a bootstrapping pin, and should be low to enter UART download mode. One way to do this is to connect GPIO0 and GPIO2 using a jumper, and then the auto-reset circuit on most development boards will pull GPIO2 low along with GPIO2, when entering download mode. + +### Note about GPIO12 + +GPIO12 is used as a bootstrapping pin to select output voltage of an internal regulator which powers the flash chip. + +- For boards which don't use the internal regulator, GPIO12 can be pulled high. +- On boards which use the internal regulator and a 3.3V flash chip, GPIO12 should be pulled up high, which is compatible with SD card operation. +- For boards which use 1.8V flash chip, GPIO12 needs to be low at reset. + * In this case, internal pullup can be enabled using a `gpio_pullup_en(GPIO_NUM_12);` call. Most SD cards work fine when an internal pullup on GPIO12 line is enabled. Note that if ESP32 experiences a power-on reset while the SD card is sending data, high level on GPIO12 can be latched into the bootstrapping register, and ESP32 will enter a boot loop until external reset with correct GPIO12 level is applied. + * Another option is to program flash voltage selection efuses: set `SDIO_TIEH=0` and `SDIO_FORCE=1`. This will permanently select 1.8v output voltage for the internal regulator, and GPIO12 will not be used as a bootstrapping pin anymore. Then it is safe to connect a pullup resistor to GPIO12. This option is suggested for production use. + +## 4-line and 1-line modes + +By default, example code uses the following initializer for SDMMC host peripheral configuration: + +```c++ +sdmmc_host_t host = SDMMC_HOST_DEFAULT(); +``` + +Among other things, this sets `host.flags` to `SDMMC_HOST_FLAG_4BIT`, which means that SD/MMC driver will switch to 4-line mode when initializing the card (initial communication always happens in 1-line mode). If some of D1, D2, D3 pins are not connected to the card, set `host.flags` to `SDMMC_HOST_FLAG_1BIT` — then the SD/MMC driver will not attempt to switch to 4-line mode. +Note that even if D3 line is not connected to the ESP32, it still has to be pulled up at card side, otherwise the card will go into SPI protocol mode. + + +## Example output + +Here is an example console output. In this case a 128MB SDSC card was connected, and `format_if_mount_failed` parameter was set to `true` in the source code. Card was unformatted, so the initial mount has failed. Card was then partitioned, formatted, and mounted again. + +``` +I (1776) example: Initializing SD card +W (1856) vfs_fat_sdmmc: failed to mount card (13) +W (1856) vfs_fat_sdmmc: partitioning card +W (1856) vfs_fat_sdmmc: formatting card +W (2726) vfs_fat_sdmmc: mounting again +I (2736) example: Card info: +I (2736) example: Name: SU128 +I (2736) example: Type: SDSC +I (2736) example: Capacity: 120 MB +I (2736) example: Max clock speed: 25 MHz +I (2736) example: Opening file +I (2756) example: File written +I (2756) example: Renaming file +I (2756) example: Reading file +I (2756) example: Read from file: 'Hello SU128!' +I (2756) example: Card unmounted +``` + diff --git a/examples/27_sd_card/main/component.mk b/examples/27_sd_card/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/27_sd_card/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/27_sd_card/main/sd_card.c b/examples/27_sd_card/main/sd_card.c new file mode 100644 index 000000000..03d0ec131 --- /dev/null +++ b/examples/27_sd_card/main/sd_card.c @@ -0,0 +1,107 @@ +/* SD card and FAT filesystem example. + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "esp_vfs_fat.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" + +static const char* TAG = "example"; + +void app_main(void) +{ + ESP_LOGI(TAG, "Initializing SD card"); + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + + // To use 1-line SD mode, uncomment the following line: + // host.flags = SDMMC_HOST_FLAG_1BIT; + + // This initializes the slot without card detect (CD) and write protect (WP) signals. + // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + // Options for mounting the filesystem. + // If format_if_mount_failed is set to true, SD card will be partitioned and formatted + // in case when mounting fails. + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 5 + }; + + // Use settings defined above to initialize SD card and mount FAT filesystem. + // Note: esp_vfs_fat_sdmmc_mount is an all-in-one convenience function. + // Please check its source code and implement error recovery when developing + // production applications. + sdmmc_card_t* card; + esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card); + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount filesystem. If you want the card to be formatted, set format_if_mount_failed = true."); + } else { + ESP_LOGE(TAG, "Failed to initialize the card (%d). Make sure SD card lines have pull-up resistors in place.", ret); + } + return; + } + + // Card has been initialized, print its properties + sdmmc_card_print_info(stdout, card); + + // Use POSIX and C standard library functions to work with files. + // First create a file. + ESP_LOGI(TAG, "Opening file"); + FILE* f = fopen("/sdcard/hello.txt", "w"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for writing"); + return; + } + fprintf(f, "Hello %s!\n", card->cid.name); + fclose(f); + ESP_LOGI(TAG, "File written"); + + // Check if destination file exists before renaming + struct stat st; + if (stat("/sdcard/foo.txt", &st) == 0) { + // Delete it if it exists + unlink("/sdcard/foo.txt"); + } + + // Rename original file + ESP_LOGI(TAG, "Renaming file"); + if (rename("/sdcard/hello.txt", "/sdcard/foo.txt") != 0) { + ESP_LOGE(TAG, "Rename failed"); + return; + } + + // Open renamed file for reading + ESP_LOGI(TAG, "Reading file"); + f = fopen("/sdcard/foo.txt", "r"); + if (f == NULL) { + ESP_LOGE(TAG, "Failed to open file for reading"); + return; + } + char line[64]; + fgets(line, sizeof(line), f); + fclose(f); + // strip newline + char* pos = strchr(line, '\n'); + if (pos) { + *pos = '\0'; + } + ESP_LOGI(TAG, "Read from file: '%s'", line); + + // All done, unmount partition and disable SDMMC host peripheral + esp_vfs_fat_sdmmc_unmount(); + ESP_LOGI(TAG, "Card unmounted"); +}