diff --git a/drivers/sdhc/CMakeLists.txt b/drivers/sdhc/CMakeLists.txt index 9e699ed43dc..8dbd84bc6bf 100644 --- a/drivers/sdhc/CMakeLists.txt +++ b/drivers/sdhc/CMakeLists.txt @@ -1,7 +1,8 @@ -# Copyright (c) 2022 NXP +# Copyright 2022 NXP # SPDX-License-Identifier: Apache-2.0 if (CONFIG_SDHC) zephyr_library() zephyr_library_sources_ifdef(CONFIG_IMX_USDHC imx_usdhc.c) +zephyr_library_sources_ifdef(CONFIG_SPI_SDHC sdhc_spi.c) endif() diff --git a/drivers/sdhc/Kconfig b/drivers/sdhc/Kconfig index bc688bdb97e..7e059d6a9ec 100644 --- a/drivers/sdhc/Kconfig +++ b/drivers/sdhc/Kconfig @@ -9,6 +9,7 @@ menuconfig SDHC if SDHC source "drivers/sdhc/Kconfig.imx" +source "drivers/sdhc/Kconfig.spi" config SDHC_INIT_PRIORITY int "SDHC driver init priority" diff --git a/drivers/sdhc/Kconfig.spi b/drivers/sdhc/Kconfig.spi new file mode 100644 index 00000000000..ea73e278d5b --- /dev/null +++ b/drivers/sdhc/Kconfig.spi @@ -0,0 +1,12 @@ +# Copyright (c) 2022, NXP +# SPDX -License-Identifier: Apache-2.0 + +DT_COMPAT_ZEPHYR_MMC_SPI_SLOT := zephyr,sdhc-spi-slot + +config SPI_SDHC + bool "SD protocol over SPI bus" + select SPI + select SDHC_SUPPORTS_SPI_MODE + default $(dt_compat_enabled,$(DT_COMPAT_ZEPHYR_MMC_SPI_SLOT)) + help + Enable the SPI SD host controller driver diff --git a/drivers/sdhc/sdhc_spi.c b/drivers/sdhc/sdhc_spi.c new file mode 100644 index 00000000000..2d64da2cad8 --- /dev/null +++ b/drivers/sdhc/sdhc_spi.c @@ -0,0 +1,771 @@ +/* + * Copyright 2022 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_sdhc_spi_slot + + + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(sdhc_spi, CONFIG_SDHC_LOG_LEVEL); + +#define MAX_CMD_READ 21 +#define SPI_R1B_TIMEOUT_MS 3000 +#define SD_SPI_SKIP_RETRIES 1000000 + +/* The SD protocol requires sending ones while reading but Zephyr + * defaults to writing zeros. This block of 512 bytes is used for writing + * 0xff while we read data blocks. Should remain const so this buffer is + * stored in flash. + */ +static const uint8_t sdhc_ones[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +BUILD_ASSERT(sizeof(sdhc_ones) == 512, "0xFF array for SDHC must be 512 bytes"); + +struct sdhc_spi_config { + const struct device *spi_dev; + const struct gpio_dt_spec pwr_gpio; + const uint32_t spi_max_freq; +}; + +struct sdhc_spi_data { + enum sdhc_power power_mode; + struct spi_config *spi_cfg; + struct spi_config cfg_a; + struct spi_config cfg_b; + uint8_t scratch[MAX_CMD_READ]; +}; + +/* Receives a block of bytes */ +static int sdhc_spi_rx(const struct device *spi_dev, struct spi_config *spi_cfg, + uint8_t *buf, int len) +{ + struct spi_buf tx_bufs[] = { + { + .buf = (uint8_t *)sdhc_ones, + .len = len + } + }; + + const struct spi_buf_set tx = { + .buffers = tx_bufs, + .count = 1, + }; + + struct spi_buf rx_bufs[] = { + { + .buf = buf, + .len = len + } + }; + + const struct spi_buf_set rx = { + .buffers = rx_bufs, + .count = 1, + }; + + return spi_transceive(spi_dev, spi_cfg, &tx, &rx); +} + +static int sdhc_spi_init_card(const struct device *dev) +{ + /* SD spec requires at least 74 clocks be send to SD to start it. + * for SPI protocol, this will be performed by sending 10 0xff values + * to the card (this should result in 80 SCK cycles) + */ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *data = dev->data; + struct spi_config *spi_cfg = data->spi_cfg; + int ret; + + if (spi_cfg->frequency == 0) { + /* Use default 400KHZ frequency */ + spi_cfg->frequency = SDMMC_CLOCK_400KHZ; + } + /* the initial 74 clocks must be sent while CS is high */ + spi_cfg->operation |= SPI_CS_ACTIVE_HIGH; + ret = sdhc_spi_rx(config->spi_dev, spi_cfg, data->scratch, 10); + if (ret != 0) { + spi_cfg->operation &= ~SPI_CS_ACTIVE_HIGH; + return ret; + } + /* Release lock on SPI bus */ + ret = spi_release(config->spi_dev, spi_cfg); + spi_cfg->operation &= ~SPI_CS_ACTIVE_HIGH; + return ret; +} + +/* Waits for SPI SD card to stop sending busy signal */ +static int sdhc_spi_wait_unbusy(const struct device *dev, + int timeout_ms, + int interval_ticks) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *data = dev->data; + int ret; + uint8_t response; + + while (timeout_ms > 0) { + ret = sdhc_spi_rx(config->spi_dev, data->spi_cfg, &response, 1); + if (ret) { + return ret; + } + if (response == 0xFF) { + return 0; + } + k_msleep(k_ticks_to_ms_floor32(interval_ticks)); + timeout_ms -= k_ticks_to_ms_floor32(interval_ticks); + } + return -ETIMEDOUT; +} + + +/* Read SD command from SPI response */ +static int sdhc_spi_response_get(const struct device *dev, struct sdhc_command *cmd, + int rx_len) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *dev_data = dev->data; + uint8_t *response = dev_data->scratch; + uint8_t *end = response + rx_len; + int ret; + uint8_t value, i; + + /* First step is finding the first valid byte of the response. + * All SPI responses start with R1, which will have MSB of zero. + * we know we can ignore the first 7 bytes, which hold the command and + * initial "card ready" byte. + */ + response += 8; + while (response < end && ((*response & SD_SPI_START) == SD_SPI_START)) { + response++; + } + if (response == end) { + /* Some cards are slow, and need more time to respond. Continue + * with single byte reads until the card responds. + */ + response = dev_data->scratch; + end = response + 1; + for (i = 0; i < 16; i++) { + ret = sdhc_spi_rx(config->spi_dev, dev_data->spi_cfg, + response, 1); + if (ret < 0) { + return ret; + } + if (*response != 0xff) { + break; + } + } + if (*response == 0xff) { + return -ETIMEDOUT; + } + } + /* Record R1 response */ + cmd->response[0] = *response++; + /* Check response for error */ + if (cmd->response[0] != 0) { + if (cmd->response[0] & (SD_SPI_R1PARAMETER_ERR | SD_SPI_R1ADDRESS_ERR)) { + return -EFAULT; /* Bad address */ + } else if (cmd->response[0] & (SD_SPI_R1ILLEGAL_CMD_ERR)) { + return -EINVAL; /* Invalid command */ + } else if (cmd->response[0] & (SD_SPI_R1CMD_CRC_ERR)) { + return -EILSEQ; /* Illegal byte sequence */ + } else if (cmd->response[0] & (SD_SPI_R1ERASE_SEQ_ERR | SD_SPI_R1ERASE_RESET)) { + return -EIO; + } + /* else IDLE_STATE bit is set, which is not an error, card is just resetting */ + } + switch ((cmd->response_type & 0xF0)) { + case SD_SPI_RSP_TYPE_R1: + /* R1 response - one byte*/ + break; + case SD_SPI_RSP_TYPE_R1b: + /* R1b response - one byte plus busy signal */ + /* Read remaining bytes to see if card is still busy. + * card will be ready when it stops driving data out + * low. + */ + while (response < end && (*response == 0x0)) { + response++; + } + if (response == end) { + value = cmd->timeout_ms; + response--; + /* Periodically check busy line */ + ret = sdhc_spi_wait_unbusy(dev, + SPI_R1B_TIMEOUT_MS, 1000); + } + break; + case SD_SPI_RSP_TYPE_R2: + case SD_SPI_RSP_TYPE_R5: + /* R2/R5 response - R1 response + 1 byte*/ + if (response == end) { + response = dev_data->scratch; + end = response + 1; + /* Read the next byte */ + ret = sdhc_spi_rx(config->spi_dev, + dev_data->spi_cfg, + response, 1); + if (ret) { + return ret; + } + } + cmd->response[0] = (*response) << 8; + break; + case SD_SPI_RSP_TYPE_R3: + case SD_SPI_RSP_TYPE_R4: + case SD_SPI_RSP_TYPE_R7: + /* R3/R4/R7 response - R1 response + 4 bytes */ + cmd->response[1] = 0; + for (i = 0; i < 4; i++) { + cmd->response[1] <<= 8; + /* Read bytes of response */ + if (response == end) { + response = dev_data->scratch; + end = response + 1; + /* Read the next byte */ + ret = sdhc_spi_rx(config->spi_dev, + dev_data->spi_cfg, + response, 1); + if (ret) { + return ret; + } + } + cmd->response[1] |= *response++; + } + break; + default: + /* Other RSP types not supported */ + return -ENOTSUP; + } + return 0; +} + +/* Send SD command using SPI */ +static int sdhc_spi_send_cmd(const struct device *dev, struct sdhc_command *cmd, + bool data_present) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *dev_data = dev->data; + int err; + uint8_t *cmd_buf; + /* To reduce overhead, we will send entire command in one SPI + * transaction. The packet takes the following format: + * - all ones byte to ensure card is ready + * - opcode byte (which includes start and transmission bits) + * - 4 bytes for argument + * - crc7 byte (with end bit) + * The SD card can take up to 8 bytes worth of SCLK cycles to respond. + * therefore, we provide 8 bytes of all ones, to read data from the card. + * the maximum spi response length is 5 bytes, so we provide an + * additional 5 bytes of data, leaving us with 13 bytes of 0xff. + * Finally, we send a padding byte of all 0xff, to ensure that + * the card recives at least one 0xff byte before next command. + */ + + /* Note: we can discard CMD data as we send it, + * so resuse the TX buf as RX + */ + struct spi_buf bufs[] = { + { + .buf = dev_data->scratch, + .len = sizeof(dev_data->scratch), + }, + }; + + const struct spi_buf_set buf_set = { + .buffers = bufs, + .count = 1, + }; + + + if (data_present) { + /* We cannot send extra SCLK cycles with our command, + * since we'll miss the data the card responds with. We + * send one 0xff byte, six command bytes, two additional 0xff + * bytes, since the min value of NCR (see SD SPI timing + * diagrams) is one, and we know there will be an R1 response. + */ + bufs[0].len = SD_SPI_CMD_SIZE + 3; + } + memset(dev_data->scratch, 0xFF, sizeof(dev_data->scratch)); + cmd_buf = dev_data->scratch + 1; + + /* Command packet holds the following bits: + * [47]: start bit, 0b0 + * [46]: transmission bit, 0b1 + * [45-40]: command index + * [39-8]: argument + * [7-1]: CRC + * [0]: end bit, 0b1 + * Note that packets are sent MSB first. + */ + /* Add start bit, tx bit, and cmd opcode */ + cmd_buf[0] = (cmd->opcode & SD_SPI_CMD); + cmd_buf[0] = ((cmd_buf[0] | SD_SPI_TX) & ~SD_SPI_START); + /* Add argument */ + sys_put_be32(cmd->arg, &cmd_buf[1]); + /* Add CRC, and set LSB as the end bit */ + cmd_buf[SD_SPI_CMD_BODY_SIZE] = crc7_be(0, cmd_buf, SD_SPI_CMD_BODY_SIZE) | 0x1; + LOG_DBG("cmd%d arg 0x%x", cmd->opcode, cmd->arg); + /* Set data, will lock SPI bus */ + err = spi_transceive(config->spi_dev, dev_data->spi_cfg, &buf_set, &buf_set); + if (err != 0) { + return err; + } + /* Read command response */ + return sdhc_spi_response_get(dev, cmd, bufs[0].len); +} + +/* Skips bytes in SDHC data stream. */ +static int sdhc_skip(const struct device *dev, uint8_t skip_val) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *data = dev->data; + uint8_t buf; + int ret; + uint32_t retries = SD_SPI_SKIP_RETRIES; + + do { + ret = sdhc_spi_rx(config->spi_dev, data->spi_cfg, + &buf, sizeof(buf)); + if (ret) { + return ret; + } + } while (buf == skip_val && retries--); + if (retries == 0) { + return -ETIMEDOUT; + } + /* Return first non-skipped value */ + return buf; +} + +/* Handles reading data from SD SPI device */ +static int sdhc_spi_read_data(const struct device *dev, struct sdhc_data *data) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *dev_data = dev->data; + uint8_t *read_location = data->data; + uint32_t remaining = data->blocks; + int ret; + uint8_t crc[SD_SPI_CRC16_SIZE + 1]; + + /* The SPI API defaults to sending 0x00 when no TX buffer is + * provided, so we are limited to 512 byte reads + * (unless we increase the size of SDHC buffer) + */ + const struct spi_buf tx_bufs[] = { + { + .buf = (uint8_t *)sdhc_ones, + .len = data->block_size, + }, + }; + + const struct spi_buf_set tx = { + .buffers = tx_bufs, + .count = 1, + }; + + struct spi_buf rx_bufs[] = { + { + .buf = read_location, + .len = data->block_size, + } + }; + + const struct spi_buf_set rx = { + .buffers = rx_bufs, + .count = 1, + }; + + if (data->block_size > 512) { + /* SPI max BLKLEN is 512 */ + return -ENOTSUP; + } + + /* Read bytes until data stream starts. SD will send 0xff until + * data is available + */ + ret = sdhc_skip(dev, 0xff); + if (ret < 0) { + return ret; + } + /* Check token */ + if (ret != SD_SPI_TOKEN_SINGLE) { + return -EIO; + } + + /* Read blocks until we are out of data */ + while (remaining--) { + ret = spi_transceive(config->spi_dev, + dev_data->spi_cfg, &tx, &rx); + if (ret) { + LOG_ERR("Data write failed"); + return ret; + } + /* Read CRC16 plus one end byte */ + ret = sdhc_spi_rx(config->spi_dev, dev_data->spi_cfg, + crc, sizeof(crc)); + if (crc16_itu_t(0, read_location, data->block_size) != + sys_get_be16(crc)) { + /* Bad CRC */ + LOG_ERR("Bad data CRC"); + return -EILSEQ; + } + /* Advance read location */ + read_location += data->block_size; + rx_bufs[0].buf = read_location; + if (remaining) { + /* Check next data token */ + ret = sdhc_skip(dev, 0xff); + if (ret != SD_SPI_TOKEN_SINGLE) { + LOG_ERR("Bad token"); + return -EIO; + } + } + } + return ret; +} + +/* Handles writing data to SD SPI device */ +static int sdhc_spi_write_data(const struct device *dev, struct sdhc_data *data) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *dev_data = dev->data; + int ret; + uint8_t token, resp; + uint8_t *write_location = data->data, crc[SD_SPI_CRC16_SIZE]; + uint32_t remaining = data->blocks; + + struct spi_buf tx_bufs[] = { + { + .buf = &token, + .len = sizeof(uint8_t), + }, + { + .buf = write_location, + .len = data->block_size, + }, + { + .buf = crc, + .len = sizeof(crc), + }, + }; + + struct spi_buf_set tx = { + .buffers = tx_bufs, + .count = 3, + }; + + /* Set the token- single block reads use different token + * than multibock + */ + if (remaining > 1) { + token = SD_SPI_TOKEN_MULTI_WRITE; + } else { + token = SD_SPI_TOKEN_SINGLE; + } + + while (remaining--) { + /* Build the CRC for this data block */ + sys_put_be16(crc16_itu_t(0, write_location, data->block_size), + crc); + ret = spi_write(config->spi_dev, dev_data->spi_cfg, &tx); + if (ret) { + return ret; + } + /* Read back the data response token from the card */ + ret = sdhc_spi_rx(config->spi_dev, dev_data->spi_cfg, + &resp, sizeof(resp)); + if (ret) { + return ret; + } + /* Check response token */ + if ((resp & 0xF) != SD_SPI_RESPONSE_ACCEPTED) { + if ((resp & 0xF) == SD_SPI_RESPONSE_CRC_ERR) { + return -EILSEQ; + } else if ((resp & 0xF) == SD_SPI_RESPONSE_WRITE_ERR) { + return -EIO; + } + LOG_DBG("Unknown write response token 0x%x", resp); + return -EIO; + } + /* Advance write location */ + write_location += data->block_size; + tx_bufs[1].buf = write_location; + /* Wait for card to stop being busy */ + ret = sdhc_spi_wait_unbusy(dev, data->timeout_ms, 0); + if (ret) { + return ret; + } + } + if (data->blocks > 1) { + /* Write stop transfer token to card */ + token = SD_SPI_TOKEN_STOP_TRAN; + tx.count = 1; + ret = spi_write(config->spi_dev, dev_data->spi_cfg, &tx); + if (ret) { + return ret; + } + /* Wait for card to stop being busy */ + ret = sdhc_spi_wait_unbusy(dev, data->timeout_ms, 0); + if (ret) { + return ret; + } + } + return 0; +} + +static int sdhc_spi_request(const struct device *dev, + struct sdhc_command *cmd, + struct sdhc_data *data) +{ + const struct sdhc_spi_config *config = dev->config; + struct sdhc_spi_data *dev_data = dev->data; + int ret, retries = cmd->retries; + const struct sdhc_command stop_cmd = { + .opcode = SD_STOP_TRANSMISSION, + .arg = 0, + .response_type = SD_SPI_RSP_TYPE_R1b, + .timeout_ms = 1000, + .retries = 1, + }; + if (data == NULL) { + do { + ret = sdhc_spi_send_cmd(dev, cmd, false); + } while ((ret != 0) && (retries-- > 0)); + } else { + do { + ret = sdhc_spi_send_cmd(dev, cmd, true); + if (ret) { + continue; + } + if ((cmd->opcode == SD_WRITE_SINGLE_BLOCK) || + (cmd->opcode == SD_WRITE_MULTIPLE_BLOCK)) { + ret = sdhc_spi_write_data(dev, data); + } else { + ret = sdhc_spi_read_data(dev, data); + } + if (ret || (cmd->opcode == SD_READ_MULTIPLE_BLOCK)) { + /* CMD12 is required after multiple read, or + * to retry failed transfer + */ + sdhc_spi_send_cmd(dev, + (struct sdhc_command *)&stop_cmd, + false); + } + } while ((ret != 0) && (retries-- > 0)); + } + if (ret) { + return ret; + } + /* Release SPI bus */ + return spi_release(config->spi_dev, dev_data->spi_cfg); +} + +static int sdhc_spi_set_io(const struct device *dev, struct sdhc_io *ios) +{ + const struct sdhc_spi_config *cfg = dev->config; + struct sdhc_spi_data *data = dev->data; + + if (ios->clock != data->spi_cfg->frequency) { + if (ios->clock > cfg->spi_max_freq) { + return -ENOTSUP; + } + /* Because pointer comparision is used, we have to + * swap to a new configuration structure to reconfigure SPI. + */ + if (ios->clock != 0) { + if (data->spi_cfg == &data->cfg_a) { + data->cfg_a.frequency = ios->clock; + memcpy(&data->cfg_b, &data->cfg_a, + sizeof(struct spi_config)); + data->spi_cfg = &data->cfg_b; + } else { + data->cfg_b.frequency = ios->clock; + memcpy(&data->cfg_a, &data->cfg_b, + sizeof(struct spi_config)); + data->spi_cfg = &data->cfg_a; + } + } + } + if (ios->bus_mode != SDHC_BUSMODE_PUSHPULL) { + /* SPI mode supports push pull */ + return -ENOTSUP; + } + if (data->power_mode != ios->power_mode) { + if (ios->power_mode == SDHC_POWER_ON) { + /* Send 74 clock cycles to start card */ + if (sdhc_spi_init_card(dev) != 0) { + LOG_ERR("Card SCLK init sequence failed"); + return -EIO; + } + } + if (cfg->pwr_gpio.port) { + /* If power control GPIO is defined, toggle SD power */ + if (ios->power_mode == SDHC_POWER_ON) { + if (gpio_pin_set_dt(&cfg->pwr_gpio, 1)) { + return -EIO; + } + } else { + if (gpio_pin_set_dt(&cfg->pwr_gpio, 0)) { + return -EIO; + } + } + } + data->power_mode = ios->power_mode; + } + if (ios->bus_width != SDHC_BUS_WIDTH1BIT) { + /* SPI mode supports 1 bit bus */ + return -ENOTSUP; + } + if (ios->signal_voltage != SD_VOL_3_3_V) { + /* SPI mode does not support UHS voltages */ + return -ENOTSUP; + } + return 0; +} + +static int sdhc_spi_get_card_present(const struct device *dev) +{ + /* SPI has no card presence method, assume card is in slot */ + return 1; +} + +static int sdhc_spi_get_host_props(const struct device *dev, + struct sdhc_host_props *props) +{ + const struct sdhc_spi_config *cfg = dev->config; + + memset(props, 0, sizeof(struct sdhc_host_props)); + + props->f_min = SDMMC_CLOCK_400KHZ; + props->f_max = cfg->spi_max_freq; + props->power_delay = 1000; /* SPI always needs 1ms power delay */ + props->host_caps.vol_330_support = true; + props->is_spi = true; + return 0; +} + +static int sdhc_spi_reset(const struct device *dev) +{ + struct sdhc_spi_data *data = dev->data; + + /* Reset host I/O */ + data->spi_cfg->frequency = SDMMC_CLOCK_400KHZ; + return 0; +} + +static int sdhc_spi_init(const struct device *dev) +{ + const struct sdhc_spi_config *cfg = dev->config; + struct sdhc_spi_data *data = dev->data; + + if (!device_is_ready(cfg->spi_dev)) { + return -ENODEV; + } + data->power_mode = SDHC_POWER_OFF; + data->spi_cfg = &data->cfg_a; + data->spi_cfg->frequency = 0; + return 0; +} + +static struct sdhc_driver_api sdhc_spi_api = { + .request = sdhc_spi_request, + .set_io = sdhc_spi_set_io, + .get_host_props = sdhc_spi_get_host_props, + .get_card_present = sdhc_spi_get_card_present, + .reset = sdhc_spi_reset, +}; + + +#define SDHC_SPI_INIT(n) \ + const struct sdhc_spi_config sdhc_spi_config_##n = { \ + .spi_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .pwr_gpio = GPIO_DT_SPEC_INST_GET_OR(n, pwr_gpios, {0}), \ + .spi_max_freq = DT_INST_PROP(n, spi_max_frequency), \ + }; \ + \ + IF_ENABLED(DT_INST_SPI_DEV_HAS_CS_GPIOS(n), \ + (struct spi_cs_control sdhc_spi_cs_##n = { \ + .gpio_dev = DEVICE_DT_GET(DT_INST_SPI_DEV_CS_GPIOS_CTLR(n)), \ + .gpio_pin = DT_INST_SPI_DEV_CS_GPIOS_PIN(n), \ + .gpio_dt_flags = DT_INST_SPI_DEV_CS_GPIOS_FLAGS(n), \ + };)) \ + struct sdhc_spi_data sdhc_spi_data_##n = { \ + .cfg_a = { \ + .operation = (SPI_LOCK_ON | \ + SPI_HOLD_ON_CS | \ + SPI_WORD_SET(8)), \ + COND_CODE_1(DT_INST_SPI_DEV_HAS_CS_GPIOS(n), \ + (.cs = &sdhc_spi_cs_##n), \ + (.cs = NULL)) \ + }, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, \ + &sdhc_spi_init, \ + NULL, \ + &sdhc_spi_data_##n, \ + &sdhc_spi_config_##n, \ + POST_KERNEL, \ + CONFIG_SDHC_INIT_PRIORITY, \ + &sdhc_spi_api); + +DT_INST_FOREACH_STATUS_OKAY(SDHC_SPI_INIT) diff --git a/dts/bindings/sdhc/zephyr,sdhc-spi-slot.yaml b/dts/bindings/sdhc/zephyr,sdhc-spi-slot.yaml new file mode 100644 index 00000000000..811c020415c --- /dev/null +++ b/dts/bindings/sdhc/zephyr,sdhc-spi-slot.yaml @@ -0,0 +1,5 @@ +description: Generic Zephyr SPI based SDHC controller + +compatible: "zephyr,sdhc-spi-slot" + +include: [spi-device.yaml]