From 64c5b93d5cf0168976680cfb95c09a966cfc6f06 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Wed, 16 Feb 2022 15:37:18 -0600 Subject: [PATCH] sd: Add sdmmc protocol stack Add SDMMC driver to subsystem. SDMMC driver will handle initialization, as well as SDMMC I/O. SD mode support is currently supported, SPI mode support is not. Signed-off-by: Daniel DeGrasse --- subsys/sd/CMakeLists.txt | 2 +- subsys/sd/Kconfig | 8 +- subsys/sd/sd.c | 2 +- subsys/sd/sdmmc.c | 1508 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1516 insertions(+), 4 deletions(-) create mode 100644 subsys/sd/sdmmc.c diff --git a/subsys/sd/CMakeLists.txt b/subsys/sd/CMakeLists.txt index 3d71356559e..f56f2dd4920 100644 --- a/subsys/sd/CMakeLists.txt +++ b/subsys/sd/CMakeLists.txt @@ -4,5 +4,5 @@ if (CONFIG_SD_STACK) zephyr_interface_library_named(SD) zephyr_library() - zephyr_library_sources (sd.c) + zephyr_library_sources (sd.c sdmmc.c) endif() diff --git a/subsys/sd/Kconfig b/subsys/sd/Kconfig index 1ac366b2d1e..04724b82dec 100644 --- a/subsys/sd/Kconfig +++ b/subsys/sd/Kconfig @@ -1,4 +1,4 @@ -# Copyright (c) 2022 NXP +# Copyright 2022 NXP # SPDX-License-Identifier: Apache-2.0 # SD stack configuration options @@ -48,7 +48,11 @@ config SD_DATA_TIMEOUT config SD_BUFFER_SIZE int - default 512 + # If SDHC required buffer alignment, we need a full block size in + # internal buffer + default 512 if SDHC_BUFFER_ALIGNMENT != 1 + # Otherwise, we only need 64 bytes to read SD switch function + default 64 help Size in bytes of internal buffer SD card uses for unaligned reads and internal data reads during initialization diff --git a/subsys/sd/sd.c b/subsys/sd/sd.c index 1395e7e6d70..d46b8cb66d3 100644 --- a/subsys/sd/sd.c +++ b/subsys/sd/sd.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 NXP + * Copyright 2022 NXP * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/subsys/sd/sdmmc.c b/subsys/sd/sdmmc.c new file mode 100644 index 00000000000..5cf029db79c --- /dev/null +++ b/subsys/sd/sdmmc.c @@ -0,0 +1,1508 @@ +/* + * Copyright 2022 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sd_utils.h" + +LOG_MODULE_DECLARE(sd, CONFIG_SD_LOG_LEVEL); + + +static inline void sdmmc_decode_csd(struct sd_csd *csd, + uint32_t *raw_csd, uint32_t *blk_cout, uint32_t *blk_size) +{ + uint32_t tmp_blk_cout, tmp_blk_size; + + 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; + } + + switch (csd->csd_structure) { + case 0: + csd->device_size = (uint32_t)((raw_csd[2U] & + 0x3FFU) << 2U); + csd->device_size |= (uint32_t)((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] & + 0x7000000U) >> 24U); + csd->write_current_min = (uint8_t)((raw_csd[1U] & + 0xE00000U) >> 20U); + csd->write_current_max = (uint8_t)((raw_csd[1U] & + 0x1C0000U) >> 18U); + csd->dev_size_mul = (uint8_t)((raw_csd[1U] & + 0x38000U) >> 15U); + + /* Get card total block count and block size. */ + tmp_blk_cout = ((csd->device_size + 1U) << + (csd->dev_size_mul + 2U)); + tmp_blk_size = (1U << (csd->read_blk_len)); + if (tmp_blk_size != SDMMC_DEFAULT_BLOCK_SIZE) { + tmp_blk_cout = (tmp_blk_cout * tmp_blk_size); + tmp_blk_size = SDMMC_DEFAULT_BLOCK_SIZE; + tmp_blk_cout = (tmp_blk_cout / tmp_blk_size); + } + if (blk_cout) { + *blk_cout = tmp_blk_cout; + } + if (blk_size) { + *blk_size = tmp_blk_size; + } + break; + case 1: + tmp_blk_size = SDMMC_DEFAULT_BLOCK_SIZE; + + csd->device_size = (uint32_t)((raw_csd[2U] & + 0x3FU) << 16U); + csd->device_size |= (uint32_t)((raw_csd[1U] & + 0xFFFF0000U) >> 16U); + + tmp_blk_cout = ((csd->device_size + 1U) * 1024U); + if (blk_cout) { + *blk_cout = tmp_blk_cout; + } + if (blk_size) { + *blk_size = tmp_blk_size; + } + break; + default: + break; + } + + if ((uint8_t)((raw_csd[1U] & 0x4000U) >> 14U)) { + csd->flags |= SD_CSD_ERASE_BLK_EN_FLAG; + } + csd->erase_size = (uint8_t)((raw_csd[1U] & + 0x3F80U) >> 7U); + csd->write_prtect_size = (uint8_t)(raw_csd[1U] & + 0x7FU); + csd->write_speed_factor = (uint8_t)((raw_csd[0U] & + 0x1C000000U) >> 26U); + csd->write_blk_len = (uint8_t)((raw_csd[0U] & + 0x3C00000U) >> 22U); + if ((uint8_t)((raw_csd[0U] & 0x200000U) >> 21U)) { + csd->flags |= SD_CSD_WRITE_BLK_PARTIAL_FLAG; + } + if ((uint8_t)((raw_csd[0U] & 0x8000U) >> 15U)) { + csd->flags |= SD_CSD_FILE_FMT_GRP_FLAG; + } + if ((uint8_t)((raw_csd[0U] & 0x4000U) >> 14U)) { + csd->flags |= SD_CSD_COPY_FLAG; + } + if ((uint8_t)((raw_csd[0U] & 0x2000U) >> 13U)) { + csd->flags |= SD_CSD_PERMANENT_WRITE_PROTECT_FLAG; + } + if ((uint8_t)((raw_csd[0U] & 0x1000U) >> 12U)) { + csd->flags |= SD_CSD_TMP_WRITE_PROTECT_FLAG; + } + csd->file_fmt = (uint8_t)((raw_csd[0U] & 0xC00U) >> 10U); +} + +static inline void sdmmc_decode_scr(struct sd_scr *scr, + uint32_t *raw_scr, uint32_t *version) +{ + uint32_t tmp_version = 0; + + scr->flags = 0U; + scr->scr_structure = (uint8_t)((raw_scr[0U] & 0xF0000000U) >> 28U); + scr->sd_spec = (uint8_t)((raw_scr[0U] & 0xF000000U) >> 24U); + if ((uint8_t)((raw_scr[0U] & 0x800000U) >> 23U)) { + scr->flags |= SD_SCR_DATA_STATUS_AFTER_ERASE; + } + scr->sd_sec = (uint8_t)((raw_scr[0U] & 0x700000U) >> 20U); + scr->sd_width = (uint8_t)((raw_scr[0U] & 0xF0000U) >> 16U); + if ((uint8_t)((raw_scr[0U] & 0x8000U) >> 15U)) { + scr->flags |= SD_SCR_SPEC3; + } + scr->sd_ext_sec = (uint8_t)((raw_scr[0U] & 0x7800U) >> 10U); + scr->cmd_support = (uint8_t)(raw_scr[0U] & 0x3U); + scr->rsvd = raw_scr[1U]; + /* Get specification version. */ + switch (scr->sd_spec) { + case 0U: + tmp_version = SD_SPEC_VER1_0; + break; + case 1U: + tmp_version = SD_SPEC_VER1_1; + break; + case 2U: + tmp_version = SD_SPEC_VER2_0; + if (scr->flags & SD_SCR_SPEC3) { + tmp_version = SD_SPEC_VER3_0; + } + break; + default: + break; + } + + if (version && tmp_version) { + *version = tmp_version; + } +} + +static inline void sdmmc_decode_cid(struct sd_cid *cid, + uint32_t *raw_cid) +{ + cid->manufacturer = (uint8_t)((raw_cid[3U] & 0xFF000000U) >> 24U); + cid->application = (uint16_t)((raw_cid[3U] & 0xFFFF00U) >> 8U); + + cid->name[0U] = (uint8_t)((raw_cid[3U] & 0xFFU)); + cid->name[1U] = (uint8_t)((raw_cid[2U] & 0xFF000000U) >> 24U); + cid->name[2U] = (uint8_t)((raw_cid[2U] & 0xFF0000U) >> 16U); + cid->name[3U] = (uint8_t)((raw_cid[2U] & 0xFF00U) >> 8U); + cid->name[4U] = (uint8_t)((raw_cid[2U] & 0xFFU)); + + cid->version = (uint8_t)((raw_cid[1U] & 0xFF000000U) >> 24U); + + cid->ser_num = (uint32_t)((raw_cid[1U] & 0xFFFFFFU) << 8U); + cid->ser_num |= (uint32_t)((raw_cid[0U] & 0xFF000000U) >> 24U); + + cid->date = (uint16_t)((raw_cid[0U] & 0xFFF00U) >> 8U); +} + +/* Checks SD status return codes */ +static inline int sdmmc_check_response(struct sdhc_command *cmd) +{ + if (cmd->response_type == SD_RSP_TYPE_R1) { + return (cmd->response[0U] & SD_R1_ERR_FLAGS); + } + return 0; +} + +/* Helper to send SD app command */ +static int sdmmc_app_command(struct sd_card *card, int relative_card_address) +{ + struct sdhc_command cmd = {0}; + int ret; + + cmd.opcode = SD_APP_CMD; + cmd.arg = relative_card_address << 16U; + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + /* We want to retry transmission */ + return SD_RETRY; + } + ret = sdmmc_check_response(&cmd); + if (ret) { + LOG_WRN("SD app command failed with R1 response of 0x%X", + cmd.response[0]); + return -EIO; + } + /* Check application command flag to determine if card is ready for APP CMD */ + if ((!card->host_props.is_spi) && !(cmd.response[0U] & SD_R1_APP_CMD)) { + /* Command succeeded, but card not ready for app command. No APP CMD support */ + return -ENOTSUP; + } + return 0; +} + +/* Reads OCR from SPI mode card using CMD58 */ +static int sdmmc_spi_send_ocr(struct sd_card *card, uint32_t arg) +{ + struct sdhc_command cmd; + int ret; + + cmd.opcode = SD_SPI_READ_OCR; + cmd.arg = arg; + cmd.response_type = SD_SPI_RSP_TYPE_R3; + + ret = sdhc_request(card->sdhc, &cmd, NULL); + + card->ocr = cmd.response[1]; + return ret; +} + +/* Sends OCR to card using ACMD41 */ +static int sdmmc_send_ocr(struct sd_card *card, int ocr_arg) +{ + struct sdhc_command cmd; + int ret; + int retries; + + cmd.opcode = SD_APP_SEND_OP_COND; + cmd.arg = ocr_arg; + cmd.response_type = (SD_RSP_TYPE_R3 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + /* Send initialization ACMD41 */ + for (retries = 0; retries < CONFIG_SD_OCR_RETRY_COUNT; retries++) { + ret = sdmmc_app_command(card, 0U); + if (ret == SD_RETRY) { + /* Retry */ + continue; + } else if (ret) { + return ret; + } + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + /* OCR failed */ + return ret; + } + if (ocr_arg == 0) { + /* Just probing, don't wait for card to exit busy state */ + return 0; + } + /* + * Check to see if card is busy with power up. PWR_BUSY + * flag will be cleared when card finishes power up sequence + */ + if (card->host_props.is_spi) { + if (!(cmd.response[0] & SD_SPI_R1IDLE_STATE)) { + break; + } + } else { + if ((cmd.response[0U] & SD_OCR_PWR_BUSY_FLAG)) { + break; + } + } + sd_delay(10); + } + if (retries >= CONFIG_SD_OCR_RETRY_COUNT) { + /* OCR timed out */ + LOG_ERR("Card never left busy state"); + return -ETIMEDOUT; + } + LOG_DBG("SDMMC responded to ACMD41 after %d attempts", retries); + if (!card->host_props.is_spi) { + /* Save OCR */ + card->ocr = cmd.response[0U]; + } + return 0; +} + +/* + * Implements signal voltage switch procedure described in section 3.6.1 of + * SD specification. + */ +static int sdmmc_switch_voltage(struct sd_card *card) +{ + int ret, sd_clock; + struct sdhc_command cmd = {0}; + + /* Check to make sure card supports 1.8V */ + if (!(card->flags & SD_1800MV_FLAG)) { + /* Do not attempt to switch voltages */ + LOG_WRN("SD card reports as SDHC/SDXC, but does not support 1.8V"); + return 0; + } + /* Send CMD11 to request a voltage switch */ + cmd.opcode = SD_VOL_SWITCH; + cmd.arg = 0U; + cmd.response_type = SD_RSP_TYPE_R1; + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + LOG_DBG("CMD11 failed"); + return ret; + } + /* Check R1 response for error */ + ret = sdmmc_check_response(&cmd); + if (ret) { + LOG_DBG("SD response to CMD11 indicates error"); + return ret; + } + /* + * Card should drive CMD and DAT[3:0] signals low at the next clock + * cycle. Some cards will only drive these + * lines low briefly, so we should check as soon as possible + */ + if (!(sdhc_card_busy(card->sdhc))) { + /* Delay 1ms to allow card to drive lines low */ + sd_delay(1); + if (!sdhc_card_busy(card->sdhc)) { + /* Card did not drive CMD and DAT lines low */ + LOG_DBG("Card did not drive DAT lines low"); + return -EAGAIN; + } + } + /* + * Per SD spec (section "Timing to Switch Signal Voltage"), + * host must gate clock at least 5ms. + */ + sd_clock = card->bus_io.clock; + card->bus_io.clock = 0; + ret = sdhc_set_io(card->sdhc, &card->bus_io); + if (ret) { + LOG_DBG("Failed to gate SD clock"); + return ret; + } + /* Now that clock is gated, change signal voltage */ + 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 SD host to 1.8V"); + return ret; + } + sd_delay(10); /* Gate for 10ms, even though spec requires 5 */ + /* Restart the clock */ + card->bus_io.clock = sd_clock; + ret = sdhc_set_io(card->sdhc, &card->bus_io); + if (ret) { + LOG_ERR("Failed to restart SD clock"); + return ret; + } + /* + * If SD does not drive at least one of + * DAT[3:0] high within 1ms, switch failed + */ + sd_delay(1); + if (sdhc_card_busy(card->sdhc)) { + LOG_DBG("Card failed to switch voltages"); + return -EAGAIN; + } + card->card_voltage = SD_VOL_1_8_V; + LOG_INF("Card switched to 1.8V signaling"); + return 0; +} + + +/* Reads card id/csd register (in SPI mode) */ +static int sdmmc_spi_read_cxd(struct sd_card *card, + uint32_t opcode, uint32_t *cxd) +{ + struct sdhc_command cmd = {0}; + struct sdhc_data data = {0}; + int ret, i; + /* Use internal card buffer for data transfer */ + uint32_t *cxd_be = (uint32_t *)card->card_buffer; + + cmd.opcode = opcode; + cmd.arg = 0; + cmd.response_type = SD_SPI_RSP_TYPE_R1; + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + /* CID/CSD is 16 bytes */ + data.block_size = 16; + data.blocks = 1U; + data.data = cxd_be; + data.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + ret = sdhc_request(card->sdhc, &cmd, &data); + if (ret) { + LOG_DBG("CMD%d failed: %d", opcode, ret); + } + /* Swap endianness of CXD */ + for (i = 0; i < 4; i++) { + cxd[3-i] = sys_be32_to_cpu(cxd_be[i]); + } + return 0; +} + +/* Reads card id/csd register (native SD mode */ +static int sdmmc_read_cxd(struct sd_card *card, + uint32_t opcode, uint32_t rca, uint32_t *cxd) +{ + struct sdhc_command cmd = {0}; + int ret; + + cmd.opcode = opcode; + cmd.arg = (rca << 16); + 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("CMD%d failed: %d", opcode, ret); + return ret; + } + + /* CSD/CID is 16 bytes */ + memcpy(cxd, cmd.response, 16); + return 0; +} + +/* Reads card identification register, and decodes it */ +static int sdmmc_read_cid(struct sd_card *card) +{ + uint32_t cid[4]; + int ret; + /* Keep CID on stack for reduced RAM usage */ + struct sd_cid card_cid; + + if (card->host_props.is_spi && IS_ENABLED(CONFIG_SDHC_SUPPORTS_SPI_MODE)) { + ret = sdmmc_spi_read_cxd(card, SD_SEND_CID, cid); + } else if (IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) { + ret = sdmmc_read_cxd(card, SD_ALL_SEND_CID, 0, cid); + } else { + /* The host controller must run in either native or SPI mode */ + return -ENOTSUP; + } + if (ret) { + return ret; + } + + /* Decode SD CID */ + sdmmc_decode_cid(&card_cid, cid); + LOG_DBG("Card MID: 0x%x, OID: %c%c", card_cid.manufacturer, + ((char *)&card_cid.application)[0], + ((char *)&card_cid.application)[1]); + return 0; +} + +/* + * Requests card to publish a new relative card address, and move from + * identification to data mode + */ +static int sdmmc_request_rca(struct sd_card *card) +{ + struct sdhc_command cmd = {0}; + int ret; + + cmd.opcode = SD_SEND_RELATIVE_ADDR; + cmd.arg = 0; + cmd.response_type = SD_RSP_TYPE_R6; + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + /* Issue CMD3 until card responds with nonzero RCA */ + do { + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + LOG_DBG("CMD3 failed"); + return ret; + } + /* Card RCA is in upper 16 bits of response */ + card->relative_addr = ((cmd.response[0U] & 0xFFFF0000) >> 16U); + } while (card->relative_addr == 0U); + LOG_DBG("Card relative addr: %d", card->relative_addr); + return 0; +} + +/* Read card specific data register */ +static int sdmmc_read_csd(struct sd_card *card) +{ + int ret; + uint32_t csd[4]; + /* Keep CSD on stack for reduced RAM usage */ + struct sd_csd card_csd; + + if (card->host_props.is_spi && IS_ENABLED(CONFIG_SDHC_SUPPORTS_SPI_MODE)) { + ret = sdmmc_spi_read_cxd(card, SD_SEND_CSD, csd); + } else if (IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) { + ret = sdmmc_read_cxd(card, SD_SEND_CSD, card->relative_addr, csd); + } else { + /* The host controller must run in either native or SPI mode */ + return -ENOTSUP; + } + if (ret) { + return ret; + } + + sdmmc_decode_csd(&card_csd, csd, + &card->block_count, &card->block_size); + LOG_DBG("Card block count %d, block size %d", + card->block_count, card->block_size); + return 0; +} + +static int sdmmc_select_card(struct sd_card *card) +{ + struct sdhc_command cmd = {0}; + int ret; + + cmd.opcode = SD_SELECT_CARD; + cmd.arg = (card->relative_addr << 16U); + cmd.response_type = SD_RSP_TYPE_R1; + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + LOG_DBG("CMD7 failed"); + return ret; + } + ret = sdmmc_check_response(&cmd); + if (ret) { + LOG_DBG("CMD7 reports error"); + return ret; + } + return 0; +} + +/* Reads SD configuration register */ +static int sdmmc_read_scr(struct sd_card *card) +{ + struct sdhc_command cmd = {0}; + struct sdhc_data data = {0}; + /* Place SCR struct on stack to reduce flash usage */ + struct sd_scr card_scr; + int ret; + /* DMA onto stack is unsafe, so we use an internal card buffer */ + uint32_t *scr = (uint32_t *)card->card_buffer; + uint32_t raw_scr[2]; + + ret = sdmmc_app_command(card, card->relative_addr); + if (ret) { + LOG_DBG("SD app command failed for SD SCR"); + return ret; + } + + cmd.opcode = SD_APP_SEND_SCR; + cmd.arg = 0; + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + data.block_size = 8U; + data.blocks = 1U; + data.data = scr; + data.timeout_ms = CONFIG_SD_DATA_TIMEOUT; + + ret = sdhc_request(card->sdhc, &cmd, &data); + if (ret) { + LOG_DBG("ACMD51 failed: %d", ret); + return ret; + } + /* Decode SCR */ + raw_scr[0] = sys_be32_to_cpu(scr[0]); + raw_scr[1] = sys_be32_to_cpu(scr[1]); + sdmmc_decode_scr(&card_scr, raw_scr, &card->sd_version); + LOG_DBG("SD reports specification version %d", card->sd_version); + /* Check card supported bus width */ + if (card_scr.sd_width & 0x4U) { + card->flags |= SD_4BITS_WIDTH; + } + /* Check if card supports speed class command (CMD20) */ + if (card_scr.cmd_support & 0x1U) { + card->flags |= SD_SPEED_CLASS_CONTROL_FLAG; + } + /* Check for set block count (CMD 23) support */ + if (card_scr.cmd_support & 0x2U) { + card->flags |= SD_CMD23_FLAG; + } + return 0; +} + +/* Sets block length of SD card */ +static int sdmmc_set_blocklen(struct sd_card *card, uint32_t block_len) +{ + struct sdhc_command cmd = {0}; + + cmd.opcode = SD_SET_BLOCK_SIZE; + cmd.arg = block_len; + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + + return sdhc_request(card->sdhc, &cmd, NULL); +} + +/* + * Sets bus width of host and card, following section 3.4 of + * SD host controller specification + */ +static int sdmmc_set_bus_width(struct sd_card *card, enum sdhc_bus_width width) +{ + struct sdhc_command cmd = {0}; + int ret; + + /* + * The specification strictly requires card interrupts to be masked, but + * Linux does not do so, so we won't either. + */ + /* Send ACMD6 to change bus width */ + ret = sdmmc_app_command(card, card->relative_addr); + if (ret) { + LOG_DBG("SD app command failed for ACMD6"); + return ret; + } + cmd.opcode = SD_APP_SET_BUS_WIDTH; + cmd.response_type = SD_RSP_TYPE_R1; + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + switch (width) { + case SDHC_BUS_WIDTH1BIT: + cmd.arg = 0U; + break; + case SDHC_BUS_WIDTH4BIT: + cmd.arg = 2U; + break; + default: + return -ENOTSUP; + } + /* Send app command */ + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + LOG_DBG("Error on ACMD6: %d", ret); + return ret; + } + ret = sdmmc_check_response(&cmd); + if (ret) { + LOG_DBG("ACMD6 reports error, response 0x%x", cmd.response[0U]); + return ret; + } + /* Card now has changed bus width. Change host bus width */ + card->bus_io.bus_width = width; + ret = sdhc_set_io(card->sdhc, &card->bus_io); + if (ret) { + LOG_DBG("Could not change host bus width"); + } + return ret; +} + +/* + * Sends SD switch function CMD6. + * See table 4-32 in SD physical specification for argument details. + * When setting a function, we should set the 4 bit block of the command + * argument corresponding to that function to "value", and all other 4 bit + * blocks should be left as 0xF (no effect on current function) + */ +static int sdmmc_switch(struct sd_card *card, enum sd_switch_arg mode, + enum sd_group_num group, uint8_t value, uint8_t *response) +{ + struct sdhc_command cmd = {0}; + struct sdhc_data data = {0}; + + cmd.opcode = SD_SWITCH; + cmd.arg = ((mode & 0x1) << 31) | 0x00FFFFFF; + cmd.arg &= ~(0xFU << (group * 4)); + cmd.arg |= (value & 0xF) << (group * 4); + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + data.block_size = 64U; + data.blocks = 1; + data.data = response; + data.timeout_ms = CONFIG_SD_DATA_TIMEOUT; + + return sdhc_request(card->sdhc, &cmd, &data); +} + +static int sdmmc_read_switch(struct sd_card *card) +{ + uint8_t *status; + int ret; + + if (card->sd_version < SD_SPEC_VER1_1) { + /* Switch not supported */ + LOG_INF("SD spec 1.01 does not support CMD6"); + return 0; + } + /* Use card internal buffer to read 64 byte switch data */ + status = card->card_buffer; + /* + * Setting switch to zero will read card's support values, + * otherwise known as SD "check function" + */ + ret = sdmmc_switch(card, SD_SWITCH_CHECK, 0, 0, status); + if (ret) { + LOG_DBG("CMD6 failed %d", ret); + return ret; + } + /* + * See table 4-11 and 4.3.10.4 of physical layer specification for + * bit definitions. Note that response is big endian, so index 13 will + * read bits 400-408. + * Bit n being set in support bit field indicates support for function + * number n on the card. (So 0x3 indicates support for functions 0 and 1) + */ + if (status[13] & HIGH_SPEED_BUS_SPEED) { + card->switch_caps.hs_max_dtr = HS_MAX_DTR; + } + if (card->sd_version >= SD_SPEC_VER3_0) { + card->switch_caps.bus_speed = status[13]; + card->switch_caps.sd_drv_type = status[9]; + card->switch_caps.sd_current_limit = status[7]; + } + return 0; +} + +/* Returns 1 if host supports UHS, zero otherwise */ +static inline int sdmmc_host_uhs(struct sdhc_host_props *props) +{ + return (props->host_caps.sdr50_support | + props->host_caps.uhs_2_support | + props->host_caps.sdr104_support | + props->host_caps.ddr50_support) + & (props->host_caps.vol_180_support); +} + +static inline void sdmmc_select_bus_speed(struct sd_card *card) +{ + /* + * Note that function support is defined using bitfields, but function + * selection is defined using values 0x0-0xF. + */ + if (card->host_props.host_caps.sdr104_support && + (card->switch_caps.bus_speed & UHS_SDR104_BUS_SPEED) && + (card->host_props.f_max >= SD_CLOCK_208MHZ)) { + card->card_speed = SD_TIMING_SDR104; + } else if (card->host_props.host_caps.ddr50_support && + (card->switch_caps.bus_speed & UHS_DDR50_BUS_SPEED) && + (card->host_props.f_max >= SD_CLOCK_50MHZ)) { + card->card_speed = SD_TIMING_DDR50; + } else if (card->host_props.host_caps.sdr50_support && + (card->switch_caps.bus_speed & UHS_SDR50_BUS_SPEED) && + (card->host_props.f_max >= SD_CLOCK_100MHZ)) { + card->card_speed = SD_TIMING_SDR50; + } else if (card->host_props.host_caps.high_spd_support && + (card->switch_caps.bus_speed & UHS_SDR12_BUS_SPEED) && + (card->host_props.f_max >= SD_CLOCK_25MHZ)) { + card->card_speed = SD_TIMING_SDR12; + } +} + +/* Selects driver type for SD card */ +static int sdmmc_select_driver_type(struct sd_card *card) +{ + int ret = 0; + uint8_t *status = card->card_buffer; + + /* + * We will only attempt to use driver type C over the default of type B, + * since it should result in lower current consumption if supported. + */ + if (card->host_props.host_caps.drv_type_c_support && + (card->switch_caps.sd_drv_type & SD_DRIVER_TYPE_C)) { + card->bus_io.driver_type = SD_DRIVER_TYPE_C; + /* Change drive strength */ + ret = sdmmc_switch(card, SD_SWITCH_SET, + SD_GRP_DRIVER_STRENGTH_MODE, + (find_msb_set(SD_DRIVER_TYPE_C) - 1), status); + } + return ret; +} + +/* Sets current limit for SD card */ +static int sdmmc_set_current_limit(struct sd_card *card) +{ + int ret; + int max_current = -1; + uint8_t *status = card->card_buffer; + + if ((card->card_speed != SD_TIMING_SDR50) && + (card->card_speed != SD_TIMING_SDR104) && + (card->card_speed != SD_TIMING_DDR50)) { + return 0; /* Cannot set current limit */ + } else if (card->host_props.max_current_180 >= 800 && + (card->switch_caps.sd_current_limit & SD_MAX_CURRENT_800MA)) { + max_current = SD_SET_CURRENT_800MA; + } else if (card->host_props.max_current_180 >= 600 && + (card->switch_caps.sd_current_limit & SD_MAX_CURRENT_600MA)) { + max_current = SD_SET_CURRENT_600MA; + } else if (card->host_props.max_current_180 >= 400 && + (card->switch_caps.sd_current_limit & SD_MAX_CURRENT_400MA)) { + max_current = SD_SET_CURRENT_400MA; + } else if (card->host_props.max_current_180 >= 200 && + (card->switch_caps.sd_current_limit & SD_MAX_CURRENT_200MA)) { + max_current = SD_SET_CURRENT_200MA; + } + if (max_current != -1) { + LOG_DBG("Changing SD current limit: %d", max_current); + /* Switch SD current */ + ret = sdmmc_switch(card, SD_SWITCH_SET, SD_GRP_CURRENT_LIMIT_MODE, + max_current, status); + if (ret) { + LOG_DBG("Failed to set SD current limit"); + return ret; + } + if (((status[15] >> 4) & 0x0F) != max_current) { + /* Status response indicates card did not select request limit */ + LOG_WRN("Card did not accept current limit"); + } + } + return 0; +} + +/* Applies selected card bus speed to card and host */ +static int sdmmc_set_bus_speed(struct sd_card *card) +{ + int ret; + int timing = 0; + uint8_t *status = card->card_buffer; + + switch (card->card_speed) { + /* Set bus clock speed */ + case SD_TIMING_SDR104: + card->switch_caps.uhs_max_dtr = SD_CLOCK_208MHZ; + timing = SDHC_TIMING_SDR104; + break; + case SD_TIMING_DDR50: + card->switch_caps.uhs_max_dtr = SD_CLOCK_50MHZ; + timing = SDHC_TIMING_DDR50; + break; + case SD_TIMING_SDR50: + card->switch_caps.uhs_max_dtr = SD_CLOCK_100MHZ; + timing = SDHC_TIMING_SDR50; + break; + case SD_TIMING_SDR25: + card->switch_caps.uhs_max_dtr = SD_CLOCK_50MHZ; + timing = SDHC_TIMING_SDR25; + break; + case SD_TIMING_SDR12: + card->switch_caps.uhs_max_dtr = SD_CLOCK_25MHZ; + timing = SDHC_TIMING_SDR12; + break; + default: + /* No need to change bus speed */ + return 0; + } + + /* Switch bus speed */ + ret = sdmmc_switch(card, SD_SWITCH_SET, SD_GRP_TIMING_MODE, + card->card_speed, status); + if (ret) { + LOG_DBG("Failed to switch SD card speed"); + return ret; + } + if ((status[16] & 0xF) != card->card_speed) { + LOG_WRN("Card did not accept new speed"); + } else { + /* Change host bus speed */ + card->bus_io.timing = timing; + card->bus_io.clock = card->switch_caps.uhs_max_dtr; + LOG_DBG("Setting bus clock to: %d", card->bus_io.clock); + ret = sdhc_set_io(card->sdhc, &card->bus_io); + if (ret) { + LOG_ERR("Failed to change host bus speed"); + return ret; + } + } + return 0; +} + +/* + * Init UHS capable SD card. Follows figure 3-16 in physical layer specification. + */ +static int sdmmc_init_uhs(struct sd_card *card) +{ + int ret; + + /* Raise bus width to 4 bits */ + ret = sdmmc_set_bus_width(card, SDHC_BUS_WIDTH4BIT); + if (ret) { + LOG_ERR("Failed to change card bus width to 4 bits"); + return ret; + } + + /* Select bus speed for card depending on host and card capability*/ + sdmmc_select_bus_speed(card); + /* Now, set the driver strength for the card */ + ret = sdmmc_select_driver_type(card); + if (ret) { + LOG_DBG("Failed to select new driver type"); + return ret; + } + ret = sdmmc_set_current_limit(card); + if (ret) { + LOG_DBG("Failed to set card current limit"); + return ret; + } + /* Apply the bus speed selected earlier */ + ret = sdmmc_set_bus_speed(card); + if (ret) { + LOG_DBG("Failed to set card bus speed"); + return ret; + } + if (card->card_speed == SD_TIMING_SDR50 || + card->card_speed == SD_TIMING_SDR104 || + card->card_speed == SD_TIMING_DDR50) { + /* SDR104, SDR50, and DDR50 mode need tuning */ + ret = sdhc_execute_tuning(card->sdhc); + if (ret) { + LOG_ERR("SD tuning failed: %d", ret); + } + } + return ret; +} + +/* Performs initialization for SD high speed cards */ +static int sdmmc_init_hs(struct sd_card *card) +{ + int ret; + + if ((!card->host_props.host_caps.high_spd_support) || + (card->sd_version < SD_SPEC_VER1_1) || + (card->switch_caps.hs_max_dtr == 0)) { + /* No high speed support. Leave card untouched */ + return 0; + } + card->card_speed = SD_TIMING_SDR25; + ret = sdmmc_set_bus_speed(card); + if (ret) { + LOG_ERR("Failed to switch card to HS mode"); + return ret; + } + return 0; +} + +/* + * Initializes SDMMC card. Note that the common SD function has already + * sent CMD0 and CMD8 to the card at function entry. + */ +int sdmmc_card_init(struct sd_card *card) +{ + int ret; + uint32_t ocr_arg = 0U; + + if (card->host_props.is_spi && IS_ENABLED(CONFIG_SDHC_SUPPORTS_SPI_MODE)) { + /* SD card needs CMD58 before ACMD41 to read OCR */ + ret = sdmmc_spi_send_ocr(card, 0); + if (ret) { + /* Card is not an SD card */ + return ret; + } + if (card->flags & SD_SDHC_FLAG) { + ocr_arg |= SD_OCR_HOST_CAP_FLAG; + } + ret = sdmmc_send_ocr(card, ocr_arg); + if (ret) { + return ret; + } + /* Send second CMD58 to get CCS bit */ + ret = sdmmc_spi_send_ocr(card, ocr_arg); + } else if (IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) { + /* Send initial probing OCR */ + ret = sdmmc_send_ocr(card, 0); + if (ret) { + /* Card is not an SD card */ + return ret; + } + if (card->flags & SD_SDHC_FLAG) { + /* High capacity card. See if host supports 1.8V */ + if (card->host_props.host_caps.vol_180_support) { + ocr_arg |= SD_OCR_SWITCH_18_REQ_FLAG; + } + /* Set host high capacity support flag */ + ocr_arg |= SD_OCR_HOST_CAP_FLAG; + } + /* Set voltage window */ + if (card->host_props.host_caps.vol_300_support) { + ocr_arg |= SD_OCR_VDD29_30FLAG; + } + ocr_arg |= (SD_OCR_VDD32_33FLAG | SD_OCR_VDD33_34FLAG); + /* Send SD OCR to card to initialize it */ + ret = sdmmc_send_ocr(card, ocr_arg); + } else { + return -ENOTSUP; + } + if (ret) { + LOG_ERR("Failed to query card OCR"); + return ret; + } + /* Check SD high capacity and 1.8V support flags */ + if (card->ocr & SD_OCR_CARD_CAP_FLAG) { + card->flags |= SD_HIGH_CAPACITY_FLAG; + } + if (card->ocr & SD_OCR_SWITCH_18_ACCEPT_FLAG) { + LOG_DBG("Card supports 1.8V signaling"); + card->flags |= SD_1800MV_FLAG; + } + /* Check OCR voltage window */ + if (card->ocr & SD_OCR_VDD29_30FLAG) { + card->flags |= SD_3000MV_FLAG; + } + /* + * If card is high capacity (SDXC or SDHC), and supports 1.8V signaling, + * switch to new signal voltage using "signal voltage switch procedure" + * described in SD specification + */ + if ((card->flags & SD_1800MV_FLAG) && + (card->host_props.host_caps.vol_180_support) && + (!card->host_props.is_spi) && + IS_ENABLED(CONFIG_SD_UHS_PROTOCOL)) { + ret = sdmmc_switch_voltage(card); + if (ret) { + /* Disable host support for 1.8 V */ + card->host_props.host_caps.vol_180_support = false; + /* + * The host or SD card may have already switched to + * 1.8V. Return SD_RESTART to indicate + * negotiation should be restarted. + */ + card->status = CARD_ERROR; + return SD_RESTART; + } + } + /* Read the card's CID (card identification register) */ + ret = sdmmc_read_cid(card); + if (ret) { + return ret; + } + if (!card->host_props.is_spi && IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) { + /* + * Request new relative card address. This moves the card from + * identification mode to data transfer mode + */ + ret = sdmmc_request_rca(card); + if (ret) { + return ret; + } + } + /* Card has entered data transfer mode. Get card specific data register */ + ret = sdmmc_read_csd(card); + if (ret) { + return ret; + } + if (!card->host_props.is_spi && IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) { + /* Move the card to transfer state (with CMD7) to run remaining commands */ + ret = sdmmc_select_card(card); + if (ret) { + return ret; + } + } + /* + * With card in data transfer state, we can set SD clock to maximum + * frequency for non high speed mode (25Mhz) + */ + if (card->host_props.f_max < SD_CLOCK_25MHZ) { + LOG_INF("Maximum SD clock is under 25MHz, using clock of %dHz", + card->host_props.f_max); + card->bus_io.clock = card->host_props.f_max; + } else { + card->bus_io.clock = SD_CLOCK_25MHZ; + } + ret = sdhc_set_io(card->sdhc, &card->bus_io); + if (ret) { + LOG_ERR("Failed to raise bus frequency to 25MHz"); + return ret; + } + /* Read SD SCR (SD configuration register), + * to get supported bus width + */ + ret = sdmmc_read_scr(card); + if (ret) { + return ret; + } + /* Read switch capabilities to determine what speeds card supports */ + if (!card->host_props.is_spi && IS_ENABLED(CONFIG_SDHC_SUPPORTS_NATIVE_MODE)) { + ret = sdmmc_read_switch(card); + if (ret) { + LOG_ERR("Failed to read card functions"); + return ret; + } + } + if ((card->flags & SD_1800MV_FLAG) && + sdmmc_host_uhs(&card->host_props) && + !(card->host_props.is_spi) && + IS_ENABLED(CONFIG_SD_UHS_PROTOCOL)) { + ret = sdmmc_init_uhs(card); + if (ret) { + LOG_ERR("UHS card init failed"); + } + } else { + if ((card->flags & SD_HIGH_CAPACITY_FLAG) == 0) { + /* Standard capacity SDSC card. set block length to 512 */ + ret = sdmmc_set_blocklen(card, SDMMC_DEFAULT_BLOCK_SIZE); + if (ret) { + LOG_ERR("Could not set SD blocklen to 512"); + return ret; + } + card->block_size = 512; + } + /* Card is not UHS. Try to use high speed mode */ + ret = sdmmc_init_hs(card); + if (ret) { + LOG_ERR("HS card init failed"); + } + } + return ret; +} + +/* Read card status. Return 0 if card is inactive */ +static int sdmmc_read_status(struct sd_card *card) +{ + struct sdhc_command cmd = {0}; + int ret; + + cmd.opcode = SD_SEND_STATUS; + if (!card->host_props.is_spi) { + cmd.arg = (card->relative_addr << 16U); + } + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R2); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + ret = sdhc_request(card->sdhc, &cmd, NULL); + if (ret) { + return SD_RETRY; + } + if (card->host_props.is_spi) { + /* Check R2 response bits */ + if ((cmd.response[0U] & SDHC_SPI_R2_CARD_LOCKED) || + (cmd.response[0U] & SDHC_SPI_R2_UNLOCK_FAIL)) { + return -EACCES; + } else if ((cmd.response[0U] & SDHC_SPI_R2_WP_VIOLATION) || + (cmd.response[0U] & SDHC_SPI_R2_ERASE_PARAM) || + (cmd.response[0U] & SDHC_SPI_R2_OUT_OF_RANGE)) { + return -EINVAL; + } else if ((cmd.response[0U] & SDHC_SPI_R2_ERR) || + (cmd.response[0U] & SDHC_SPI_R2_CC_ERR) || + (cmd.response[0U] & SDHC_SPI_R2_ECC_FAIL)) { + return -EIO; + } + /* Otherwise, no error in R2 response */ + return 0; + } + /* Otherwise, check native card response */ + if ((cmd.response[0U] & SD_R1_RDY_DATA) && + (SD_R1_CURRENT_STATE(cmd.response[0U]) == SDMMC_R1_TRANSFER)) { + return 0; + } + /* Valid response, the card is busy */ + return -EBUSY; +} + +/* Waits for SD card to be ready for data. Returns 0 if card is ready */ +static int sdmmc_wait_ready(struct sd_card *card) +{ + int ret, timeout = CONFIG_SD_DATA_TIMEOUT; + bool busy = true; + + do { + busy = sdhc_card_busy(card->sdhc); + if (!busy) { + /* Check card status */ + ret = sd_retry(sdmmc_read_status, card, CONFIG_SD_RETRY_COUNT); + busy = (ret != 0); + } else { + /* Delay 125us before polling again */ + k_busy_wait(125); + timeout -= 125; + } + } while (busy && (timeout > 0)); + return busy; +} + +static int sdmmc_read(struct sd_card *card, uint8_t *rbuf, + uint32_t start_block, uint32_t num_blocks) +{ + int ret; + struct sdhc_command cmd = {0}; + struct sdhc_data data = {0}; + + /* + * Note: The SD specification allows for CMD23 to be sent before a + * transfer in order to set the block length (often preferable). + * The specification also requires that CMD12 be sent to stop a transfer. + * However, the host specification defines support for "Auto CMD23" and + * "Auto CMD12", where the host sends CMD23 and CMD12 automatically to + * remove the overhead of interrupts in software from sending these + * commands. Therefore, we will not handle CMD12 or CMD23 at this layer. + * The host SDHC driver is expected to recognize CMD17, CMD18, CMD24, + * and CMD25 as special read/write commands and handle CMD23 and + * CMD12 appropriately. + */ + cmd.opcode = (num_blocks == 1U) ? SD_READ_SINGLE_BLOCK : SD_READ_MULTIPLE_BLOCK; + if (!(card->flags & SD_HIGH_CAPACITY_FLAG)) { + /* SDSC cards require block size in bytes, not blocks */ + cmd.arg = start_block * card->block_size; + } else { + cmd.arg = start_block; + } + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + cmd.retries = CONFIG_SD_DATA_RETRIES; + + data.block_addr = start_block; + data.block_size = card->block_size; + data.blocks = num_blocks; + data.data = rbuf; + data.timeout_ms = CONFIG_SD_DATA_TIMEOUT; + + LOG_DBG("READ: Sector = %u, Count = %u", start_block, num_blocks); + + ret = sdhc_request(card->sdhc, &cmd, &data); + if (ret) { + LOG_ERR("Failed to read from SDMMC %d", ret); + return ret; + } + + /* Verify card is back in transfer state after read */ + if (!card->host_props.is_spi) { + ret = sdmmc_wait_ready(card); + if (ret) { + LOG_ERR("Card did not return to ready state"); + k_mutex_unlock(&card->lock); + return -ETIMEDOUT; + } + } + return 0; +} + +/* Reads data from SD card memory card */ +int sdmmc_read_blocks(struct sd_card *card, uint8_t *rbuf, + uint32_t start_block, uint32_t num_blocks) +{ + int ret; + uint32_t rlen; + uint32_t sector; + uint8_t *buf_offset; + + if ((start_block + num_blocks) > card->block_count) { + return -EINVAL; + } + if (card->type == CARD_SDIO) { + LOG_WRN("SDIO does not support MMC commands"); + return -ENOTSUP; + } + ret = k_mutex_lock(&card->lock, K_NO_WAIT); + if (ret) { + LOG_WRN("Could not get SD card mutex"); + return -EBUSY; + } + + /* + * If the buffer we are provided with is aligned, we can use it + * directly. Otherwise, we need to use the card's internal buffer + * and memcpy the data back out + */ + if ((((uintptr_t)rbuf) & (CONFIG_SDHC_BUFFER_ALIGNMENT - 1)) != 0) { + /* lower bits of address are set, not aligned. Use internal buffer */ + LOG_DBG("Unaligned buffer access to SD card may incur performance penalty"); + if (sizeof(card->card_buffer) < card->block_size) { + LOG_ERR("Card buffer size needs to be increased for " + "unaligned writes to work"); + k_mutex_unlock(&card->lock); + return -ENOBUFS; + } + rlen = sizeof(card->card_buffer) / card->block_size; + sector = 0; + buf_offset = rbuf; + while (sector < num_blocks) { + /* Read from disk to card buffer */ + ret = sdmmc_read(card, card->card_buffer, + sector + start_block, rlen); + if (ret) { + LOG_ERR("Write failed"); + k_mutex_unlock(&card->lock); + return ret; + } + /* Copy data from card buffer */ + memcpy(buf_offset, card->card_buffer, rlen * card->block_size); + /* Increase sector count and buffer offset */ + sector += rlen; + buf_offset += rlen * card->block_size; + } + } else { + /* Aligned buffers can be used directly */ + ret = sdmmc_read(card, rbuf, start_block, num_blocks); + if (ret) { + LOG_ERR("Card read failed"); + k_mutex_unlock(&card->lock); + return ret; + } + } + k_mutex_unlock(&card->lock); + return 0; +} + +/* + * Sends ACMD22 (number of written blocks) to see how many blocks were written + * to a card + */ +static int sdmmc_query_written(struct sd_card *card, uint32_t *num_written) +{ + int ret; + struct sdhc_command cmd = {0}; + struct sdhc_data data = {0}; + uint32_t *blocks = (uint32_t *)card->card_buffer; + + ret = sdmmc_app_command(card, card->relative_addr); + if (ret) { + LOG_DBG("App CMD for ACMD22 failed"); + return ret; + } + + cmd.opcode = SD_APP_SEND_NUM_WRITTEN_BLK; + cmd.arg = 0; + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + + data.block_size = 4U; + data.blocks = 1U; + data.data = blocks; + data.timeout_ms = CONFIG_SD_DATA_TIMEOUT; + + ret = sdhc_request(card->sdhc, &cmd, &data); + if (ret) { + LOG_DBG("ACMD22 failed: %d", ret); + return ret; + } + ret = sdmmc_check_response(&cmd); + if (ret) { + LOG_DBG("ACMD22 reports error"); + return ret; + } + + /* Decode blocks */ + *num_written = sys_be32_to_cpu(blocks[0]); + return 0; +} + +static int sdmmc_write(struct sd_card *card, const uint8_t *wbuf, + uint32_t start_block, uint32_t num_blocks) +{ + int ret; + uint32_t blocks; + struct sdhc_command cmd = {0}; + struct sdhc_data data = {0}; + + /* + * See the note in sdmmc_read() above. We will not issue CMD23 + * or CMD12, and expect the host to handle those details. + */ + cmd.opcode = (num_blocks == 1) ? SD_WRITE_SINGLE_BLOCK : SD_WRITE_MULTIPLE_BLOCK; + if (!(card->flags & SD_HIGH_CAPACITY_FLAG)) { + /* SDSC cards require block size in bytes, not blocks */ + cmd.arg = start_block * card->block_size; + } else { + cmd.arg = start_block; + } + cmd.response_type = (SD_RSP_TYPE_R1 | SD_SPI_RSP_TYPE_R1); + cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; + cmd.retries = CONFIG_SD_DATA_RETRIES; + + data.block_addr = start_block; + data.block_size = card->block_size; + data.blocks = num_blocks; + data.data = (uint8_t *)wbuf; + data.timeout_ms = CONFIG_SD_DATA_TIMEOUT; + + LOG_DBG("WRITE: Sector = %u, Count = %u", start_block, num_blocks); + + ret = sdhc_request(card->sdhc, &cmd, &data); + if (ret) { + LOG_DBG("Write failed: %d", ret); + if (card->host_props.is_spi) { + /* Just check card status */ + ret = sdmmc_read_status(card); + } else { + /* Wait for card to be idle */ + ret = sdmmc_wait_ready(card); + } + if (ret) { + return ret; + } + /* Query card to see how many blocks were actually written */ + ret = sdmmc_query_written(card, &blocks); + if (ret) { + return ret; + } + LOG_ERR("Only %d blocks of %d were written", blocks, num_blocks); + return -EIO; + } + /* Verify card is back in transfer state after write */ + if (card->host_props.is_spi) { + /* Just check card status */ + ret = sdmmc_read_status(card); + } else { + /* Wait for card to be idle */ + ret = sdmmc_wait_ready(card); + } + if (ret) { + LOG_ERR("Card did not return to ready state"); + return -ETIMEDOUT; + } + return 0; +} + +/* Writes data to SD card memory card */ +int sdmmc_write_blocks(struct sd_card *card, const uint8_t *wbuf, + uint32_t start_block, uint32_t num_blocks) +{ + int ret; + uint32_t wlen; + uint32_t sector; + const uint8_t *buf_offset; + + if ((start_block + num_blocks) > card->block_count) { + return -EINVAL; + } + if (card->type == CARD_SDIO) { + LOG_WRN("SDIO does not support MMC commands"); + return -ENOTSUP; + } + ret = k_mutex_lock(&card->lock, K_NO_WAIT); + if (ret) { + LOG_WRN("Could not get SD card mutex"); + return -EBUSY; + } + /* + * If the buffer we are provided with is aligned, we can use it + * directly. Otherwise, we need to use the card's internal buffer + * and memcpy the data back out + */ + if ((((uintptr_t)wbuf) & (CONFIG_SDHC_BUFFER_ALIGNMENT - 1)) != 0) { + /* lower bits of address are set, not aligned. Use internal buffer */ + LOG_DBG("Unaligned buffer access to SD card may incur performance penalty"); + if (sizeof(card->card_buffer) < card->block_size) { + LOG_ERR("Card buffer size needs to be increased for " + "unaligned writes to work"); + k_mutex_unlock(&card->lock); + return -ENOBUFS; + } + wlen = sizeof(card->card_buffer) / card->block_size; + sector = 0; + buf_offset = wbuf; + while (sector < num_blocks) { + /* Copy data into card buffer */ + memcpy(card->card_buffer, buf_offset, wlen * card->block_size); + /* Write card buffer to disk */ + ret = sdmmc_write(card, card->card_buffer, + sector + start_block, wlen); + if (ret) { + LOG_ERR("Write failed"); + k_mutex_unlock(&card->lock); + return ret; + } + /* Increase sector count and buffer offset */ + sector += wlen; + buf_offset += wlen * card->block_size; + } + } else { + /* We can use aligned buffers directly */ + ret = sdmmc_write(card, wbuf, start_block, num_blocks); + if (ret) { + LOG_ERR("Write failed"); + k_mutex_unlock(&card->lock); + return ret; + } + } + k_mutex_unlock(&card->lock); + return 0; +} + + +/* IO Control handler for SD MMC */ +int sdmmc_ioctl(struct sd_card *card, uint8_t cmd, void *buf) +{ + switch (cmd) { + case DISK_IOCTL_GET_SECTOR_COUNT: + (*(uint32_t *)buf) = card->block_count; + break; + case DISK_IOCTL_GET_SECTOR_SIZE: + case DISK_IOCTL_GET_ERASE_BLOCK_SZ: + (*(uint32_t *)buf) = card->block_size; + break; + default: + return -ENOTSUP; + } + return 0; +}