| /* |
| * Copyright 2022 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/zephyr.h> |
| #include <zephyr/drivers/sdhc.h> |
| #include <zephyr/sd/sd.h> |
| #include <zephyr/sd/sdmmc.h> |
| #include <zephyr/sd/sd_spec.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/__assert.h> |
| |
| #include "sd_utils.h" |
| #include "sdmmc_priv.h" |
| |
| |
| LOG_MODULE_REGISTER(sd, CONFIG_SD_LOG_LEVEL); |
| |
| /* Idle all cards on bus. Can be used to clear errors on cards */ |
| static inline int sd_idle(struct sd_card *card) |
| { |
| struct sdhc_command cmd = {0}; |
| |
| /* Reset card with CMD0 */ |
| cmd.opcode = SD_GO_IDLE_STATE; |
| cmd.response_type = (SD_RSP_TYPE_NONE | SD_SPI_RSP_TYPE_R1); |
| cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; |
| return sdhc_request(card->sdhc, &cmd, NULL); |
| } |
| |
| /* Sends CMD8 during SD initialization */ |
| static int sd_send_interface_condition(struct sd_card *card) |
| { |
| struct sdhc_command cmd = {0}; |
| int ret; |
| uint32_t resp; |
| |
| cmd.opcode = SD_SEND_IF_COND; |
| cmd.arg = SD_IF_COND_VHS_3V3 | SD_IF_COND_CHECK; |
| cmd.response_type = (SD_RSP_TYPE_R7 | SD_SPI_RSP_TYPE_R7); |
| cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; |
| ret = sdhc_request(card->sdhc, &cmd, NULL); |
| if (ret) { |
| LOG_DBG("SD CMD8 failed with error %d", ret); |
| /* Retry */ |
| return SD_RETRY; |
| } |
| if (card->host_props.is_spi) { |
| resp = cmd.response[1]; |
| } else { |
| resp = cmd.response[0]; |
| } |
| if ((resp & 0xFF) != SD_IF_COND_CHECK) { |
| LOG_INF("Legacy card detected, no CMD8 support"); |
| /* Retry probe */ |
| return SD_RETRY; |
| } |
| if ((resp & SD_IF_COND_VHS_MASK) != SD_IF_COND_VHS_3V3) { |
| /* Card does not support 3.3V */ |
| return -ENOTSUP; |
| } |
| LOG_DBG("Found SDHC with CMD8 support"); |
| card->flags |= SD_SDHC_FLAG; |
| return 0; |
| } |
| |
| /* Sends CMD59 to enable CRC checking for SD card in SPI mode */ |
| static int sd_enable_crc(struct sd_card *card) |
| { |
| struct sdhc_command cmd = {0}; |
| |
| /* CMD59 for CRC mode is only valid for SPI hosts */ |
| __ASSERT_NO_MSG(card->host_props.is_spi); |
| cmd.opcode = SD_SPI_CRC_ON_OFF; |
| cmd.arg = 0x1; /* Enable CRC */ |
| cmd.response_type = SD_SPI_RSP_TYPE_R1; |
| cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; |
| return sdhc_request(card->sdhc, &cmd, NULL); |
| } |
| |
| /* |
| * Perform init required for both SD and SDIO cards. |
| * This function performs the following portions of SD initialization |
| * - CMD0 (SD reset) |
| * - CMD8 (SD voltage check) |
| */ |
| static int sd_common_init(struct sd_card *card) |
| { |
| int ret; |
| |
| /* Reset card with CMD0 */ |
| ret = sd_idle(card); |
| if (ret) { |
| LOG_ERR("Card error on CMD0"); |
| return ret; |
| } |
| /* Perform voltage check using SD CMD8 */ |
| ret = sd_retry(sd_send_interface_condition, card, CONFIG_SD_RETRY_COUNT); |
| if (ret == -ETIMEDOUT) { |
| LOG_INF("Card does not support CMD8, assuming legacy card"); |
| return sd_idle(card); |
| } else if (ret) { |
| LOG_ERR("Card error on CMD 8"); |
| return ret; |
| } |
| if (card->host_props.is_spi && |
| IS_ENABLED(CONFIG_SDHC_SUPPORTS_SPI_MODE)) { |
| /* Enable CRC for spi commands using CMD59 */ |
| ret = sd_enable_crc(card); |
| } |
| return ret; |
| } |
| |
| static int sd_init_io(struct sd_card *card) |
| { |
| struct sdhc_io *bus_io = &card->bus_io; |
| int ret; |
| |
| /* SD clock should start gated */ |
| bus_io->clock = 0; |
| /* SPI requires SDHC PUSH PULL, and open drain buses use more power */ |
| bus_io->bus_mode = SDHC_BUSMODE_PUSHPULL; |
| bus_io->power_mode = SDHC_POWER_ON; |
| bus_io->bus_width = SDHC_BUS_WIDTH1BIT; |
| /* Cards start with legacy timing and 3.3V signalling at power on */ |
| bus_io->timing = SDHC_TIMING_LEGACY; |
| bus_io->signal_voltage = SD_VOL_3_3_V; |
| |
| /* Toggle power to card to reset it */ |
| LOG_DBG("Resetting power to card"); |
| bus_io->power_mode = SDHC_POWER_OFF; |
| ret = sdhc_set_io(card->sdhc, bus_io); |
| if (ret) { |
| LOG_ERR("Could not disable card power via SDHC"); |
| return ret; |
| } |
| sd_delay(card->host_props.power_delay); |
| bus_io->power_mode = SDHC_POWER_ON; |
| ret = sdhc_set_io(card->sdhc, bus_io); |
| if (ret) { |
| LOG_ERR("Could not disable card power via SDHC"); |
| return ret; |
| } |
| /* After reset or init, card voltage should be 3.3V */ |
| card->card_voltage = SD_VOL_3_3_V; |
| /* Reset card flags */ |
| card->flags = 0U; |
| /* Delay so card can power up */ |
| sd_delay(card->host_props.power_delay); |
| /* Start bus clock */ |
| bus_io->clock = SDMMC_CLOCK_400KHZ; |
| ret = sdhc_set_io(card->sdhc, bus_io); |
| if (ret) { |
| LOG_ERR("Could not start bus clock"); |
| return ret; |
| } |
| return 0; |
| } |
| |
| /* |
| * Sends CMD5 to SD card, and uses response to determine if card |
| * is SDIO or SDMMC card. Return 0 if SDIO card, positive value if not, or |
| * negative errno on error |
| */ |
| int sd_test_sdio(struct sd_card *card) |
| { |
| struct sdhc_command cmd = {0}; |
| int ret; |
| |
| cmd.opcode = SDIO_SEND_OP_COND; |
| cmd.arg = 0; |
| cmd.response_type = (SD_RSP_TYPE_R4 | SD_SPI_RSP_TYPE_R4); |
| cmd.timeout_ms = CONFIG_SD_CMD_TIMEOUT; |
| ret = sdhc_request(card->sdhc, &cmd, NULL); |
| if (ret) { |
| /* |
| * We are just probing card, and it is likely an SD. |
| * return error |
| */ |
| card->type = CARD_SDMMC; |
| return SD_NOT_SDIO; |
| } |
| /* Check the number of I/O functions */ |
| card->num_io = ((cmd.response[0] & SDIO_OCR_IO_NUMBER) |
| >> SDIO_OCR_IO_NUMBER_SHIFT); |
| if ((card->num_io == 0) | ((cmd.response[0] & SDIO_IO_OCR_MASK) == 0)) { |
| if (cmd.response[0] & SDIO_OCR_MEM_PRESENT_FLAG) { |
| /* Card is not an SDIO card. */ |
| card->type = CARD_SDMMC; |
| return SD_NOT_SDIO; |
| } |
| /* Card is not a valid SD device. We do not support it */ |
| return -ENOTSUP; |
| } |
| /* Since we got a valid OCR response, |
| * we know this card is an SDIO card. |
| */ |
| card->type = CARD_SDIO; |
| return 0; |
| } |
| |
| /* |
| * Check SD card type |
| * Uses SDIO OCR response to determine what type of card is present. |
| */ |
| static int sd_check_card_type(struct sd_card *card) |
| { |
| int ret; |
| |
| /* Test if the card response to CMD5 (only SDIO cards will) */ |
| /* Note that CMD5 can take many retries */ |
| ret = sd_test_sdio(card); |
| if ((ret == SD_NOT_SDIO) && card->type == CARD_SDMMC) { |
| LOG_INF("Detected SD card"); |
| return 0; |
| } else if ((ret == 0) && card->type == CARD_SDIO) { |
| LOG_INF("Detected SDIO card"); |
| return 0; |
| } |
| LOG_ERR("No usable card type was found"); |
| return -ENOTSUP; |
| } |
| |
| |
| /* |
| * Performs init flow described in section 3.6 of SD specification. |
| */ |
| static int sd_command_init(struct sd_card *card) |
| { |
| int ret; |
| /* |
| * We must wait 74 clock cycles, per SD spec, to use card after power |
| * on. At 400000KHz, this is a 185us delay. Wait 1ms to be safe. |
| */ |
| sd_delay(1); |
| /* |
| * Start card initialization and identification |
| * flow described in section 3.6 of SD specification |
| */ |
| ret = sd_common_init(card); |
| if (ret) { |
| return ret; |
| } |
| /* Use CMD5 to determine card type */ |
| ret = sd_check_card_type(card); |
| if (ret) { |
| LOG_ERR("Unusable card"); |
| return -ENOTSUP; |
| } |
| if (card->type == CARD_SDMMC) { |
| /* |
| * Reset the card first- CMD5 sent to see if it is SDIO card |
| * may have left it in error state |
| */ |
| ret = sd_common_init(card); |
| if (ret) { |
| LOG_ERR("Init after CMD5 failed"); |
| return ret; |
| } |
| /* Perform memory card initialization */ |
| ret = sdmmc_card_init(card); |
| } else if (card->type == CARD_SDIO) { |
| LOG_ERR("SDIO cards not currently supported"); |
| return -ENOTSUP; |
| } |
| if (ret) { |
| LOG_ERR("Card init failed"); |
| return ret; |
| } |
| return 0; |
| } |
| |
| /* Initializes SD/SDIO card */ |
| int sd_init(const struct device *sdhc_dev, struct sd_card *card) |
| { |
| int ret; |
| |
| if (!sdhc_dev) { |
| return -ENODEV; |
| } |
| card->sdhc = sdhc_dev; |
| ret = sdhc_get_host_props(card->sdhc, &card->host_props); |
| if (ret) { |
| LOG_ERR("SD host controller returned invalid properties"); |
| return ret; |
| } |
| |
| /* init and lock card mutex */ |
| ret = k_mutex_init(&card->lock); |
| if (ret) { |
| LOG_DBG("Could not init card mutex"); |
| return ret; |
| } |
| ret = k_mutex_lock(&card->lock, K_MSEC(CONFIG_SD_INIT_TIMEOUT)); |
| if (ret) { |
| LOG_ERR("Timeout while trying to acquire card mutex"); |
| return ret; |
| } |
| |
| /* Initialize SDHC IO with defaults */ |
| ret = sd_init_io(card); |
| if (ret) { |
| k_mutex_unlock(&card->lock); |
| return ret; |
| } |
| |
| /* |
| * SD protocol is stateful, so we must account for the possibility |
| * that the card is in a bad state. The return code SD_RESTART |
| * indicates that the initialization left the card in a bad state. |
| * In this case the subsystem takes the following steps: |
| * - set card status to error |
| * - re init host I/O (will also toggle power to the SD card) |
| * - retry initialization once more |
| * If initialization then fails, the sd_init routine will assume the |
| * card is inaccessible |
| */ |
| ret = sd_command_init(card); |
| if (ret == SD_RESTART) { |
| /* Reset I/O, and retry sd initialization once more */ |
| card->status = CARD_ERROR; |
| /* Reset I/O to default */ |
| ret = sd_init_io(card); |
| if (ret) { |
| LOG_ERR("Failed to reset SDHC I/O"); |
| k_mutex_unlock(&card->lock); |
| return ret; |
| } |
| ret = sd_command_init(card); |
| if (ret) { |
| LOG_ERR("Failed to init SD card after I/O reset"); |
| k_mutex_unlock(&card->lock); |
| return ret; |
| } |
| } else if (ret != 0) { |
| /* Initialization failed */ |
| k_mutex_unlock(&card->lock); |
| card->status = CARD_ERROR; |
| return ret; |
| } |
| /* Card initialization succeeded. */ |
| card->status = CARD_INITIALIZED; |
| /* Unlock card mutex */ |
| ret = k_mutex_unlock(&card->lock); |
| if (ret) { |
| LOG_DBG("Could not unlock card mutex"); |
| return ret; |
| } |
| return ret; |
| } |
| |
| /* Return true if card is present, false otherwise */ |
| bool sd_is_card_present(const struct device *sdhc_dev) |
| { |
| if (!sdhc_dev) { |
| return false; |
| } |
| return sdhc_card_present(sdhc_dev) == 1; |
| } |