ATA-Trim support (#2781)

* Add files via upload

* Update main.cpp

* Update main.cpp

* Update main.cpp

* Update Helper.cpp

* Update Helper.h

* Update CMakeLists.txt

* Update CMakeLists.txt

* Update diskio_sdmmc_mh.c

* Update diskio_sdmmc_mh.h

* Update ff_mh.c

* Update vfs_fat_sdmmc_mh.c

* Update sdmmc_common_mh.h

* Update sdmmc_common_mh.c

* Update Helper.cpp

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update ff_mh.c

---------

Co-authored-by: CaCO3 <caco3@ruinelli.ch>
This commit is contained in:
michael
2024-01-02 08:56:46 +01:00
committed by GitHub
parent b5213b01af
commit 2ed6fb0f0d
39 changed files with 30756 additions and 44 deletions

View File

@@ -0,0 +1,11 @@
idf_component_register(SRCS "sdmmc_cmd_mh.c"
"sdmmc_common_mh.c"
"sdmmc_init_mh.c"
"sdmmc_io_mh.c"
"sdmmc_mmc_mh.c"
"sdmmc_sd_mh.c"
INCLUDE_DIRS "." "include"
REQUIRES driver esp-fatfs
PRIV_REQUIRES soc)
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

View File

@@ -0,0 +1,279 @@
# AIOTED related changes, see https://github.com/jomjol/AI-on-the-edge-device/pull/2781
These files/folders were copied from `framework-espidf@3.50002.230601/components/` and adapted to our own needs.
Since not every SD/MMC was recognized and this was due to the implementation of ATA trim support, this was revised.
Furthermore, files that we don't need were deleted from it.
## The most relevant changes are:
### fatfs/diskio/diskio_sdmmc.c
DRESULT ff_sdmmc_ioctl (BYTE pdrv, BYTE cmd, void* buff), at lines 106 to 110 changed from:
```c
#if FF_USE_TRIM
case CTRL_TRIM:
return ff_sdmmc_trim (pdrv, *((DWORD*)buff), //start_sector
(*((DWORD*)buff + 1) - *((DWORD*)buff) + 1)); //sector_count
#endif //FF_USE_TRIM
```
to:
```c
#if (FF_USE_TRIM)
case CTRL_TRIM:
if(FF_CAN_TRIM){
return ff_sdmmc_trim (pdrv, *((DWORD*)buff), //start_sector
(*((DWORD*)buff + 1) - *((DWORD*)buff) + 1)); //sector_count
}
else{
return RES_ERROR;
}
#endif //FF_USE_TRIM
```
### fatfs/src/ff.c
added:
```c
#include "sdmmc_cmd.h"
```
static FRESULT remove_chain(FFOBJID* obj, DWORD clst, DWORD pclst), at lines 1437 to 1454 changed from:
```c
#if FF_FS_EXFAT || FF_USE_TRIM
if (ecl + 1 == nxt) { /* Is next cluster contiguous? */
ecl = nxt;
} else { /* End of contiguous cluster block */
#if FF_FS_EXFAT
if (fs->fs_type == FS_EXFAT) {
res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */
if (res != FR_OK) return res;
}
#endif
#if FF_USE_TRIM
rt[0] = clst2sect(fs, scl); /* Start of data area to be freed */
rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area to be freed */
disk_ioctl(fs->pdrv, CTRL_TRIM, rt); /* Inform storage device that the data in the block may be erased */
#endif
scl = ecl = nxt;
}
#endif
```
to:
```c
#if FF_FS_EXFAT || FF_USE_TRIM
if(FF_FS_EXFAT || FF_CAN_TRIM){
if (ecl + 1 == nxt) { /* Is next cluster contiguous? */
ecl = nxt;
}
else { /* End of contiguous cluster block */
#if FF_FS_EXFAT
if (fs->fs_type == FS_EXFAT) {
res = change_bitmap(fs, scl, ecl - scl + 1, 0); /* Mark the cluster block 'free' on the bitmap */
if (res != FR_OK) return res;
}
#endif
#if FF_USE_TRIM
if(FF_CAN_TRIM){
rt[0] = clst2sect(fs, scl); /* Start of data area to be freed */
rt[1] = clst2sect(fs, ecl) + fs->csize - 1; /* End of data area to be freed */
disk_ioctl(fs->pdrv, CTRL_TRIM, rt); /* Inform storage device that the data in the block may be erased */
}
#endif
scl = ecl = nxt;
}
}
#endif
```
FRESULT f_mkfs(const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len), at lines 5946 to 5949 changed from:
```c
#if FF_USE_TRIM
lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */
disk_ioctl(pdrv, CTRL_TRIM, lba);
#endif
```
to:
```c
#if FF_USE_TRIM
if(FF_CAN_TRIM){
lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */
disk_ioctl(pdrv, CTRL_TRIM, lba);
}
#endif
```
FRESULT f_mkfs(const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len), at lines 6175 to 6178 changed from:
```c
#if FF_USE_TRIM
lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */
disk_ioctl(pdrv, CTRL_TRIM, lba);
#endif
```
to:
```c
#if FF_USE_TRIM
if(FF_CAN_TRIM){
lba[0] = b_vol; lba[1] = b_vol + sz_vol - 1; /* Inform storage device that the volume area may be erased */
disk_ioctl(pdrv, CTRL_TRIM, lba);
}
#endif
```
### sdmmc/sdmmc_cmd.c
added:
```c
int FF_CAN_TRIM = 0;
```
esp_err_t sdmmc_can_trim(sdmmc_card_t* card), at lines 630 to 636 changed from:
```c
esp_err_t sdmmc_can_trim(sdmmc_card_t* card)
{
if ((card->is_mmc) && (card->ext_csd.sec_feature & EXT_CSD_SEC_GB_CL_EN)) {
return ESP_OK;
}
return ESP_FAIL;
}
```
to:
```c
esp_err_t sdmmc_can_trim(sdmmc_card_t* card)
{
if ((card->is_mmc) && (card->ext_csd.sec_feature & EXT_CSD_SEC_GB_CL_EN)) {
FF_CAN_TRIM = 1;
return ESP_OK;
}
FF_CAN_TRIM = 0;
return ESP_FAIL;
}
```
### sdmmc/include/sdmmc_cmd.h
added:
```c
extern int FF_CAN_TRIM;
```
# Espressif IoT Development Framework
* [中文版](./README_CN.md)
ESP-IDF is the development framework for Espressif SoCs supported on Windows, Linux and macOS.
# ESP-IDF Release Support Schedule
![Support Schedule](https://dl.espressif.com/dl/esp-idf/support-periods.svg)
- Please read [the support policy](SUPPORT_POLICY.md) and [the documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/versions.html) for more information about ESP-IDF versions.
- Please see the [End-of-Life Advisories](https://www.espressif.com/en/support/documents/advisories?keys=&field_type_of_advisory_tid%5B%5D=817) for information about ESP-IDF releases with discontinued support.
# ESP-IDF Release and SoC Compatibility
The following table shows ESP-IDF support of Espressif SoCs where ![alt text][preview] and ![alt text][supported] denote preview status and support, respectively. The preview support is usually limited in time and intended for beta versions of chips. Please use an ESP-IDF release where the desired SoC is already supported.
|Chip | v4.1 | v4.2 | v4.3 | v4.4 | v5.0 | |
|:----------- |:---------------------:| :---------------------:| :---------------------:| :---------------------:| :---------------------:|:------------------------------------------------------------------------------------ |
|ESP32 |![alt text][supported] | ![alt text][supported] | ![alt text][supported] | ![alt text][supported] | ![alt text][supported] | |
|ESP32-S2 | | ![alt text][supported] | ![alt text][supported] | ![alt text][supported] | ![alt text][supported] | |
|ESP32-C3 | | | ![alt text][supported] | ![alt text][supported] | ![alt text][supported] | |
|ESP32-S3 | | | | ![alt text][supported] | ![alt text][supported] | [Announcement](https://www.espressif.com/en/news/ESP32_S3) |
|ESP32-C2 | | | | | ![alt text][supported] | [Announcement](https://blog.espressif.com/esp32-c2-and-why-it-matter-s-bcf4d7d0b2c6) |
|ESP32-H2 | | | | ![alt text][preview] | ![alt text][preview] | [Announcement](https://www.espressif.com/en/news/ESP32_H2) |
[supported]: https://img.shields.io/badge/-supported-green "supported"
[preview]: https://img.shields.io/badge/-preview-orange "preview"
Espressif SoCs released before 2016 (ESP8266 and ESP8285) are supported by [RTOS SDK](https://github.com/espressif/ESP8266_RTOS_SDK) instead.
# Developing With ESP-IDF
## Setting Up ESP-IDF
See https://idf.espressif.com/ for links to detailed instructions on how to set up the ESP-IDF depending on chip you use.
**Note:** Each SoC series and each ESP-IDF release has its own documentation. Please see Section [Versions](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/versions.html) on how to find documentation and how to checkout specific release of ESP-IDF.
### Non-GitHub forks
ESP-IDF uses relative locations as its submodules URLs ([.gitmodules](.gitmodules)). So they link to GitHub. If ESP-IDF is forked to a Git repository which is not on GitHub, you will need to run the script [tools/set-submodules-to-github.sh](tools/set-submodules-to-github.sh) after git clone.
The script sets absolute URLs for all submodules, allowing `git submodule update --init --recursive` to complete. If cloning ESP-IDF from GitHub, this step is not needed.
## Finding a Project
As well as the [esp-idf-template](https://github.com/espressif/esp-idf-template) project mentioned in Getting Started, ESP-IDF comes with some example projects in the [examples](examples) directory.
Once you've found the project you want to work with, change to its directory and you can configure and build it.
To start your own project based on an example, copy the example project directory outside of the ESP-IDF directory.
# Quick Reference
See the Getting Started guide links above for a detailed setup guide. This is a quick reference for common commands when working with ESP-IDF projects:
## Setup Build Environment
(See the Getting Started guide listed above for a full list of required steps with more details.)
* Install host build dependencies mentioned in the Getting Started guide.
* Run the install script to set up the build environment. The options include `install.bat` or `install.ps1` for Windows, and `install.sh` or `install.fish` for Unix shells.
* Run the export script on Windows (`export.bat`) or source it on Unix (`source export.sh`) in every shell environment before using ESP-IDF.
## Configuring the Project
* `idf.py set-target <chip_name>` sets the target of the project to `<chip_name>`. Run `idf.py set-target` without any arguments to see a list of supported targets.
* `idf.py menuconfig` opens a text-based configuration menu where you can configure the project.
## Compiling the Project
`idf.py build`
... will compile app, bootloader and generate a partition table based on the config.
## Flashing the Project
When the build finishes, it will print a command line to use esptool.py to flash the chip. However you can also do this automatically by running:
`idf.py -p PORT flash`
Replace PORT with the name of your serial port (like `COM3` on Windows, `/dev/ttyUSB0` on Linux, or `/dev/cu.usbserial-X` on MacOS. If the `-p` option is left out, `idf.py flash` will try to flash the first available serial port.
This will flash the entire project (app, bootloader and partition table) to a new chip. The settings for serial port flashing can be configured with `idf.py menuconfig`.
You don't need to run `idf.py build` before running `idf.py flash`, `idf.py flash` will automatically rebuild anything which needs it.
## Viewing Serial Output
The `idf.py monitor` target uses the [idf_monitor tool](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/idf-monitor.html) to display serial output from Espressif SoCs. idf_monitor also has a range of features to decode crash output and interact with the device. [Check the documentation page for details](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/idf-monitor.html).
Exit the monitor by typing Ctrl-].
To build, flash and monitor output in one pass, you can run:
`idf.py flash monitor`
## Compiling & Flashing Only the App
After the initial flash, you may just want to build and flash just your app, not the bootloader and partition table:
* `idf.py app` - build just the app.
* `idf.py app-flash` - flash just the app.
`idf.py app-flash` will automatically rebuild the app if any source files have changed.
(In normal development there's no downside to reflashing the bootloader and partition table each time, if they haven't changed.)
## Erasing Flash
The `idf.py flash` target does not erase the entire flash contents. However it is sometimes useful to set the device back to a totally erased state, particularly when making partition table changes or OTA app updates. To erase the entire flash, run `idf.py erase-flash`.
This can be combined with other targets, ie `idf.py -p PORT erase-flash flash` will erase everything and then re-flash the new app, bootloader and partition table.
# Resources
* Documentation for the latest version: https://docs.espressif.com/projects/esp-idf/. This documentation is built from the [docs directory](docs) of this repository.
* The [esp32.com forum](https://esp32.com/) is a place to ask questions and find community resources.
* [Check the Issues section on github](https://github.com/espressif/esp-idf/issues) if you find a bug or have a feature request. Please check existing Issues before opening a new one.
* If you're interested in contributing to ESP-IDF, please check the [Contributions Guide](https://docs.espressif.com/projects/esp-idf/en/latest/contribute/index.html).

View File

@@ -0,0 +1,357 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdio.h>
#include "esp_err.h"
#include "driver/sdmmc_types.h"
#ifdef __cplusplus
extern "C" {
#endif
extern int FF_CAN_TRIM;
/**
* Probe and initialize SD/MMC card using given host
*
* @note Only SD cards (SDSC and SDHC/SDXC) are supported now.
* Support for MMC/eMMC cards will be added later.
*
* @param host pointer to structure defining host controller
* @param out_card pointer to structure which will receive information
* about the card when the function completes
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_card_init(const sdmmc_host_t* host,
sdmmc_card_t* out_card);
/**
* @brief Print information about the card to a stream
* @param stream stream obtained using fopen or fdopen
* @param card card information structure initialized using sdmmc_card_init
*/
void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card);
/**
* Get status of SD/MMC card
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_get_status(sdmmc_card_t* card);
/**
* Write given number of sectors to SD/MMC card
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param src pointer to data buffer to read data from; data size must be
* equal to sector_count * card->csd.sector_size
* @param start_sector sector where to start writing
* @param sector_count number of sectors to write
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
size_t start_sector, size_t sector_count);
/**
* 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
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
size_t start_sector, size_t sector_count);
/**
* Erase given number of sectors from the SD/MMC card
*
* @note When sdmmc_erase_sectors used with cards in SDSPI mode, it was
* observed that card requires re-init after erase operation.
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param start_sector sector where to start erase
* @param sector_count number of sectors to erase
* @param arg erase command (CMD38) argument
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
size_t sector_count, sdmmc_erase_arg_t arg);
/**
* Check if SD/MMC card supports discard
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @return
* - ESP_OK if supported by the card/device
* - ESP_FAIL if not supported by the card/device
*/
esp_err_t sdmmc_can_discard(sdmmc_card_t* card);
/**
* Check if SD/MMC card supports trim
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @return
* - ESP_OK if supported by the card/device
* - ESP_FAIL if not supported by the card/device
*/
esp_err_t sdmmc_can_trim(sdmmc_card_t* card);
/**
* Check if SD/MMC card supports sanitize
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @return
* - ESP_OK if supported by the card/device
* - ESP_FAIL if not supported by the card/device
*/
esp_err_t sdmmc_mmc_can_sanitize(sdmmc_card_t* card);
/**
* Sanitize the data that was unmapped by a Discard command
*
* @note Discard command has to precede sanitize operation. To discard, use
* MMC_DICARD_ARG with sdmmc_erase_sectors argument
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param timeout_ms timeout value in milliseconds required to sanitize the
* selected range of sectors.
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_mmc_sanitize(sdmmc_card_t* card, uint32_t timeout_ms);
/**
* Erase complete SD/MMC card
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_full_erase(sdmmc_card_t* card);
/**
* Read one byte from an SDIO card using IO_RW_DIRECT (CMD52)
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param function IO function number
* @param reg byte address within IO function
* @param[out] out_byte output, receives the value read from the card
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function,
uint32_t reg, uint8_t *out_byte);
/**
* Write one byte to an SDIO card using IO_RW_DIRECT (CMD52)
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param function IO function number
* @param reg byte address within IO function
* @param in_byte value to be written
* @param[out] out_byte if not NULL, receives new byte value read
* from the card (read-after-write).
* @return
* - ESP_OK on success
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function,
uint32_t reg, uint8_t in_byte, uint8_t* out_byte);
/**
* Read multiple bytes from an SDIO card using IO_RW_EXTENDED (CMD53)
*
* This function performs read operation using CMD53 in byte mode.
* For block mode, see sdmmc_io_read_blocks.
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param function IO function number
* @param addr byte address within IO function where reading starts
* @param dst buffer which receives the data read from card
* @param size number of bytes to read
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_SIZE if size exceeds 512 bytes
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function,
uint32_t addr, void* dst, size_t size);
/**
* Write multiple bytes to an SDIO card using IO_RW_EXTENDED (CMD53)
*
* This function performs write operation using CMD53 in byte mode.
* For block mode, see sdmmc_io_write_blocks.
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param function IO function number
* @param addr byte address within IO function where writing starts
* @param src data to be written
* @param size number of bytes to write
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_SIZE if size exceeds 512 bytes
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_io_write_bytes(sdmmc_card_t* card, uint32_t function,
uint32_t addr, const void* src, size_t size);
/**
* Read blocks of data from an SDIO card using IO_RW_EXTENDED (CMD53)
*
* This function performs read operation using CMD53 in block mode.
* For byte mode, see sdmmc_io_read_bytes.
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param function IO function number
* @param addr byte address within IO function where writing starts
* @param dst buffer which receives the data read from card
* @param size number of bytes to read, must be divisible by the card block
* size.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_SIZE if size is not divisible by 512 bytes
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function,
uint32_t addr, void* dst, size_t size);
/**
* Write blocks of data to an SDIO card using IO_RW_EXTENDED (CMD53)
*
* This function performs write operation using CMD53 in block mode.
* For byte mode, see sdmmc_io_write_bytes.
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param function IO function number
* @param addr byte address within IO function where writing starts
* @param src data to be written
* @param size number of bytes to read, must be divisible by the card block
* size.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_SIZE if size is not divisible by 512 bytes
* - One of the error codes from SDMMC host controller
*/
esp_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function,
uint32_t addr, const void* src, size_t size);
/**
* Enable SDIO interrupt in the SDMMC host
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if the host controller does not support
* IO interrupts
*/
esp_err_t sdmmc_io_enable_int(sdmmc_card_t* card);
/**
* Block until an SDIO interrupt is received
*
* Slave uses D1 line to signal interrupt condition to the host.
* This function can be used to wait for the interrupt.
*
* @param card pointer to card information structure previously initialized
* using sdmmc_card_init
* @param timeout_ticks time to wait for the interrupt, in RTOS ticks
* @return
* - ESP_OK if the interrupt is received
* - ESP_ERR_NOT_SUPPORTED if the host controller does not support
* IO interrupts
* - ESP_ERR_TIMEOUT if the interrupt does not happen in timeout_ticks
*/
esp_err_t sdmmc_io_wait_int(sdmmc_card_t* card, TickType_t timeout_ticks);
/**
* Get the data of CIS region of an SDIO card.
*
* You may provide a buffer not sufficient to store all the CIS data. In this
* case, this function stores 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
* - ESP_OK: on success
* - ESP_ERR_INVALID_RESPONSE: if the card does not (correctly) support CIS.
* - ESP_ERR_INVALID_SIZE: CIS_CODE_END found, but buffer_size is less than
* required size, which is stored in the inout_cis_size then.
* - ESP_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
*/
esp_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 an 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
* - ESP_OK: on success
* - ESP_ERR_NOT_SUPPORTED: if the value from the card is not supported to be parsed.
* - ESP_ERR_INVALID_SIZE: if the CIS size fields are not correct.
*/
esp_err_t sdmmc_io_print_cis_info(uint8_t* buffer, size_t buffer_size, FILE* fp);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,701 @@
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdmmc_common_mh.h"
int FF_CAN_TRIM = 0;
static const char* TAG = "sdmmc_cmd";
esp_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);
esp_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;
}
int state = MMC_R1_CURRENT_STATE(cmd->response);
ESP_LOGV(TAG, "cmd response %08x %08x %08x %08x err=0x%x state=%d",
cmd->response[0],
cmd->response[1],
cmd->response[2],
cmd->response[3],
cmd->error,
state);
return cmd->error;
}
esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd)
{
sdmmc_command_t app_cmd = {
.opcode = MMC_APP_CMD,
.flags = SCF_CMD_AC | SCF_RSP_R1,
.arg = MMC_ARG_RCA(card->rca),
};
esp_err_t err = sdmmc_send_cmd(card, &app_cmd);
if (err != ESP_OK) {
return err;
}
// 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 ESP_ERR_NOT_SUPPORTED;
}
return sdmmc_send_cmd(card, cmd);
}
esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card)
{
sdmmc_command_t cmd = {
.opcode = MMC_GO_IDLE_STATE,
.flags = SCF_CMD_BC | SCF_RSP_R0,
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (host_is_spi(card)) {
/* To enter SPI mode, CMD0 needs to be sent twice (see figure 4-1 in
* SD Simplified spec v4.10). Some cards enter SD mode on first CMD0,
* so don't expect the above command to succeed.
* SCF_RSP_R1 flag below tells the lower layer to expect correct R1
* response (in SPI mode).
*/
(void) err;
vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS);
cmd.flags |= SCF_RSP_R1;
err = sdmmc_send_cmd(card, &cmd);
}
if (err == ESP_OK) {
vTaskDelay(SDMMC_GO_IDLE_DELAY_MS / portTICK_PERIOD_MS);
}
return err;
}
esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr)
{
const uint8_t pattern = 0xaa; /* any pattern will do here */
sdmmc_command_t cmd = {
.opcode = SD_SEND_IF_COND,
.arg = (((ocr & SD_OCR_VOL_MASK) != 0) << 8) | pattern,
.flags = SCF_CMD_BCR | SCF_RSP_R7,
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
uint8_t response = cmd.response[0] & 0xff;
if (response != pattern) {
ESP_LOGD(TAG, "%s: received=0x%x expected=0x%x", __func__, response, pattern);
return ESP_ERR_INVALID_RESPONSE;
}
return ESP_OK;
}
esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp)
{
esp_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 != ESP_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;
}
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
if (nretries == 0) {
return ESP_ERR_TIMEOUT;
}
if (ocrp) {
*ocrp = MMC_R3(cmd.response);
}
return ESP_OK;
}
esp_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
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
*ocrp = SD_SPI_R3(cmd.response);
return ESP_OK;
}
esp_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
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
memcpy(out_raw_cid, &cmd.response, sizeof(sdmmc_response_t));
return ESP_OK;
}
esp_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)
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
sdmmc_flip_byte_order(buf, sizeof(buf));
return sdmmc_decode_cid(buf, out_cid);
}
esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca)
{
assert(out_rca);
sdmmc_command_t cmd = {
.opcode = SD_SEND_RELATIVE_ADDR,
.flags = SCF_CMD_BCR | SCF_RSP_R6
};
/* 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);
}
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
*out_rca = (card->is_mmc) ? mmc_rca : SD_R6_RCA(cmd.response);
return ESP_OK;
}
esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd)
{
sdmmc_command_t cmd = {
.opcode = MMC_SET_BLOCKLEN,
.arg = csd->sector_size,
.flags = SCF_CMD_AC | SCF_RSP_R1
};
return sdmmc_send_cmd(card, &cmd);
}
esp_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,
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_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;
}
esp_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);
}
esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr)
{
size_t datalen = 8;
uint32_t* buf = (uint32_t*) heap_caps_malloc(datalen, MALLOC_CAP_DMA);
if (buf == NULL) {
return ESP_ERR_NO_MEM;
}
sdmmc_command_t cmd = {
.data = buf,
.datalen = datalen,
.blklen = datalen,
.flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1,
.opcode = SD_APP_SEND_SCR
};
esp_err_t err = sdmmc_send_app_cmd(card, &cmd);
if (err == ESP_OK) {
err = sdmmc_decode_scr(buf, out_scr);
}
free(buf);
return err;
}
esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width)
{
sdmmc_command_t cmd = {
.opcode = SD_APP_SET_BUS_WIDTH,
.flags = SCF_RSP_R1 | SCF_CMD_AC,
.arg = (width == 4) ? SD_ARG_BUS_WIDTH_4 : SD_ARG_BUS_WIDTH_1,
};
return sdmmc_send_app_cmd(card, &cmd);
}
esp_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable)
{
assert(host_is_spi(card) && "CRC_ON_OFF can only be used in SPI mode");
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);
}
esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status)
{
sdmmc_command_t cmd = {
.opcode = MMC_SEND_STATUS,
.arg = MMC_ARG_RCA(card->rca),
.flags = SCF_CMD_AC | SCF_RSP_R1
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
return err;
}
if (out_status) {
if (host_is_spi(card)) {
*out_status = SD_SPI_R2(cmd.response);
} else {
*out_status = MMC_R1(cmd.response);
}
}
return ESP_OK;
}
esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
size_t start_block, size_t block_count)
{
esp_err_t err = ESP_OK;
size_t block_size = card->csd.sector_size;
if (esp_ptr_dma_capable(src) && (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.
void* tmp_buf = heap_caps_malloc(block_size, MALLOC_CAP_DMA);
if (tmp_buf == NULL) {
return ESP_ERR_NO_MEM;
}
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 != ESP_OK) {
ESP_LOGD(TAG, "%s: error 0x%x writing block %d+%d",
__func__, err, start_block, i);
break;
}
}
free(tmp_buf);
}
return err;
}
esp_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 > card->csd.capacity) {
return ESP_ERR_INVALID_SIZE;
}
size_t block_size = card->csd.sector_size;
sdmmc_command_t cmd = {
.flags = SCF_CMD_ADTC | SCF_RSP_R1,
.blklen = block_size,
.data = (void*) src,
.datalen = block_count * block_size,
.timeout_ms = SDMMC_WRITE_CMD_TIMEOUT_MS
};
if (block_count == 1) {
cmd.opcode = MMC_WRITE_BLOCK_SINGLE;
} else {
cmd.opcode = MMC_WRITE_BLOCK_MULTIPLE;
}
if (card->ocr & SD_OCR_SDHC_CAP) {
cmd.arg = start_block;
} else {
cmd.arg = start_block * block_size;
}
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
uint32_t status = 0;
size_t count = 0;
/* SD mode: wait for the card to become idle based on R1 status */
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 != ESP_OK) {
return err;
}
if (++count % 10 == 0) {
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
}
}
/* SPI mode: although card busy indication is based on the busy token,
* SD spec recommends that the host checks the results of programming by sending
* SEND_STATUS command. Some of the conditions reported in SEND_STATUS are not
* reported via a data error token.
*/
if (host_is_spi(card)) {
err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
return err;
}
if (status & SD_SPI_R2_CARD_LOCKED) {
ESP_LOGE(TAG, "%s: write failed, card is locked: r2=0x%04x",
__func__, status);
return ESP_ERR_INVALID_STATE;
}
if (status != 0) {
ESP_LOGE(TAG, "%s: card status indicates an error after write operation: r2=0x%04x",
__func__, status);
return ESP_ERR_INVALID_RESPONSE;
}
}
return ESP_OK;
}
esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
size_t start_block, size_t block_count)
{
esp_err_t err = ESP_OK;
size_t block_size = card->csd.sector_size;
if (esp_ptr_dma_capable(dst) && (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.
void* tmp_buf = heap_caps_malloc(block_size, MALLOC_CAP_DMA);
if (tmp_buf == NULL) {
return ESP_ERR_NO_MEM;
}
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 != ESP_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;
}
free(tmp_buf);
}
return err;
}
esp_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 > card->csd.capacity) {
return ESP_ERR_INVALID_SIZE;
}
size_t block_size = card->csd.sector_size;
sdmmc_command_t cmd = {
.flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1,
.blklen = block_size,
.data = (void*) dst,
.datalen = block_count * block_size
};
if (block_count == 1) {
cmd.opcode = MMC_READ_BLOCK_SINGLE;
} else {
cmd.opcode = MMC_READ_BLOCK_MULTIPLE;
}
if (card->ocr & SD_OCR_SDHC_CAP) {
cmd.arg = start_block;
} else {
cmd.arg = start_block * block_size;
}
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
uint32_t status = 0;
size_t count = 0;
while (!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 != ESP_OK) {
return err;
}
if (++count % 10 == 0) {
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
}
}
return ESP_OK;
}
esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
size_t sector_count, sdmmc_erase_arg_t arg)
{
if (start_sector + sector_count > card->csd.capacity) {
return ESP_ERR_INVALID_SIZE;
}
uint32_t cmd38_arg;
if (arg == SDMMC_ERASE_ARG) {
cmd38_arg = card->is_mmc ? SDMMC_MMC_TRIM_ARG : SDMMC_SD_ERASE_ARG;
} else {
cmd38_arg = card->is_mmc ? SDMMC_MMC_DISCARD_ARG : SDMMC_SD_DISCARD_ARG;
}
/* validate the CMD38 argument against card supported features */
if (card->is_mmc) {
if ((cmd38_arg == SDMMC_MMC_TRIM_ARG) && (sdmmc_can_trim(card) != ESP_OK)) {
return ESP_ERR_NOT_SUPPORTED;
}
if ((cmd38_arg == SDMMC_MMC_DISCARD_ARG) && (sdmmc_can_discard(card) != ESP_OK)) {
return ESP_ERR_NOT_SUPPORTED;
}
} else { // SD card
if ((cmd38_arg == SDMMC_SD_DISCARD_ARG) && (sdmmc_can_discard(card) != ESP_OK)) {
return ESP_ERR_NOT_SUPPORTED;
}
}
/* default as block unit address */
size_t addr_unit_mult = 1;
if (!(card->ocr & SD_OCR_SDHC_CAP)) {
addr_unit_mult = card->csd.sector_size;
}
/* prepare command to set the start address */
sdmmc_command_t cmd = {
.flags = SCF_CMD_AC | SCF_RSP_R1 | SCF_WAIT_BUSY,
.opcode = card->is_mmc ? MMC_ERASE_GROUP_START :
SD_ERASE_GROUP_START,
.arg = (start_sector * addr_unit_mult),
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE_GROUP_START) returned 0x%x", __func__, err);
return err;
}
/* prepare command to set the end address */
cmd.opcode = card->is_mmc ? MMC_ERASE_GROUP_END : SD_ERASE_GROUP_END;
cmd.arg = ((start_sector + (sector_count - 1)) * addr_unit_mult);
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE_GROUP_END) returned 0x%x", __func__, err);
return err;
}
/* issue erase command */
memset((void *)&cmd, 0 , sizeof(sdmmc_command_t));
cmd.flags = SCF_CMD_AC | SCF_RSP_R1B | SCF_WAIT_BUSY;
cmd.opcode = MMC_ERASE;
cmd.arg = cmd38_arg;
cmd.timeout_ms = sdmmc_get_erase_timeout_ms(card, cmd38_arg, sector_count * card->csd.sector_size / 1024);
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE) returned 0x%x", __func__, err);
return err;
}
if (host_is_spi(card)) {
uint32_t status;
err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
return err;
}
if (status != 0) {
ESP_LOGE(TAG, "%s: card status indicates an error after erase operation: r2=0x%04x",
__func__, status);
return ESP_ERR_INVALID_RESPONSE;
}
}
return ESP_OK;
}
esp_err_t sdmmc_can_discard(sdmmc_card_t* card)
{
if ((card->is_mmc) && (card->ext_csd.rev >= EXT_CSD_REV_1_6)) {
return ESP_OK;
}
// SD card
if ((!card->is_mmc) && !host_is_spi(card) && (card->ssr.discard_support == 1)) {
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t sdmmc_can_trim(sdmmc_card_t* card)
{
if ((card->is_mmc) && (card->ext_csd.sec_feature & EXT_CSD_SEC_GB_CL_EN)) {
FF_CAN_TRIM = 1;
return ESP_OK;
}
FF_CAN_TRIM = 0;
return ESP_FAIL;
}
esp_err_t sdmmc_mmc_can_sanitize(sdmmc_card_t* card)
{
if ((card->is_mmc) && (card->ext_csd.sec_feature & EXT_CSD_SEC_SANITIZE)) {
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t sdmmc_mmc_sanitize(sdmmc_card_t* card, uint32_t timeout_ms)
{
esp_err_t err;
uint8_t index = EXT_CSD_SANITIZE_START;
uint8_t set = EXT_CSD_CMD_SET_NORMAL;
uint8_t value = 0x01;
if (sdmmc_mmc_can_sanitize(card) != ESP_OK) {
return ESP_ERR_NOT_SUPPORTED;
}
/*
* A Sanitize operation is initiated by writing a value to the extended
* CSD[165] SANITIZE_START. While the device is performing the sanitize
* operation, the busy line is asserted.
* SWITCH command is used to write the EXT_CSD register.
*/
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,
.timeout_ms = timeout_ms,
};
err = sdmmc_send_cmd(card, &cmd);
if (err == ESP_OK) {
//check response bit to see that switch was accepted
if (MMC_R1(cmd.response) & MMC_R1_SWITCH_ERROR) {
err = ESP_ERR_INVALID_RESPONSE;
}
}
return err;
}
esp_err_t sdmmc_full_erase(sdmmc_card_t* card)
{
sdmmc_erase_arg_t arg = SDMMC_SD_ERASE_ARG; // erase by default for SD card
esp_err_t err;
if (card->is_mmc) {
arg = sdmmc_mmc_can_sanitize(card) == ESP_OK ? SDMMC_MMC_DISCARD_ARG: SDMMC_MMC_TRIM_ARG;
}
err = sdmmc_erase_sectors(card, 0, card->csd.capacity, arg);
if ((err == ESP_OK) && (arg == SDMMC_MMC_DISCARD_ARG)) {
uint32_t timeout_ms = sdmmc_get_erase_timeout_ms(card, SDMMC_MMC_DISCARD_ARG, card->csd.capacity * ((uint64_t) card->csd.sector_size) / 1024);
return sdmmc_mmc_sanitize(card, timeout_ms);
}
return err;
}
esp_err_t sdmmc_get_status(sdmmc_card_t* card)
{
uint32_t stat;
return sdmmc_send_cmd_send_status(card, &stat);
}

View File

@@ -0,0 +1,332 @@
/*
* 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_mh.h"
bool card_is_mmc = 0;
static const char* TAG = "sdmmc_common";
esp_err_t sdmmc_init_ocr(sdmmc_card_t* card)
{
esp_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 == ESP_ERR_TIMEOUT && !host_is_spi(card)) {
ESP_LOGD(TAG, "send_op_cond timeout, trying MMC");
card->is_mmc = 1;
card_is_mmc = 1;
err = sdmmc_send_cmd_send_op_cond(card, host_ocr, &card->ocr);
}
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_op_cond (1) returned 0x%x", __func__, err);
return err;
}
if (host_is_spi(card)) {
err = sdmmc_send_cmd_read_ocr(card, &card->ocr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: read_ocr returned 0x%x", __func__, err);
return err;
}
}
ESP_LOGD(TAG, "host_ocr=0x%x card_ocr=0x%x", host_ocr, card->ocr);
/* Clear all voltage bits in host's OCR which the card doesn't support.
* Don't touch CCS bit because in SPI mode cards don't report CCS in ACMD41
* response.
*/
host_ocr &= (card->ocr | (~SD_OCR_VOL_MASK));
ESP_LOGD(TAG, "sdmmc_card_init: host_ocr=%08x, card_ocr=%08x", host_ocr, card->ocr);
return ESP_OK;
}
esp_err_t sdmmc_init_cid(sdmmc_card_t* card)
{
esp_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 != ESP_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 != ESP_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 != ESP_OK) {
ESP_LOGE(TAG, "%s: send_cid returned 0x%x", __func__, err);
return err;
}
}
return ESP_OK;
}
esp_err_t sdmmc_init_rca(sdmmc_card_t* card)
{
esp_err_t err;
err = sdmmc_send_cmd_set_relative_addr(card, &card->rca);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: set_relative_addr returned 0x%x", __func__, err);
return err;
}
return ESP_OK;
}
esp_err_t sdmmc_init_mmc_decode_cid(sdmmc_card_t* card)
{
esp_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 != ESP_OK) {
ESP_LOGE(TAG, "%s: decoding CID failed (0x%x)", __func__, err);
return err;
}
return ESP_OK;
}
esp_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. */
esp_err_t err = sdmmc_send_cmd_send_csd(card, &card->csd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_csd returned 0x%x", __func__, err);
return err;
}
const size_t max_sdsc_capacity = UINT32_MAX / card->csd.sector_size + 1;
if (!(card->ocr & SD_OCR_SDHC_CAP) &&
card->csd.capacity > max_sdsc_capacity) {
ESP_LOGW(TAG, "%s: SDSC card reports capacity=%u. Limiting to %u.",
__func__, card->csd.capacity, max_sdsc_capacity);
card->csd.capacity = max_sdsc_capacity;
}
return ESP_OK;
}
esp_err_t sdmmc_init_select_card(sdmmc_card_t* card)
{
assert(!host_is_spi(card));
esp_err_t err = sdmmc_send_cmd_select_card(card, card->rca);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: select_card returned 0x%x", __func__, err);
return err;
}
return ESP_OK;
}
esp_err_t sdmmc_init_card_hs_mode(sdmmc_card_t* card)
{
esp_err_t err = ESP_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 == ESP_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 != ESP_OK) {
return err;
}
return ESP_OK;
}
esp_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) {
esp_err_t err = (*card->host.set_bus_width)(card->host.slot, bus_width);
if (err != ESP_OK) {
ESP_LOGE(TAG, "host.set_bus_width failed (0x%x)", err);
return err;
}
}
return ESP_OK;
}
esp_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) {
esp_err_t err = (*card->host.set_card_clk)(card->host.slot, selected_freq);
if (err != ESP_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 ESP_ERR_NOT_SUPPORTED;
}
esp_err_t err = (*card->host.set_bus_ddr_mode)(card->host.slot, true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "failed to switch bus to DDR mode (0x%x)", err);
return err;
}
}
return ESP_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 (int 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";
print_csd = true;
}
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: %lluMB\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->is_mmc ? card->csd.csd_ver : card->csd.csd_ver + 1),
card->csd.sector_size, card->csd.capacity, card->csd.read_block_len);
if (card->is_mmc) {
fprintf(stream, "EXT CSD: bus_width=%d\n", (1 << card->log_bus_width));
} else if (!card->is_sdio){ // make sure card is SD
fprintf(stream, "SSR: bus_width=%d\n", (card->ssr.cur_bus_width ? 4 : 1));
}
}
if (print_scr) {
fprintf(stream, "SCR: sd_spec=%d, bus_width=%d\n", card->scr.sd_spec, card->scr.bus_width);
}
}
esp_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 ESP_OK;
}
uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb)
{
if (card->is_mmc) {
return sdmmc_mmc_get_erase_timeout_ms(card, arg, erase_size_kb);
} else {
return sdmmc_sd_get_erase_timeout_ms(card, arg, erase_size_kb);
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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 "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/sdmmc_defs.h"
#include "driver/sdmmc_types.h"
#include "sdmmc_cmd_mh.h"
#include "sys/param.h"
#include "soc/soc_memory_layout.h"
extern bool card_is_mmc;
#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
#define SDMMC_SD_DISCARD_TIMEOUT 250 // SD erase (discard) timeout
/* 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
/* supported arguments for erase command 38 */
#define SDMMC_SD_ERASE_ARG 0
#define SDMMC_SD_DISCARD_ARG 1
#define SDMMC_MMC_TRIM_ARG 1
#define SDMMC_MMC_DISCARD_ARG 3
/* Functions to send individual commands */
esp_err_t sdmmc_send_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
esp_err_t sdmmc_send_app_cmd(sdmmc_card_t* card, sdmmc_command_t* cmd);
esp_err_t sdmmc_send_cmd_go_idle_state(sdmmc_card_t* card);
esp_err_t sdmmc_send_cmd_send_if_cond(sdmmc_card_t* card, uint32_t ocr);
esp_err_t sdmmc_send_cmd_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp);
esp_err_t sdmmc_send_cmd_read_ocr(sdmmc_card_t *card, uint32_t *ocrp);
esp_err_t sdmmc_send_cmd_send_cid(sdmmc_card_t *card, sdmmc_cid_t *out_cid);
esp_err_t sdmmc_send_cmd_all_send_cid(sdmmc_card_t* card, sdmmc_response_t* out_raw_cid);
esp_err_t sdmmc_send_cmd_set_relative_addr(sdmmc_card_t* card, uint16_t* out_rca);
esp_err_t sdmmc_send_cmd_set_blocklen(sdmmc_card_t* card, sdmmc_csd_t* csd);
esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card,
uint32_t mode, uint32_t group, uint32_t function,
sdmmc_switch_func_rsp_t* resp);
esp_err_t sdmmc_send_cmd_send_csd(sdmmc_card_t* card, sdmmc_csd_t* out_csd);
esp_err_t sdmmc_send_cmd_select_card(sdmmc_card_t* card, uint32_t rca);
esp_err_t sdmmc_send_cmd_send_scr(sdmmc_card_t* card, sdmmc_scr_t *out_scr);
esp_err_t sdmmc_send_cmd_set_bus_width(sdmmc_card_t* card, int width);
esp_err_t sdmmc_send_cmd_send_status(sdmmc_card_t* card, uint32_t* out_status);
esp_err_t sdmmc_send_cmd_crc_on_off(sdmmc_card_t* card, bool crc_enable);
/* Higher level functions */
esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card);
esp_err_t sdmmc_enable_hs_mode_and_check(sdmmc_card_t* card);
esp_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src,
size_t start_block, size_t block_count);
esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
size_t start_block, size_t block_count);
uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb);
/* SD specific */
esp_err_t sdmmc_check_scr(sdmmc_card_t* card);
esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid);
esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr);
esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr);
uint32_t sdmmc_sd_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb);
/* SDIO specific */
esp_err_t sdmmc_io_reset(sdmmc_card_t* card);
esp_err_t sdmmc_io_enable_hs_mode(sdmmc_card_t* card);
esp_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp);
esp_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int function,
uint32_t reg, uint32_t arg, uint8_t *byte);
esp_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int function,
uint32_t reg, int arg, void *data, size_t size);
/* MMC specific */
esp_err_t sdmmc_mmc_send_ext_csd_data(sdmmc_card_t* card, void *out_data, size_t datalen);
esp_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8_t value);
esp_err_t sdmmc_mmc_decode_cid(int mmc_ver, sdmmc_response_t resp, sdmmc_cid_t* out_cid);
esp_err_t sdmmc_mmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
esp_err_t sdmmc_mmc_enable_hs_mode(sdmmc_card_t* card);
uint32_t sdmmc_mmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb);
/* Parts of card initialization flow */
esp_err_t sdmmc_init_sd_if_cond(sdmmc_card_t* card);
esp_err_t sdmmc_init_select_card(sdmmc_card_t* card);
esp_err_t sdmmc_init_csd(sdmmc_card_t* card);
esp_err_t sdmmc_init_cid(sdmmc_card_t* card);
esp_err_t sdmmc_init_rca(sdmmc_card_t* card);
esp_err_t sdmmc_init_mmc_decode_cid(sdmmc_card_t* card);
esp_err_t sdmmc_init_ocr(sdmmc_card_t* card);
esp_err_t sdmmc_init_spi_crc(sdmmc_card_t* card);
esp_err_t sdmmc_init_io(sdmmc_card_t* card);
esp_err_t sdmmc_init_sd_blocklen(sdmmc_card_t* card);
esp_err_t sdmmc_init_sd_scr(sdmmc_card_t* card);
esp_err_t sdmmc_init_sd_ssr(sdmmc_card_t* card);
esp_err_t sdmmc_init_sd_wait_data_ready(sdmmc_card_t* card);
esp_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card);
esp_err_t sdmmc_init_mmc_read_cid(sdmmc_card_t* card);
esp_err_t sdmmc_init_host_bus_width(sdmmc_card_t* card);
esp_err_t sdmmc_init_sd_bus_width(sdmmc_card_t* card);
esp_err_t sdmmc_init_io_bus_width(sdmmc_card_t* card);
esp_err_t sdmmc_init_mmc_bus_width(sdmmc_card_t* card);
esp_err_t sdmmc_init_card_hs_mode(sdmmc_card_t* card);
esp_err_t sdmmc_init_host_frequency(sdmmc_card_t* card);
esp_err_t sdmmc_init_mmc_check_ext_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);
esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card);

