blob: b634e369c4b836625acbcf4ab65cb4dc95929a1d [file] [log] [blame]
/*
* Copyright (c) 2017 Google LLC.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(sdhc_spi, CONFIG_DISK_LOG_LEVEL);
#include <disk/disk_access.h>
#include <drivers/gpio.h>
#include <sys/byteorder.h>
#include <drivers/spi.h>
#include <sys/crc.h>
#include "disk_access_sdhc.h"
/* Clock speed used during initialisation */
#define SDHC_SPI_INITIAL_SPEED 400000
/* Clock speed used after initialisation */
#define SDHC_SPI_SPEED 4000000
#ifndef DT_INST_0_ZEPHYR_MMC_SPI_SLOT_LABEL
#warning NO SDHC slot specified on board
#else
struct sdhc_spi_data {
struct device *spi;
struct spi_config cfg;
struct device *cs;
u32_t pin;
u32_t sector_count;
u8_t status;
int trace_dir;
};
DEVICE_DECLARE(sdhc_spi_0);
/* Traces card traffic for LOG_LEVEL_DBG */
static int sdhc_spi_trace(struct sdhc_spi_data *data, int dir, int err,
const u8_t *buf, int len)
{
#if LOG_LEVEL >= LOG_LEVEL_DBG
if (err != 0) {
printk("(err=%d)", err);
data->trace_dir = 0;
}
if (dir != data->trace_dir) {
data->trace_dir = dir;
printk("\n");
if (dir == 1) {
printk(">>");
} else if (dir == -1) {
printk("<<");
}
}
for (; len != 0; len--) {
printk(" %x", *buf++);
}
#endif
return err;
}
/* Asserts or deasserts chip select */
static void sdhc_spi_set_cs(struct sdhc_spi_data *data, int value)
{
gpio_pin_write(data->cs, data->pin, value);
}
/* Receives a fixed number of bytes */
static int sdhc_spi_rx_bytes(struct sdhc_spi_data *data, u8_t *buf, int len)
{
struct spi_buf tx_bufs[] = {
{
.buf = (u8_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 sdhc_spi_trace(data, -1,
spi_transceive(data->spi, &data->cfg, &tx, &rx),
buf, len);
}
/* Receives and returns a single byte */
static int sdhc_spi_rx_u8(struct sdhc_spi_data *data)
{
u8_t buf[1];
int err = sdhc_spi_rx_bytes(data, buf, sizeof(buf));
if (err != 0) {
return err;
}
return buf[0];
}
/* Transmits a block of bytes */
static int sdhc_spi_tx(struct sdhc_spi_data *data, const u8_t *buf, int len)
{
struct spi_buf spi_bufs[] = {
{
.buf = (u8_t *)buf,
.len = len
}
};
const struct spi_buf_set tx = {
.buffers = spi_bufs,
.count = 1
};
return sdhc_spi_trace(data, 1,
spi_write(data->spi, &data->cfg, &tx), buf,
len);
}
/* Transmits the command and payload */
static int sdhc_spi_tx_cmd(struct sdhc_spi_data *data, u8_t cmd, u32_t payload)
{
u8_t buf[SDHC_CMD_SIZE];
LOG_DBG("cmd%d payload=%u", cmd, payload);
sdhc_spi_trace(data, 0, 0, NULL, 0);
/* Encode the command */
buf[0] = SDHC_TX | (cmd & ~SDHC_START);
sys_put_be32(payload, &buf[1]);
buf[SDHC_CMD_BODY_SIZE] = crc7_be(0, buf, SDHC_CMD_BODY_SIZE);
return sdhc_spi_tx(data, buf, sizeof(buf));
}
/* Reads until anything but `discard` is received */
static int sdhc_spi_skip(struct sdhc_spi_data *data, int discard)
{
int err;
struct sdhc_retry retry;
sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0);
do {
err = sdhc_spi_rx_u8(data);
if (err != discard) {
return err;
}
} while (sdhc_retry_ok(&retry));
LOG_WRN("Timeout while waiting for !%d", discard);
return -ETIMEDOUT;
}
/* Reads until the first byte in a response is received */
static int sdhc_spi_skip_until_start(struct sdhc_spi_data *data)
{
struct sdhc_retry retry;
int status;
sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0);
do {
status = sdhc_spi_rx_u8(data);
if (status < 0) {
return status;
}
if ((status & SDHC_START) == 0) {
return status;
}
} while (sdhc_retry_ok(&retry));
return -ETIMEDOUT;
}
/* Reads until the bus goes high */
static int sdhc_spi_skip_until_ready(struct sdhc_spi_data *data)
{
struct sdhc_retry retry;
int status;
sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0);
do {
status = sdhc_spi_rx_u8(data);
if (status < 0) {
return status;
}
if (status == 0) {
/* Card is still busy */
continue;
}
if (status == 0xFF) {
return 0;
}
/* Got something else. Some cards release MISO part
* way through the transfer. Read another and see if
* MISO went high.
*/
status = sdhc_spi_rx_u8(data);
if (status < 0) {
return status;
}
if (status == 0xFF) {
return 0;
}
return -EPROTO;
} while (sdhc_retry_ok(&retry));
return -ETIMEDOUT;
}
/* Sends a command and returns the received R1 status code */
static int sdhc_spi_cmd_r1_raw(struct sdhc_spi_data *data,
u8_t cmd, u32_t payload)
{
int err;
err = sdhc_spi_tx_cmd(data, cmd, payload);
if (err != 0) {
return err;
}
err = sdhc_spi_skip_until_start(data);
/* Ensure there's a idle byte between commands */
if (cmd != SDHC_SEND_CSD && cmd != SDHC_SEND_CID &&
cmd != SDHC_READ_SINGLE_BLOCK && cmd != SDHC_READ_MULTIPLE_BLOCK &&
cmd != SDHC_WRITE_BLOCK && cmd != SDHC_WRITE_MULTIPLE_BLOCK) {
sdhc_spi_rx_u8(data);
}
return err;
}
/* Sends a command and returns the mapped error code */
static int sdhc_spi_cmd_r1(struct sdhc_spi_data *data,
u8_t cmd, uint32_t payload)
{
return sdhc_map_r1_status(sdhc_spi_cmd_r1_raw(data, cmd, payload));
}
/* Sends a command in idle mode returns the mapped error code */
static int sdhc_spi_cmd_r1_idle(struct sdhc_spi_data *data, u8_t cmd,
uint32_t payload)
{
return sdhc_map_r1_idle_status(sdhc_spi_cmd_r1_raw(data, cmd, payload));
}
/* Sends a command and returns the received multi-byte R2 status code */
static int sdhc_spi_cmd_r2(struct sdhc_spi_data *data,
u8_t cmd, uint32_t payload)
{
int err;
int r1;
int r2;
err = sdhc_spi_tx_cmd(data, cmd, payload);
if (err != 0) {
return err;
}
r1 = sdhc_map_r1_status(sdhc_spi_skip_until_start(data));
/* Always read the rest of the reply */
r2 = sdhc_spi_rx_u8(data);
/* Ensure there's a idle byte between commands */
sdhc_spi_rx_u8(data);
if (r1 < 0) {
return r1;
}
return r2;
}
/* Sends a command and returns the received multi-byte status code */
static int sdhc_spi_cmd_r37_raw(struct sdhc_spi_data *data,
u8_t cmd, u32_t payload, u32_t *reply)
{
int err;
int status;
u8_t buf[sizeof(*reply)];
err = sdhc_spi_tx_cmd(data, cmd, payload);
if (err != 0) {
return err;
}
status = sdhc_spi_skip_until_start(data);
/* Always read the rest of the reply */
err = sdhc_spi_rx_bytes(data, buf, sizeof(buf));
*reply = sys_get_be32(buf);
/* Ensure there's a idle byte between commands */
sdhc_spi_rx_u8(data);
if (err != 0) {
return err;
}
return status;
}
/* Sends a command in idle mode returns the mapped error code */
static int sdhc_spi_cmd_r7_idle(struct sdhc_spi_data *data,
u8_t cmd, u32_t payload, u32_t *reply)
{
return sdhc_map_r1_idle_status(
sdhc_spi_cmd_r37_raw(data, cmd, payload, reply));
}
/* Sends a command and returns the received multi-byte R3 error code */
static int sdhc_spi_cmd_r3(struct sdhc_spi_data *data,
u8_t cmd, uint32_t payload, u32_t *reply)
{
return sdhc_map_r1_status(
sdhc_spi_cmd_r37_raw(data, cmd, payload, reply));
}
/* Receives a SDHC data block */
static int sdhc_spi_rx_block(struct sdhc_spi_data *data,
u8_t *buf, int len)
{
int err;
int token;
int i;
/* Note the one extra byte to ensure there's an idle byte
* between commands.
*/
u8_t crc[SDHC_CRC16_SIZE + 1];
token = sdhc_spi_skip(data, 0xFF);
if (token < 0) {
return token;
}
if (token != SDHC_TOKEN_SINGLE) {
/* No start token */
return -EIO;
}
/* Read the data in batches */
for (i = 0; i < len; i += sizeof(sdhc_ones)) {
int remain = MIN(sizeof(sdhc_ones), len - i);
struct spi_buf tx_bufs[] = {
{
.buf = (u8_t *)sdhc_ones,
.len = remain
}
};
const struct spi_buf_set tx = {
.buffers = tx_bufs,
.count = 1,
};
struct spi_buf rx_bufs[] = {
{
.buf = &buf[i],
.len = remain
}
};
const struct spi_buf_set rx = {
.buffers = rx_bufs,
.count = 1,
};
err = sdhc_spi_trace(data, -1,
spi_transceive(data->spi, &data->cfg,
&tx, &rx),
&buf[i], remain);
if (err != 0) {
return err;
}
}
err = sdhc_spi_rx_bytes(data, crc, sizeof(crc));
if (err != 0) {
return err;
}
if (sys_get_be16(crc) != crc16_itu_t(0, buf, len)) {
/* Bad CRC */
return -EILSEQ;
}
return 0;
}
/* Transmits a SDHC data block */
static int sdhc_spi_tx_block(struct sdhc_spi_data *data,
u8_t *send, int len)
{
u8_t buf[SDHC_CRC16_SIZE];
int err;
/* Start the block */
buf[0] = SDHC_TOKEN_SINGLE;
err = sdhc_spi_tx(data, buf, 1);
if (err != 0) {
return err;
}
/* Write the payload */
err = sdhc_spi_tx(data, send, len);
if (err != 0) {
return err;
}
/* Build and write the trailing CRC */
sys_put_be16(crc16_itu_t(0, send, len), buf);
err = sdhc_spi_tx(data, buf, sizeof(buf));
if (err != 0) {
return err;
}
return sdhc_map_data_status(sdhc_spi_rx_u8(data));
}
static int sdhc_spi_recover(struct sdhc_spi_data *data)
{
/* TODO(nzmichaelh): implement */
return sdhc_spi_cmd_r1(data, SDHC_SEND_STATUS, 0);
}
/* Attempts to return the card to idle mode */
static int sdhc_spi_go_idle(struct sdhc_spi_data *data)
{
sdhc_spi_set_cs(data, 1);
/* Write the initial >= 74 clocks */
sdhc_spi_tx(data, sdhc_ones, 10);
sdhc_spi_set_cs(data, 0);
return sdhc_spi_cmd_r1_idle(data, SDHC_GO_IDLE_STATE, 0);
}
/* Checks the supported host volatage and basic protocol */
static int sdhc_spi_check_card(struct sdhc_spi_data *data)
{
u32_t cond;
int err;
/* Check that the current voltage is supported */
err = sdhc_spi_cmd_r7_idle(data, SDHC_SEND_IF_COND,
SDHC_VHS_3V3 | SDHC_CHECK, &cond);
if (err != 0) {
return err;
}
if ((cond & 0xFF) != SDHC_CHECK) {
/* Card returned a different check pattern */
return -ENOENT;
}
if ((cond & SDHC_VHS_MASK) != SDHC_VHS_3V3) {
/* Card doesn't support this voltage */
return -ENOTSUP;
}
return 0;
}
/* Detect and initialise the card */
static int sdhc_spi_detect(struct sdhc_spi_data *data)
{
int err;
u32_t ocr;
struct sdhc_retry retry;
u8_t structure;
u32_t csize;
u8_t buf[SDHC_CSD_SIZE];
data->cfg.frequency = SDHC_SPI_INITIAL_SPEED;
data->status = DISK_STATUS_UNINIT;
sdhc_retry_init(&retry, SDHC_INIT_TIMEOUT, SDHC_RETRY_DELAY);
/* Synchronise with the card by sending it to idle */
do {
err = sdhc_spi_go_idle(data);
if (err == 0) {
err = sdhc_spi_check_card(data);
if (err == 0) {
break;
}
}
if (!sdhc_retry_ok(&retry)) {
return -ENOENT;
}
} while (true);
/* Enable CRC mode */
err = sdhc_spi_cmd_r1_idle(data, SDHC_CRC_ON_OFF, 1);
if (err != 0) {
return err;
}
/* Wait for the card to leave idle state */
do {
sdhc_spi_cmd_r1_raw(data, SDHC_APP_CMD, 0);
err = sdhc_spi_cmd_r1(data, SDHC_SEND_OP_COND, SDHC_HCS);
if (err == 0) {
break;
}
} while (sdhc_retry_ok(&retry));
if (err != 0) {
/* Card never exited idle */
return -ETIMEDOUT;
}
/* Read OCR and confirm this is a SDHC card */
err = sdhc_spi_cmd_r3(data, SDHC_READ_OCR, 0, &ocr);
if (err != 0) {
return err;
}
if ((ocr & SDHC_CCS) == 0U) {
/* A 'SDSC' card */
return -ENOTSUP;
}
/* Read the CSD */
err = sdhc_spi_cmd_r1(data, SDHC_SEND_CSD, 0);
if (err != 0) {
return err;
}
err = sdhc_spi_rx_block(data, buf, sizeof(buf));
if (err != 0) {
return err;
}
/* Bits 126..127 are the structure version */
structure = (buf[0] >> 6);
if (structure != SDHC_CSD_V2) {
/* Unsupported CSD format */
return -ENOTSUP;
}
/* Bits 48..69 are the capacity of the card in 512 KiB units, minus 1.
*/
csize = sys_get_be32(&buf[6]) & ((1 << 22) - 1);
if (csize < 4112) {
/* Invalid capacity according to section 5.3.3 */
return -ENOTSUP;
}
data->sector_count = (csize + 1) *
(512 * 1024 / SDMMC_DEFAULT_BLOCK_SIZE);
LOG_INF("Found a ~%u MiB SDHC card.",
data->sector_count / (1024 * 1024 / SDMMC_DEFAULT_BLOCK_SIZE));
/* Read the CID */
err = sdhc_spi_cmd_r1(data, SDHC_SEND_CID, 0);
if (err != 0) {
return err;
}
err = sdhc_spi_rx_block(data, buf, sizeof(buf));
if (err != 0) {
return err;
}
LOG_INF("Manufacturer ID=%d OEM='%c%c' Name='%c%c%c%c%c' "
"Revision=0x%x Serial=0x%x",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6],
buf[7], buf[8], sys_get_be32(&buf[9]));
/* Initilisation complete */
data->cfg.frequency = SDHC_SPI_SPEED;
data->status = DISK_STATUS_OK;
return 0;
}
static int sdhc_spi_read(struct sdhc_spi_data *data,
u8_t *buf, u32_t sector, u32_t count)
{
int err;
err = sdhc_map_disk_status(data->status);
if (err != 0) {
return err;
}
sdhc_spi_set_cs(data, 0);
/* Send the start read command */
err = sdhc_spi_cmd_r1(data, SDHC_READ_MULTIPLE_BLOCK, sector);
if (err != 0) {
goto error;
}
/* Read the sectors */
for (; count != 0U; count--) {
err = sdhc_spi_rx_block(data, buf, SDMMC_DEFAULT_BLOCK_SIZE);
if (err != 0) {
goto error;
}
buf += SDMMC_DEFAULT_BLOCK_SIZE;
}
/* Ignore the error as STOP_TRANSMISSION always returns 0x7F */
sdhc_spi_cmd_r1(data, SDHC_STOP_TRANSMISSION, 0);
/* Wait until the card becomes ready */
err = sdhc_spi_skip_until_ready(data);
error:
sdhc_spi_set_cs(data, 1);
return err;
}
static int sdhc_spi_write(struct sdhc_spi_data *data,
const u8_t *buf, u32_t sector, u32_t count)
{
int err;
err = sdhc_map_disk_status(data->status);
if (err != 0) {
return err;
}
sdhc_spi_set_cs(data, 0);
/* Write the blocks one-by-one */
for (; count != 0U; count--) {
err = sdhc_spi_cmd_r1(data, SDHC_WRITE_BLOCK, sector);
if (err < 0) {
goto error;
}
err = sdhc_spi_tx_block(data, (u8_t *)buf,
SDMMC_DEFAULT_BLOCK_SIZE);
if (err != 0) {
goto error;
}
/* Wait for the card to finish programming */
err = sdhc_spi_skip_until_ready(data);
if (err != 0) {
goto error;
}
err = sdhc_spi_cmd_r2(data, SDHC_SEND_STATUS, 0);
if (err != 0) {
goto error;
}
buf += SDMMC_DEFAULT_BLOCK_SIZE;
sector++;
}
err = 0;
error:
sdhc_spi_set_cs(data, 1);
return err;
}
static int disk_spi_sdhc_init(struct device *dev);
static int sdhc_spi_init(struct device *dev)
{
struct sdhc_spi_data *data = dev->driver_data;
data->spi = device_get_binding(DT_INST_0_ZEPHYR_MMC_SPI_SLOT_BUS_NAME);
data->cfg.frequency = SDHC_SPI_INITIAL_SPEED;
data->cfg.operation = SPI_WORD_SET(8) | SPI_HOLD_ON_CS;
data->cfg.slave = DT_INST_0_ZEPHYR_MMC_SPI_SLOT_BASE_ADDRESS;
data->cs = device_get_binding(
DT_INST_0_ZEPHYR_MMC_SPI_SLOT_CS_GPIOS_CONTROLLER);
__ASSERT_NO_MSG(data->cs != NULL);
data->pin = DT_INST_0_ZEPHYR_MMC_SPI_SLOT_CS_GPIOS_PIN;
disk_spi_sdhc_init(dev);
return gpio_pin_configure(data->cs, data->pin, GPIO_DIR_OUT);
}
static int disk_spi_sdhc_access_status(struct disk_info *disk)
{
struct device *dev = disk->dev;
struct sdhc_spi_data *data = dev->driver_data;
return data->status;
}
static int disk_spi_sdhc_access_read(struct disk_info *disk,
u8_t *buf, u32_t sector, u32_t count)
{
struct device *dev = disk->dev;
struct sdhc_spi_data *data = dev->driver_data;
int err;
LOG_DBG("sector=%u count=%u", sector, count);
err = sdhc_spi_read(data, buf, sector, count);
if (err != 0 && sdhc_is_retryable(err)) {
sdhc_spi_recover(data);
err = sdhc_spi_read(data, buf, sector, count);
}
return err;
}
static int disk_spi_sdhc_access_write(struct disk_info *disk,
const u8_t *buf, u32_t sector, u32_t count)
{
struct device *dev = disk->dev;
struct sdhc_spi_data *data = dev->driver_data;
int err;
LOG_DBG("sector=%u count=%u", sector, count);
err = sdhc_spi_write(data, buf, sector, count);
if (err != 0 && sdhc_is_retryable(err)) {
sdhc_spi_recover(data);
err = sdhc_spi_write(data, buf, sector, count);
}
return err;
}
static int disk_spi_sdhc_access_ioctl(struct disk_info *disk,
u8_t cmd, void *buf)
{
struct device *dev = disk->dev;
struct sdhc_spi_data *data = dev->driver_data;
int err;
err = sdhc_map_disk_status(data->status);
if (err != 0) {
return err;
}
switch (cmd) {
case DISK_IOCTL_CTRL_SYNC:
break;
case DISK_IOCTL_GET_SECTOR_COUNT:
*(u32_t *)buf = data->sector_count;
break;
case DISK_IOCTL_GET_SECTOR_SIZE:
*(u32_t *)buf = SDMMC_DEFAULT_BLOCK_SIZE;
break;
case DISK_IOCTL_GET_ERASE_BLOCK_SZ:
*(u32_t *)buf = SDMMC_DEFAULT_BLOCK_SIZE;
break;
default:
return -EINVAL;
}
return 0;
}
static int disk_spi_sdhc_access_init(struct disk_info *disk)
{
struct device *dev = disk->dev;
struct sdhc_spi_data *data = dev->driver_data;
int err;
if (data->status == DISK_STATUS_OK) {
/* Called twice, don't re-init. */
return 0;
}
err = sdhc_spi_detect(data);
sdhc_spi_set_cs(data, 1);
return err;
}
static const struct disk_operations spi_sdhc_disk_ops = {
.init = disk_spi_sdhc_access_init,
.status = disk_spi_sdhc_access_status,
.read = disk_spi_sdhc_access_read,
.write = disk_spi_sdhc_access_write,
.ioctl = disk_spi_sdhc_access_ioctl,
};
static struct disk_info spi_sdhc_disk = {
.name = CONFIG_DISK_SDHC_VOLUME_NAME,
.ops = &spi_sdhc_disk_ops,
};
static int disk_spi_sdhc_init(struct device *dev)
{
struct sdhc_spi_data *data = dev->driver_data;
data->status = DISK_STATUS_UNINIT;
spi_sdhc_disk.dev = dev;
return disk_access_register(&spi_sdhc_disk);
}
static struct sdhc_spi_data sdhc_spi_data_0;
DEVICE_AND_API_INIT(sdhc_spi_0,
DT_INST_0_ZEPHYR_MMC_SPI_SLOT_LABEL,
sdhc_spi_init, &sdhc_spi_data_0, NULL,
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);
#endif