mirror of
https://github.com/jomjol/AI-on-the-edge-device.git
synced 2026-01-30 14:20:43 +03:00
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:
11
code/components/esp-sdmmc/CMakeLists.txt
Normal file
11
code/components/esp-sdmmc/CMakeLists.txt
Normal 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")
|
||||
279
code/components/esp-sdmmc/README.md
Normal file
279
code/components/esp-sdmmc/README.md
Normal 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
|
||||
|
||||

|
||||
|
||||
- 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).
|
||||
357
code/components/esp-sdmmc/include/sdmmc_cmd_mh.h
Normal file
357
code/components/esp-sdmmc/include/sdmmc_cmd_mh.h
Normal 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
|
||||
701
code/components/esp-sdmmc/sdmmc_cmd_mh.c
Normal file
701
code/components/esp-sdmmc/sdmmc_cmd_mh.c
Normal 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);
|
||||
}
|
||||
332
code/components/esp-sdmmc/sdmmc_common_mh.c
Normal file
332
code/components/esp-sdmmc/sdmmc_common_mh.c
Normal 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);
|
||||
}
|
||||
}
|
||||
154
code/components/esp-sdmmc/sdmmc_common_mh.h
Normal file
154
code/components/esp-sdmmc/sdmmc_common_mh.h
Normal 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);
|
||||
128
code/components/esp-sdmmc/sdmmc_init_mh.c
Normal file
128
code/components/esp-sdmmc/sdmmc_init_mh.c
Normal 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;
|
||||
}
|
||||
636
code/components/esp-sdmmc/sdmmc_io_mh.c
Normal file
636
code/components/esp-sdmmc/sdmmc_io_mh.c
Normal 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;
|
||||
}
|
||||
}
|
||||
300
code/components/esp-sdmmc/sdmmc_mmc_mh.c
Normal file
300
code/components/esp-sdmmc/sdmmc_mmc_mh.c
Normal 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;
|
||||
}
|
||||
449
code/components/esp-sdmmc/sdmmc_sd_mh.c
Normal file
449
code/components/esp-sdmmc/sdmmc_sd_mh.c
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user