View File

@@ -0,0 +1,128 @@
/*
* 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_mh.h"
static const char* TAG = "sdmmc_init";
#define SDMMC_INIT_STEP(condition, function) \
do { \
if ((condition)) { \
esp_err_t err = (function)(card); \
if (err != ESP_OK) { \
ESP_LOGD(TAG, "%s: %s returned 0x%x", __func__, #function, err); \
return err; \
} \
} \
} while(0);
esp_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);
}
/* SD card: read SD Status register */
SDMMC_INIT_STEP(is_sdmem, sdmmc_init_sd_ssr);
/* 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);
/* Sanity check after eMMC switch to HS mode */
SDMMC_INIT_STEP(is_mmc, sdmmc_init_mmc_check_ext_csd);
/* TODO: add similar checks for SDIO */
return ESP_OK;
}

View File

@@ -0,0 +1,636 @@
/*
* 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_mh.h"
#include "esp_attr.h"
#include "esp_compiler.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 ESP_ERR_INVALID_SIZE;} while(0)
#define CIS_CHECK_UNSUPPORTED(COND) do {if(!(COND)) return ESP_ERR_NOT_SUPPORTED;} while(0)
#define CIS_GET_MINIMAL_SIZE 32
typedef esp_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 const char* TAG = "sdmmc_io";
static esp_err_t cis_tuple_func_default(const void* p, uint8_t* data, FILE* fp);
static esp_err_t cis_tuple_func_manfid(const void* p, uint8_t* data, FILE* fp);
static esp_err_t cis_tuple_func_cftable_entry(const void* p, uint8_t* data, FILE* fp);
static esp_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),
};
esp_err_t sdmmc_io_reset(sdmmc_card_t* card)
{
uint8_t sdio_reset = CCCR_CTL_RES;
esp_err_t err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_CTL, SD_ARG_CMD52_WRITE, &sdio_reset);
if (err == ESP_ERR_TIMEOUT || (host_is_spi(card) && err == ESP_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 == ESP_ERR_NOT_FOUND) {
ESP_LOGD(TAG, "%s: card not present", __func__);
return err;
} else if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: unexpected return: 0x%x", __func__, err );
return err;
}
return ESP_OK;
}
esp_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.
*/
esp_err_t err = sdmmc_io_send_op_cond(card, 0, &card->ocr);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: io_send_op_cond (1) returned 0x%x; not IO card", __func__, err);
card->is_sdio = 0;
card->is_mem = 1;
} else {
card->is_sdio = 1;
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 != ESP_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 != ESP_OK) {
ESP_LOGD(TAG, "%s: sdmmc_enable_int failed (0x%x)", __func__, err);
}
}
return ESP_OK;
}
esp_err_t sdmmc_init_io_bus_width(sdmmc_card_t* card)
{
esp_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 != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read SD_IO_CCCR_CARD_CAP) returned 0x%0x", __func__, err);
return err;
}
ESP_LOGD(TAG, "IO card capabilities byte: %02x", card_cap);
if (!(card_cap & CCCR_CARD_CAP_LSC) ||
(card_cap & CCCR_CARD_CAP_4BLS)) {
// This card supports 4-bit bus mode
uint8_t bus_width = CCCR_BUS_WIDTH_4;
err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_BUS_WIDTH,
SD_ARG_CMD52_WRITE, &bus_width);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write SD_IO_CCCR_BUS_WIDTH) returned 0x%0x", __func__, err);
return err;
}
card->log_bus_width = 2;
}
}
return ESP_OK;
}
esp_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 ESP_OK;
} else if (card->host.max_freq_khz < SDMMC_FREQ_HIGHSPEED) {
card->max_freq_khz = SDMMC_FREQ_DEFAULT;
return ESP_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;
esp_err_t err = sdmmc_io_rw_direct(card, 0, SD_IO_CCCR_HIGHSPEED,
SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &val);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: sdmmc_io_rw_direct returned 0x%x", __func__, err);
return err;
}
ESP_LOGD(TAG, "%s: CCCR_HIGHSPEED=0x%02x", __func__, val);
const uint8_t hs_mask = CCCR_HIGHSPEED_ENABLE | CCCR_HIGHSPEED_SUPPORT;
if ((val & hs_mask) != hs_mask) {
return ESP_ERR_NOT_SUPPORTED;
}
card->max_freq_khz = SDMMC_FREQ_HIGHSPEED;
return ESP_OK;
}
esp_err_t sdmmc_io_send_op_cond(sdmmc_card_t* card, uint32_t ocr, uint32_t *ocrp)
{
esp_err_t err = ESP_OK;
sdmmc_command_t cmd = {
.flags = SCF_CMD_BCR | SCF_RSP_R4,
.arg = ocr,
.opcode = SD_IO_SEND_OP_COND
};
for (size_t i = 0; i < 100; i++) {
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
break;
}
if ((MMC_R4(cmd.response) & SD_IO_OCR_MEM_READY) ||
ocr == 0) {
break;
}
err = ESP_ERR_TIMEOUT;
vTaskDelay(SDMMC_IO_SEND_OP_COND_DELAY_MS / portTICK_PERIOD_MS);
}
if (err == ESP_OK && ocrp != NULL)
*ocrp = MMC_R4(cmd.response);
return err;
}
esp_err_t sdmmc_io_rw_direct(sdmmc_card_t* card, int func,
uint32_t reg, uint32_t arg, uint8_t *byte)
{
esp_err_t err;
sdmmc_command_t cmd = {
.flags = SCF_CMD_AC | SCF_RSP_R5,
.arg = 0,
.opcode = SD_IO_RW_DIRECT
};
arg |= (func & SD_ARG_CMD52_FUNC_MASK) << SD_ARG_CMD52_FUNC_SHIFT;
arg |= (reg & SD_ARG_CMD52_REG_MASK) << SD_ARG_CMD52_REG_SHIFT;
arg |= (*byte & SD_ARG_CMD52_DATA_MASK) << SD_ARG_CMD52_DATA_SHIFT;
cmd.arg = arg;
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGV(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
*byte = SD_R5_DATA(cmd.response);
return ESP_OK;
}
esp_err_t sdmmc_io_read_byte(sdmmc_card_t* card, uint32_t function,
uint32_t addr, uint8_t *out_byte)
{
esp_err_t ret = sdmmc_io_rw_direct(card, function, addr, SD_ARG_CMD52_READ, out_byte);
if (unlikely(ret != ESP_OK)) {
ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read 0x%x) returned 0x%x", __func__, addr, ret);
}
return ret;
}
esp_err_t sdmmc_io_write_byte(sdmmc_card_t* card, uint32_t function,
uint32_t addr, uint8_t in_byte, uint8_t* out_byte)
{
uint8_t tmp_byte = in_byte;
esp_err_t ret = sdmmc_io_rw_direct(card, function, addr,
SD_ARG_CMD52_WRITE | SD_ARG_CMD52_EXCHANGE, &tmp_byte);
if (unlikely(ret != ESP_OK)) {
ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write 0x%x) returned 0x%x", __func__, addr, ret);
return ret;
}
if (out_byte != NULL) {
*out_byte = tmp_byte;
}
return ESP_OK;
}
esp_err_t sdmmc_io_rw_extended(sdmmc_card_t* card, int func,
uint32_t reg, int arg, void *datap, size_t datalen)
{
esp_err_t err;
const size_t max_byte_transfer_size = 512;
sdmmc_command_t cmd = {
.flags = SCF_CMD_AC | SCF_RSP_R5,
.arg = 0,
.opcode = SD_IO_RW_EXTENDED,
.data = datap,
.datalen = datalen,
.blklen = max_byte_transfer_size /* TODO: read max block size from CIS */
};
uint32_t count; /* number of bytes or blocks, depending on transfer mode */
if (arg & SD_ARG_CMD53_BLOCK_MODE) {
if (cmd.datalen % cmd.blklen != 0) {
return ESP_ERR_INVALID_SIZE;
}
count = cmd.datalen / cmd.blklen;
} else {
if (datalen > max_byte_transfer_size) {
/* TODO: split into multiple operations? */
return ESP_ERR_INVALID_SIZE;
}
if (datalen == max_byte_transfer_size) {
count = 0; // See 5.3.1 SDIO simplifed spec
} else {
count = datalen;
}
cmd.blklen = datalen;
}
arg |= (func & SD_ARG_CMD53_FUNC_MASK) << SD_ARG_CMD53_FUNC_SHIFT;
arg |= (reg & SD_ARG_CMD53_REG_MASK) << SD_ARG_CMD53_REG_SHIFT;
arg |= (count & SD_ARG_CMD53_LENGTH_MASK) << SD_ARG_CMD53_LENGTH_SHIFT;
cmd.arg = arg;
if ((arg & SD_ARG_CMD53_WRITE) == 0) {
cmd.flags |= SCF_CMD_READ;
}
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
return ESP_OK;
}
esp_err_t sdmmc_io_read_bytes(sdmmc_card_t* card, uint32_t function,
uint32_t addr, void* dst, size_t size)
{
/* 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;
esp_err_t err = sdmmc_io_rw_extended(card, function, addr,
SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT,
pc_dst, will_transfer);
if (unlikely(err != ESP_OK)) {
return err;
}
pc_dst += will_transfer;
size -= will_transfer;
addr += will_transfer;
}
return ESP_OK;
}
esp_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;
esp_err_t err = sdmmc_io_rw_extended(card, function, addr,
SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT,
(void*) pc_src, will_transfer);
if (unlikely(err != ESP_OK)) {
return err;
}
pc_src += will_transfer;
size -= will_transfer;
addr += will_transfer;
}
return ESP_OK;
}
esp_err_t sdmmc_io_read_blocks(sdmmc_card_t* card, uint32_t function,
uint32_t addr, void* dst, size_t size)
{
if (unlikely(size % 4 != 0)) {
return ESP_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);
}
esp_err_t sdmmc_io_write_blocks(sdmmc_card_t* card, uint32_t function,
uint32_t addr, const void* src, size_t size)
{
if (unlikely(size % 4 != 0)) {
return ESP_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);
}
esp_err_t sdmmc_io_enable_int(sdmmc_card_t* card)
{
if (card->host.io_int_enable == NULL) {
return ESP_ERR_NOT_SUPPORTED;
}
return (*card->host.io_int_enable)(card->host.slot);
}
esp_err_t sdmmc_io_wait_int(sdmmc_card_t* card, TickType_t timeout_ticks)
{
if (card->host.io_int_wait == NULL) {
return ESP_ERR_NOT_SUPPORTED;
}
return (*card->host.io_int_wait)(card->host.slot, timeout_ticks);
}
/*
* Print the CIS information of a CIS card, currently only ESP slave supported.
*/
static esp_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 ESP_OK;
}
static esp_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);
fprintf(fp, " MANF: %04X, CARD: %04X\n", *(uint16_t*)(data), *(uint16_t*)(data+2));
return ESP_OK;
}
static esp_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 ESP_OK;
}
static esp_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
int len = *(uint16_t*)data;
fprintf(fp, " LEN: %04X\n", len);
data+=2;
}
CIS_CHECK_UNSUPPORTED(misc==0); //misc descriptor is not handled yet
return ESP_OK;
}
static const cis_tuple_t* get_tuple(uint8_t code)
{
for (int i = 0; i < sizeof(cis_table)/sizeof(cis_tuple_t); i++) {
if (code == cis_table[i].code) return &cis_table[i];
}
return NULL;
}
esp_err_t sdmmc_io_print_cis_info(uint8_t* buffer, size_t buffer_size, FILE* fp)
{
ESP_LOG_BUFFER_HEXDUMP("CIS", buffer, buffer_size, ESP_LOG_DEBUG);
if (!fp) fp = stdout;
uint8_t* cis = buffer;
do {
const cis_tuple_t* tuple = get_tuple(cis[0]);
int size = cis[1];
esp_err_t ret = ESP_OK;
if (tuple) {
ret = tuple->func(tuple, cis, fp);
} else {
ret = cis_tuple_func_default(NULL, cis, fp);
}
if (ret != ESP_OK) return ret;
cis += 2 + size;
if (tuple && tuple->code == CISTPL_CODE_END) break;
} while (cis < buffer + buffer_size) ;
return ESP_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;
}
esp_err_t sdmmc_io_get_cis_data(sdmmc_card_t* card, uint8_t* out_buffer, size_t buffer_size, size_t* inout_cis_size)
{
esp_err_t ret = ESP_OK;
WORD_ALIGNED_ATTR uint8_t buf[CIS_GET_MINIMAL_SIZE];
/* 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 != ESP_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 ESP_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.
*/
int buffer_offset = 0;
int 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 != ESP_OK) return ret;
//calculate relative to the beginning of the buffer
int offset = cur_cis_offset - buffer_offset;
bool finish = check_tuples_in_buffer(buf, CIS_GET_MINIMAL_SIZE, &offset);
int remain_size = buffer_size - buffer_offset;
int copy_len;
if (finish) {
copy_len = MIN(offset, remain_size);
end_tuple_found = true;
} else {
copy_len = 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 ESP_ERR_INVALID_SIZE;
} else {
return ESP_OK;
}
} else {
return ESP_ERR_NOT_FOUND;
}
}

