diff options
Diffstat (limited to 'circuitpython/lib/sdmmc')
-rw-r--r-- | circuitpython/lib/sdmmc/CMakeLists.txt | 9 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/component.mk | 0 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/include/sdmmc_cmd.h | 274 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/include/sdmmc_defs.h | 498 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/include/sdmmc_types.h | 199 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_cmd.c | 501 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_common.c | 314 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_common.h | 132 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_init.c | 123 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_io.c | 639 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_mmc.c | 251 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/sdmmc_sd.c | 343 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/test/CMakeLists.txt | 4 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/test/component.mk | 1 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/test/test_sd.c | 584 | ||||
-rw-r--r-- | circuitpython/lib/sdmmc/test/test_sdio.c | 388 |
16 files changed, 4260 insertions, 0 deletions
diff --git a/circuitpython/lib/sdmmc/CMakeLists.txt b/circuitpython/lib/sdmmc/CMakeLists.txt new file mode 100644 index 0000000..f6e8fe2 --- /dev/null +++ b/circuitpython/lib/sdmmc/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_register(SRCS "sdmmc_cmd.c" + "sdmmc_common.c" + "sdmmc_init.c" + "sdmmc_io.c" + "sdmmc_mmc.c" + "sdmmc_sd.c" + INCLUDE_DIRS include + REQUIRES driver + PRIV_REQUIRES soc) diff --git a/circuitpython/lib/sdmmc/component.mk b/circuitpython/lib/sdmmc/component.mk new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/lib/sdmmc/component.mk diff --git a/circuitpython/lib/sdmmc/include/sdmmc_cmd.h b/circuitpython/lib/sdmmc/include/sdmmc_cmd.h new file mode 100644 index 0000000..7952ada --- /dev/null +++ b/circuitpython/lib/sdmmc/include/sdmmc_cmd.h @@ -0,0 +1,274 @@ +// Copyright 2015-2018 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 <stdio.h> +#include "sdmmc_defs.h" +#include "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 + * - SDMMC_OK on success + * - One of the error codes from SDMMC host controller + */ +sdmmc_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 + * - SDMMC_OK on success + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, + size_t start_sector, size_t sector_count); + +/** + * Read given number of sectors from the 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 + * - SDMMC_OK on success + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, + size_t start_sector, size_t sector_count); + +/** + * Read one byte from an SDIO card using IO_RW_DIRECT (CMD52) + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param reg byte address within IO function + * @param[out] out_byte output, receives the value read from the card + * @return + * - SDMMC_OK on success + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function, + uint32_t reg, uint8_t *out_byte); + +/** + * Write one byte to an SDIO card using IO_RW_DIRECT (CMD52) + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param reg byte address within IO function + * @param in_byte value to be written + * @param[out] out_byte if not NULL, receives new byte value read + * from the card (read-after-write). + * @return + * - SDMMC_OK on success + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function, + uint32_t reg, uint8_t in_byte, uint8_t* out_byte); + +/** + * Read multiple bytes from an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs read operation using CMD53 in byte mode. + * For block mode, see sdmmc_io_read_blocks. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where reading starts + * @param dst buffer which receives the data read from card + * @param size number of bytes to read + * @return + * - SDMMC_OK on success + * - SDMMC_ERR_INVALID_SIZE if size exceeds 512 bytes + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size); + +/** + * Write multiple bytes to an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs write operation using CMD53 in byte mode. + * For block mode, see sdmmc_io_write_blocks. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where writing starts + * @param src data to be written + * @param size number of bytes to write + * @return + * - SDMMC_OK on success + * - SDMMC_ERR_INVALID_SIZE if size exceeds 512 bytes + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_io_write_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size); + +/** + * Read blocks of data from an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs read operation using CMD53 in block mode. + * For byte mode, see sdmmc_io_read_bytes. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where writing starts + * @param dst buffer which receives the data read from card + * @param size number of bytes to read, must be divisible by the card block + * size. + * @return + * - SDMMC_OK on success + * - SDMMC_ERR_INVALID_SIZE if size is not divisible by 512 bytes + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size); + +/** + * Write blocks of data to an SDIO card using IO_RW_EXTENDED (CMD53) + * + * This function performs write operation using CMD53 in block mode. + * For byte mode, see sdmmc_io_write_bytes. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param function IO function number + * @param addr byte address within IO function where writing starts + * @param src data to be written + * @param size number of bytes to read, must be divisible by the card block + * size. + * @return + * - SDMMC_OK on success + * - SDMMC_ERR_INVALID_SIZE if size is not divisible by 512 bytes + * - One of the error codes from SDMMC host controller + */ +sdmmc_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size); + +/** + * Enable SDIO interrupt in the SDMMC host + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @return + * - SDMMC_OK on success + * - SDMMC_ERR_NOT_SUPPORTED if the host controller does not support + * IO interrupts + */ +sdmmc_err_t sdmmc_io_enable_int(sdmmc_card_t* card); + +/** + * Block until an SDIO interrupt is received + * + * Slave uses D1 line to signal interrupt condition to the host. + * This function can be used to wait for the interrupt. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param timeout_ticks time to wait for the interrupt, in RTOS ticks + * @return + * - SDMMC_OK if the interrupt is received + * - SDMMC_ERR_NOT_SUPPORTED if the host controller does not support + * IO interrupts + * - SDMMC_ERR_TIMEOUT if the interrupt does not happen in timeout_ticks + */ +sdmmc_err_t sdmmc_io_wait_int(sdmmc_card_t* card, int timeout_ms); + +/** + * Get the data of CIS region of a SDIO card. + * + * You may provide a buffer not sufficient to store all the CIS data. In this + * case, this functions store as much data into your buffer as possible. Also, + * this function will try to get and return the size required for you. + * + * @param card pointer to card information structure previously initialized + * using sdmmc_card_init + * @param out_buffer Output buffer of the CIS data + * @param buffer_size Size of the buffer. + * @param inout_cis_size Mandatory, pointer to a size, input and output. + * - input: Limitation of maximum searching range, should be 0 or larger than + * buffer_size. The function searches for CIS_CODE_END until this range. Set to + * 0 to search infinitely. + * - output: The size required to store all the CIS data, if CIS_CODE_END is found. + * + * @return + * - SDMMC_OK: on success + * - SDMMC_ERR_INVALID_RESPONSE: if the card does not (correctly) support CIS. + * - SDMMC_ERR_INVALID_SIZE: CIS_CODE_END found, but buffer_size is less than + * required size, which is stored in the inout_cis_size then. + * - SDMMC_ERR_NOT_FOUND: if the CIS_CODE_END not found. Increase input value of + * inout_cis_size or set it to 0, if you still want to search for the end; + * output value of inout_cis_size is invalid in this case. + * - and other error code return from sdmmc_io_read_bytes + */ +sdmmc_err_t sdmmc_io_get_cis_data(sdmmc_card_t* card, uint8_t* out_buffer, size_t buffer_size, size_t* inout_cis_size); + +/** + * Parse and print the CIS information of a SDIO card. + * + * @note Not all the CIS codes and all kinds of tuples are supported. If you + * see some unresolved code, you can add the parsing of these code in + * sdmmc_io.c and contribute to the IDF through the Github repository. + * + * using sdmmc_card_init + * @param buffer Buffer to parse + * @param buffer_size Size of the buffer. + * @param fp File pointer to print to, set to NULL to print to stdout. + * + * @return + * - SDMMC_OK: on success + * - SDMMC_ERR_NOT_SUPPORTED: if the value from the card is not supported to be parsed. + * - SDMMC_ERR_INVALID_SIZE: if the CIS size fields are not correct. + */ +sdmmc_err_t sdmmc_io_print_cis_info(uint8_t* buffer, size_t buffer_size, FILE* fp); + + +#ifdef __cplusplus +} +#endif diff --git a/circuitpython/lib/sdmmc/include/sdmmc_defs.h b/circuitpython/lib/sdmmc/include/sdmmc_defs.h new file mode 100644 index 0000000..7ac6ae1 --- /dev/null +++ b/circuitpython/lib/sdmmc/include/sdmmc_defs.h @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * 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 <stdint.h> +#include <limits.h> + +/* 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_SEND_CID 10 /* R1 */ +#define MMC_READ_DAT_UNTIL_STOP 11 /* R1 */ +#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_WRITE_DAT_UNTIL_STOP 20 /* 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 */ +#define SD_READ_OCR 58 /* R3 */ +#define SD_CRC_ON_OFF 59 /* R1 */ + +/* SD application commands */ /* response type */ +#define SD_APP_SET_BUS_WIDTH 6 /* R1 */ +#define SD_APP_SD_STATUS 13 /* R2 */ +#define SD_APP_OP_COND 41 /* R3 */ +#define SD_APP_SEND_SCR 51 /* R1 */ + +/* SD IO commands */ +#define SD_IO_SEND_OP_COND 5 /* R4 */ +#define SD_IO_RW_DIRECT 52 /* R5 */ +#define SD_IO_RW_EXTENDED 53 /* R5 */ + + +/* OCR bits */ +#define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */ +#define MMC_OCR_ACCESS_MODE_MASK 0x60000000 /* bits 30:29 */ +#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 */ + +/* SD mode 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 */ +#define MMC_R1_SWITCH_ERROR (1<<7) /* switch command did not succeed */ + +/* SPI mode R1 response type bits */ +#define SD_SPI_R1_IDLE_STATE (1<<0) +#define SD_SPI_R1_ERASE_RST (1<<1) +#define SD_SPI_R1_ILLEGAL_CMD (1<<2) +#define SD_SPI_R1_CMD_CRC_ERR (1<<3) +#define SD_SPI_R1_ERASE_SEQ_ERR (1<<4) +#define SD_SPI_R1_ADDR_ERR (1<<5) +#define SD_SPI_R1_PARAM_ERR (1<<6) +#define SD_SPI_R1_NO_RESPONSE (1<<7) + +#define SDIO_R1_FUNC_NUM_ERR (1<<4) + +/* 48-bit response decoding (32 bits w/o CRC) */ +#define MMC_R1(resp) ((resp)[0]) +#define MMC_R3(resp) ((resp)[0]) +#define MMC_R4(resp) ((resp)[0]) +#define MMC_R5(resp) ((resp)[0]) +#define SD_R6(resp) ((resp)[0]) +#define MMC_R1_CURRENT_STATE(resp) (((resp)[0] >> 9) & 0xf) + +/* SPI mode response decoding */ +#define SD_SPI_R1(resp) ((resp)[0] & 0xff) +#define SD_SPI_R2(resp) ((resp)[0] & 0xffff) +#define SD_SPI_R3(resp) ((resp)[0]) +#define SD_SPI_R7(resp) ((resp)[0]) + +/* SPI mode data response decoding */ +#define SD_SPI_DATA_RSP_VALID(resp_byte) (((resp_byte)&0x11)==0x1) +#define SD_SPI_DATA_RSP(resp_byte) (((resp_byte)>>1)&0x7) +#define SD_SPI_DATA_ACCEPTED 0x2 +#define SD_SPI_DATA_CRC_ERROR 0x5 +#define SD_SPI_DATA_WR_ERROR 0x6 + +/* 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 */ +#define EXT_CSD_PWR_CL_26_360 203 /* RO */ +#define EXT_CSD_PWR_CL_52_360 202 /* RO */ +#define EXT_CSD_PWR_CL_26_195 201 /* RO */ +#define EXT_CSD_PWR_CL_52_195 200 /* RO */ +#define EXT_CSD_POWER_CLASS 187 /* R/W */ +#define EXT_CSD_CMD_SET 191 /* R/W */ +#define EXT_CSD_S_CMD_SET 504 /* 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) /* SDR at "rated voltages */ +#define EXT_CSD_CARD_TYPE_F_52M (1 << 1) /* SDR at "rated voltages */ +#define EXT_CSD_CARD_TYPE_F_52M_1_8V (1 << 2) /* DDR, 1.8V or 3.3V I/O */ +#define EXT_CSD_CARD_TYPE_F_52M_1_2V (1 << 3) /* DDR, 1.2V I/O */ +#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 + +/* EXT_CSD MMC */ +#define EXT_CSD_MMC_SIZE 512 + +/* 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) + +/* Max supply current in SWITCH_FUNC response (in mA) */ +#define SD_SFUNC_I_MAX(status) (MMC_RSP_BITS((uint32_t *)(status), 496, 16)) + +/* Supported flags in SWITCH_FUNC response */ +#define SD_SFUNC_SUPPORTED(status, group) \ + (MMC_RSP_BITS((uint32_t *)(status), 400 + (group - 1) * 16, 16)) + +/* Selected function in SWITCH_FUNC response */ +#define SD_SFUNC_SELECTED(status, group) \ + (MMC_RSP_BITS((uint32_t *)(status), 376 + (group - 1) * 4, 4)) + +/* Busy flags in SWITCH_FUNC response */ +#define SD_SFUNC_BUSY(status, group) \ + (MMC_RSP_BITS((uint32_t *)(status), 272 + (group - 1) * 16, 16)) + +/* Version of SWITCH_FUNC response */ +#define SD_SFUNC_VER(status) (MMC_RSP_BITS((uint32_t *)(status), 368, 8)) + +#define SD_SFUNC_GROUP_MAX 6 +#define SD_SFUNC_FUNC_MAX 15 + +#define SD_ACCESS_MODE 1 /* Function group 1, Access Mode */ + +#define SD_ACCESS_MODE_SDR12 0 /* 25 MHz clock */ +#define SD_ACCESS_MODE_SDR25 1 /* 50 MHz clock */ +#define SD_ACCESS_MODE_SDR50 2 /* UHS-I, 100 MHz clock */ +#define SD_ACCESS_MODE_SDR104 3 /* UHS-I, 208 MHz clock */ +#define SD_ACCESS_MODE_DDR50 4 /* UHS-I, 50 MHz clock, DDR */ + +/** + * @brief Extract up to 32 sequential bits from an array of 32-bit words + * + * Bits within the word are numbered in the increasing order from LSB to MSB. + * + * As an example, consider 2 32-bit words: + * + * 0x01234567 0x89abcdef + * + * On a little-endian system, the bytes are stored in memory as follows: + * + * 67 45 23 01 ef cd ab 89 + * + * MMC_RSP_BITS will extact bits as follows: + * + * start=0 len=4 -> result=0x00000007 + * start=0 len=12 -> result=0x00000567 + * start=28 len=8 -> result=0x000000f0 + * start=59 len=5 -> result=0x00000011 + * + * @param src array of words to extract bits from + * @param start index of the first bit to extract + * @param len number of bits to extract, 1 to 32 + * @return 32-bit word where requested bits start from LSB + */ +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 = 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; +} + +/* SD R4 response (IO OCR) */ +#define SD_IO_OCR_MEM_READY (1<<31) +#define SD_IO_OCR_NUM_FUNCTIONS(ocr) (((ocr) >> 28) & 0x7) +#define SD_IO_OCR_MEM_PRESENT (1<<27) +#define SD_IO_OCR_MASK 0x00fffff0 + +/* CMD52 arguments */ +#define SD_ARG_CMD52_READ (0<<31) +#define SD_ARG_CMD52_WRITE (1<<31) +#define SD_ARG_CMD52_FUNC_SHIFT 28 +#define SD_ARG_CMD52_FUNC_MASK 0x7 +#define SD_ARG_CMD52_EXCHANGE (1<<27) +#define SD_ARG_CMD52_REG_SHIFT 9 +#define SD_ARG_CMD52_REG_MASK 0x1ffff +#define SD_ARG_CMD52_DATA_SHIFT 0 +#define SD_ARG_CMD52_DATA_MASK 0xff +#define SD_R5_DATA(resp) ((resp)[0] & 0xff) + +/* CMD53 arguments */ +#define SD_ARG_CMD53_READ (0<<31) +#define SD_ARG_CMD53_WRITE (1<<31) +#define SD_ARG_CMD53_FUNC_SHIFT 28 +#define SD_ARG_CMD53_FUNC_MASK 0x7 +#define SD_ARG_CMD53_BLOCK_MODE (1<<27) +#define SD_ARG_CMD53_INCREMENT (1<<26) +#define SD_ARG_CMD53_REG_SHIFT 9 +#define SD_ARG_CMD53_REG_MASK 0x1ffff +#define SD_ARG_CMD53_LENGTH_SHIFT 0 +#define SD_ARG_CMD53_LENGTH_MASK 0x1ff +#define SD_ARG_CMD53_LENGTH_MAX 512 + +/* Card Common Control Registers (CCCR) */ +#define SD_IO_CCCR_START 0x00000 +#define SD_IO_CCCR_SIZE 0x100 +#define SD_IO_CCCR_FN_ENABLE 0x02 +#define SD_IO_CCCR_FN_READY 0x03 +#define SD_IO_CCCR_INT_ENABLE 0x04 +#define SD_IO_CCCR_INT_PENDING 0x05 +#define SD_IO_CCCR_CTL 0x06 +#define CCCR_CTL_RES (1<<3) +#define SD_IO_CCCR_BUS_WIDTH 0x07 +#define CCCR_BUS_WIDTH_1 (0<<0) +#define CCCR_BUS_WIDTH_4 (2<<0) +#define CCCR_BUS_WIDTH_8 (3<<0) +#define CCCR_BUS_WIDTH_ECSI (1<<5) +#define SD_IO_CCCR_CARD_CAP 0x08 +#define CCCR_CARD_CAP_LSC BIT(6) +#define CCCR_CARD_CAP_4BLS BIT(7) +#define SD_IO_CCCR_CISPTR 0x09 +#define SD_IO_CCCR_BLKSIZEL 0x10 +#define SD_IO_CCCR_BLKSIZEH 0x11 +#define SD_IO_CCCR_HIGHSPEED 0x13 +#define CCCR_HIGHSPEED_SUPPORT BIT(0) +#define CCCR_HIGHSPEED_ENABLE BIT(1) + +/* Function Basic Registers (FBR) */ +#define SD_IO_FBR_START 0x00100 +#define SD_IO_FBR_SIZE 0x00700 + +/* Card Information Structure (CIS) */ +#define SD_IO_CIS_START 0x01000 +#define SD_IO_CIS_SIZE 0x17000 + +/* CIS tuple codes (based on PC Card 16) */ +#define CISTPL_CODE_NULL 0x00 +#define CISTPL_CODE_DEVICE 0x01 +#define CISTPL_CODE_CHKSUM 0x10 +#define CISTPL_CODE_VERS1 0x15 +#define CISTPL_CODE_ALTSTR 0x16 +#define CISTPL_CODE_CONFIG 0x1A +#define CISTPL_CODE_CFTABLE_ENTRY 0x1B +#define CISTPL_CODE_MANFID 0x20 +#define CISTPL_CODE_FUNCID 0x21 +#define TPLFID_FUNCTION_SDIO 0x0c +#define CISTPL_CODE_FUNCE 0x22 +#define CISTPL_CODE_VENDER_BEGIN 0x80 +#define CISTPL_CODE_VENDER_END 0x8F +#define CISTPL_CODE_SDIO_STD 0x91 +#define CISTPL_CODE_SDIO_EXT 0x92 +#define CISTPL_CODE_END 0xFF + + +/* Timing */ +#define SDMMC_TIMING_LEGACY 0 +#define SDMMC_TIMING_HIGHSPEED 1 +#define SDMMC_TIMING_MMC_DDR52 2 + +#include "py/runtime.h" + +// Logging macros +// #define ESP_LOGD(tag, string, ...) mp_printf(&mp_plat_print, string "\n" __VA_OPT__(,) __VA_ARGS__) +// #define ESP_LOGV(tag, string, ...) mp_printf(&mp_plat_print, string "\n" __VA_OPT__(,) __VA_ARGS__) +// #define ESP_LOGW(tag, string, ...) mp_printf(&mp_plat_print, string "\n" __VA_OPT__(,) __VA_ARGS__) +// #define ESP_LOGE(tag, string, ...) mp_printf(&mp_plat_print, string "\n" __VA_OPT__(,) __VA_ARGS__) + +#define ESP_LOGD(tag, string, ...) +#define ESP_LOGV(tag, string, ...) +#define ESP_LOGW(tag, string, ...) +#define ESP_LOGE(tag, string, ...) +#define TSD_MIN(_x, _y) ( ( (_x) < (_y) ) ? (_x) : (_y) ) + +#endif //_SDMMC_DEFS_H_ diff --git a/circuitpython/lib/sdmmc/include/sdmmc_types.h b/circuitpython/lib/sdmmc/include/sdmmc_types.h new file mode 100644 index 0000000..79a15e2 --- /dev/null +++ b/circuitpython/lib/sdmmc/include/sdmmc_types.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * 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 <stdbool.h> +#include <stdint.h> +#include <stddef.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; + +/** + * Decoded values of Extended Card Specific Data + */ +typedef struct { + uint8_t power_class; /*!< Power class used by the card */ +} sdmmc_ext_csd_t; + +/** + * SD/MMC command response buffer + */ +typedef uint32_t sdmmc_response_t[4]; + +/** + * SD SWITCH_FUNC response buffer + */ +typedef struct { + uint32_t data[512 / 8 / sizeof(uint32_t)]; /*!< response data */ +} sdmmc_switch_func_rsp_t; + +typedef enum { + SDMMC_OK = 0, + SDMMC_ERR_NOT_SUPPORTED = 1, + SDMMC_ERR_INVALID_RESPONSE = 2, + SDMMC_ERR_TIMEOUT = 3, + SDMMC_ERR_NO_MEM = 4, + SDMMC_ERR_INVALID_SIZE = 5, + SDMMC_ERR_NO_CARD = 6, + SDMMC_ERR_INVALID_ARG = 7, + SDMMC_ERR_BUSY = 8, +} sdmmc_err_t; + +/** + * 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 */ +/** @cond */ +#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) +/* special flags */ +#define SCF_WAIT_BUSY 0x2000 /*!< Wait for completion of card busy signal before returning */ +#define SCF_AUTO_STOP 0x4000 /*!< Auto stop with command 12 */ +/** @endcond */ + sdmmc_err_t error; /*!< error returned from transfer */ + int timeout_ms; /*!< response timeout, in milliseconds */ +} 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. + */ +#define BIT(x) (1 << x) + +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 */ +#define SDMMC_HOST_FLAG_DDR BIT(4) /*!< host supports DDR mode for SD/MMC */ +#define SDMMC_HOST_FLAG_DEINIT_ARG BIT(5) /*!< host `deinit` function called with the slot argument */ + 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 400 /*!< SD/MMC probing speed */ +#define SDMMC_FREQ_52M 52000 /*!< MMC 52MHz speed */ +#define SDMMC_FREQ_26M 26000 /*!< MMC 26MHz speed */ + float io_voltage; /*!< I/O voltage used by the controller (voltage switching is not supported) */ + sdmmc_err_t (*init)(void); /*!< Host function to initialize the driver */ + sdmmc_err_t (*set_bus_width)(int slot, size_t width); /*!< host function to set bus width */ + size_t (*get_bus_width)(int slot); /*!< host function to get bus width */ + sdmmc_err_t (*set_bus_ddr_mode)(int slot, bool ddr_enable); /*!< host function to set DDR mode */ + sdmmc_err_t (*set_card_clk)(int slot, uint32_t freq_khz); /*!< host function to set card clock frequency */ + sdmmc_err_t (*do_transaction)(int slot, sdmmc_command_t* cmdinfo); /*!< host function to do a transaction */ + union { + sdmmc_err_t (*deinit)(void); /*!< host function to deinitialize the driver */ + sdmmc_err_t (*deinit_p)(int slot); /*!< host function to deinitialize the driver, called with the `slot` */ + }; + sdmmc_err_t (*io_int_enable)(int slot); /*!< Host function to enable SDIO interrupt line */ + sdmmc_err_t (*io_int_wait)(int slot, int timeout_ms); /*!< Host function to wait for SDIO interrupt line to be active */ + int command_timeout_ms; /*!< timeout, in milliseconds, of a single command. Set to 0 to use the default value. */ +} sdmmc_host_t; + +/** + * 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 */ + union { + sdmmc_cid_t cid; /*!< decoded CID (Card IDentification) register value */ + sdmmc_response_t raw_cid; /*!< raw CID of MMC card to be decoded + after the CSD is fetched in the data transfer mode*/ + }; + sdmmc_csd_t csd; /*!< decoded CSD (Card-Specific Data) register value */ + sdmmc_scr_t scr; /*!< decoded SCR (SD card Configuration Register) value */ + sdmmc_ext_csd_t ext_csd; /*!< decoded EXT_CSD (Extended Card Specific Data) register value */ + uint16_t rca; /*!< RCA (Relative Card Address) */ + uint16_t max_freq_khz; /*!< Maximum frequency, in kHz, supported by the card */ + uint32_t is_mem : 1; /*!< Bit indicates if the card is a memory card */ + uint32_t is_sdio : 1; /*!< Bit indicates if the card is an IO card */ + uint32_t is_mmc : 1; /*!< Bit indicates if the card is MMC */ + uint32_t num_io_functions : 3; /*!< If is_sdio is 1, contains the number of IO functions on the card */ + uint32_t log_bus_width : 2; /*!< log2(bus width supported by card) */ + uint32_t is_ddr : 1; /*!< Card supports DDR mode */ + uint32_t reserved : 23; /*!< Reserved for future expansion */ +} sdmmc_card_t; + +// OS abstraction layer (copied from TinyUSB) +void osal_task_delay(uint32_t msec); + +#endif // _SDMMC_TYPES_H_ diff --git a/circuitpython/lib/sdmmc/sdmmc_cmd.c b/circuitpython/lib/sdmmc/sdmmc_cmd.c new file mode 100644 index 0000000..e2d853d --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_cmd.c @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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 <assert.h> + +#include "sdmmc_common.h" + +sdmmc_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd) +{ + if (card->host.command_timeout_ms != 0) { + cmd->timeout_ms = card->host.command_timeout_ms; + } else if (cmd->timeout_ms == 0) { + cmd->timeout_ms = SDMMC_DEFAULT_CMD_TIMEOUT_MS; + } + + int slot = card->host.slot; + ESP_LOGV(TAG, "sending cmd slot=%d op=%d arg=%x flags=%x data=%p blklen=%d datalen=%d timeout=%d", + slot, cmd->opcode, cmd->arg, cmd->flags, cmd->data, cmd->blklen, cmd->datalen, cmd->timeout_ms); + sdmmc_err_t err = (*card->host.do_transaction)(slot, cmd); + if (err != 0) { + ESP_LOGD(TAG, "cmd=%d, sdmmc_req_run returned 0x%x", cmd->opcode, err); + return err; + } + 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, + MMC_R1_CURRENT_STATE(cmd->response)); + return cmd->error; +} + +sdmmc_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), + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &app_cmd); + if (err != SDMMC_OK) { + return err; + } + // Check APP_CMD status bit (only in SD mode) + if (!host_is_spi(card) && !(MMC_R1(app_cmd.response) & MMC_R1_APP_CMD)) { + ESP_LOGW(TAG, "card doesn't support APP_CMD"); + return SDMMC_ERR_NOT_SUPPORTED; + } + return sdmmc_send_cmd(card, cmd); +} + + +sdmmc_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, + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (host_is_spi(card)) { + /* To enter SPI mode, CMD0 needs to be sent twice (see figure 4-1 in + * SD Simplified spec v4.10). Some cards enter SD mode on first CMD0, + * so don't expect the above command to succeed. + * SCF_RSP_R1 flag below tells the lower layer to expect correct R1 + * response (in SPI mode). + */ + (void) err; + osal_task_delay(SDMMC_GO_IDLE_DELAY_MS); + + cmd.flags |= SCF_RSP_R1; + err = sdmmc_send_cmd(card, &cmd); + } + if (err == SDMMC_OK) { + osal_task_delay(SDMMC_GO_IDLE_DELAY_MS); + } + return err; +} + + +sdmmc_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, + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + uint8_t response = cmd.response[0] & 0xff; + if (response != pattern) { + ESP_LOGD(TAG, "%s: received=0x%x expected=0x%x", __func__, response, pattern); + return SDMMC_ERR_INVALID_RESPONSE; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp) +{ + sdmmc_err_t err; + + sdmmc_command_t cmd = { + .arg = ocr, + .flags = SCF_CMD_BCR | SCF_RSP_R3, + .opcode = SD_APP_OP_COND + }; + int nretries = SDMMC_SEND_OP_COND_MAX_RETRIES; + int err_cnt = SDMMC_SEND_OP_COND_MAX_ERRORS; + for (; nretries != 0; --nretries) { + bzero(&cmd, sizeof cmd); + cmd.arg = ocr; + cmd.flags = SCF_CMD_BCR | SCF_RSP_R3; + if (!card->is_mmc) { /* SD mode */ + cmd.opcode = SD_APP_OP_COND; + err = sdmmc_send_app_cmd(card, &cmd); + } else { /* MMC mode */ + cmd.arg &= ~MMC_OCR_ACCESS_MODE_MASK; + cmd.arg |= MMC_OCR_SECTOR_MODE; + cmd.opcode = MMC_SEND_OP_COND; + err = sdmmc_send_cmd(card, &cmd); + } + + if (err != SDMMC_OK) { + if (--err_cnt == 0) { + ESP_LOGD(TAG, "%s: sdmmc_send_app_cmd err=0x%x", __func__, err); + return err; + } else { + ESP_LOGV(TAG, "%s: ignoring err=0x%x", __func__, err); + continue; + } + } + // In SD protocol, card sets MEM_READY bit in OCR when it is ready. + // In SPI protocol, card clears IDLE_STATE bit in R1 response. + if (!host_is_spi(card)) { + if ((MMC_R3(cmd.response) & MMC_OCR_MEM_READY) || + ocr == 0) { + break; + } + } else { + if ((SD_SPI_R1(cmd.response) & SD_SPI_R1_IDLE_STATE) == 0) { + break; + } + } + osal_task_delay(10); + } + if (nretries == 0) { + return SDMMC_ERR_TIMEOUT; + } + if (ocrp) { + *ocrp = MMC_R3(cmd.response); + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_send_cmd_read_ocr(sdmmc_card_t *card, uint32_t *ocrp) +{ + assert(ocrp); + sdmmc_command_t cmd = { + .opcode = SD_READ_OCR, + .flags = SCF_CMD_BCR | SCF_RSP_R2 + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + *ocrp = SD_SPI_R3(cmd.response); + return SDMMC_OK; +} + + +sdmmc_err_t sdmmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_response_t* out_raw_cid) +{ + assert(out_raw_cid); + sdmmc_command_t cmd = { + .opcode = MMC_ALL_SEND_CID, + .flags = SCF_CMD_BCR | SCF_RSP_R2 + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + memcpy(out_raw_cid, &cmd.response, sizeof(sdmmc_response_t)); + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_send_cmd_send_cid(sdmmc_card_t *card, sdmmc_cid_t *out_cid) +{ + assert(out_cid); + assert(host_is_spi(card) && "SEND_CID should only be used in SPI mode"); + assert(!card->is_mmc && "MMC cards are not supported in SPI mode"); + sdmmc_response_t buf; + sdmmc_command_t cmd = { + .opcode = MMC_SEND_CID, + .flags = SCF_CMD_READ | SCF_CMD_ADTC, + .arg = 0, + .data = &buf[0], + .datalen = sizeof(buf) + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + sdmmc_flip_byte_order(buf, sizeof(buf)); + return sdmmc_decode_cid(buf, out_cid); +} + + +sdmmc_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 + }; + + /* MMC cards expect us to set the RCA. + * Set RCA to 1 since we don't support multiple cards on the same bus, for now. + */ + uint16_t mmc_rca = 1; + if (card->is_mmc) { + cmd.arg = MMC_ARG_RCA(mmc_rca); + } + + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + *out_rca = (card->is_mmc) ? mmc_rca : SD_R6_RCA(cmd.response); + return SDMMC_OK; +} + +sdmmc_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); +} + +sdmmc_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd) +{ + /* The trick with SEND_CSD is that in SPI mode, it acts as a data read + * command, while in SD mode it is an AC command with R2 response. + */ + sdmmc_response_t spi_buf; + const bool is_spi = host_is_spi(card); + sdmmc_command_t cmd = { + .opcode = MMC_SEND_CSD, + .arg = is_spi ? 0 : MMC_ARG_RCA(card->rca), + .flags = is_spi ? (SCF_CMD_READ | SCF_CMD_ADTC | SCF_RSP_R1) : + (SCF_CMD_AC | SCF_RSP_R2), + .data = is_spi ? &spi_buf[0] : 0, + .datalen = is_spi ? sizeof(spi_buf) : 0, + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + uint32_t* ptr = cmd.response; + if (is_spi) { + sdmmc_flip_byte_order(spi_buf, sizeof(spi_buf)); + ptr = spi_buf; + } + if (card->is_mmc) { + err = sdmmc_mmc_decode_csd(cmd.response, out_csd); + } else { + err = sdmmc_decode_csd(ptr, out_csd); + } + return err; +} + +sdmmc_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card, uint32_t rca) +{ + /* Don't expect to see a response when de-selecting a card */ + uint32_t response = (rca == 0) ? 0 : SCF_RSP_R1; + sdmmc_command_t cmd = { + .opcode = MMC_SELECT_CARD, + .arg = MMC_ARG_RCA(rca), + .flags = SCF_CMD_AC | response + }; + return sdmmc_send_cmd(card, &cmd); +} + +sdmmc_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr) +{ + size_t datalen = 8; + uint32_t buf[datalen]; + 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 + }; + sdmmc_err_t err = sdmmc_send_app_cmd(card, &cmd); + if (err == SDMMC_OK) { + err = sdmmc_decode_scr(buf, out_scr); + } + return err; +} + +sdmmc_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); +} + +sdmmc_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable) +{ + assert(host_is_spi(card) && "CRC_ON_OFF can only be used in SPI mode"); + sdmmc_command_t cmd = { + .opcode = SD_CRC_ON_OFF, + .arg = crc_enable ? 1 : 0, + .flags = SCF_CMD_AC | SCF_RSP_R1 + }; + return sdmmc_send_cmd(card, &cmd); +} + +sdmmc_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 + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + return err; + } + if (out_status) { + *out_status = MMC_R1(cmd.response); + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src, + size_t start_block, size_t block_count) +{ + sdmmc_err_t err = SDMMC_OK; + size_t block_size = card->csd.sector_size; + if ((intptr_t)src % 4 == 0) { + err = sdmmc_write_sectors_dma(card, src, start_block, block_count); + } else { + // SDMMC peripheral needs DMA-capable buffers. Split the write into + // separate single block writes, if needed, and allocate a temporary + // DMA-capable buffer. + uint32_t tmp_buf[block_size / sizeof(uint32_t)]; + const uint8_t* cur_src = (const uint8_t*) src; + for (size_t i = 0; i < block_count; ++i) { + memcpy(tmp_buf, cur_src, block_size); + cur_src += block_size; + err = sdmmc_write_sectors_dma(card, tmp_buf, start_block + i, 1); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: error 0x%x writing block %d+%d", + __func__, err, start_block, i); + break; + } + } + } + return err; +} + +sdmmc_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src, + size_t start_block, size_t block_count) +{ + if (start_block + block_count > (size_t) card->csd.capacity) { + return SDMMC_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, + .timeout_ms = SDMMC_WRITE_CMD_TIMEOUT_MS + }; + if (block_count == 1) { + cmd.opcode = MMC_WRITE_BLOCK_SINGLE; + } else { + cmd.opcode = MMC_WRITE_BLOCK_MULTIPLE; + cmd.flags |= SCF_AUTO_STOP; + } + if (card->ocr & SD_OCR_SDHC_CAP) { + cmd.arg = start_block; + } else { + cmd.arg = start_block * block_size; + } + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + uint32_t status = 0; + size_t count = 0; + while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + err = sdmmc_send_cmd_send_status(card, &status); + if (err != SDMMC_OK) { + return err; + } + if (++count % 10 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst, + size_t start_block, size_t block_count) +{ + sdmmc_err_t err = SDMMC_OK; + size_t block_size = card->csd.sector_size; + if ((intptr_t)dst % 4 == 0) { + err = sdmmc_read_sectors_dma(card, dst, start_block, block_count); + } else { + // SDMMC peripheral needs DMA-capable buffers. Split the read into + // separate single block reads, if needed, and allocate a temporary + // DMA-capable buffer. + uint32_t tmp_buf[block_size / sizeof(uint32_t)]; + uint8_t* cur_dst = (uint8_t*) dst; + for (size_t i = 0; i < block_count; ++i) { + err = sdmmc_read_sectors_dma(card, tmp_buf, start_block + i, 1); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: error 0x%x writing block %d+%d", + __func__, err, start_block, i); + break; + } + memcpy(cur_dst, tmp_buf, block_size); + cur_dst += block_size; + } + } + return err; +} + +sdmmc_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst, + size_t start_block, size_t block_count) +{ + if (start_block + block_count > (size_t) card->csd.capacity) { + return SDMMC_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; + cmd.flags |= SCF_AUTO_STOP; + } + if (card->ocr & SD_OCR_SDHC_CAP) { + cmd.arg = start_block; + } else { + cmd.arg = start_block * block_size; + } + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + uint32_t status = 0; + size_t count = 0; + while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + err = sdmmc_send_cmd_send_status(card, &status); + if (err != SDMMC_OK) { + return err; + } + if (++count % 10 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } + } + return SDMMC_OK; +} diff --git a/circuitpython/lib/sdmmc/sdmmc_common.c b/circuitpython/lib/sdmmc/sdmmc_common.c new file mode 100644 index 0000000..fea29ab --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_common.c @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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 <assert.h> + +#include "sdmmc_common.h" + +sdmmc_err_t sdmmc_init_ocr(sdmmc_card_t* card) +{ + sdmmc_err_t err; + /* In SPI mode, READ_OCR (CMD58) command is used to figure out which voltage + * ranges the card can support. This step is skipped since 1.8V isn't + * supported on the ESP32. + */ + + uint32_t host_ocr = get_host_ocr(card->host.io_voltage); + if ((card->ocr & SD_OCR_SDHC_CAP) != 0) { + host_ocr |= SD_OCR_SDHC_CAP; + } + /* Send SEND_OP_COND (ACMD41) command to the card until it becomes ready. */ + err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr); + + /* If time-out, re-try send_op_cond as MMC */ + if (err == SDMMC_ERR_TIMEOUT && !host_is_spi(card)) { + ESP_LOGD(TAG, "send_op_cond timeout, trying MMC"); + card->is_mmc = 1; + err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr); + } + + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err); + return err; + } + if (host_is_spi(card)) { + err = sdmmc_send_cmd_read_ocr(card, &card->ocr); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: read_ocr returned 0x%x", __func__, err); + return err; + } + } + ESP_LOGD(TAG, "host_ocr=0x%x card_ocr=0x%x", host_ocr, card->ocr); + + /* Clear all voltage bits in host's OCR which the card doesn't support. + * Don't touch CCS bit because in SPI mode cards don't report CCS in ACMD41 + * response. + */ + host_ocr &= (card->ocr | (~SD_OCR_VOL_MASK)); + ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr); + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_cid(sdmmc_card_t* card) +{ + sdmmc_err_t err; + sdmmc_response_t raw_cid; + if (!host_is_spi(card)) { + err = sdmmc_send_cmd_all_send_cid(card, &raw_cid); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: all_send_cid returned 0x%x", __func__, err); + return err; + } + if (!card->is_mmc) { + err = sdmmc_decode_cid(raw_cid, &card->cid); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: decoding CID failed (0x%x)", __func__, err); + return err; + } + } else { + /* For MMC, need to know CSD to decode CID. But CSD can only be read + * in data transfer mode, and it is not possible to read CID in data + * transfer mode. We temporiliy store the raw cid and do the + * decoding after the RCA is set and the card is in data transfer + * mode. + */ + memcpy(card->raw_cid, raw_cid, sizeof(sdmmc_response_t)); + } + } else { + err = sdmmc_send_cmd_send_cid(card, &card->cid); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: send_cid returned 0x%x", __func__, err); + return err; + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_rca(sdmmc_card_t* card) +{ + sdmmc_err_t err; + err = sdmmc_send_cmd_set_relative_addr(card, &card->rca); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: set_relative_addr returned 0x%x", __func__, err); + return err; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_mmc_decode_cid(sdmmc_card_t* card) +{ + sdmmc_err_t err; + sdmmc_response_t raw_cid; + memcpy(raw_cid, card->raw_cid, sizeof(raw_cid)); + err = sdmmc_mmc_decode_cid(card->csd.mmc_ver, raw_cid, &card->cid); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: decoding CID failed (0x%x)", __func__, err); + return err; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_csd(sdmmc_card_t* card) +{ + assert(card->is_mem == 1); + /* Get and decode the contents of CSD register. Determine card capacity. */ + sdmmc_err_t err = sdmmc_send_cmd_send_csd(card, &card->csd); + if (err != SDMMC_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) && + (size_t) 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; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_select_card(sdmmc_card_t* card) +{ + assert(!host_is_spi(card)); + sdmmc_err_t err = sdmmc_send_cmd_select_card(card, card->rca); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err); + return err; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_card_hs_mode(sdmmc_card_t* card) +{ + sdmmc_err_t err = SDMMC_ERR_NOT_SUPPORTED; + if (card->is_mem && !card->is_mmc) { + err = sdmmc_enable_hs_mode_and_check(card); + } else if (card->is_sdio) { + err = sdmmc_io_enable_hs_mode(card); + } else if (card->is_mmc){ + err = sdmmc_mmc_enable_hs_mode(card); + } + if (err == SDMMC_ERR_NOT_SUPPORTED) { + ESP_LOGD(TAG, "%s: host supports HS mode, but card doesn't", __func__); + card->max_freq_khz = SDMMC_FREQ_DEFAULT; + } else if (err != SDMMC_OK) { + return err; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_host_bus_width(sdmmc_card_t* card) +{ + int bus_width = 1; + + if ((card->host.flags & SDMMC_HOST_FLAG_4BIT) && + (card->log_bus_width == 2)) { + bus_width = 4; + } else if ((card->host.flags & SDMMC_HOST_FLAG_8BIT) && + (card->log_bus_width == 3)) { + bus_width = 8; + } + ESP_LOGD(TAG, "%s: using %d-bit bus", __func__, bus_width); + if (bus_width > 1) { + sdmmc_err_t err = (*card->host.set_bus_width)(card->host.slot, bus_width); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "host.set_bus_width failed (0x%x)", err); + return err; + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_host_frequency(sdmmc_card_t* card) +{ + assert(card->max_freq_khz <= card->host.max_freq_khz); + + /* Find highest frequency in the following list, + * which is below card->max_freq_khz. + */ + const uint32_t freq_values[] = { + SDMMC_FREQ_52M, + SDMMC_FREQ_HIGHSPEED, + SDMMC_FREQ_26M, + SDMMC_FREQ_DEFAULT + //NOTE: in sdspi mode, 20MHz may not work. in that case, add 10MHz here. + }; + const int n_freq_values = sizeof(freq_values) / sizeof(freq_values[0]); + + uint32_t selected_freq = SDMMC_FREQ_PROBING; + for (int i = 0; i < n_freq_values; ++i) { + uint32_t freq = freq_values[i]; + if (card->max_freq_khz >= freq) { + selected_freq = freq; + break; + } + } + + ESP_LOGD(TAG, "%s: using %d kHz bus frequency", __func__, selected_freq); + if (selected_freq > SDMMC_FREQ_PROBING) { + sdmmc_err_t err = (*card->host.set_card_clk)(card->host.slot, selected_freq); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "failed to switch bus frequency (0x%x)", err); + return err; + } + } + + if (card->is_ddr) { + if (card->host.set_bus_ddr_mode == NULL) { + ESP_LOGE(TAG, "host doesn't support DDR mode or voltage switching"); + return SDMMC_ERR_NOT_SUPPORTED; + } + sdmmc_err_t err = (*card->host.set_bus_ddr_mode)(card->host.slot, true); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "failed to switch bus to DDR mode (0x%x)", err); + return err; + } + } + return SDMMC_OK; +} + +void sdmmc_flip_byte_order(uint32_t* response, size_t size) +{ + assert(size % (2 * sizeof(uint32_t)) == 0); + const size_t n_words = size / sizeof(uint32_t); + for (size_t i = 0; i < n_words / 2; ++i) { + uint32_t left = __builtin_bswap32(response[i]); + uint32_t right = __builtin_bswap32(response[n_words - i - 1]); + response[i] = right; + response[n_words - i - 1] = left; + } +} + +void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card) +{ + // bool print_scr = false; + // bool print_csd = false; + // const char* type; + // fprintf(stream, "Name: %s\n", card->cid.name); + // if (card->is_sdio) { + // type = "SDIO"; + // print_scr = true; + // print_csd = true; + // } else if (card->is_mmc) { + // type = "MMC"; + // print_csd = true; + // } else { + // type = (card->ocr & SD_OCR_SDHC_CAP) ? "SDHC/SDXC" : "SDSC"; + // } + // fprintf(stream, "Type: %s\n", type); + // if (card->max_freq_khz < 1000) { + // fprintf(stream, "Speed: %d kHz\n", card->max_freq_khz); + // } else { + // fprintf(stream, "Speed: %d MHz%s\n", card->max_freq_khz / 1000, + // card->is_ddr ? ", DDR" : ""); + // } + // fprintf(stream, "Size: %luMB\n", ((uint64_t) card->csd.capacity) * card->csd.sector_size / (1024 * 1024)); + + // if (print_csd) { + // 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); + // } + // if (print_scr) { + // fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width); + // } +} + +sdmmc_err_t sdmmc_fix_host_flags(sdmmc_card_t* card) +{ + const uint32_t width_1bit = SDMMC_HOST_FLAG_1BIT; + const uint32_t width_4bit = SDMMC_HOST_FLAG_4BIT; + const uint32_t width_8bit = SDMMC_HOST_FLAG_8BIT; + const uint32_t width_mask = width_1bit | width_4bit | width_8bit; + + int slot_bit_width = card->host.get_bus_width(card->host.slot); + if (slot_bit_width == 1 && + (card->host.flags & (width_4bit | width_8bit))) { + card->host.flags &= ~width_mask; + card->host.flags |= width_1bit; + } else if (slot_bit_width == 4 && (card->host.flags & width_8bit)) { + if ((card->host.flags & width_4bit) == 0) { + ESP_LOGW(TAG, "slot width set to 4, but host flags don't have 4 line mode enabled; using 1 line mode"); + card->host.flags &= ~width_mask; + card->host.flags |= width_1bit; + } else { + card->host.flags &= ~width_mask; + card->host.flags |= width_4bit; + } + } + return SDMMC_OK; +} diff --git a/circuitpython/lib/sdmmc/sdmmc_common.h b/circuitpython/lib/sdmmc/sdmmc_common.h new file mode 100644 index 0000000..583bf9d --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_common.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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. + */ +#pragma once + +#include <string.h> +#include "sdmmc_defs.h" +#include "sdmmc_types.h" +#include "sdmmc_cmd.h" + +#define SDMMC_GO_IDLE_DELAY_MS 20 +#define SDMMC_IO_SEND_OP_COND_DELAY_MS 10 + +/* These delay values are mostly useful for cases when CD pin is not used, and + * the card is removed. In this case, SDMMC peripheral may not always return + * CMD_DONE / DATA_DONE interrupts after signaling the error. These timeouts work + * as a safety net in such cases. + */ +#define SDMMC_DEFAULT_CMD_TIMEOUT_MS 1000 // Max timeout of ordinary commands +#define SDMMC_WRITE_CMD_TIMEOUT_MS 5000 // Max timeout of write commands + +/* Maximum retry/error count for SEND_OP_COND (CMD1). + * These are somewhat arbitrary, values originate from OpenBSD driver. + */ +#define SDMMC_SEND_OP_COND_MAX_RETRIES 100 +#define SDMMC_SEND_OP_COND_MAX_ERRORS 3 + +/* Functions to send individual commands */ +sdmmc_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd); +sdmmc_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd); +sdmmc_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card); +sdmmc_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr); +sdmmc_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp); +sdmmc_err_t sdmmc_send_cmd_read_ocr(sdmmc_card_t *card, uint32_t *ocrp); +sdmmc_err_t sdmmc_send_cmd_send_cid(sdmmc_card_t *card, sdmmc_cid_t *out_cid); +sdmmc_err_t sdmmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_response_t* out_raw_cid); +sdmmc_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca); +sdmmc_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd); +sdmmc_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card, + uint32_t mode, uint32_t group, uint32_t function, + sdmmc_switch_func_rsp_t* resp); +sdmmc_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd); +sdmmc_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card, uint32_t rca); +sdmmc_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr); +sdmmc_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width); +sdmmc_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status); +sdmmc_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable); + +/* Higher level functions */ +sdmmc_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card); +sdmmc_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card); +sdmmc_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src, + size_t start_block, size_t block_count); +sdmmc_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst, + size_t start_block, size_t block_count); + +/* SD specific */ +sdmmc_err_t sdmmc_check_scr(sdmmc_card_t* card); +sdmmc_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid); +sdmmc_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd); +sdmmc_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr); + +/* SDIO specific */ +sdmmc_err_t sdmmc_io_reset(sdmmc_card_t* card); +sdmmc_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card); +sdmmc_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp); +sdmmc_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int function, + uint32_t reg, uint32_t arg, uint8_t *byte); +sdmmc_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int function, + uint32_t reg, int arg, void *data, size_t size); + + +/* MMC specific */ +sdmmc_err_t sdmmc_mmc_send_ext_csd_data(sdmmc_card_t* card, void *out_data, size_t datalen); +sdmmc_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8_t value); +sdmmc_err_t sdmmc_mmc_decode_cid(int mmc_ver, sdmmc_response_t resp, sdmmc_cid_t* out_cid); +sdmmc_err_t sdmmc_mmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd); +sdmmc_err_t sdmmc_mmc_enable_hs_mode(sdmmc_card_t* card); + +/* Parts of card initialization flow */ +sdmmc_err_t sdmmc_init_sd_if_cond(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_select_card(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_csd(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_cid(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_rca(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_mmc_decode_cid(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_ocr(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_spi_crc(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_io(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_sd_blocklen(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_sd_scr(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_sd_wait_data_ready(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_mmc_read_cid(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_host_bus_width(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_sd_bus_width(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_io_bus_width(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_mmc_bus_width(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_card_hs_mode(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_host_frequency(sdmmc_card_t* card); +sdmmc_err_t sdmmc_init_mmc_check_csd(sdmmc_card_t* card); + +/* Various helper functions */ +static inline bool host_is_spi(const sdmmc_card_t* card) +{ + return (card->host.flags & SDMMC_HOST_FLAG_SPI) != 0; +} + +static inline 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; +} + +void sdmmc_flip_byte_order(uint32_t* response, size_t size); + +sdmmc_err_t sdmmc_fix_host_flags(sdmmc_card_t* card); diff --git a/circuitpython/lib/sdmmc/sdmmc_init.c b/circuitpython/lib/sdmmc/sdmmc_init.c new file mode 100644 index 0000000..de5c739 --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_init.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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 "sdmmc_common.h" + +#define SDMMC_INIT_STEP(condition, function) \ + do { \ + if ((condition)) { \ + sdmmc_err_t err = (function)(card); \ + if (err != SDMMC_OK) { \ + ESP_LOGD(TAG, "%s: %s returned 0x%x", __func__, #function, err); \ + return err; \ + } \ + } \ + } while(0); + + +sdmmc_err_t sdmmc_card_init(const sdmmc_host_t* config, sdmmc_card_t* card) +{ + memset(card, 0, sizeof(*card)); + memcpy(&card->host, config, sizeof(*config)); + const bool is_spi = host_is_spi(card); + const bool always = true; + const bool io_supported = true; + + /* Check if host flags are compatible with slot configuration. */ + SDMMC_INIT_STEP(!is_spi, sdmmc_fix_host_flags); + + /* Reset SDIO (CMD52, RES) before re-initializing IO (CMD5). */ + SDMMC_INIT_STEP(io_supported, sdmmc_io_reset); + + /* GO_IDLE_STATE (CMD0) command resets the card */ + SDMMC_INIT_STEP(always, sdmmc_send_cmd_go_idle_state); + + /* SEND_IF_COND (CMD8) command is used to identify SDHC/SDXC cards. */ + SDMMC_INIT_STEP(always, sdmmc_init_sd_if_cond); + + /* IO_SEND_OP_COND(CMD5), Determine if the card is an IO card. */ + SDMMC_INIT_STEP(io_supported, sdmmc_init_io); + + const bool is_mem = card->is_mem; + const bool is_sdio = !is_mem; + + /* Enable CRC16 checks for data transfers in SPI mode */ + SDMMC_INIT_STEP(is_spi, sdmmc_init_spi_crc); + + /* Use SEND_OP_COND to set up card OCR */ + SDMMC_INIT_STEP(is_mem, sdmmc_init_ocr); + + const bool is_mmc = is_mem && card->is_mmc; + const bool is_sdmem = is_mem && !is_mmc; + + ESP_LOGD(TAG, "%s: card type is %s", __func__, + is_sdio ? "SDIO" : is_mmc ? "MMC" : "SD"); + + /* Read the contents of CID register*/ + SDMMC_INIT_STEP(is_mem, sdmmc_init_cid); + + /* Assign RCA */ + SDMMC_INIT_STEP(!is_spi, sdmmc_init_rca); + + /* Read and decode the contents of CSD register */ + SDMMC_INIT_STEP(is_mem, sdmmc_init_csd); + + /* Decode the contents of mmc CID register */ + SDMMC_INIT_STEP(is_mmc && !is_spi, sdmmc_init_mmc_decode_cid); + + /* Switch the card from stand-by mode to data transfer mode (not needed if + * SPI interface is used). This is needed to issue SET_BLOCKLEN and + * SEND_SCR commands. + */ + SDMMC_INIT_STEP(!is_spi, sdmmc_init_select_card); + + /* SD memory cards: + * Set block len for SDSC cards to 512 bytes (same as SDHC) + * Read SCR + * Wait to enter data transfer state + */ + SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_blocklen); + SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_scr); + SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_wait_data_ready); + + /* MMC cards: read CXD */ + SDMMC_INIT_STEP(is_mmc, sdmmc_init_mmc_read_ext_csd); + + /* Try to switch card to HS mode if the card supports it. + * Set card->max_freq_khz value accordingly. + */ + SDMMC_INIT_STEP(always, sdmmc_init_card_hs_mode); + + /* Set bus width. One call for every kind of card, then one for the host */ + if (!is_spi) { + SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_bus_width); + SDMMC_INIT_STEP(is_sdio, sdmmc_init_io_bus_width); + SDMMC_INIT_STEP(is_mmc, sdmmc_init_mmc_bus_width); + SDMMC_INIT_STEP(always, sdmmc_init_host_bus_width); + } + + /* Switch to the host to use card->max_freq_khz frequency. */ + SDMMC_INIT_STEP(always, sdmmc_init_host_frequency); + + /* Sanity check after switching the bus mode and frequency */ + SDMMC_INIT_STEP(is_sdmem, sdmmc_check_scr); + /* TODO: this is CMD line only, add data checks for eMMC */ + SDMMC_INIT_STEP(is_mmc, sdmmc_init_mmc_check_csd); + /* TODO: add similar checks for SDIO */ + + return SDMMC_OK; +} diff --git a/circuitpython/lib/sdmmc/sdmmc_io.c b/circuitpython/lib/sdmmc/sdmmc_io.c new file mode 100644 index 0000000..17ab291 --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_io.c @@ -0,0 +1,639 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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 <assert.h> + +#include "sdmmc_common.h" + + +#define CIS_TUPLE(NAME) (cis_tuple_t) {.code=CISTPL_CODE_##NAME, .name=#NAME, .func=&cis_tuple_func_default, } +#define CIS_TUPLE_WITH_FUNC(NAME, FUNC) (cis_tuple_t) {.code=CISTPL_CODE_##NAME, .name=#NAME, .func=&(FUNC), } + +#define CIS_CHECK_SIZE(SIZE, MINIMAL) do {int store_size = (SIZE); if((store_size) < (MINIMAL)) return SDMMC_ERR_INVALID_SIZE;} while(0) +#define CIS_CHECK_UNSUPPORTED(COND) do {if(!(COND)) return SDMMC_ERR_NOT_SUPPORTED;} while(0) +#define CIS_GET_MINIMAL_SIZE 32 + +typedef sdmmc_err_t (*cis_tuple_info_func_t)(const void* tuple_info, uint8_t* data, FILE* fp); + +typedef struct { + int code; + const char *name; + cis_tuple_info_func_t func; +} cis_tuple_t; + +static sdmmc_err_t cis_tuple_func_default(const void* p, uint8_t* data, FILE* fp); +static sdmmc_err_t cis_tuple_func_manfid(const void* p, uint8_t* data, FILE* fp); +static sdmmc_err_t cis_tuple_func_cftable_entry(const void* p, uint8_t* data, FILE* fp); +static sdmmc_err_t cis_tuple_func_end(const void* p, uint8_t* data, FILE* fp); + +static const cis_tuple_t cis_table[] = { + CIS_TUPLE(NULL), + CIS_TUPLE(DEVICE), + CIS_TUPLE(CHKSUM), + CIS_TUPLE(VERS1), + CIS_TUPLE(ALTSTR), + CIS_TUPLE(CONFIG), + CIS_TUPLE_WITH_FUNC(CFTABLE_ENTRY, cis_tuple_func_cftable_entry), + CIS_TUPLE_WITH_FUNC(MANFID, cis_tuple_func_manfid), + CIS_TUPLE(FUNCID), + CIS_TUPLE(FUNCE), + CIS_TUPLE(VENDER_BEGIN), + CIS_TUPLE(VENDER_END), + CIS_TUPLE(SDIO_STD), + CIS_TUPLE(SDIO_EXT), + CIS_TUPLE_WITH_FUNC(END, cis_tuple_func_end), +}; + + +sdmmc_err_t sdmmc_io_reset(sdmmc_card_t* card) +{ + uint8_t sdio_reset = CCCR_CTL_RES; + sdmmc_err_t err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CTL, SD_ARG_CMD52_WRITE, &sdio_reset); + if (err == SDMMC_ERR_TIMEOUT || (host_is_spi(card) && err == SDMMC_ERR_NOT_SUPPORTED)) { + /* Non-IO cards are allowed to time out (in SD mode) or + * return "invalid command" error (in SPI mode). + */ + } else if (err == SDMMC_ERR_NO_CARD) { + ESP_LOGD(TAG, "%s: card not present", __func__); + return err; + } else if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: unexpected return: 0x%x", __func__, err ); + return err; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_io(sdmmc_card_t* card) +{ + /* IO_SEND_OP_COND(CMD5), Determine if the card is an IO card. + * Non-IO cards will not respond to this command. + */ + sdmmc_err_t err = sdmmc_io_send_op_cond(card, 0, &card->ocr); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: io_send_op_cond (1) returned 0x%x; not IO card", __func__, err); + card->is_sdio = 0; + card->is_mem = 1; + } else { + card->is_sdio = 1; + + if (card->ocr & SD_IO_OCR_MEM_PRESENT) { + ESP_LOGD(TAG, "%s: IO-only card", __func__); + card->is_mem = 0; + } + card->num_io_functions = SD_IO_OCR_NUM_FUNCTIONS(card->ocr); + ESP_LOGD(TAG, "%s: number of IO functions: %d", __func__, card->num_io_functions); + if (card->num_io_functions == 0) { + card->is_sdio = 0; + } + uint32_t host_ocr = get_host_ocr(card->host.io_voltage); + host_ocr &= card->ocr; + err = sdmmc_io_send_op_cond(card, host_ocr, &card->ocr); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_send_op_cond (1) returned 0x%x", __func__, err); + return err; + } + err = sdmmc_io_enable_int(card); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: sdmmc_enable_int failed (0x%x)", __func__, err); + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_io_bus_width(sdmmc_card_t* card) +{ + sdmmc_err_t err; + card->log_bus_width = 0; + if (card->host.flags & SDMMC_HOST_FLAG_4BIT) { + uint8_t card_cap = 0; + err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CARD_CAP, + SD_ARG_CMD52_READ, &card_cap); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read SD_IO_CCCR_CARD_CAP) returned 0x%0x", __func__, err); + return err; + } + ESP_LOGD(TAG, "IO card capabilities byte: %02x", card_cap); + if (!(card_cap & CCCR_CARD_CAP_LSC) || + (card_cap & CCCR_CARD_CAP_4BLS)) { + // This card supports 4-bit bus mode + uint8_t bus_width = CCCR_BUS_WIDTH_4; + err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_BUS_WIDTH, + SD_ARG_CMD52_WRITE, &bus_width); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write SD_IO_CCCR_BUS_WIDTH) returned 0x%0x", __func__, err); + return err; + } + card->log_bus_width = 2; + } + } + return SDMMC_OK; +} + + +sdmmc_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card) +{ + /* If the host is configured to use low frequency, don't attempt to switch */ + if (card->host.max_freq_khz < SDMMC_FREQ_DEFAULT) { + card->max_freq_khz = card->host.max_freq_khz; + return SDMMC_OK; + } else if (card->host.max_freq_khz < SDMMC_FREQ_HIGHSPEED) { + card->max_freq_khz = SDMMC_FREQ_DEFAULT; + return SDMMC_OK; + } + + /* For IO cards, do write + read operation on "High Speed" register, + * setting EHS bit. If both EHS and SHS read back as set, then HS mode + * has been enabled. + */ + uint8_t val = CCCR_HIGHSPEED_ENABLE; + sdmmc_err_t err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_HIGHSPEED, + SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &val); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: sdmmc_io_rw_direct returned 0x%x", __func__, err); + return err; + } + + ESP_LOGD(TAG, "%s: CCCR_HIGHSPEED=0x%02x", __func__, val); + const uint8_t hs_mask = CCCR_HIGHSPEED_ENABLE | CCCR_HIGHSPEED_SUPPORT; + if ((val & hs_mask) != hs_mask) { + return SDMMC_ERR_NOT_SUPPORTED; + } + card->max_freq_khz = SDMMC_FREQ_HIGHSPEED; + return SDMMC_OK; +} + + +sdmmc_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp) +{ + sdmmc_err_t err = SDMMC_OK; + sdmmc_command_t cmd = { + .flags = SCF_CMD_BCR | SCF_RSP_R4, + .arg = ocr, + .opcode = SD_IO_SEND_OP_COND + }; + for (size_t i = 0; i < 100; i++) { + err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + break; + } + if ((MMC_R4(cmd.response) & SD_IO_OCR_MEM_READY) || + ocr == 0) { + break; + } + err = SDMMC_ERR_TIMEOUT; + osal_task_delay(SDMMC_IO_SEND_OP_COND_DELAY_MS); + } + if (err == SDMMC_OK && ocrp != NULL) + *ocrp = MMC_R4(cmd.response); + + return err; +} + +sdmmc_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int func, + uint32_t reg, uint32_t arg, uint8_t *data) +{ + sdmmc_err_t err; + sdmmc_command_t cmd = { + .flags = SCF_CMD_AC | SCF_RSP_R5, + .arg = 0, + .opcode = SD_IO_RW_DIRECT + }; + + arg |= (func & SD_ARG_CMD52_FUNC_MASK) << SD_ARG_CMD52_FUNC_SHIFT; + arg |= (reg & SD_ARG_CMD52_REG_MASK) << SD_ARG_CMD52_REG_SHIFT; + arg |= (*data & SD_ARG_CMD52_DATA_MASK) << SD_ARG_CMD52_DATA_SHIFT; + cmd.arg = arg; + + err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + ESP_LOGV(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + + *data = SD_R5_DATA(cmd.response); + + return SDMMC_OK; +} + + +sdmmc_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function, + uint32_t addr, uint8_t *out_byte) +{ + sdmmc_err_t ret = sdmmc_io_rw_direct(card, function, addr, SD_ARG_CMD52_READ, out_byte); + if (ret != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read 0x%x) returned 0x%x", __func__, addr, ret); + } + return ret; +} + +sdmmc_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function, + uint32_t addr, uint8_t in_byte, uint8_t* out_byte) +{ + uint8_t tmp_byte = in_byte; + sdmmc_err_t ret = sdmmc_io_rw_direct(card, function, addr, + SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &tmp_byte); + if (ret != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write 0x%x) returned 0x%x", __func__, addr, ret); + return ret; + } + if (out_byte != NULL) { + *out_byte = tmp_byte; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int func, + uint32_t reg, int arg, void *datap, size_t datalen) +{ + sdmmc_err_t err; + const size_t max_byte_transfer_size = 512; + sdmmc_command_t cmd = { + .flags = SCF_CMD_AC | SCF_RSP_R5, + .arg = 0, + .opcode = SD_IO_RW_EXTENDED, + .data = datap, + .datalen = datalen, + .blklen = max_byte_transfer_size /* TODO: read max block size from CIS */ + }; + + uint32_t count; /* number of bytes or blocks, depending on transfer mode */ + if (arg & SD_ARG_CMD53_BLOCK_MODE) { + if (cmd.datalen % cmd.blklen != 0) { + return SDMMC_ERR_INVALID_SIZE; + } + count = cmd.datalen / cmd.blklen; + } else { + if (datalen > max_byte_transfer_size) { + /* TODO: split into multiple operations? */ + return SDMMC_ERR_INVALID_SIZE; + } + if (datalen == max_byte_transfer_size) { + count = 0; // See 5.3.1 SDIO simplifed spec + } else { + count = datalen; + } + cmd.blklen = datalen; + } + + arg |= (func & SD_ARG_CMD53_FUNC_MASK) << SD_ARG_CMD53_FUNC_SHIFT; + arg |= (reg & SD_ARG_CMD53_REG_MASK) << SD_ARG_CMD53_REG_SHIFT; + arg |= (count & SD_ARG_CMD53_LENGTH_MASK) << SD_ARG_CMD53_LENGTH_SHIFT; + cmd.arg = arg; + + if ((arg & SD_ARG_CMD53_WRITE) == 0) { + cmd.flags |= SCF_CMD_READ; + } + + err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size) +{ + /* host quirk: SDIO transfer with length not divisible by 4 bytes + * has to be split into two transfers: one with aligned length, + * the other one for the remaining 1-3 bytes. + */ + uint8_t *pc_dst = dst; + while (size > 0) { + size_t size_aligned = size & (~3); + size_t will_transfer = size_aligned > 0 ? size_aligned : size; + + sdmmc_err_t err = sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT, + pc_dst, will_transfer); + if (err != SDMMC_OK) { + return err; + } + pc_dst += will_transfer; + size -= will_transfer; + addr += will_transfer; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_io_write_bytes(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size) +{ + /* same host quirk as in sdmmc_io_read_bytes */ + const uint8_t *pc_src = (const uint8_t*) src; + + while (size > 0) { + size_t size_aligned = size & (~3); + size_t will_transfer = size_aligned > 0 ? size_aligned : size; + + sdmmc_err_t err = sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT, + (void*) pc_src, will_transfer); + if (err != SDMMC_OK) { + return err; + } + pc_src += will_transfer; + size -= will_transfer; + addr += will_transfer; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, void* dst, size_t size) +{ + if (size % 4 != 0) { + return SDMMC_ERR_INVALID_SIZE; + } + return sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT | SD_ARG_CMD53_BLOCK_MODE, + dst, size); +} + +sdmmc_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function, + uint32_t addr, const void* src, size_t size) +{ + if (size % 4 != 0) { + return SDMMC_ERR_INVALID_SIZE; + } + return sdmmc_io_rw_extended(card, function, addr, + SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT | SD_ARG_CMD53_BLOCK_MODE, + (void*) src, size); +} + +sdmmc_err_t sdmmc_io_enable_int(sdmmc_card_t* card) +{ + if (card->host.io_int_enable == NULL) { + return SDMMC_ERR_NOT_SUPPORTED; + } + return (*card->host.io_int_enable)(card->host.slot); +} + +sdmmc_err_t sdmmc_io_wait_int(sdmmc_card_t* card, int timeout_ms) +{ + if (card->host.io_int_wait == NULL) { + return SDMMC_ERR_NOT_SUPPORTED; + } + return (*card->host.io_int_wait)(card->host.slot, timeout_ms); +} + + +/* + * Print the CIS information of a CIS card, currently only ESP slave supported. + */ + +static sdmmc_err_t cis_tuple_func_default(const void* p, uint8_t* data, FILE* fp) +{ + const cis_tuple_t* tuple = (const cis_tuple_t*)p; + uint8_t code = *(data++); + int size = *(data++); + if (tuple) { + fprintf(fp, "TUPLE: %s, size: %d: ", tuple->name, size); + } else { + fprintf(fp, "TUPLE: unknown(%02X), size: %d: ", code, size); + } + for (int i = 0; i < size; i++) fprintf(fp, "%02X ", *(data++)); + fprintf(fp, "\n"); + return SDMMC_OK; +} + +static sdmmc_err_t cis_tuple_func_manfid(const void* p, uint8_t* data, FILE* fp) +{ + const cis_tuple_t* tuple = (const cis_tuple_t*)p; + data++; + int size = *(data++); + fprintf(fp, "TUPLE: %s, size: %d\n", tuple->name, size); + CIS_CHECK_SIZE(size, 4); + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + fprintf(fp, " MANF: %04X, CARD: %04X\n", *(uint16_t*)(data), *(uint16_t*)(data+2)); + #pragma GCC diagnostic pop + return SDMMC_OK; +} + +static sdmmc_err_t cis_tuple_func_end(const void* p, uint8_t* data, FILE* fp) +{ + const cis_tuple_t* tuple = (const cis_tuple_t*)p; + data++; + fprintf(fp, "TUPLE: %s\n", tuple->name); + return SDMMC_OK; +} + +static sdmmc_err_t cis_tuple_func_cftable_entry(const void* p, uint8_t* data, FILE* fp) +{ + const cis_tuple_t* tuple = (const cis_tuple_t*)p; + data++; + int size = *(data++); + fprintf(fp, "TUPLE: %s, size: %d\n", tuple->name, size); + CIS_CHECK_SIZE(size, 2); + + CIS_CHECK_SIZE(size--, 1); + bool interface = data[0] & BIT(7); + bool def = data[0] & BIT(6); + int conf_ent_num = data[0] & 0x3F; + fprintf(fp, " INDX: %02X, Intface: %d, Default: %d, Conf-Entry-Num: %d\n", *(data++), interface, def, conf_ent_num); + + if (interface) { + CIS_CHECK_SIZE(size--, 1); + fprintf(fp, " IF: %02X\n", *(data++)); + } + + CIS_CHECK_SIZE(size--, 1); + bool misc = data[0] & BIT(7); + int mem_space = (data[0] >> 5 )&(0x3); + bool irq = data[0] & BIT(4); + bool io_sp = data[0] & BIT(3); + bool timing = data[0] & BIT(2); + int power = data[0] & 3; + fprintf(fp, " FS: %02X, misc: %d, mem_space: %d, irq: %d, io_space: %d, timing: %d, power: %d\n", *(data++), misc, mem_space, irq, io_sp, timing, power); + + CIS_CHECK_UNSUPPORTED(power == 0); //power descriptor is not handled yet + CIS_CHECK_UNSUPPORTED(!timing); //timing descriptor is not handled yet + CIS_CHECK_UNSUPPORTED(!io_sp); //io space descriptor is not handled yet + + if (irq) { + CIS_CHECK_SIZE(size--, 1); + bool mask = data[0] & BIT(4); + fprintf(fp, " IR: %02X, mask: %d, ",*(data++), mask); + if (mask) { + CIS_CHECK_SIZE(size, 2); + size-=2; + fprintf(fp, " IRQ: %02X %02X\n", data[0], data[1]); + data+=2; + } + } + + if (mem_space) { + CIS_CHECK_SIZE(size, 2); + size-=2; + CIS_CHECK_UNSUPPORTED(mem_space==1); //other cases not handled yet + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-align" + int len = *(uint16_t*)data; + #pragma GCC diagnostic pop + fprintf(fp, " LEN: %04X\n", len); + data+=2; + } + + CIS_CHECK_UNSUPPORTED(misc==0); //misc descriptor is not handled yet + return SDMMC_OK; +} + +static const cis_tuple_t* get_tuple(uint8_t code) +{ + for (size_t i = 0; i < sizeof(cis_table)/sizeof(cis_tuple_t); i++) { + if (code == cis_table[i].code) return &cis_table[i]; + } + return NULL; +} + +sdmmc_err_t sdmmc_io_print_cis_info(uint8_t* buffer, size_t buffer_size, FILE* fp) +{ + if (!fp) fp = stdout; + + uint8_t* cis = buffer; + do { + const cis_tuple_t* tuple = get_tuple(cis[0]); + int size = cis[1]; + sdmmc_err_t ret = SDMMC_OK; + if (tuple) { + ret = tuple->func(tuple, cis, fp); + } else { + ret = cis_tuple_func_default(NULL, cis, fp); + } + if (ret != SDMMC_OK) return ret; + cis += 2 + size; + if (tuple && tuple->code == CISTPL_CODE_END) break; + } while (cis < buffer + buffer_size) ; + return SDMMC_OK; +} + +/** + * Check tuples in the buffer. + * + * @param buf Buffer to check + * @param buffer_size Size of the buffer + * @param inout_cis_offset + * - input: the last cis_offset, relative to the beginning of the buf. -1 if + * this buffer begin with the tuple length, otherwise should be no smaller than + * zero. + * - output: when the end tuple found, output offset of the CISTPL_CODE_END + * byte + 1 (relative to the beginning of the buffer; when not found, output + * the address of next tuple code. + * + * @return true if found, false if haven't. + */ +static bool check_tuples_in_buffer(uint8_t* buf, int buffer_size, int* inout_cis_offset) +{ + int cis_offset = *inout_cis_offset; + if (cis_offset == -1) { + //the CIS code is checked in the last buffer, skip to next tuple + cis_offset += buf[0] + 2; + } + assert(cis_offset >= 0); + while (1) { + if (cis_offset < buffer_size) { + //A CIS code in the buffer, check it + if (buf[cis_offset] == CISTPL_CODE_END) { + *inout_cis_offset = cis_offset + 1; + return true; + } + } + if (cis_offset + 1 < buffer_size) { + cis_offset += buf[cis_offset+1] + 2; + } else { + break; + } + } + *inout_cis_offset = cis_offset; + return false; +} + +sdmmc_err_t sdmmc_io_get_cis_data(sdmmc_card_t* card, uint8_t* out_buffer, size_t buffer_size, size_t* inout_cis_size) +{ + sdmmc_err_t ret = SDMMC_OK; + uint32_t buf[CIS_GET_MINIMAL_SIZE / sizeof(uint32_t)]; + + /* Pointer to size is a mandatory parameter */ + assert(inout_cis_size); + + /* + * CIS region exist in 0x1000~0x17FFF of FUNC 0, get the start address of it + * from CCCR register. + */ + uint32_t addr; + ret = sdmmc_io_read_bytes(card, 0, 9, &addr, 3); + if (ret != SDMMC_OK) return ret; + //the sdmmc_io driver reads 4 bytes, the most significant byte is not the address. + addr &= 0xffffff; + if (addr < 0x1000 || addr > 0x17FFF) { + return SDMMC_ERR_INVALID_RESPONSE; + } + + /* + * To avoid reading too long, take the input value as limitation if + * existing. + */ + size_t max_reading = UINT32_MAX; + if (*inout_cis_size != 0) { + max_reading = *inout_cis_size; + } + + /* + * Parse the length while reading. If find the end tuple, or reaches the + * limitation, read no more and return both the data and the size already + * read. + */ + size_t buffer_offset = 0; + size_t cur_cis_offset = 0; + bool end_tuple_found = false; + do { + ret = sdmmc_io_read_bytes(card, 0, addr + buffer_offset, &buf, CIS_GET_MINIMAL_SIZE); + if (ret != SDMMC_OK) return ret; + + //calculate relative to the beginning of the buffer + int offset = cur_cis_offset - buffer_offset; + bool finish = check_tuples_in_buffer((uint8_t*) buf, CIS_GET_MINIMAL_SIZE, &offset); + + int remain_size = buffer_size - buffer_offset; + int copy_len; + if (finish) { + copy_len = TSD_MIN(offset, remain_size); + end_tuple_found = true; + } else { + copy_len = TSD_MIN(CIS_GET_MINIMAL_SIZE, remain_size); + } + if (copy_len > 0) { + memcpy(out_buffer + buffer_offset, buf, copy_len); + } + cur_cis_offset = buffer_offset + offset; + buffer_offset += CIS_GET_MINIMAL_SIZE; + } while (!end_tuple_found && buffer_offset < max_reading); + + if (end_tuple_found) { + *inout_cis_size = cur_cis_offset; + if (cur_cis_offset > buffer_size) { + return SDMMC_ERR_INVALID_SIZE; + } else { + return SDMMC_OK; + } + } else { + return SDMMC_ERR_NO_CARD; + } +} diff --git a/circuitpython/lib/sdmmc/sdmmc_mmc.c b/circuitpython/lib/sdmmc/sdmmc_mmc.c new file mode 100644 index 0000000..b77d7dd --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_mmc.c @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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 <assert.h> +#include <unistd.h> +#include "sdmmc_common.h" + +sdmmc_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card) +{ + int card_type; + sdmmc_err_t err = SDMMC_OK; + + uint32_t ext_csd[EXT_CSD_MMC_SIZE / sizeof(uint32_t)]; + + uint32_t sectors = 0; + + ESP_LOGD(TAG, "MMC version: %d", card->csd.mmc_ver); + if (card->csd.mmc_ver < MMC_CSD_MMCVER_4_0) { + return SDMMC_ERR_NOT_SUPPORTED; + } + + /* read EXT_CSD */ + err = sdmmc_mmc_send_ext_csd_data(card, ext_csd, EXT_CSD_MMC_SIZE); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: send_ext_csd_data error 0x%x", __func__, err); + return err; + } + card_type = ext_csd[EXT_CSD_CARD_TYPE / sizeof(uint32_t)]; + + card->is_ddr = 0; + if (card_type & EXT_CSD_CARD_TYPE_F_52M_1_8V) { + card->max_freq_khz = SDMMC_FREQ_52M; + if ((card->host.flags & SDMMC_HOST_FLAG_DDR) && + card->host.max_freq_khz >= SDMMC_FREQ_26M && + card->host.get_bus_width(card->host.slot) == 4) { + ESP_LOGD(TAG, "card and host support DDR mode"); + card->is_ddr = 1; + } + } else if (card_type & EXT_CSD_CARD_TYPE_F_52M) { + card->max_freq_khz = SDMMC_FREQ_52M; + } else if (card_type & EXT_CSD_CARD_TYPE_F_26M) { + card->max_freq_khz = SDMMC_FREQ_26M; + } else { + ESP_LOGW(TAG, "%s: unknown CARD_TYPE 0x%x", __func__, card_type); + } + /* For MMC cards, use speed value from EXT_CSD */ + card->csd.tr_speed = card->max_freq_khz * 1000; + ESP_LOGD(TAG, "MMC card type %d, max_freq_khz=%d, is_ddr=%d", card_type, card->max_freq_khz, card->is_ddr); + card->max_freq_khz = TSD_MIN(card->max_freq_khz, card->host.max_freq_khz); + + if (card->host.flags & SDMMC_HOST_FLAG_8BIT) { + card->ext_csd.power_class = ext_csd[((card->max_freq_khz > SDMMC_FREQ_26M) ? + EXT_CSD_PWR_CL_52_360 : EXT_CSD_PWR_CL_26_360) / sizeof(uint32_t)] >> 4; + card->log_bus_width = 3; + } else if (card->host.flags & SDMMC_HOST_FLAG_4BIT) { + card->ext_csd.power_class = ext_csd[((card->max_freq_khz > SDMMC_FREQ_26M) ? + EXT_CSD_PWR_CL_52_360 : EXT_CSD_PWR_CL_26_360) / sizeof(uint32_t)] & 0x0f; + card->log_bus_width = 2; + } else { + card->ext_csd.power_class = 0; //card must be able to do full rate at powerclass 0 in 1-bit mode + card->log_bus_width = 0; + } + + sectors = ext_csd[EXT_CSD_SEC_COUNT / sizeof(uint32_t)]; + + if (sectors > (2u * 1024 * 1024 * 1024) / 512) { + card->csd.capacity = sectors; + } + + return err; +} + +sdmmc_err_t sdmmc_init_mmc_bus_width(sdmmc_card_t* card) +{ + sdmmc_err_t err; + if (card->ext_csd.power_class != 0) { + err = sdmmc_mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_CLASS, card->ext_csd.power_class); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: can't change power class (%d bit), 0x%x" + , __func__, card->ext_csd.power_class, err); + return err; + } + } + + if (card->log_bus_width > 0) { + int csd_bus_width_value = EXT_CSD_BUS_WIDTH_1; + int bus_width = 1; + if (card->log_bus_width == 2) { + if (card->is_ddr) { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_4_DDR; + } else { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_4; + } + bus_width = 4; + } else if (card->log_bus_width == 3) { + if (card->is_ddr) { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_8_DDR; + } else { + csd_bus_width_value = EXT_CSD_BUS_WIDTH_8; + } + bus_width = 8; + } + err = sdmmc_mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_BUS_WIDTH, csd_bus_width_value); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: can't change bus width (%d bit), 0x%x", + __func__, bus_width, err); + (void) bus_width; + return err; + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_mmc_enable_hs_mode(sdmmc_card_t* card) +{ + sdmmc_err_t err; + if (card->max_freq_khz > SDMMC_FREQ_26M) { + /* switch to high speed timing */ + err = sdmmc_mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, EXT_CSD_HS_TIMING_HS); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: mmc_switch EXT_CSD_HS_TIMING_HS error 0x%x", + __func__, err); + return err; + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_mmc_decode_cid(int mmc_ver, sdmmc_response_t resp, sdmmc_cid_t* out_cid) +{ + if (mmc_ver == MMC_CSD_MMCVER_1_0 || + mmc_ver == MMC_CSD_MMCVER_1_4) { + out_cid->mfg_id = MMC_CID_MID_V1(resp); + out_cid->oem_id = 0; + MMC_CID_PNM_V1_CPY(resp, out_cid->name); + out_cid->revision = MMC_CID_REV_V1(resp); + out_cid->serial = MMC_CID_PSN_V1(resp); + out_cid->date = MMC_CID_MDT_V1(resp); + } else if (mmc_ver == MMC_CSD_MMCVER_2_0 || + mmc_ver == MMC_CSD_MMCVER_3_1 || + mmc_ver == MMC_CSD_MMCVER_4_0) { + out_cid->mfg_id = MMC_CID_MID_V2(resp); + out_cid->oem_id = MMC_CID_OID_V2(resp); + MMC_CID_PNM_V1_CPY(resp, out_cid->name); + out_cid->revision = 0; + out_cid->serial = MMC_CID_PSN_V1(resp); + out_cid->date = 0; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_mmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd) +{ + out_csd->csd_ver = MMC_CSD_CSDVER(response); + if (out_csd->csd_ver == MMC_CSD_CSDVER_1_0 || + out_csd->csd_ver == MMC_CSD_CSDVER_2_0 || + out_csd->csd_ver == MMC_CSD_CSDVER_EXT_CSD) { + out_csd->mmc_ver = MMC_CSD_MMCVER(response); + out_csd->capacity = MMC_CSD_CAPACITY(response); + out_csd->read_block_len = MMC_CSD_READ_BL_LEN(response); + } else { + ESP_LOGE(TAG, "unknown MMC CSD structure version 0x%x\n", out_csd->csd_ver); + return 1; + } + int read_bl_size = 1 << out_csd->read_block_len; + out_csd->sector_size = TSD_MIN(read_bl_size, 512); + if (out_csd->sector_size < read_bl_size) { + out_csd->capacity *= read_bl_size / out_csd->sector_size; + } + /* tr_speed will be determined when reading CXD */ + out_csd->tr_speed = 0; + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_mmc_send_ext_csd_data(sdmmc_card_t* card, void *out_data, size_t datalen) +{ + sdmmc_command_t cmd = { + .data = out_data, + .datalen = datalen, + .blklen = datalen, + .opcode = MMC_SEND_EXT_CSD, + .arg = 0, + .flags = SCF_CMD_ADTC | SCF_RSP_R1 | SCF_CMD_READ + }; + return sdmmc_send_cmd(card, &cmd); +} + +sdmmc_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8_t value) +{ + sdmmc_command_t cmd = { + .opcode = MMC_SWITCH, + .arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | (index << 16) | (value << 8) | set, + .flags = SCF_RSP_R1B | SCF_CMD_AC | SCF_WAIT_BUSY, + }; + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err == SDMMC_OK) { + //check response bit to see that switch was accepted + if (MMC_R1(cmd.response) & MMC_R1_SWITCH_ERROR) + err = SDMMC_ERR_INVALID_RESPONSE; + } + + return err; +} + +sdmmc_err_t sdmmc_init_mmc_check_csd(sdmmc_card_t* card) +{ + sdmmc_err_t err; + assert(card->is_mem == 1); + assert(card->rca != 0); + //The card will not respond to send_csd command in the transfer state. + //Deselect it first. + err = sdmmc_send_cmd_select_card(card, 0); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err); + return err; + } + + sdmmc_csd_t csd; + /* Get the contents of CSD register to verify the communication over CMD line + is OK. */ + err = sdmmc_send_cmd_send_csd(card, &csd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: send_csd returned 0x%x", __func__, err); + return err; + } + + //Select the card again + err = sdmmc_send_cmd_select_card(card, card->rca); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err); + return err; + } + return SDMMC_OK; +} diff --git a/circuitpython/lib/sdmmc/sdmmc_sd.c b/circuitpython/lib/sdmmc/sdmmc_sd.c new file mode 100644 index 0000000..b4391f2 --- /dev/null +++ b/circuitpython/lib/sdmmc/sdmmc_sd.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> + * Adaptations to ESP-IDF Copyright (c) 2016-2018 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 <assert.h> + +#include "sdmmc_common.h" + +sdmmc_err_t sdmmc_init_sd_if_cond(sdmmc_card_t* card) +{ + /* SEND_IF_COND (CMD8) command is used to identify SDHC/SDXC cards. + * SD v1 and non-SD cards will not respond to this command. + */ + uint32_t host_ocr = get_host_ocr(card->host.io_voltage); + sdmmc_err_t err = sdmmc_send_cmd_send_if_cond(card, host_ocr); + if (err == SDMMC_OK) { + ESP_LOGD(TAG, "SDHC/SDXC card"); + host_ocr |= SD_OCR_SDHC_CAP; + } else if (err == SDMMC_ERR_TIMEOUT) { + ESP_LOGD(TAG, "CMD8 timeout; not an SD v2.00 card"); + } else if (host_is_spi(card) && err == SDMMC_ERR_NOT_SUPPORTED) { + ESP_LOGD(TAG, "CMD8 rejected; not an SD v2.00 card"); + } else { + ESP_LOGE(TAG, "%s: send_if_cond (1) returned 0x%x", __func__, err); + return err; + } + card->ocr = host_ocr; + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_sd_blocklen(sdmmc_card_t* card) +{ + /* SDSC cards support configurable data block lengths. + * We don't use this feature and set the block length to 512 bytes, + * same as the block length for SDHC cards. + */ + if ((card->ocr & SD_OCR_SDHC_CAP) == 0) { + sdmmc_err_t err = sdmmc_send_cmd_set_blocklen(card, &card->csd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err); + return err; + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_sd_scr(sdmmc_card_t* card) +{ + sdmmc_err_t err; + /* Get the contents of SCR register: bus width and the version of SD spec + * supported by the card. + * In SD mode, this is the first command which uses D0 line. Errors at + * this step usually indicate connection issue or lack of pull-up resistor. + */ + err = sdmmc_send_cmd_send_scr(card, &card->scr); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: send_scr (1) returned 0x%x", __func__, err); + return err; + } + + if ((card->scr.bus_width & SCR_SD_BUS_WIDTHS_4BIT) + && (card->host.flags & SDMMC_HOST_FLAG_4BIT)) { + card->log_bus_width = 2; + } else { + card->log_bus_width = 0; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_sd_bus_width(sdmmc_card_t* card) +{ + int width = 1; + if (card->log_bus_width == 2) { + width = 4; + } else if (card->log_bus_width == 3) { + width = 8; + } + sdmmc_err_t err = sdmmc_send_cmd_set_bus_width(card, width); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "set_bus_width failed (0x%x)", err); + return err; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_sd_wait_data_ready(sdmmc_card_t* card) +{ + /* Wait for the card to be ready for data transfers */ + uint32_t status = 0; + uint32_t count = 0; + while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) { + // TODO: add some timeout here + sdmmc_err_t err = sdmmc_send_cmd_send_status(card, &status); + if (err != SDMMC_OK) { + return err; + } + if (++count % 16 == 0) { + ESP_LOGV(TAG, "waiting for card to become ready (%d)", count); + } + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card, + uint32_t mode, uint32_t group, uint32_t function, + sdmmc_switch_func_rsp_t* resp) +{ + if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 || + ((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) { + return SDMMC_ERR_NOT_SUPPORTED; + } + + if (group == 0 || + group > SD_SFUNC_GROUP_MAX || + function > SD_SFUNC_FUNC_MAX) { + return SDMMC_ERR_INVALID_ARG; + } + + if (mode > 1) { + return SDMMC_ERR_INVALID_ARG; + } + + uint32_t group_shift = (group - 1) << 2; + /* all functions which should not be affected are set to 0xf (no change) */ + uint32_t other_func_mask = (0x00ffffff & ~(0xf << group_shift)); + uint32_t func_val = (function << group_shift) | other_func_mask; + + sdmmc_command_t cmd = { + .opcode = MMC_SWITCH, + .flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1, + .blklen = sizeof(sdmmc_switch_func_rsp_t), + .data = resp->data, + .datalen = sizeof(sdmmc_switch_func_rsp_t), + .arg = (!!mode << 31) | func_val + }; + + sdmmc_err_t err = sdmmc_send_cmd(card, &cmd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err); + return err; + } + sdmmc_flip_byte_order(resp->data, sizeof(sdmmc_switch_func_rsp_t)); + uint32_t resp_ver = SD_SFUNC_VER(resp->data); + if (resp_ver == 0) { + /* busy response is never sent */ + } else if (resp_ver == 1) { + if (SD_SFUNC_BUSY(resp->data, group) & (1 << function)) { + ESP_LOGD(TAG, "%s: response indicates function %d:%d is busy", + __func__, group, function); + return SDMMC_ERR_BUSY; + } + } else { + ESP_LOGD(TAG, "%s: got an invalid version of SWITCH_FUNC response: 0x%02x", + __func__, resp_ver); + return SDMMC_ERR_INVALID_RESPONSE; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card) +{ + /* This will determine if the card supports SWITCH_FUNC command, + * and high speed mode. If the cards supports both, this will enable + * high speed mode at the card side. + */ + if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 || + ((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) { + return SDMMC_ERR_NOT_SUPPORTED; + } + sdmmc_switch_func_rsp_t response; + + sdmmc_err_t err = sdmmc_send_cmd_switch_func(card, 0, SD_ACCESS_MODE, 0, &response); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (1) returned 0x%x", __func__, err); + return err; + } + uint32_t supported_mask = SD_SFUNC_SUPPORTED(response.data, 1); + if ((supported_mask & BIT(SD_ACCESS_MODE_SDR25)) == 0) { + return SDMMC_ERR_NOT_SUPPORTED; + } + err = sdmmc_send_cmd_switch_func(card, 1, SD_ACCESS_MODE, SD_ACCESS_MODE_SDR25, &response); + if (err != SDMMC_OK) { + ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (2) returned 0x%x", __func__, err); + return err; + } + + return err; +} + +sdmmc_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card) +{ + /* All cards should support at least default speed */ + card->max_freq_khz = SDMMC_FREQ_DEFAULT; + if (card->host.max_freq_khz <= card->max_freq_khz) { + /* Host is configured to use low frequency, don't attempt to switch */ + card->max_freq_khz = card->host.max_freq_khz; + return SDMMC_OK; + } + + /* Try to enabled HS mode */ + sdmmc_err_t err = sdmmc_enable_hs_mode(card); + if (err != SDMMC_OK) { + return err; + } + /* HS mode has been enabled on the card. + * Read CSD again, it should now indicate that the card supports + * 50MHz clock. + * Since SEND_CSD is allowed only in standby mode, and the card is currently in data transfer + * mode, deselect the card first, then get the CSD, then select the card again. This step is + * not required in SPI mode, since CMD7 (select_card) is not supported. + */ + const bool is_spi = host_is_spi(card); + if (!is_spi) { + err = sdmmc_send_cmd_select_card(card, 0); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: select_card (1) returned 0x%x", __func__, err); + return err; + } + } + err = sdmmc_send_cmd_send_csd(card, &card->csd); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: send_csd returned 0x%x", __func__, err); + return err; + } + if (!is_spi) { + err = sdmmc_send_cmd_select_card(card, card->rca); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: select_card (2) returned 0x%x", __func__, err); + return err; + } + } + + if (card->csd.tr_speed != 50000000) { + ESP_LOGW(TAG, "unexpected: after enabling HS mode, tr_speed=%d", card->csd.tr_speed); + return SDMMC_ERR_NOT_SUPPORTED; + } + + card->max_freq_khz = SDMMC_FREQ_HIGHSPEED; + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_check_scr(sdmmc_card_t* card) +{ + /* If frequency switch has been performed, read SCR register one more time + * and compare the result with the previous one. Use this simple check as + * an indicator of potential signal integrity issues. + */ + sdmmc_scr_t scr_tmp; + sdmmc_err_t err = sdmmc_send_cmd_send_scr(card, &scr_tmp); + if (err != SDMMC_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, "got corrupted data after increasing clock frequency"); + return SDMMC_ERR_INVALID_RESPONSE; + } + return SDMMC_OK; +} + +sdmmc_err_t sdmmc_init_spi_crc(sdmmc_card_t* card) +{ + /* In SD mode, CRC checks of data transfers are mandatory and performed + * by the hardware. In SPI mode, CRC16 of data transfers is optional and + * needs to be enabled. + */ + assert(host_is_spi(card)); + sdmmc_err_t err = sdmmc_send_cmd_crc_on_off(card, true); + if (err != SDMMC_OK) { + ESP_LOGE(TAG, "%s: sdmmc_send_cmd_crc_on_off returned 0x%x", __func__, err); + return err; + } + return SDMMC_OK; +} + +sdmmc_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 SDMMC_OK; +} + +sdmmc_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 SDMMC_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 = TSD_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 SDMMC_OK; +} + +sdmmc_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr) +{ + sdmmc_response_t resp = { 0 }; + resp[1] = __builtin_bswap32(raw_scr[0]); + resp[0] = __builtin_bswap32(raw_scr[1]); + int ver = SCR_STRUCTURE(resp); + if (ver != 0) { + return SDMMC_ERR_NOT_SUPPORTED; + } + out_scr->sd_spec = SCR_SD_SPEC(resp); + out_scr->bus_width = SCR_SD_BUS_WIDTHS(resp); + return SDMMC_OK; +} diff --git a/circuitpython/lib/sdmmc/test/CMakeLists.txt b/circuitpython/lib/sdmmc/test/CMakeLists.txt new file mode 100644 index 0000000..d6d5859 --- /dev/null +++ b/circuitpython/lib/sdmmc/test/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS "." + PRIV_REQUIRES cmock sdmmc + ) diff --git a/circuitpython/lib/sdmmc/test/component.mk b/circuitpython/lib/sdmmc/test/component.mk new file mode 100644 index 0000000..ce464a2 --- /dev/null +++ b/circuitpython/lib/sdmmc/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/circuitpython/lib/sdmmc/test/test_sd.c b/circuitpython/lib/sdmmc/test/test_sd.c new file mode 100644 index 0000000..fd7051c --- /dev/null +++ b/circuitpython/lib/sdmmc/test/test_sd.c @@ -0,0 +1,584 @@ +// 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/time.h> +#include <unistd.h> +#include "unity.h" +#include "driver/gpio.h" +#include "soc/soc_caps.h" +#if SOC_SDMMC_HOST_SUPPORTED +#include "driver/sdmmc_host.h" +#endif +#include "driver/sdspi_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "esp_rom_gpio.h" + +// Can't test eMMC (slot 0) and PSRAM together +#ifndef CONFIG_SPIRAM +#define WITH_EMMC_TEST +#endif + +/* power supply enable pin */ +#define SD_TEST_BOARD_VSEL_EN_GPIO 27 + +/* power supply voltage select pin */ +#define SD_TEST_BOARD_VSEL_GPIO 26 +#define SD_TEST_BOARD_VSEL_3V3 1 +#define SD_TEST_BOARD_VSEL_1V8 0 + +#define TEST_SDSPI_DMACHAN 1 + +/* time to wait for reset / power-on */ +#define SD_TEST_BOARD_PWR_RST_DELAY_MS 5 +#define SD_TEST_BOARD_PWR_ON_DELAY_MS 50 + +/* gpio which is not connected to actual CD pin, used to simulate CD behavior */ +#define CD_WP_TEST_GPIO 18 + + +__attribute__((unused)) static void sd_test_board_power_on(void) +{ + gpio_set_direction(SD_TEST_BOARD_VSEL_GPIO, GPIO_MODE_OUTPUT); + gpio_set_level(SD_TEST_BOARD_VSEL_GPIO, SD_TEST_BOARD_VSEL_3V3); + gpio_set_direction(SD_TEST_BOARD_VSEL_EN_GPIO, GPIO_MODE_OUTPUT); + gpio_set_level(SD_TEST_BOARD_VSEL_EN_GPIO, 0); + usleep(SD_TEST_BOARD_PWR_RST_DELAY_MS * 1000); + gpio_set_level(SD_TEST_BOARD_VSEL_EN_GPIO, 1); + usleep(SD_TEST_BOARD_PWR_ON_DELAY_MS * 1000); +} + +__attribute__((unused)) static void sd_test_board_power_off(void) +{ + gpio_set_level(SD_TEST_BOARD_VSEL_EN_GPIO, 0); + gpio_set_direction(SD_TEST_BOARD_VSEL_GPIO, GPIO_MODE_INPUT); + gpio_set_level(SD_TEST_BOARD_VSEL_GPIO, 0); + gpio_set_direction(SD_TEST_BOARD_VSEL_EN_GPIO, GPIO_MODE_INPUT); +} + +TEST_CASE("MMC_RSP_BITS", "[sd]") +{ + uint32_t data[2] = { 0x01234567, 0x89abcdef }; + TEST_ASSERT_EQUAL_HEX32(0x7, MMC_RSP_BITS(data, 0, 4)); + TEST_ASSERT_EQUAL_HEX32(0x567, MMC_RSP_BITS(data, 0, 12)); + TEST_ASSERT_EQUAL_HEX32(0xf0, MMC_RSP_BITS(data, 28, 8)); + TEST_ASSERT_EQUAL_HEX32(0x3, MMC_RSP_BITS(data, 1, 3)); + TEST_ASSERT_EQUAL_HEX32(0x11, MMC_RSP_BITS(data, 59, 5)); +} + +#if SOC_SDMMC_HOST_SUPPORTED + +static void probe_sd(int slot, int width, int freq_khz, int ddr) +{ + sd_test_board_power_on(); + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.slot = slot; + config.max_freq_khz = freq_khz; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + if (width == 1) { + config.flags = SDMMC_HOST_FLAG_1BIT; + slot_config.width = 1; + } else if (width == 4) { + config.flags &= ~SDMMC_HOST_FLAG_8BIT; + slot_config.width = 4; + } else { + assert(!ddr && "host driver does not support 8-line DDR mode yet"); + } + if (!ddr) { + config.flags &= ~SDMMC_HOST_FLAG_DDR; + } + TEST_ESP_OK(sdmmc_host_init()); + TEST_ESP_OK(sdmmc_host_init_slot(slot, &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); + uint8_t* buffer = heap_caps_malloc(512, MALLOC_CAP_DMA); + TEST_ESP_OK(sdmmc_read_sectors(card, buffer, 0, 1)); + free(buffer); + TEST_ESP_OK(sdmmc_host_deinit()); + free(card); + sd_test_board_power_off(); +} + +TEST_CASE("probe SD, slot 1, 4-bit", "[sd][test_env=UT_T1_SDMODE]") +{ + probe_sd(SDMMC_HOST_SLOT_1, 4, SDMMC_FREQ_PROBING, 0); + probe_sd(SDMMC_HOST_SLOT_1, 4, SDMMC_FREQ_DEFAULT, 0); + probe_sd(SDMMC_HOST_SLOT_1, 4, SDMMC_FREQ_HIGHSPEED, 0); +} + +TEST_CASE("probe SD, slot 1, 1-bit", "[sd][test_env=UT_T1_SDMODE]") +{ + probe_sd(SDMMC_HOST_SLOT_1, 1, SDMMC_FREQ_PROBING, 0); + probe_sd(SDMMC_HOST_SLOT_1, 1, SDMMC_FREQ_DEFAULT, 0); + probe_sd(SDMMC_HOST_SLOT_1, 1, SDMMC_FREQ_HIGHSPEED, 0); +} + +#ifdef WITH_EMMC_TEST +TEST_CASE("probe eMMC, slot 0, 4-bit, DDR", "[sd][test_env=EMMC]") +{ + probe_sd(SDMMC_HOST_SLOT_0, 4, SDMMC_FREQ_HIGHSPEED, 1); +} + +TEST_CASE("probe eMMC, slot 0, 8-bit", "[sd][test_env=EMMC]") +{ + probe_sd(SDMMC_HOST_SLOT_0, 8, SDMMC_FREQ_PROBING, 0); + probe_sd(SDMMC_HOST_SLOT_0, 8, SDMMC_FREQ_DEFAULT, 0); + probe_sd(SDMMC_HOST_SLOT_0, 8, SDMMC_FREQ_HIGHSPEED, 0); +} +#endif // WITH_EMMC_TEST + +TEST_CASE("probe SD, slot 0, 4-bit", "[sd][test_env=UT_T1_SDCARD][ignore]") +{ + probe_sd(SDMMC_HOST_SLOT_0, 4, SDMMC_FREQ_PROBING, 0); + probe_sd(SDMMC_HOST_SLOT_0, 4, SDMMC_FREQ_DEFAULT, 0); + probe_sd(SDMMC_HOST_SLOT_0, 4, SDMMC_FREQ_HIGHSPEED, 0); +} + +TEST_CASE("probe SD, slot 0, 1-bit", "[sd][test_env=UT_T1_SDCARD][ignore]") +{ + probe_sd(SDMMC_HOST_SLOT_0, 1, SDMMC_FREQ_PROBING, 0); + probe_sd(SDMMC_HOST_SLOT_0, 1, SDMMC_FREQ_DEFAULT, 0); + probe_sd(SDMMC_HOST_SLOT_0, 1, SDMMC_FREQ_HIGHSPEED, 0); +} + +#endif + +#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) +//No runners +static void test_sdspi_init_bus(spi_host_device_t host, int mosi_pin, int miso_pin, int clk_pin, int dma_chan) +{ + spi_bus_config_t bus_config = { + .mosi_io_num = mosi_pin, + .miso_io_num = miso_pin, + .sclk_io_num = clk_pin, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + esp_err_t err = spi_bus_initialize(host, &bus_config, dma_chan); + TEST_ESP_OK(err); +} + +static void test_sdspi_deinit_bus(spi_host_device_t host) +{ + esp_err_t err = spi_bus_free(host); + TEST_ESP_OK(err); +} + +static void probe_core(int slot) +{ + sdmmc_host_t config = SDSPI_HOST_DEFAULT(); + config.slot = slot; + + 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); + free(card); +} + +static void probe_spi(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int pin_cs) +{ + sd_test_board_power_on(); + + sdspi_dev_handle_t handle; + sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + dev_config.gpio_cs = pin_cs; + test_sdspi_init_bus(dev_config.host_id, pin_mosi, pin_miso, pin_sck, TEST_SDSPI_DMACHAN); + TEST_ESP_OK(sdspi_host_init()); + TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle)); + + probe_core(handle); + + TEST_ESP_OK(sdspi_host_deinit()); + test_sdspi_deinit_bus(dev_config.host_id); + sd_test_board_power_off(); +} + +static void probe_spi_legacy(int freq_khz, int pin_miso, int pin_mosi, int pin_sck, int pin_cs) +{ + sd_test_board_power_on(); + sdmmc_host_t config = SDSPI_HOST_DEFAULT(); + sdspi_slot_config_t slot_config = SDSPI_SLOT_CONFIG_DEFAULT(); + slot_config.gpio_miso = pin_miso; + slot_config.gpio_mosi = pin_mosi; + slot_config.gpio_sck = pin_sck; + slot_config.gpio_cs = pin_cs; + + TEST_ESP_OK(sdspi_host_init()); + TEST_ESP_OK(sdspi_host_init_slot(config.slot, &slot_config)); + + probe_core(config.slot); + + TEST_ESP_OK(sdspi_host_deinit()); + sd_test_board_power_off(); +} + +TEST_CASE("probe SD in SPI mode, slot 1", "[sd][test_env=UT_T1_SPIMODE]") +{ + probe_spi(SDMMC_FREQ_DEFAULT, 2, 15, 14, 13); + probe_spi_legacy(SDMMC_FREQ_DEFAULT, 2, 15, 14, 13); +} + +TEST_CASE("probe SD in SPI mode, slot 0", "[sd][test_env=UT_T1_SDCARD][ignore]") +{ + probe_spi(SDMMC_FREQ_DEFAULT, 7, 11, 6, 10); + probe_spi_legacy(SDMMC_FREQ_DEFAULT, 7, 11, 6, 10); +} + +#endif //DISABLED(ESP32S2) + +// Fill buffer pointed to by 'dst' with 'count' 32-bit ints generated +// from 'rand' with the starting value of 'seed' +__attribute__((unused)) static void fill_buffer(uint32_t seed, uint8_t* dst, size_t count) { + srand(seed); + for (size_t i = 0; i < count; ++i) { + uint32_t val = rand(); + memcpy(dst + i * sizeof(uint32_t), &val, sizeof(val)); + } +} + +// Check if the buffer pointed to by 'dst' contains 'count' 32-bit +// ints generated from 'rand' with the starting value of 'seed' +__attribute__((unused)) static void check_buffer(uint32_t seed, const uint8_t* src, size_t count) { + srand(seed); + for (size_t i = 0; i < count; ++i) { + uint32_t val; + memcpy(&val, src + i * sizeof(uint32_t), sizeof(val)); + TEST_ASSERT_EQUAL_HEX32(rand(), val); + } +} + +__attribute__((unused)) static void do_single_write_read_test(sdmmc_card_t* card, + size_t start_block, size_t block_count, size_t alignment) +{ + size_t block_size = card->csd.sector_size; + size_t total_size = block_size * block_count; + printf(" %8d | %3d | %d | %4.1f ", start_block, block_count, alignment, total_size / 1024.0f); + + uint32_t* buffer = heap_caps_malloc(total_size + 4, MALLOC_CAP_DMA); + size_t offset = alignment % 4; + uint8_t* c_buffer = (uint8_t*) buffer + offset; + fill_buffer(start_block, c_buffer, total_size / sizeof(buffer[0])); + + struct timeval t_start_wr; + gettimeofday(&t_start_wr, NULL); + TEST_ESP_OK(sdmmc_write_sectors(card, c_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 + 4); + + struct timeval t_start_rd; + gettimeofday(&t_start_rd, NULL); + TEST_ESP_OK(sdmmc_read_sectors(card, c_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 | %5.2f | %6.2f | %5.2f\n", + time_wr, total_size / (time_wr / 1000) / (1024 * 1024), + time_rd, total_size / (time_rd / 1000) / (1024 * 1024)); + check_buffer(start_block, c_buffer, total_size / sizeof(buffer[0])); + free(buffer); +} + +__attribute__((unused)) static void read_write_test(sdmmc_card_t* card) +{ + sdmmc_card_print_info(stdout, card); + printf(" sector | count | align | 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, 4); + do_single_write_read_test(card, 0, 4, 4); + do_single_write_read_test(card, 1, 16, 4); + do_single_write_read_test(card, 16, 32, 4); + do_single_write_read_test(card, 48, 64, 4); + do_single_write_read_test(card, 128, 128, 4); + do_single_write_read_test(card, card->csd.capacity - 64, 32, 4); + do_single_write_read_test(card, card->csd.capacity - 64, 64, 4); + do_single_write_read_test(card, card->csd.capacity - 8, 1, 4); + do_single_write_read_test(card, card->csd.capacity/2, 1, 4); + do_single_write_read_test(card, card->csd.capacity/2, 4, 4); + do_single_write_read_test(card, card->csd.capacity/2, 8, 4); + do_single_write_read_test(card, card->csd.capacity/2, 16, 4); + do_single_write_read_test(card, card->csd.capacity/2, 32, 4); + do_single_write_read_test(card, card->csd.capacity/2, 64, 4); + do_single_write_read_test(card, card->csd.capacity/2, 128, 4); + do_single_write_read_test(card, card->csd.capacity/2, 1, 1); + do_single_write_read_test(card, card->csd.capacity/2, 8, 1); + do_single_write_read_test(card, card->csd.capacity/2, 128, 1); +} + +#if SOC_SDMMC_HOST_SUPPORTED +void test_sd_rw_blocks(int slot, int width) +{ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + config.slot = slot; + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + if (width != 0) { + slot_config.width = width; + } + if (slot_config.width == 8) { + config.flags &= ~SDMMC_HOST_FLAG_DDR; + } + TEST_ESP_OK(sdmmc_host_init()); + TEST_ESP_OK(sdmmc_host_init_slot(slot, &slot_config)); + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + TEST_ESP_OK(sdmmc_card_init(&config, card)); + read_write_test(card); + free(card); + TEST_ESP_OK(sdmmc_host_deinit()); +} + +TEST_CASE("SDMMC read/write test (SD slot 1)", "[sd][test_env=UT_T1_SDMODE]") +{ + sd_test_board_power_on(); + test_sd_rw_blocks(1, 4); + sd_test_board_power_off(); +} + +#ifdef WITH_EMMC_TEST +TEST_CASE("SDMMC read/write test (eMMC slot 0, 4 line DDR)", "[sd][test_env=EMMC]") +{ + sd_test_board_power_on(); + test_sd_rw_blocks(0, 4); + sd_test_board_power_off(); +} + +TEST_CASE("SDMMC read/write test (eMMC slot 0, 8 line)", "[sd][test_env=EMMC]") +{ + sd_test_board_power_on(); + test_sd_rw_blocks(0, 8); + sd_test_board_power_off(); +} +#endif // WITH_EMMC_TEST +#endif // SDMMC_HOST_SUPPORTED + +#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) +//No runners +TEST_CASE("SDMMC read/write test (SD slot 1, in SPI mode)", "[sdspi][test_env=UT_T1_SPIMODE]") +{ + sd_test_board_power_on(); + + sdspi_dev_handle_t handle; + sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + test_sdspi_init_bus(dev_config.host_id, GPIO_NUM_15, GPIO_NUM_2, GPIO_NUM_14, TEST_SDSPI_DMACHAN); + TEST_ESP_OK(sdspi_host_init()); + TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle)); + + sdmmc_host_t config = SDSPI_HOST_DEFAULT(); + config.slot = handle; + // This test can only run under 20MHz on ESP32, because the runner connects the card to + // non-IOMUX pins of HSPI. + + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + TEST_ESP_OK(sdmmc_card_init(&config, card)); + read_write_test(card); + TEST_ESP_OK(sdspi_host_deinit()); + free(card); + test_sdspi_deinit_bus(dev_config.host_id); + sd_test_board_power_off(); +} +#endif //DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) + +#if SOC_SDMMC_HOST_SUPPORTED +TEST_CASE("reads and writes with an unaligned buffer", "[sd][test_env=UT_T1_SDMODE]") +{ + sd_test_board_power_on(); + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + TEST_ESP_OK(sdmmc_host_init()); + + TEST_ESP_OK(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)); + + const size_t buffer_size = 4096; + const size_t block_count = buffer_size / 512; + const size_t extra = 4; + uint8_t* buffer = heap_caps_malloc(buffer_size + extra, MALLOC_CAP_DMA); + + // Check read behavior: do aligned write, then unaligned read + const uint32_t seed = 0x89abcdef; + fill_buffer(seed, buffer, buffer_size / sizeof(uint32_t)); + TEST_ESP_OK(sdmmc_write_sectors(card, buffer, 0, block_count)); + memset(buffer, 0xcc, buffer_size + extra); + TEST_ESP_OK(sdmmc_read_sectors(card, buffer + 1, 0, block_count)); + check_buffer(seed, buffer + 1, buffer_size / sizeof(uint32_t)); + + // Check write behavior: do unaligned write, then aligned read + fill_buffer(seed, buffer + 1, buffer_size / sizeof(uint32_t)); + TEST_ESP_OK(sdmmc_write_sectors(card, buffer + 1, 8, block_count)); + memset(buffer, 0xcc, buffer_size + extra); + TEST_ESP_OK(sdmmc_read_sectors(card, buffer, 8, block_count)); + check_buffer(seed, buffer, buffer_size / sizeof(uint32_t)); + + free(buffer); + free(card); + TEST_ESP_OK(sdmmc_host_deinit()); + sd_test_board_power_off(); +} +#endif + +__attribute__((unused)) static void test_cd_input(int gpio_cd_num, const sdmmc_host_t* config) +{ + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + + // SDMMC host should have configured CD as input. + // Enable output as well (not using the driver, to avoid touching input + // enable bits). + esp_rom_gpio_connect_out_signal(gpio_cd_num, SIG_GPIO_OUT_IDX, false, false); + REG_WRITE(GPIO_ENABLE_W1TS_REG, BIT(gpio_cd_num)); + + // Check that card initialization fails if CD is high + REG_WRITE(GPIO_OUT_W1TS_REG, BIT(gpio_cd_num)); + usleep(1000); + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, sdmmc_card_init(config, card)); + + // Check that card initialization succeeds if CD is low + REG_WRITE(GPIO_OUT_W1TC_REG, BIT(gpio_cd_num)); + usleep(1000); + TEST_ESP_OK(sdmmc_card_init(config, card)); + + free(card); +} + +#if SOC_SDMMC_HOST_SUPPORTED +TEST_CASE("CD input works in SD mode", "[sd][test_env=UT_T1_SDMODE]") +{ + sd_test_board_power_on(); + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.gpio_cd = CD_WP_TEST_GPIO; + TEST_ESP_OK(sdmmc_host_init()); + TEST_ESP_OK(sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config)); + + test_cd_input(CD_WP_TEST_GPIO, &config); + + TEST_ESP_OK(sdmmc_host_deinit()); + sd_test_board_power_off(); +} +#endif + +#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) +//No runners +TEST_CASE("CD input works in SPI mode", "[sd][test_env=UT_T1_SPIMODE]") +{ + sd_test_board_power_on(); + + sdspi_dev_handle_t handle; + sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + dev_config.gpio_cd = CD_WP_TEST_GPIO; + test_sdspi_init_bus(dev_config.host_id, GPIO_NUM_15, GPIO_NUM_2, GPIO_NUM_14, TEST_SDSPI_DMACHAN); + TEST_ESP_OK(sdspi_host_init()); + TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle)); + + sdmmc_host_t config = SDSPI_HOST_DEFAULT(); + config.slot = handle; + + test_cd_input(CD_WP_TEST_GPIO, &config); + + TEST_ESP_OK(sdspi_host_deinit()); + test_sdspi_deinit_bus(dev_config.host_id); + sd_test_board_power_off(); +} +#endif //DISABLED_FOR_TARGETS(ESP32S2) + +__attribute__((unused)) static void test_wp_input(int gpio_wp_num, const sdmmc_host_t* config) +{ + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + + // SDMMC host should have configured WP as input. + // Enable output as well (not using the driver, to avoid touching input + // enable bits). + esp_rom_gpio_connect_out_signal(gpio_wp_num, SIG_GPIO_OUT_IDX, false, false); + REG_WRITE(GPIO_ENABLE_W1TS_REG, BIT(gpio_wp_num)); + + // Check that the card can be initialized with WP low + REG_WRITE(GPIO_OUT_W1TC_REG, BIT(gpio_wp_num)); + TEST_ESP_OK(sdmmc_card_init(config, card)); + + uint32_t* data = heap_caps_calloc(1, 512, MALLOC_CAP_DMA); + + // Check that card write succeeds if WP is high + REG_WRITE(GPIO_OUT_W1TS_REG, BIT(gpio_wp_num)); + usleep(1000); + TEST_ESP_OK(sdmmc_write_sectors(card, &data, 0, 1)); + + // Check that write fails if WP is low + REG_WRITE(GPIO_OUT_W1TC_REG, BIT(gpio_wp_num)); + usleep(1000); + TEST_ESP_ERR(ESP_ERR_INVALID_STATE, sdmmc_write_sectors(card, &data, 0, 1)); + // ...but reads still work + TEST_ESP_OK(sdmmc_read_sectors(card, &data, 0, 1)); + + free(data); + free(card); +} + +#if SOC_SDMMC_HOST_SUPPORTED +TEST_CASE("WP input works in SD mode", "[sd][test_env=UT_T1_SDMODE]") +{ + sd_test_board_power_on(); + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.gpio_wp = CD_WP_TEST_GPIO; + TEST_ESP_OK(sdmmc_host_init()); + TEST_ESP_OK(sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config)); + + test_wp_input(CD_WP_TEST_GPIO, &config); + + TEST_ESP_OK(sdmmc_host_deinit()); + sd_test_board_power_off(); +} +#endif + +#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32C3) +//No runners +TEST_CASE("WP input works in SPI mode", "[sd][test_env=UT_T1_SPIMODE]") +{ + sd_test_board_power_on(); + + sdspi_dev_handle_t handle; + sdspi_device_config_t dev_config = SDSPI_DEVICE_CONFIG_DEFAULT(); + dev_config.gpio_wp = CD_WP_TEST_GPIO; + test_sdspi_init_bus(dev_config.host_id, GPIO_NUM_15, GPIO_NUM_2, GPIO_NUM_14, TEST_SDSPI_DMACHAN); + + TEST_ESP_OK(sdspi_host_init()); + TEST_ESP_OK(sdspi_host_init_device(&dev_config, &handle)); + + sdmmc_host_t config = SDSPI_HOST_DEFAULT(); + config.slot = handle; + + test_wp_input(CD_WP_TEST_GPIO, &config); + + TEST_ESP_OK(sdspi_host_deinit()); + test_sdspi_deinit_bus(dev_config.host_id); + sd_test_board_power_off(); +} +#endif //DISABLED_FOR_TARGETS(ESP32S2) diff --git a/circuitpython/lib/sdmmc/test/test_sdio.c b/circuitpython/lib/sdmmc/test/test_sdio.c new file mode 100644 index 0000000..ecafb16 --- /dev/null +++ b/circuitpython/lib/sdmmc/test/test_sdio.c @@ -0,0 +1,388 @@ +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "soc/soc_caps.h" +#if SOC_SDMMC_HOST_SUPPORTED + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/sdmmc_host.h" +#include "driver/sdmmc_defs.h" +#include "sdmmc_cmd.h" +#include "unity.h" + +/* Second ESP32 board attached as follows: + * Master Slave + * IO18 EN + * IO19 IO0 + * IO14 SD_CLK + * IO15 SD_CMD + * IO2 SD_D0 + * IO4 SD_D1 + * IO12 SD_D2 + * IO13 SD_D3 + */ + + +/* TODO: add SDIO slave header files, remove these definitions */ + +#define DR_REG_SLC_MASK 0xfffffc00 + +#define SLCCONF1 (DR_REG_SLC_BASE + 0x60) +#define SLC_SLC0_RX_STITCH_EN (BIT(6)) +#define SLC_SLC0_TX_STITCH_EN (BIT(5)) + +#define SLC0TX_LINK (DR_REG_SLC_BASE + 0x40) +#define SLC_SLC0_TXLINK_PARK (BIT(31)) +#define SLC_SLC0_TXLINK_RESTART (BIT(30)) +#define SLC_SLC0_TXLINK_START (BIT(29)) + +#define DR_REG_SLCHOST_MASK 0xfffffc00 +#define SLCHOST_STATE_W0 (DR_REG_SLCHOST_BASE + 0x64) +#define SLCHOST_CONF_W0 (DR_REG_SLCHOST_BASE + 0x6C) +#define SLCHOST_CONF_W5 (DR_REG_SLCHOST_BASE + 0x80) +#define SLCHOST_WIN_CMD (DR_REG_SLCHOST_BASE + 0x84) + +#define SLC_WIN_CMD_READ 0x80 +#define SLC_WIN_CMD_WRITE 0xC0 +#define SLC_WIN_CMD_S 8 + +#define SLC_THRESHOLD_ADDR 0x1f800 + +static const char* TAG = "sdio_test"; + +static esp_err_t slave_slchost_reg_read(sdmmc_card_t* card, uint32_t addr, uint32_t* out_val) +{ + if ((addr & DR_REG_SLCHOST_MASK) != DR_REG_SLCHOST_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + return sdmmc_io_read_bytes(card, 1, addr & (~DR_REG_SLCHOST_MASK), out_val, sizeof(*out_val)); +} + +static esp_err_t slave_slchost_reg_write(sdmmc_card_t* card, uint32_t addr, uint32_t val) +{ + if ((addr & DR_REG_SLCHOST_MASK) != DR_REG_SLCHOST_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + return sdmmc_io_write_bytes(card, 1, addr & (~DR_REG_SLCHOST_MASK), &val, sizeof(val)); +} + +static esp_err_t slave_slc_reg_read(sdmmc_card_t* card, uint32_t addr, uint32_t* val) +{ + if ((addr & DR_REG_SLC_MASK) != DR_REG_SLC_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + uint32_t word = (addr - DR_REG_SLC_BASE) / 4; + if (word > INT8_MAX) { + return ESP_ERR_INVALID_ARG; + } + + uint32_t window_command = word | (SLC_WIN_CMD_READ << SLC_WIN_CMD_S); + esp_err_t err = slave_slchost_reg_write(card, SLCHOST_WIN_CMD, window_command); + if (err != ESP_OK) { + return err; + } + + return slave_slchost_reg_read(card, SLCHOST_STATE_W0, val); +} + +static esp_err_t slave_slc_reg_write(sdmmc_card_t* card, uint32_t addr, uint32_t val) +{ + if ((addr & DR_REG_SLC_MASK) != DR_REG_SLC_BASE) { + ESP_LOGW(TAG, "%s: invalid addr 0x%08x\n", __func__, addr); + return ESP_ERR_INVALID_ARG; + } + uint32_t word = (addr - DR_REG_SLC_BASE) / 4; + if (word > INT8_MAX) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = slave_slchost_reg_write(card, SLCHOST_CONF_W5, val); + if (err != ESP_OK) { + return err; + } + + uint32_t window_command = word | (SLC_WIN_CMD_WRITE << SLC_WIN_CMD_S); + return slave_slchost_reg_write(card, SLCHOST_WIN_CMD, window_command); +} + +/** Reset and put slave into download mode */ +static void reset_slave(void) +{ + const int pin_en = 18; + const int pin_io0 = 19; + gpio_config_t gpio_cfg = { + .pin_bit_mask = BIT64(pin_en) | BIT64(pin_io0), + .mode = GPIO_MODE_OUTPUT_OD, + }; + TEST_ESP_OK(gpio_config(&gpio_cfg)); + gpio_set_level(pin_en, 0); + gpio_set_level(pin_io0, 0); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(pin_en, 1); + vTaskDelay(10 / portTICK_PERIOD_MS); + gpio_set_level(pin_io0, 1); +} + +static void sdio_slave_common_init(sdmmc_card_t* card) +{ + uint8_t card_cap; + esp_err_t err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_CARD_CAP, &card_cap); + TEST_ESP_OK(err); + printf("CAP: 0x%02x\n", card_cap); + + uint8_t hs; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_HIGHSPEED, &hs); + TEST_ESP_OK(err); + printf("HS: 0x%02x\n", hs); + + +#define FUNC1_EN_MASK (BIT(1)) + + uint8_t ioe; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &ioe); + TEST_ESP_OK(err); + printf("IOE: 0x%02x\n", ioe); + + uint8_t ior = 0; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); + TEST_ESP_OK(err); + printf("IOR: 0x%02x\n", ior); + + // enable function 1 + ioe |= FUNC1_EN_MASK; + err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_FN_ENABLE, ioe, NULL); + TEST_ESP_OK(err); + + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_ENABLE, &ioe); + TEST_ESP_OK(err); + printf("IOE: 0x%02x\n", ioe); + + // wait for the card to become ready + while ( (ior & FUNC1_EN_MASK) == 0 ) { + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_FN_READY, &ior); + TEST_ESP_OK(err); + printf("IOR: 0x%02x\n", ior); + } + + // get interrupt status + uint8_t ie; + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_INT_ENABLE, &ie); + TEST_ESP_OK(err); + printf("IE: 0x%02x\n", ie); + + // enable interrupts for function 1&2 and master enable + ie |= BIT(0) | FUNC1_EN_MASK; + err = sdmmc_io_write_byte(card, 0, SD_IO_CCCR_INT_ENABLE, ie, NULL); + TEST_ESP_OK(err); + + err = sdmmc_io_read_byte(card, 0, SD_IO_CCCR_INT_ENABLE, &ie); + TEST_ESP_OK(err); + printf("IE: 0x%02x\n", ie); +} + +/** Common for all SDIO devices, set block size for specific function */ +static void sdio_slave_set_blocksize(sdmmc_card_t* card, int function, uint16_t bs) +{ + const uint8_t* bs_u8 = (const uint8_t*) &bs; + uint16_t bs_read = 0; + uint8_t* bs_read_u8 = (uint8_t*) &bs_read; + uint32_t offset = SD_IO_FBR_START * function; + TEST_ESP_OK( sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, bs_u8[0], NULL)); + TEST_ESP_OK( sdmmc_io_write_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, bs_u8[1], NULL)); + TEST_ESP_OK( sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEL, &bs_read_u8[0])); + TEST_ESP_OK( sdmmc_io_read_byte(card, 0, offset + SD_IO_CCCR_BLKSIZEH, &bs_read_u8[1])); + TEST_ASSERT_EQUAL_HEX16(bs, bs_read); +} + +/** + * ESP32 ROM code does not set some SDIO slave registers to the defaults + * we need, this function clears/sets some bits. + */ +static void esp32_slave_init_extra(sdmmc_card_t* card) +{ + printf("Initialize some ESP32 SDIO slave registers\n"); + + uint32_t reg_val; + TEST_ESP_OK( slave_slc_reg_read(card, SLCCONF1, ®_val) ); + reg_val &= ~(SLC_SLC0_RX_STITCH_EN | SLC_SLC0_TX_STITCH_EN); + TEST_ESP_OK( slave_slc_reg_write(card, SLCCONF1, reg_val) ); + + TEST_ESP_OK( slave_slc_reg_read(card, SLC0TX_LINK, ®_val) ); + reg_val |= SLC_SLC0_TXLINK_START; + TEST_ESP_OK( slave_slc_reg_write(card, SLC0TX_LINK, reg_val) ); +} + +/** + * ESP32 bootloader implements "SIP" protocol which can be used to exchange + * some commands, events, and data packets between the host and the slave. + * This function sends a SIP command, testing CMD53 block writes along the way. + */ +static void esp32_send_sip_command(sdmmc_card_t* card) +{ + printf("Test block write using CMD53\n"); + const size_t block_size = 512; + uint8_t* data = heap_caps_calloc(1, block_size, MALLOC_CAP_DMA); + struct sip_cmd_bootup { + uint32_t boot_addr; + uint32_t discard_link; + }; + struct sip_cmd_write_reg { + uint32_t addr; + uint32_t val; + }; + struct sip_hdr { + uint8_t fc[2]; + uint16_t len; + uint32_t cmdid; + uint32_t seq; + }; + + struct sip_hdr* hdr = (struct sip_hdr*) data; + size_t len; + +#define SEND_WRITE_REG_CMD + +#ifdef SEND_WRITE_REG_CMD + struct sip_cmd_write_reg *write_reg = (struct sip_cmd_write_reg*) (data + sizeof(*hdr)); + len = sizeof(*hdr) + sizeof(*write_reg); + hdr->cmdid = 3; /* SIP_CMD_WRITE_REG */ + write_reg->addr = GPIO_ENABLE_W1TS_REG; + write_reg->val = BIT(0) | BIT(2) | BIT(4); /* Turn of RGB LEDs on WROVER-KIT */ +#else + struct sip_cmd_bootup *bootup = (struct sip_cmd_bootup*) (data + sizeof(*hdr)); + len = sizeof(*hdr) + sizeof(*bootup); + hdr->cmdid = 5; /* SIP_CMD_BOOTUP */ + bootup->boot_addr = 0x4005a980; /* start_tb_console function in ROM */ + bootup->discard_link = 1; +#endif + hdr->len = len; + + TEST_ESP_OK( sdmmc_io_write_blocks(card, 1, SLC_THRESHOLD_ADDR - len, data, block_size) ); + free(data); +} + +static void test_cmd52_read_write_single_byte(sdmmc_card_t* card) +{ + esp_err_t err; + printf("Write bytes to slave's W0_REG using CMD52\n"); + const size_t scratch_area_reg = SLCHOST_CONF_W0 - DR_REG_SLCHOST_BASE; + + const uint8_t test_byte_1 = 0xa5; + const uint8_t test_byte_2 = 0xb6; + // used to check Read-After-Write + uint8_t test_byte_1_raw; + uint8_t test_byte_2_raw; + uint8_t val = 0; + err = sdmmc_io_write_byte(card, 1, scratch_area_reg, test_byte_1, &test_byte_1_raw); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_UINT8(test_byte_1, test_byte_1_raw); + err = sdmmc_io_write_byte(card, 1, scratch_area_reg + 1, test_byte_2, &test_byte_2_raw); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_UINT8(test_byte_2, test_byte_2_raw); + + printf("Read back bytes using CMD52\n"); + TEST_ESP_OK(sdmmc_io_read_byte(card, 1, scratch_area_reg, &val)); + TEST_ASSERT_EQUAL_UINT8(test_byte_1, val); + + TEST_ESP_OK(sdmmc_io_read_byte(card, 1, scratch_area_reg + 1, &val)); + TEST_ASSERT_EQUAL_UINT8(test_byte_2, val); +} + +static void test_cmd53_read_write_multiple_bytes(sdmmc_card_t* card, size_t n_bytes) +{ + printf("Write multiple bytes using CMD53\n"); + const size_t scratch_area_reg = SLCHOST_CONF_W0 - DR_REG_SLCHOST_BASE; + + uint8_t* src = heap_caps_malloc(512, MALLOC_CAP_DMA); + uint32_t* src_32 = (uint32_t*) src; + + for (size_t i = 0; i < (n_bytes + 3) / 4; ++i) { + src_32[i] = rand(); + } + + TEST_ESP_OK(sdmmc_io_write_bytes(card, 1, scratch_area_reg, src, n_bytes)); + ESP_LOG_BUFFER_HEX(TAG, src, n_bytes); + + printf("Read back using CMD52\n"); + uint8_t* dst = heap_caps_malloc(512, MALLOC_CAP_DMA); + for (size_t i = 0; i < n_bytes; ++i) { + TEST_ESP_OK(sdmmc_io_read_byte(card, 1, scratch_area_reg + i, &dst[i])); + } + ESP_LOG_BUFFER_HEX(TAG, dst, n_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(src, dst, n_bytes); + + printf("Read back using CMD53\n"); + TEST_ESP_OK(sdmmc_io_read_bytes(card, 1, scratch_area_reg, dst, n_bytes)); + ESP_LOG_BUFFER_HEX(TAG, dst, n_bytes); + TEST_ASSERT_EQUAL_UINT8_ARRAY(src, dst, n_bytes); + + free(src); + free(dst); +} + + +TEST_CASE("can probe and talk to ESP32 SDIO slave", "[sdio][ignore]") +{ + reset_slave(); + + /* Probe */ + sdmmc_host_t config = SDMMC_HOST_DEFAULT(); + config.flags = SDMMC_HOST_FLAG_1BIT; + config.max_freq_khz = SDMMC_FREQ_PROBING; + + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + (sdmmc_host_init()); + (sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config)); + sdmmc_card_t* card = malloc(sizeof(sdmmc_card_t)); + TEST_ASSERT_NOT_NULL(card); + TEST_ESP_OK(sdmmc_card_init(&config, card)); + sdmmc_card_print_info(stdout, card); + + /* Set up standard SDIO registers */ + sdio_slave_common_init(card); + + srand(0); + for (int repeat = 0; repeat < 4; ++repeat) { + test_cmd52_read_write_single_byte(card); + test_cmd53_read_write_multiple_bytes(card, 1); + test_cmd53_read_write_multiple_bytes(card, 2); + test_cmd53_read_write_multiple_bytes(card, 3); + test_cmd53_read_write_multiple_bytes(card, 4); + test_cmd53_read_write_multiple_bytes(card, 5); + test_cmd53_read_write_multiple_bytes(card, 23); + test_cmd53_read_write_multiple_bytes(card, 24); + } + + sdio_slave_set_blocksize(card, 0, 512); + sdio_slave_set_blocksize(card, 1, 512); + + esp32_slave_init_extra(card); + + esp32_send_sip_command(card); + + TEST_ESP_OK(sdmmc_host_deinit()); + free(card); +} + +#endif //SOC_SDMMC_HOST_SUPPORTED |