blob: 8ea0b5746d78ec5a33be99320568cb154d25a956 [file] [log] [blame]
/*
* Copyright 2022 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/sdhc.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sd/mmc.h>
#include <zephyr/sd/sd.h>
#include <zephyr/sd/sd_spec.h>
#include "sd_ops.h"
#include "sd_utils.h"
/* These are used as specific arguments to commands */
/* For switch commands, the argument structure is:
* [31:26] : Set to 0
* [25:24] : Access
* [23:16] : Index (byte address in EXT_CSD)
* [15:8] : Value (data)
* [7:3] : Set to 0
* [2:0] : Cmd Set
*/
#define MMC_SWITCH_8_BIT_DDR_BUS_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (183U << 16)) + \
(0x0000FF00 & (6U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_SWITCH_8_BIT_BUS_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (183U << 16)) + \
(0x0000FF00 & (2U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_SWITCH_4_BIT_BUS_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (183U << 16)) + \
(0x0000FF00 & (1U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_SWITCH_HS_TIMING_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (185U << 16)) + \
(0x0000FF00 & (1U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_SWITCH_HS400_TIMING_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (185U << 16)) + \
(0x0000FF00 & (3U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_SWITCH_HS200_TIMING_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (185U << 16)) + \
(0x0000FF00 & (2U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_RCA_ARG (CONFIG_MMC_RCA << 16U)
#define MMC_REL_ADR_ARG (card->relative_addr << 16U)
#define MMC_SWITCH_PWR_CLASS_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (187U << 16)) + \
(0x0000FF00 & (0U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
#define MMC_SWITCH_CACHE_ON_ARG \
(0xFC000000 & (0U << 26)) + (0x03000000 & (0b11 << 24)) + (0x00FF0000 & (33U << 16)) + \
(0x0000FF00 & (1U << 8)) + (0x000000F7 & (0U << 3)) + (0x00000000 & (3U << 0))
LOG_MODULE_DECLARE(sd, CONFIG_SD_LOG_LEVEL);
inline int mmc_write_blocks(struct sd_card *card, const uint8_t *wbuf, uint32_t start_block,
uint32_t num_blocks)
{
return card_write_blocks(card, wbuf, start_block, num_blocks);
}
inline int mmc_read_blocks(struct sd_card *card, uint8_t *rbuf, uint32_t start_block,
uint32_t num_blocks)
{
return card_read_blocks(card, rbuf, start_block, num_blocks);
}
inline int mmc_ioctl(struct sd_card *card, uint8_t cmd, void *buf)
{
return card_ioctl(card, cmd, buf);
}
/* Sends CMD1 */
static int mmc_send_op_cond(struct sd_card *card, int ocr);
/* Sends CMD3 */
static int mmc_set_rca(struct sd_card *card);
/* Sends CMD9 */
static int mmc_read_csd(struct sd_card *card, struct sd_csd *card_csd);
static inline void mmc_decode_csd(struct sd_csd *csd, uint32_t *raw_csd);
/* Sends CMD8 */
static int mmc_read_ext_csd(struct sd_card *card, struct mmc_ext_csd *card_ext_csd);
static inline void mmc_decode_ext_csd(struct mmc_ext_csd *ext_csd, uint8_t *raw);
/* Sets SDHC max frequency in legacy timing */
static inline int mmc_set_max_freq(struct sd_card *card, struct sd_csd *card_csd);
/* Sends CMD6 to switch bus width*/
static int mmc_set_bus_width(struct sd_card *card);
/* Sets card to the fastest timing mode (using CMD6) and SDHC to max frequency */
static int mmc_set_timing(struct sd_card *card, struct mmc_ext_csd *card_ext_csd);
/* Enable cache for emmc if applicable */
static int mmc_set_cache(struct sd_card *card, struct mmc_ext_csd *card_ext_csd);
/*
* Initialize MMC card for use with subsystem
*/
int mmc_card_init(struct sd_card *card)
{
int ret = 0;
uint32_t ocr_arg = 0U;
/* Keep CSDs on stack for reduced RAM usage */
struct sd_csd card_csd = {0};
struct mmc_ext_csd card_ext_csd = {0};
/* SPI is not supported for MMC */
if (card->host_props.is_spi) {
return -EINVAL;
}
/* Probe to see if card is an MMC card */
ret = mmc_send_op_cond(card, ocr_arg);
if (ret) {
return ret;
}
/* Card is MMC card if no error
* (Only MMC protocol supports CMD1)
*/
card->type = CARD_MMC;
/* Set OCR Arguments */
if (card->host_props.host_caps.vol_180_support) {
ocr_arg |= MMC_OCR_VDD170_195FLAG;
}
if (card->host_props.host_caps.vol_330_support ||
card->host_props.host_caps.vol_300_support) {
ocr_arg |= MMC_OCR_VDD27_36FLAG;
}
/* Modern SDHC always at least supports 512 byte block sizes,
* which is enough to support sectors
*/
ocr_arg |= MMC_OCR_SECTOR_MODE;
/* CMD1 */
ret = mmc_send_op_cond(card, ocr_arg);
if (ret) {
LOG_ERR("Failed to query card OCR");
return ret;
}
/* CMD2 */
ret = card_read_cid(card);
if (ret) {
return ret;
}
/* CMD3 */
ret = mmc_set_rca(card);
if (ret) {
LOG_ERR("Failed on sending RCA to card");
return ret;
}
/* CMD9 */
ret = mmc_read_csd(card, &card_csd);
if (ret) {
return ret;
}
/* Set max bus clock in legacy timing to speed up initialization
* Currently only eMMC is supported for this command
* Legacy MMC cards will initialize slowly
*/
ret = mmc_set_max_freq(card, &card_csd);
if (ret) {
return ret;
}
/* CMD7 */
ret = sdmmc_select_card(card);
if (ret) {
return ret;
}
/* CMD6: Set bus width to max supported*/
ret = mmc_set_bus_width(card);
if (ret) {
return ret;
}
/* CMD8 */
ret = mmc_read_ext_csd(card, &card_ext_csd);
if (ret) {
return ret;
}
/* Set timing to fastest supported */
ret = mmc_set_timing(card, &card_ext_csd);
if (ret) {
return ret;
}
/* Turn on cache if it exists */
ret = mmc_set_cache(card, &card_ext_csd);
if (ret) {
return ret;
}
return 0;
}
static int mmc_send_op_cond(struct sd_card *card, int ocr)
{
struct sdhc_command cmd = {0};
int ret = 0;
int retries;
cmd.opcode = MMC_SEND_OP_COND;
cmd.arg = ocr;
cmd.response_type = SD_RSP_TYPE_R3;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
for (retries = 0;
retries < CONFIG_SD_OCR_RETRY_COUNT && !(cmd.response[0] & SD_OCR_PWR_BUSY_FLAG);
retries++) {
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
/* OCR failed */
return ret;
}
if (ocr == 0) {
/* Just probing */
return 0;
}
sd_delay(10);
}
if (retries >= CONFIG_SD_OCR_RETRY_COUNT) {
/* OCR timed out */
LOG_ERR("Card never left busy state");
return -ETIMEDOUT;
}
LOG_DBG("MMC responded to CMD1 after %d attempts", retries);
if (cmd.response[0] & MMC_OCR_VDD170_195FLAG) {
card->flags |= SD_1800MV_FLAG;
}
if (cmd.response[0] & MMC_OCR_VDD27_36FLAG) {
card->flags |= SD_3000MV_FLAG;
}
/* Switch to 1.8V */
if (card->host_props.host_caps.vol_180_support && (card->flags & SD_1800MV_FLAG)) {
card->bus_io.signal_voltage = SD_VOL_1_8_V;
ret = sdhc_set_io(card->sdhc, &card->bus_io);
if (ret) {
LOG_DBG("Failed to switch MMC host to 1.8V");
return ret;
}
sd_delay(10);
card->card_voltage = SD_VOL_1_8_V;
LOG_INF("Card switched to 1.8V signaling");
}
/* SD high Capacity is >2GB, the same as sector supporting MMC cards. */
if (cmd.response[0] & MMC_OCR_SECTOR_MODE) {
card->flags |= SD_HIGH_CAPACITY_FLAG;
}
return 0;
}
static int mmc_set_rca(struct sd_card *card)
{
struct sdhc_command cmd = {0};
int ret;
cmd.opcode = MMC_SEND_RELATIVE_ADDR;
cmd.arg = MMC_RCA_ARG;
cmd.response_type = SD_RSP_TYPE_R1;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
return ret;
}
ret = sd_check_response(&cmd);
if (ret) {
return ret;
}
card->relative_addr = CONFIG_MMC_RCA;
return 0;
}
static int mmc_read_csd(struct sd_card *card, struct sd_csd *card_csd)
{
int ret;
struct sdhc_command cmd = {0};
cmd.opcode = SD_SEND_CSD;
cmd.arg = MMC_REL_ADR_ARG;
cmd.response_type = SD_RSP_TYPE_R2;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
LOG_DBG("CMD9 failed: %d", ret);
return ret;
}
mmc_decode_csd(card_csd, cmd.response);
if (card_csd->csd_structure < 2) {
LOG_ERR("Legacy MMC cards are not supported.");
return -ENOTSUP;
}
return 0;
}
static inline void mmc_decode_csd(struct sd_csd *csd, uint32_t *raw_csd)
{
csd->csd_structure = (uint8_t)((raw_csd[3U] & 0xC0000000U) >> 30U);
csd->read_time1 = (uint8_t)((raw_csd[3U] & 0xFF0000U) >> 16U);
csd->read_time2 = (uint8_t)((raw_csd[3U] & 0xFF00U) >> 8U);
csd->xfer_rate = (uint8_t)(raw_csd[3U] & 0xFFU);
csd->cmd_class = (uint16_t)((raw_csd[2U] & 0xFFF00000U) >> 20U);
csd->read_blk_len = (uint8_t)((raw_csd[2U] & 0xF0000U) >> 16U);
if (raw_csd[2U] & 0x8000U) {
csd->flags |= SD_CSD_READ_BLK_PARTIAL_FLAG;
}
if (raw_csd[2U] & 0x4000U) {
csd->flags |= SD_CSD_READ_BLK_PARTIAL_FLAG;
}
if (raw_csd[2U] & 0x2000U) {
csd->flags |= SD_CSD_READ_BLK_MISALIGN_FLAG;
}
if (raw_csd[2U] & 0x1000U) {
csd->flags |= SD_CSD_DSR_IMPLEMENTED_FLAG;
}
csd->device_size =
(uint16_t)(((raw_csd[2U] & 0x3FFU) << 2U) + ((raw_csd[1U] & 0xC0000000U) >> 30U));
csd->read_current_min = (uint8_t)((raw_csd[1U] & 0x38000000U) >> 27U);
csd->read_current_max = (uint8_t)((raw_csd[1U] & 0x07000000U) >> 24U);
csd->write_current_min = (uint8_t)((raw_csd[1U] & 0x00E00000U) >> 21U);
csd->write_current_max = (uint8_t)((raw_csd[1U] & 0x001C0000U) >> 18U);
csd->dev_size_mul = (uint8_t)((raw_csd[1U] & 0x00038000U) >> 15U);
csd->erase_size = (uint8_t)((raw_csd[1U] & 0x00007C00U) >> 10U);
csd->write_prtect_size = (uint8_t)(raw_csd[1U] & 0x0000001FU);
csd->write_speed_factor = (uint8_t)((raw_csd[0U] & 0x1C000000U) >> 26U);
csd->write_blk_len = (uint8_t)((raw_csd[0U] & 0x03C00000U) >> 22U);
csd->file_fmt = (uint8_t)((raw_csd[0U] & 0x00000C00U) >> 10U);
}
static inline int mmc_set_max_freq(struct sd_card *card, struct sd_csd *card_csd)
{
int ret;
enum mmc_csd_freq frequency_code = (card_csd->xfer_rate & 0x7);
enum mmc_csd_freq multiplier_code = (card_csd->xfer_rate & 0x78);
/* 4.3 - 5.1 emmc spec says 26 MHz */
if (frequency_code == MMC_MAXFREQ_10MHZ && multiplier_code == MMC_MAXFREQ_MULT_26) {
card->bus_io.clock = 26000000U;
card->bus_io.timing = SDHC_TIMING_LEGACY;
}
/* 4.0 - 4.2 emmc spec says 20 MHz */
else if (frequency_code == MMC_MAXFREQ_10MHZ && multiplier_code == MMC_MAXFREQ_MULT_20) {
card->bus_io.clock = 20000000U;
card->bus_io.timing = SDHC_TIMING_LEGACY;
} else {
LOG_INF("Using Legacy MMC will have slow initialization");
return 0;
}
ret = sdhc_set_io(card->sdhc, &card->bus_io);
if (ret) {
LOG_ERR("Error seetting initial clock frequency");
return ret;
}
return 0;
}
static int mmc_set_bus_width(struct sd_card *card)
{
int ret;
struct sdhc_command cmd = {0};
if (card->host_props.host_caps.bus_8_bit_support && card->bus_width == 8) {
cmd.arg = MMC_SWITCH_8_BIT_BUS_ARG;
card->bus_io.bus_width = SDHC_BUS_WIDTH8BIT;
} else if (card->host_props.host_caps.bus_4_bit_support && card->bus_width >= 4) {
cmd.arg = MMC_SWITCH_4_BIT_BUS_ARG;
card->bus_io.bus_width = SDHC_BUS_WIDTH4BIT;
} else {
/* If only 1 bit bus is supported, nothing to be done */
return 0;
}
/* Set Card Bus Width */
cmd.opcode = SD_SWITCH;
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
sdmmc_wait_ready(card);
if (ret) {
LOG_ERR("Setting card data bus width failed: %d", ret);
return ret;
}
/* Set host controller bus width */
ret = sdhc_set_io(card->sdhc, &card->bus_io);
if (ret) {
LOG_ERR("Setting SDHC data bus width failed: %d", ret);
return ret;
}
return ret;
}
static int mmc_set_hs_timing(struct sd_card *card)
{
int ret = 0;
struct sdhc_command cmd = {0};
/* Change Card Timing Mode */
cmd.opcode = SD_SWITCH;
cmd.arg = MMC_SWITCH_HS_TIMING_ARG;
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
LOG_DBG("Error setting bus timing: %d", ret);
return ret;
}
sdmmc_wait_ready(card);
/* Max frequency in HS mode is 52 MHz */
card->bus_io.clock = MMC_CLOCK_52MHZ;
card->bus_io.timing = SDHC_TIMING_HS;
/* Change SDHC bus timing */
ret = sdhc_set_io(card->sdhc, &card->bus_io);
if (ret) {
return ret;
}
return ret;
}
static int mmc_set_power_class_HS200(struct sd_card *card, struct mmc_ext_csd *ext)
{
int ret = 0;
struct sdhc_command cmd = {0};
cmd.opcode = SD_SWITCH;
cmd.arg = ((MMC_SWITCH_PWR_CLASS_ARG) | (ext->pwr_class_200MHZ_VCCQ195 << 8));
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
sdmmc_wait_ready(card);
return ret;
}
static int mmc_set_timing(struct sd_card *card, struct mmc_ext_csd *ext)
{
int ret = 0;
struct sdhc_command cmd = {0};
/* Timing depends on EXT_CSD register information */
if ((ext->device_type.MMC_HS200_SDR_1200MV || ext->device_type.MMC_HS200_SDR_1800MV) &&
(card->host_props.host_caps.hs200_support) &&
(card->bus_io.signal_voltage == SD_VOL_1_8_V) &&
(card->bus_io.bus_width >= SDHC_BUS_WIDTH4BIT)) {
ret = mmc_set_hs_timing(card);
if (ret) {
return ret;
}
cmd.arg = MMC_SWITCH_HS200_TIMING_ARG;
card->bus_io.clock = MMC_CLOCK_HS200;
card->bus_io.timing = SDHC_TIMING_HS200;
} else if (ext->device_type.MMC_HS_52_DV) {
return mmc_set_hs_timing(card);
} else if (ext->device_type.MMC_HS_26_DV) {
/* Nothing to do, card is already configured for this */
return 0;
} else {
return -ENOTSUP;
}
/* Set card timing mode */
cmd.opcode = SD_SWITCH;
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
LOG_DBG("Error setting bus timing: %d", ret);
return ret;
}
ret = sdmmc_wait_ready(card);
if (ret) {
return ret;
}
card->card_speed = ((cmd.arg & 0xFF << 8) >> 8);
/* Set power class to match timing mode */
if (card->card_speed == MMC_HS200_TIMING) {
ret = mmc_set_power_class_HS200(card, ext);
if (ret) {
return ret;
}
}
/* Set SDHC bus io parameters */
ret = sdhc_set_io(card->sdhc, &card->bus_io);
if (ret) {
return ret;
}
/* Execute Tuning for HS200 */
if (card->card_speed == MMC_HS200_TIMING) {
ret = sdhc_execute_tuning(card->sdhc);
if (ret) {
LOG_ERR("MMC Tuning failed: %d", ret);
return ret;
}
}
/* Switch to HS400 if applicable */
if ((ext->device_type.MMC_HS400_DDR_1200MV || ext->device_type.MMC_HS400_DDR_1800MV) &&
(card->host_props.host_caps.hs400_support) &&
(card->bus_io.bus_width == SDHC_BUS_WIDTH8BIT)) {
/* Switch back to regular HS timing */
ret = mmc_set_hs_timing(card);
if (ret) {
LOG_ERR("Switching MMC back to HS from HS200 during HS400 init failed.");
return ret;
}
/* Set bus width to DDR 8 bit */
cmd.opcode = SD_SWITCH;
cmd.arg = MMC_SWITCH_8_BIT_DDR_BUS_ARG;
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
sdmmc_wait_ready(card);
if (ret) {
LOG_ERR("Setting DDR data bus width failed during HS400 init: %d", ret);
return ret;
}
/* Set card timing mode to HS400 */
cmd.opcode = SD_SWITCH;
cmd.arg = MMC_SWITCH_HS400_TIMING_ARG;
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
LOG_DBG("Error setting card to HS400 bus timing: %d", ret);
return ret;
}
ret = sdmmc_wait_ready(card);
if (ret) {
return ret;
}
/* Set SDHC bus io parameters */
card->bus_io.clock = MMC_CLOCK_HS400;
card->bus_io.timing = SDHC_TIMING_HS400;
ret = sdhc_set_io(card->sdhc, &card->bus_io);
if (ret) {
return ret;
}
card->card_speed = ((cmd.arg & 0xFF << 8) >> 8);
}
return ret;
}
static int mmc_read_ext_csd(struct sd_card *card, struct mmc_ext_csd *card_ext_csd)
{
int ret;
struct sdhc_command cmd = {0};
struct sdhc_data data = {0};
cmd.opcode = MMC_SEND_EXT_CSD;
cmd.arg = 0;
cmd.response_type = SD_RSP_TYPE_R1;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
data.block_size = MMC_EXT_CSD_BYTES;
data.blocks = 1;
data.data = card->card_buffer;
data.timeout_ms = CONFIG_SD_DATA_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, &data);
if (ret) {
LOG_ERR("CMD8 (send_ext_csd) failed: %d", ret);
return ret;
}
mmc_decode_ext_csd(card_ext_csd, card->card_buffer);
card->block_count = card_ext_csd->sec_count;
card->block_size = SDMMC_DEFAULT_BLOCK_SIZE;
LOG_INF("Card block count is %d, block size is %d", card->block_count, card->block_size);
return 0;
}
static inline void mmc_decode_ext_csd(struct mmc_ext_csd *ext, uint8_t *raw)
{
ext->sec_count =
(raw[215U] << 24U) + (raw[214U] << 16U) + (raw[213U] << 8U) + (raw[212U] << 0U);
ext->bus_width = raw[183U];
ext->hs_timing = raw[185U];
ext->device_type.MMC_HS400_DDR_1200MV = ((1 << 7U) & raw[196U]);
ext->device_type.MMC_HS400_DDR_1800MV = ((1 << 6U) & raw[196U]);
ext->device_type.MMC_HS200_SDR_1200MV = ((1 << 5U) & raw[196U]);
ext->device_type.MMC_HS200_SDR_1800MV = ((1 << 4U) & raw[196U]);
ext->device_type.MMC_HS_DDR_1200MV = ((1 << 3U) & raw[196U]);
ext->device_type.MMC_HS_DDR_1800MV = ((1 << 2U) & raw[196U]);
ext->device_type.MMC_HS_52_DV = ((1 << 1U) & raw[196U]);
ext->device_type.MMC_HS_26_DV = ((1 << 0U) & raw[196U]);
ext->rev = raw[192U];
ext->power_class = (raw[187] & 0x0F);
ext->mmc_driver_strengths = raw[197U];
ext->pwr_class_200MHZ_VCCQ195 = raw[237U];
ext->cache_size =
(raw[252] << 24U) + (raw[251] << 16U) + (raw[250] << 8U) + (raw[249] << 0U);
}
static int mmc_set_cache(struct sd_card *card, struct mmc_ext_csd *card_ext_csd)
{
int ret = 0;
struct sdhc_command cmd = {0};
/* If there is no cache, don't use cache */
if (card_ext_csd->cache_size == 0) {
return 0;
}
/* CMD6 to write to EXT CSD to turn on cache */
cmd.opcode = SD_SWITCH;
cmd.arg = MMC_SWITCH_CACHE_ON_ARG;
cmd.response_type = SD_RSP_TYPE_R1b;
cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT;
ret = sdhc_request(card->sdhc, &cmd, NULL);
if (ret) {
LOG_DBG("Error turning on card cache: %d", ret);
return ret;
}
ret = sdmmc_wait_ready(card);
return ret;
}