View File

@@ -0,0 +1,300 @@
/*
* 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 <unistd.h>
#include "sdmmc_common_mh.h"
static const char* TAG = "sdmmc_mmc";
esp_err_t sdmmc_init_mmc_read_ext_csd(sdmmc_card_t* card)
{
int card_type;
esp_err_t err = ESP_OK;
uint8_t* ext_csd = heap_caps_malloc(EXT_CSD_MMC_SIZE, MALLOC_CAP_DMA);
if (!ext_csd) {
ESP_LOGE(TAG, "%s: could not allocate ext_csd", __func__);
return ESP_ERR_NO_MEM;
}
uint32_t sectors = 0;
ESP_LOGD(TAG, "MMC version: %d", card->csd.mmc_ver);
if (card->csd.mmc_ver < MMC_CSD_MMCVER_4_0) {
err = ESP_ERR_NOT_SUPPORTED;
goto out;
}
/* read EXT_CSD */
err = sdmmc_mmc_send_ext_csd_data(card, ext_csd, EXT_CSD_MMC_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_ext_csd_data error 0x%x", __func__, err);
goto out;
}
card_type = ext_csd[EXT_CSD_CARD_TYPE];
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 = 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] >> 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] & 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 + 0] << 0 )
| ( ext_csd[EXT_CSD_SEC_COUNT + 1] << 8 )
| ( ext_csd[EXT_CSD_SEC_COUNT + 2] << 16 )
| ( ext_csd[EXT_CSD_SEC_COUNT + 3] << 24 );
if (sectors > (2u * 1024 * 1024 * 1024) / 512) {
card->csd.capacity = sectors;
}
/* erased state of a bit, if 1 byte value read is 0xFF else 0x00 */
card->ext_csd.erase_mem_state = ext_csd[EXT_CSD_ERASED_MEM_CONT];
card->ext_csd.rev = ext_csd[EXT_CSD_REV];
card->ext_csd.sec_feature = ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT];
out:
free(ext_csd);
return err;
}
esp_err_t sdmmc_init_mmc_bus_width(sdmmc_card_t* card)
{
esp_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 != ESP_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 != ESP_OK) {
ESP_LOGE(TAG, "%s: can't change bus width (%d bit), 0x%x",
__func__, bus_width, err);
return err;
}
}
return ESP_OK;
}
esp_err_t sdmmc_mmc_enable_hs_mode(sdmmc_card_t* card)
{
esp_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 != ESP_OK) {
ESP_LOGE(TAG, "%s: mmc_switch EXT_CSD_HS_TIMING_HS error 0x%x",
__func__, err);
return err;
}
}
return ESP_OK;
}
esp_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 ESP_OK;
}
esp_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 = 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 ESP_OK;
}
esp_err_t sdmmc_mmc_send_ext_csd_data(sdmmc_card_t* card, void *out_data, size_t datalen)
{
assert(esp_ptr_dma_capable(out_data));
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);
}
esp_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,
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err == ESP_OK) {
//check response bit to see that switch was accepted
if (MMC_R1(cmd.response) & MMC_R1_SWITCH_ERROR) {
err = ESP_ERR_INVALID_RESPONSE;
}
}
return err;
}
esp_err_t sdmmc_init_mmc_check_ext_csd(sdmmc_card_t* card)
{
assert(card->is_mem == 1 && card->rca != 0);
/*
* Integrity check required if card switched to HS mode
* card->max_freq_khz = MIN(card->max_freq_khz, card->host.max_freq_khz)
* For 26MHz limit background see sdmmc_mmc_enable_hs_mode()
*/
if (card->max_freq_khz <= SDMMC_FREQ_26M) {
return ESP_OK;
}
/* ensure EXT_CSD buffer is available before starting any SD-card operation */
uint8_t* ext_csd = heap_caps_malloc(EXT_CSD_MMC_SIZE, MALLOC_CAP_DMA);
if (!ext_csd) {
ESP_LOGE(TAG, "%s: could not allocate ext_csd", __func__);
return ESP_ERR_NO_MEM;
}
/* ensure card is in transfer state before read ext_csd */
uint32_t status;
esp_err_t err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_status returned 0x%x", __func__, err);
goto out;
}
status = ((status & MMC_R1_CURRENT_STATE_MASK) >> MMC_R1_CURRENT_STATE_POS);
if (status != MMC_R1_CURRENT_STATE_TRAN) {
ESP_LOGE(TAG, "%s: card not in transfer state", __func__);
err = ESP_ERR_INVALID_STATE;
goto out;
}
/* read EXT_CSD to ensure device works fine in HS mode */
err = sdmmc_mmc_send_ext_csd_data(card, ext_csd, EXT_CSD_MMC_SIZE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_ext_csd_data error 0x%x", __func__, err);
goto out;
}
/* EXT_CSD static fields should match the previous read values in sdmmc_card_init */
if ((card->ext_csd.rev != ext_csd[EXT_CSD_REV]) ||
(card->ext_csd.sec_feature != ext_csd[EXT_CSD_SEC_FEATURE_SUPPORT])) {
ESP_LOGE(TAG, "%s: Data integrity test fail in HS mode", __func__);
err = ESP_FAIL;
}
out:
free(ext_csd);
return err;
}
uint32_t sdmmc_mmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb)
{
/* TODO: calculate erase timeout based on ext_csd (trim_timeout) */
uint32_t timeout_ms = SDMMC_SD_DISCARD_TIMEOUT * erase_size_kb / card->csd.sector_size;
timeout_ms = MAX(1000, timeout_ms);
ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, %ums per sector)",
__func__, timeout_ms / 1000, erase_size_kb, SDMMC_SD_DISCARD_TIMEOUT);
return timeout_ms;
}

