2018-06-20 11:59:11 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2023-10-17 14:13:32 +00:00
|
|
|
#include <inttypes.h>
|
2018-06-20 11:59:11 +00:00
|
|
|
#include "sdmmc_common.h"
|
2019-07-12 05:20:04 +00:00
|
|
|
#include "esp_attr.h"
|
2019-10-15 21:01:05 +00:00
|
|
|
#include "esp_compiler.h"
|
2019-07-12 05:20:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
#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;
|
2018-06-20 11:59:11 +00:00
|
|
|
|
|
|
|
static const char* TAG = "sdmmc_io";
|
|
|
|
|
2019-07-12 05:20:04 +00:00
|
|
|
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),
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2018-06-20 11:59:11 +00:00
|
|
|
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) {
|
2023-04-24 14:06:01 +00:00
|
|
|
ESP_LOGD(TAG, "%s: Combination card", __func__);
|
|
|
|
card->is_mem = 1;
|
|
|
|
} else {
|
2018-06-20 11:59:11 +00:00
|
|
|
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)
|
|
|
|
{
|
2018-12-28 18:04:37 +00:00
|
|
|
/* If the host is configured to use low frequency, don't attempt to switch */
|
|
|
|
if (card->host.max_freq_khz < SDMMC_FREQ_DEFAULT) {
|
2018-06-20 11:59:11 +00:00
|
|
|
card->max_freq_khz = card->host.max_freq_khz;
|
|
|
|
return ESP_OK;
|
2018-12-28 18:04:37 +00:00
|
|
|
} else if (card->host.max_freq_khz < SDMMC_FREQ_HIGHSPEED) {
|
|
|
|
card->max_freq_khz = SDMMC_FREQ_DEFAULT;
|
|
|
|
return ESP_OK;
|
2018-06-20 11:59:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
2019-10-15 21:01:05 +00:00
|
|
|
if (unlikely(ret != ESP_OK)) {
|
2023-10-17 14:13:32 +00:00
|
|
|
ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (read 0x%" PRIx32 ") returned 0x%x", __func__, addr, ret);
|
2018-06-20 11:59:11 +00:00
|
|
|
}
|
|
|
|
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);
|
2019-10-15 21:01:05 +00:00
|
|
|
if (unlikely(ret != ESP_OK)) {
|
2023-10-17 14:13:32 +00:00
|
|
|
ESP_LOGE(TAG, "%s: sdmmc_io_rw_direct (write 0x%" PRIu32 ") returned 0x%x", __func__, addr, ret);
|
2018-06-20 11:59:11 +00:00
|
|
|
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;
|
2024-01-01 18:57:13 +00:00
|
|
|
const int buflen = (datalen + 3) & (~3); //round up to 4
|
2018-06-20 11:59:11 +00:00
|
|
|
sdmmc_command_t cmd = {
|
|
|
|
.flags = SCF_CMD_AC | SCF_RSP_R5,
|
|
|
|
.arg = 0,
|
|
|
|
.opcode = SD_IO_RW_EXTENDED,
|
|
|
|
.data = datap,
|
|
|
|
.datalen = datalen,
|
2024-01-01 18:57:13 +00:00
|
|
|
.buflen = buflen,
|
|
|
|
.blklen = SDMMC_IO_BLOCK_SIZE /* TODO: read max block size from CIS */
|
2018-06-20 11:59:11 +00:00
|
|
|
};
|
|
|
|
|
2024-01-01 18:57:13 +00:00
|
|
|
if (unlikely(datalen > 0 && !esp_dma_is_buffer_aligned(datap, buflen, ESP_DMA_BUF_LOCATION_AUTO))) {
|
|
|
|
if (datalen > SDMMC_IO_BLOCK_SIZE || card->host.dma_aligned_buffer == NULL) {
|
|
|
|
// User gives unaligned buffer while `SDMMC_HOST_FLAG_ALLOC_ALIGNED_BUF` not set.
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
|
|
|
}
|
|
|
|
memset(card->host.dma_aligned_buffer, 0xcc, SDMMC_IO_BLOCK_SIZE);
|
|
|
|
memcpy(card->host.dma_aligned_buffer, datap, datalen);
|
|
|
|
cmd.data = card->host.dma_aligned_buffer;
|
|
|
|
cmd.buflen = SDMMC_IO_BLOCK_SIZE;
|
|
|
|
}
|
|
|
|
|
2018-06-20 11:59:11 +00:00
|
|
|
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 {
|
2024-01-01 18:57:13 +00:00
|
|
|
if (datalen > SDMMC_IO_BLOCK_SIZE) {
|
2018-06-20 11:59:11 +00:00
|
|
|
/* TODO: split into multiple operations? */
|
|
|
|
return ESP_ERR_INVALID_SIZE;
|
|
|
|
}
|
2024-01-01 18:57:13 +00:00
|
|
|
if (datalen == SDMMC_IO_BLOCK_SIZE) {
|
2018-06-20 11:59:11 +00:00
|
|
|
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);
|
2024-01-01 18:57:13 +00:00
|
|
|
|
|
|
|
if (datalen > 0 && cmd.data == card->host.dma_aligned_buffer) {
|
|
|
|
assert(datalen <= SDMMC_IO_BLOCK_SIZE);
|
|
|
|
memcpy(datap, card->host.dma_aligned_buffer, datalen);
|
|
|
|
}
|
|
|
|
|
2018-06-20 11:59:11 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-18 23:22:07 +00:00
|
|
|
// Note: sdmmc_io_rw_extended has an internal timeout,
|
|
|
|
// typically SDMMC_DEFAULT_CMD_TIMEOUT_MS
|
2018-06-20 11:59:11 +00:00
|
|
|
esp_err_t err = sdmmc_io_rw_extended(card, function, addr,
|
|
|
|
SD_ARG_CMD53_READ | SD_ARG_CMD53_INCREMENT,
|
|
|
|
pc_dst, will_transfer);
|
2019-10-15 21:01:05 +00:00
|
|
|
if (unlikely(err != ESP_OK)) {
|
2018-06-20 11:59:11 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-18 23:22:07 +00:00
|
|
|
// Note: sdmmc_io_rw_extended has an internal timeout,
|
|
|
|
// typically SDMMC_DEFAULT_CMD_TIMEOUT_MS
|
2018-06-20 11:59:11 +00:00
|
|
|
esp_err_t err = sdmmc_io_rw_extended(card, function, addr,
|
|
|
|
SD_ARG_CMD53_WRITE | SD_ARG_CMD53_INCREMENT,
|
|
|
|
(void*) pc_src, will_transfer);
|
2019-10-15 21:01:05 +00:00
|
|
|
if (unlikely(err != ESP_OK)) {
|
2018-06-20 11:59:11 +00:00
|
|
|
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)
|
|
|
|
{
|
2024-01-01 18:57:13 +00:00
|
|
|
if (unlikely(!esp_dma_is_buffer_aligned(dst, size, ESP_DMA_BUF_LOCATION_INTERNAL))) {
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
2018-06-20 11:59:11 +00:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
{
|
2024-01-01 18:57:13 +00:00
|
|
|
if (unlikely(!esp_dma_is_buffer_aligned(src, size, ESP_DMA_BUF_LOCATION_INTERNAL))) {
|
|
|
|
return ESP_ERR_INVALID_ARG;
|
2018-06-20 11:59:11 +00:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
}
|
2019-07-12 05:20:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
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];
|
|
|
|
|
2019-10-21 13:34:34 +00:00
|
|
|
/* Pointer to size is a mandatory parameter */
|
|
|
|
assert(inout_cis_size);
|
|
|
|
|
2019-07-12 05:20:04 +00:00
|
|
|
/*
|
|
|
|
* 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;
|
2019-10-21 13:34:34 +00:00
|
|
|
if (*inout_cis_size != 0) {
|
2019-07-12 05:20:04 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|