398 lines
10 KiB
C
398 lines
10 KiB
C
/*
|
|
* 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/sd_spec.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
#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_count, uint32_t *blk_size)
|
|
{
|
|
uint32_t tmp_blk_count, 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_count = ((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_count = (tmp_blk_count * tmp_blk_size);
|
|
tmp_blk_size = SDMMC_DEFAULT_BLOCK_SIZE;
|
|
tmp_blk_count = (tmp_blk_count / tmp_blk_size);
|
|
}
|
|
if (blk_count) {
|
|
*blk_count = tmp_blk_count;
|
|
}
|
|
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_count = ((csd->device_size + 1U) * 1024U);
|
|
if (blk_count) {
|
|
*blk_count = tmp_blk_count;
|
|
}
|
|
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_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);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
/* Read card specific data register */
|
|
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;
|
|
}
|
|
|
|
/* Reads card identification register, and decodes it */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Implements signal voltage switch procedure described in section 3.6.1 of
|
|
* SD specification.
|
|
*/
|
|
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 = sd_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;
|
|
}
|
|
|
|
/*
|
|
* Requests card to publish a new relative card address, and move from
|
|
* identification to data mode
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Selects card, moving it into data transfer mode
|
|
*/
|
|
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 = sd_check_response(&cmd);
|
|
if (ret) {
|
|
LOG_DBG("CMD7 reports error");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|