View File

@@ -0,0 +1,449 @@
/*
* 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_mh.h"
static const char* TAG = "sdmmc_sd";
esp_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);
esp_err_t err = sdmmc_send_cmd_send_if_cond(card, host_ocr);
if (err == ESP_OK) {
ESP_LOGD(TAG, "SDHC/SDXC card");
host_ocr |= SD_OCR_SDHC_CAP;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGD(TAG, "CMD8 timeout; not an SD v2.00 card");
} else if (host_is_spi(card) && err == ESP_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 ESP_OK;
}
esp_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) {
esp_err_t err = sdmmc_send_cmd_set_blocklen(card, &card->csd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: set_blocklen returned 0x%x", __func__, err);
return err;
}
}
return ESP_OK;
}
esp_err_t sdmmc_init_sd_scr(sdmmc_card_t* card)
{
esp_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 != ESP_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 ESP_OK;
}
esp_err_t sdmmc_init_sd_ssr(sdmmc_card_t* card)
{
esp_err_t err = ESP_OK;
/* Get the contents of SSR register: SD additional information
* ACMD13 to read 512byte SD status information
*/
uint32_t* sd_ssr = heap_caps_calloc(1, SD_SSR_SIZE, MALLOC_CAP_DMA);
if (!sd_ssr) {
ESP_LOGE(TAG, "%s: could not allocate sd_ssr", __func__);
return ESP_ERR_NO_MEM;
}
sdmmc_command_t cmd = {
.data = sd_ssr,
.datalen = SD_SSR_SIZE,
.blklen = SD_SSR_SIZE,
.opcode = SD_APP_SD_STATUS,
.arg = 0,
.flags = SCF_CMD_ADTC | SCF_RSP_R1 | SCF_CMD_READ
};
// read SD status register
err = sdmmc_send_app_cmd(card, &cmd);
if (err != ESP_OK) {
free(sd_ssr);
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
err = sdmmc_decode_ssr(sd_ssr, &card->ssr);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: error sdmmc_decode_scr returned 0x%x", __func__, err);
}
free(sd_ssr);
return err;
}
esp_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;
}
esp_err_t err = sdmmc_send_cmd_set_bus_width(card, width);
if (err != ESP_OK) {
ESP_LOGE(TAG, "set_bus_width failed (0x%x)", err);
return err;
}
return ESP_OK;
}
esp_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
esp_err_t err = sdmmc_send_cmd_send_status(card, &status);
if (err != ESP_OK) {
return err;
}
if (++count % 16 == 0) {
ESP_LOGV(TAG, "waiting for card to become ready (%d)", count);
}
}
return ESP_OK;
}
esp_err_t sdmmc_send_cmd_switch_func(sdmmc_card_t* card,
uint32_t mode, uint32_t group, uint32_t function,
sdmmc_switch_func_rsp_t* resp)
{
if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 ||
((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (group == 0 ||
group > SD_SFUNC_GROUP_MAX ||
function > SD_SFUNC_FUNC_MAX) {
return ESP_ERR_INVALID_ARG;
}
if (mode > 1) {
return ESP_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
};
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
return err;
}
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 ESP_ERR_INVALID_STATE;
}
} else {
ESP_LOGD(TAG, "%s: got an invalid version of SWITCH_FUNC response: 0x%02x",
__func__, resp_ver);
return ESP_ERR_INVALID_RESPONSE;
}
return ESP_OK;
}
esp_err_t sdmmc_enable_hs_mode(sdmmc_card_t* card)
{
/* This will determine if the card supports SWITCH_FUNC command,
* and high speed mode. If the cards supports both, this will enable
* high speed mode at the card side.
*/
if (card->scr.sd_spec < SCR_SD_SPEC_VER_1_10 ||
((card->csd.card_command_class & SD_CSD_CCC_SWITCH) == 0)) {
return ESP_ERR_NOT_SUPPORTED;
}
sdmmc_switch_func_rsp_t* response = (sdmmc_switch_func_rsp_t*)
heap_caps_malloc(sizeof(*response), MALLOC_CAP_DMA);
if (response == NULL) {
return ESP_ERR_NO_MEM;
}
esp_err_t err = sdmmc_send_cmd_switch_func(card, 0, SD_ACCESS_MODE, 0, response);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (1) returned 0x%x", __func__, err);
goto out;
}
uint32_t supported_mask = SD_SFUNC_SUPPORTED(response->data, 1);
if ((supported_mask & BIT(SD_ACCESS_MODE_SDR25)) == 0) {
err = ESP_ERR_NOT_SUPPORTED;
goto out;
}
err = sdmmc_send_cmd_switch_func(card, 1, SD_ACCESS_MODE, SD_ACCESS_MODE_SDR25, response);
if (err != ESP_OK) {
ESP_LOGD(TAG, "%s: sdmmc_send_cmd_switch_func (2) returned 0x%x", __func__, err);
goto out;
}
out:
free(response);
return err;
}
esp_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 ESP_OK;
}
/* Try to enabled HS mode */
esp_err_t err = sdmmc_enable_hs_mode(card);
if (err != ESP_OK) {
return err;
}
/* HS mode has been enabled on the card.
* Read CSD again, it should now indicate that the card supports
* 50MHz clock.
* Since SEND_CSD is allowed only in standby mode, and the card is currently in data transfer
* 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 != ESP_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 != ESP_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 != ESP_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 ESP_ERR_NOT_SUPPORTED;
}
card->max_freq_khz = SDMMC_FREQ_HIGHSPEED;
return ESP_OK;
}
esp_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 = { 0 };
esp_err_t err = sdmmc_send_cmd_send_scr(card, &scr_tmp);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: send_scr returned 0x%x", __func__, err);
return err;
}
if (memcmp(&card->scr, &scr_tmp, sizeof(scr_tmp)) != 0) {
ESP_LOGE(TAG, "got corrupted data after increasing clock frequency");
return ESP_ERR_INVALID_RESPONSE;
}
return ESP_OK;
}
esp_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));
esp_err_t err = sdmmc_send_cmd_crc_on_off(card, true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd_crc_on_off returned 0x%x", __func__, err);
return err;
}
return ESP_OK;
}
esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid)
{
out_cid->mfg_id = SD_CID_MID(resp);
out_cid->oem_id = SD_CID_OID(resp);
SD_CID_PNM_CPY(resp, out_cid->name);
out_cid->revision = SD_CID_REV(resp);
out_cid->serial = SD_CID_PSN(resp);
out_cid->date = SD_CID_MDT(resp);
return ESP_OK;
}
esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd)
{
out_csd->csd_ver = SD_CSD_CSDVER(response);
switch (out_csd->csd_ver) {
case SD_CSD_CSDVER_2_0:
out_csd->capacity = SD_CSD_V2_CAPACITY(response);
out_csd->read_block_len = SD_CSD_V2_BL_LEN;
break;
case SD_CSD_CSDVER_1_0:
out_csd->capacity = SD_CSD_CAPACITY(response);
out_csd->read_block_len = SD_CSD_READ_BL_LEN(response);
break;
default:
ESP_LOGE(TAG, "unknown SD CSD structure version 0x%x", out_csd->csd_ver);
return ESP_ERR_NOT_SUPPORTED;
}
out_csd->card_command_class = SD_CSD_CCC(response);
int read_bl_size = 1 << out_csd->read_block_len;
out_csd->sector_size = MIN(read_bl_size, 512);
if (out_csd->sector_size < read_bl_size) {
out_csd->capacity *= read_bl_size / out_csd->sector_size;
}
int speed = SD_CSD_SPEED(response);
if (speed == SD_CSD_SPEED_50_MHZ) {
out_csd->tr_speed = 50000000;
} else {
out_csd->tr_speed = 25000000;
}
return ESP_OK;
}
esp_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 ESP_ERR_NOT_SUPPORTED;
}
out_scr->sd_spec = SCR_SD_SPEC(resp);
out_scr->erase_mem_state = SCR_DATA_STAT_AFTER_ERASE(resp);
out_scr->bus_width = SCR_SD_BUS_WIDTHS(resp);
return ESP_OK;
}
static const uint32_t s_au_to_size_kb[] = {
0, 16, 32, 64,
128, 256, 512, 1024,
2 * 1024, 4 * 1024,
8 * 1024, 12 * 1024,
16 * 1024, 24 * 1024,
32 * 1024, 64 * 1024
};
_Static_assert(sizeof(s_au_to_size_kb)/sizeof(s_au_to_size_kb[0]) == 16, "invalid number of elements in s_au_to_size_kb");
esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr)
{
uint32_t ssr[(SD_SSR_SIZE/sizeof(uint32_t))] = { 0 };
size_t j = (SD_SSR_SIZE/sizeof(uint32_t) - 1);
for(size_t i = 0; i < (SD_SSR_SIZE/sizeof(uint32_t)); i++) {
ssr[j - i] = __builtin_bswap32(raw_ssr[i]);
}
out_ssr->cur_bus_width = SSR_DAT_BUS_WIDTH(ssr);
out_ssr->discard_support = SSR_DISCARD_SUPPORT(ssr);
out_ssr->fule_support = SSR_FULE_SUPPORT(ssr);
uint32_t au = SSR_AU_SIZE(ssr);
out_ssr->alloc_unit_kb = s_au_to_size_kb[au];
out_ssr->erase_timeout = SSR_ERASE_TIMEOUT(ssr);
out_ssr->erase_size_au = SSR_ERASE_SIZE(ssr);
out_ssr->erase_offset = SSR_ERASE_OFFSET(ssr);
return ESP_OK;
}
uint32_t sdmmc_sd_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb)
{
if (arg == SDMMC_SD_DISCARD_ARG) {
return SDMMC_SD_DISCARD_TIMEOUT;
} else if (arg == SDMMC_SD_ERASE_ARG) {
if (card->ssr.alloc_unit_kb != 0 &&
card->ssr.erase_size_au != 0 &&
card->ssr.erase_timeout != 0 &&
card->ssr.erase_offset != 0) {
/* Card supports erase timeout estimation. See the erase timeout equation in SD spec. */
uint32_t timeout_sec = card->ssr.erase_offset +
card->ssr.erase_timeout * (erase_size_kb + card->ssr.alloc_unit_kb - 1) /
(card->ssr.erase_size_au * card->ssr.alloc_unit_kb);
ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, ES=%u, ET=%u, EO=%u, AU=%u kB)",
__func__, timeout_sec, erase_size_kb, card->ssr.erase_size_au,
card->ssr.erase_timeout, card->ssr.erase_offset, card->ssr.alloc_unit_kb);
return timeout_sec * 1000;
} else {
uint32_t timeout_ms = SDMMC_SD_DISCARD_TIMEOUT * erase_size_kb / card->csd.sector_size;
timeout_ms = MAX(1000, timeout_ms);
ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, %ums per sector)",
__func__, timeout_ms / 1000, erase_size_kb, SDMMC_SD_DISCARD_TIMEOUT);
return timeout_ms;
}
} else {
assert(false && "unexpected SD erase argument");
return 0;
}
}