disk: add a SDHC card over SPI driver.
Features:
- Uses the SPI bus to communicate with the card
- Detects and safely rejects SDSC (<= 2 GiB) cards
- Uses the optional CRC support for data integrity
- Retries resumable errors like CRC failure or temporary IO failure
- Works well with ELMFAT
- When used on a device with a FIFO or DMA, achieves >= 310 KiB/s on a
4 MHz bus
Tested on a mix of SanDisk, Samsung, 4V, and ADATA cards from 4 GiB to
32 GiB.
Signed-off-by: Michael Hope <mlhx@google.com>
diff --git a/doc/subsystems/disk/sdhc.rst b/doc/subsystems/disk/sdhc.rst
new file mode 100644
index 0000000..3ab3197
--- /dev/null
+++ b/doc/subsystems/disk/sdhc.rst
@@ -0,0 +1,54 @@
+.. _SDHC_disks:
+
+SDHC disks
+##########
+
+Zephyr includes support for connecting an SDHC card via the SPI bus.
+This can be used with Zephyr's built-in filesystem support to read and
+write FAT formatted cards.
+
+The system has been tested with cards from Samsung, SanDisk, and 4V
+with sizes from 2 GiB to 32 GiB in single partition mode. Higher
+capacity cards should also work but haven't been tested. Please let
+us know if they work!
+
+MMC and SDSC (<= 2 GiB) cards are not supported and will be ignored.
+
+.. note:: The system does not support inserting or removing cards while the
+ system is running. The cards must be present at boot and must not be
+ removed. This may be fixed in future releases.
+
+ FAT filesystems are not power safe so the filesystem may become
+ corrupted if power is lost or if the card is removed.
+
+Enabling
+********
+
+For example, this device tree fragment adds an SDHC card slot on `spi1`,
+uses `PA27` for chip select, and runs the SPI bus at 24 MHz once the
+SDHC card has been initialized:
+
+.. code-block:: none
+
+ &spi1 {
+ status = "ok";
+ cs-gpios = <&porta 27 0>;
+
+ sdhc0: sdhc@0 {
+ compatible = "zephyr,mmc-spi-slot";
+ reg = <0>;
+ status = "ok";
+ label = "SDHC0";
+ spi-max-frequency = <24000000>;
+ };
+ };
+
+Usage
+*****
+
+The SDHC card will be automatically detected and initialized by the
+filesystem driver when the board boots.
+
+To read and write files and directories, see the :ref:`file_system` in
+:file:`include/fs.h` such as :c:func:`fs_open()`,
+:c:func:`fs_read()`, and :c:func:`fs_write()`.
diff --git a/doc/subsystems/subsystems.rst b/doc/subsystems/subsystems.rst
index 64a659b..909100b 100644
--- a/doc/subsystems/subsystems.rst
+++ b/doc/subsystems/subsystems.rst
@@ -23,3 +23,4 @@
usb/usb.rst
settings/settings.rst
nvs/nvs.rst
+ disk/sdhc.rst
diff --git a/dts/bindings/mmc/mmc-spi-slot.yaml b/dts/bindings/mmc/mmc-spi-slot.yaml
new file mode 100644
index 0000000..00bb9ec
--- /dev/null
+++ b/dts/bindings/mmc/mmc-spi-slot.yaml
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2018 Google LLC.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+---
+title: MMC/SD/SDIO slot connected via SPI
+version: 0.1
+
+description: MMC/SD/SDIO slot connected via SPI
+
+inherits:
+ !include spi-device.yaml
+
+properties:
+ compatible:
+ constraint: "zephyr,mmc-spi-slot"
+
+...
diff --git a/subsys/disk/CMakeLists.txt b/subsys/disk/CMakeLists.txt
index 1819cd1..d4fa4e7 100644
--- a/subsys/disk/CMakeLists.txt
+++ b/subsys/disk/CMakeLists.txt
@@ -1,3 +1,4 @@
zephyr_sources_ifdef(CONFIG_DISK_ACCESS disk_access.c)
zephyr_sources_ifdef(CONFIG_DISK_ACCESS_FLASH disk_access_flash.c)
zephyr_sources_ifdef(CONFIG_DISK_ACCESS_RAM disk_access_ram.c)
+zephyr_sources_ifdef(CONFIG_DISK_ACCESS_SDHC disk_access_sdhc.c)
diff --git a/subsys/disk/Kconfig b/subsys/disk/Kconfig
index 8bd1857..b0fcf26 100644
--- a/subsys/disk/Kconfig
+++ b/subsys/disk/Kconfig
@@ -36,6 +36,13 @@
help
Flash device is used for the file system.
+config DISK_ACCESS_SDHC
+ bool "SDHC card over SPI"
+ select SPI
+ select FLASH
+ help
+ File system on a SDHC card accessed over SPI.
+
endif # DISK_ACCESS
if DISK_ACCESS_RAM
@@ -90,4 +97,15 @@
This is the file system volume size in bytes.
endif # DISK_ACCESS_FLASH
+
+if DISK_ACCESS_SDHC
+
+config DISK_SDHC_VOLUME_NAME
+ string "SDHC Disk mount point or drive name"
+ default "SDHC"
+ help
+ Disk name as per file system naming guidelines.
+
+endif # DISK_ACCESS_SDHC
+
endmenu
diff --git a/subsys/disk/disk_access_sdhc.c b/subsys/disk/disk_access_sdhc.c
new file mode 100644
index 0000000..fadd965
--- /dev/null
+++ b/subsys/disk/disk_access_sdhc.c
@@ -0,0 +1,981 @@
+/*
+ * Copyright (c) 2017 Google LLC.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define SYS_LOG_DOMAIN "sdhc"
+#define SYS_LOG_LEVEL SYS_LOG_LEVEL_INFO
+#include <logging/sys_log.h>
+
+#include <disk_access.h>
+#include <gpio.h>
+#include <misc/byteorder.h>
+#include <spi.h>
+#include <crc7.h>
+#include <crc16.h>
+
+#define SDHC_SECTOR_SIZE 512
+#define SDHC_CMD_SIZE 6
+#define SDHC_CMD_BODY_SIZE (SDHC_CMD_SIZE - 1)
+#define SDHC_CRC16_SIZE 2
+
+/* Command IDs */
+#define SDHC_GO_IDLE_STATE 0
+#define SDHC_SEND_IF_COND 8
+#define SDHC_SEND_CSD 9
+#define SDHC_SEND_CID 10
+#define SDHC_STOP_TRANSMISSION 12
+#define SDHC_SEND_STATUS 13
+#define SDHC_READ_SINGLE_BLOCK 17
+#define SDHC_READ_MULTIPLE_BLOCK 18
+#define SDHC_WRITE_BLOCK 24
+#define SDHC_WRITE_MULTIPLE_BLOCK 25
+#define SDHC_APP_CMD 55
+#define SDHC_READ_OCR 58
+#define SDHC_CRC_ON_OFF 59
+#define SDHC_SEND_OP_COND 41
+
+/* Command flags */
+#define SDHC_START 0x80
+#define SDHC_TX 0x40
+
+/* Fields in various card registers */
+#define SDHC_HCS (1 << 30)
+#define SDHC_CCS (1 << 30)
+#define SDHC_VHS_MASK (0x0F << 8)
+#define SDHC_VHS_3V3 (1 << 8)
+#define SDHC_CHECK 0xAA
+#define SDHC_CSD_SIZE 16
+#define SDHC_CSD_V2 1
+
+/* R1 response status */
+#define SDHC_R1_IDLE 0x01
+#define SDHC_R1_ERASE_RESET 0x02
+#define SDHC_R1_ILLEGAL_COMMAND 0x04
+#define SDHC_R1_COM_CRC 0x08
+#define SDHC_R1_ERASE_SEQ 0x10
+#define SDHC_R1_ADDRESS 0x20
+#define SDHC_R1_PARAMETER 0x40
+
+/* Data block tokens */
+#define SDHC_TOKEN_SINGLE 0xFE
+#define SDHC_TOKEN_MULTI_WRITE 0xFC
+#define SDHC_TOKEN_STOP_TRAN 0xFD
+
+/* Data block responses */
+#define SDHC_RESPONSE_ACCEPTED 0x05
+#define SDHC_RESPONSE_CRC_ERR 0x0B
+#define SDHC_RESPONSE_WRITE_ERR 0x0E
+
+/* Clock speed used during initialisation */
+#define SDHC_INITIAL_SPEED 400000
+/* Clock speed used after initialisation */
+#define SDHC_SPEED 4000000
+
+#define SDHC_MIN_TRIES 20
+#define SDHC_RETRY_DELAY K_MSEC(20)
+/* Time to wait for the card to initialise */
+#define SDHC_INIT_TIMEOUT K_MSEC(5000)
+/* Time to wait for the card to respond or come ready */
+#define SDHC_READY_TIMEOUT K_MSEC(500)
+
+struct sdhc_data {
+ struct spi_config cfg;
+ struct device *cs;
+ u32_t pin;
+
+ u32_t sector_count;
+ u8_t status;
+ int trace_dir;
+};
+
+struct sdhc_retry {
+ u32_t end;
+ s16_t tries;
+ u16_t sleep;
+};
+
+struct sdhc_flag_map {
+ u8_t mask;
+ u8_t err;
+};
+
+DEVICE_DECLARE(sdhc_0);
+
+/* The SD protocol requires sending ones while reading but Zephyr
+ * defaults to writing zeros.
+ */
+static const u8_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,
+};
+
+BUILD_ASSERT(sizeof(sdhc_ones) % SDHC_CSD_SIZE == 0);
+BUILD_ASSERT(SDHC_SECTOR_SIZE % sizeof(sdhc_ones) == 0);
+
+/* Maps R1 response flags to error codes */
+static const struct sdhc_flag_map sdhc_r1_flags[] = {
+ {SDHC_R1_PARAMETER, EFAULT}, {SDHC_R1_ADDRESS, EFAULT},
+ {SDHC_R1_ILLEGAL_COMMAND, EINVAL}, {SDHC_R1_COM_CRC, EILSEQ},
+ {SDHC_R1_ERASE_SEQ, EIO}, {SDHC_R1_ERASE_RESET, EIO},
+ {SDHC_R1_IDLE, ECONNRESET}, {0, 0},
+};
+
+/* Maps disk status flags to error codes */
+static const struct sdhc_flag_map sdhc_disk_status_flags[] = {
+ {DISK_STATUS_UNINIT, ENODEV},
+ {DISK_STATUS_NOMEDIA, ENOENT},
+ {DISK_STATUS_WR_PROTECT, EROFS},
+ {0, 0},
+};
+
+/* Maps data block flags to error codes */
+static const struct sdhc_flag_map sdhc_data_response_flags[] = {
+ {SDHC_RESPONSE_WRITE_ERR, EIO},
+ {SDHC_RESPONSE_CRC_ERR, EILSEQ},
+ {SDHC_RESPONSE_ACCEPTED, 0},
+ /* Unrecognised value */
+ {0, EPROTO},
+};
+
+/* Traces card traffic for SYS_LOG_LEVEL_DEBUG */
+static int sdhc_trace(struct sdhc_data *data, int dir, int err,
+ const u8_t *buf, int len)
+{
+#if SYS_LOG_LEVEL >= SYS_LOG_LEVEL_DEBUG
+ 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;
+}
+
+/* Returns true if an error code is retryable at the disk layer */
+static bool sdhc_is_retryable(int err)
+{
+ switch (err) {
+ case 0:
+ return false;
+ case -EILSEQ:
+ case -EIO:
+ case -ETIMEDOUT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* Maps a flag based error code into a Zephyr errno */
+static int sdhc_map_flags(const struct sdhc_flag_map *map, int flags)
+{
+ if (flags < 0) {
+ return flags;
+ }
+
+ for (; map->mask != 0; map++) {
+ if ((flags & map->mask) == map->mask) {
+ return -map->err;
+ }
+ }
+
+ return -map->err;
+}
+
+/* Converts disk status into an error code */
+static int sdhc_map_disk_status(int status)
+{
+ return sdhc_map_flags(sdhc_disk_status_flags, status);
+}
+
+/* Converts the R1 response flags into an error code */
+static int sdhc_map_r1_status(int status)
+{
+ return sdhc_map_flags(sdhc_r1_flags, status);
+}
+
+/* Converts an eary stage idle mode R1 code into an error code */
+static int sdhc_map_r1_idle_status(int status)
+{
+ if (status < 0) {
+ return status;
+ }
+
+ if (status == SDHC_R1_IDLE) {
+ return 0;
+ }
+
+ return sdhc_map_r1_status(status);
+}
+
+/* Converts the data block response flags into an error code */
+static int sdhc_map_data_status(int status)
+{
+ return sdhc_map_flags(sdhc_data_response_flags, status);
+}
+
+/* Initialises a retry helper */
+static void sdhc_retry_init(struct sdhc_retry *retry, u32_t timeout,
+ u16_t sleep)
+{
+ retry->end = k_uptime_get_32() + timeout;
+ retry->tries = 0;
+ retry->sleep = sleep;
+}
+
+/* Called at the end of a retry loop. Returns if the minimum try
+ * count and timeout has passed. Delays/yields on retry.
+ */
+static bool sdhc_retry_ok(struct sdhc_retry *retry)
+{
+ s32_t remain = retry->end - k_uptime_get_32();
+
+ if (retry->tries < SDHC_MIN_TRIES) {
+ retry->tries++;
+ if (retry->sleep != 0) {
+ k_sleep(retry->sleep);
+ }
+
+ return true;
+ }
+
+ if (remain >= 0) {
+ if (retry->sleep > 0) {
+ k_sleep(retry->sleep);
+ } else {
+ k_yield();
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+/* Asserts or deasserts chip select */
+static void sdhc_set_cs(struct sdhc_data *data, int value)
+{
+ gpio_pin_write(data->cs, data->pin, value);
+}
+
+/* Receives a fixed number of bytes */
+static int sdhc_rx_bytes(struct sdhc_data *data, u8_t *buf, int len)
+{
+ struct spi_buf tx_bufs[] = {
+ {
+ .buf = (u8_t *)sdhc_ones,
+ .len = len
+ }
+ };
+
+ struct spi_buf rx_bufs[] = {
+ {
+ .buf = buf,
+ .len = len
+ }
+ };
+
+ return sdhc_trace(data, -1,
+ spi_transceive(&data->cfg, tx_bufs, 1, rx_bufs, 1),
+ buf, len);
+}
+
+/* Receives and returns a single byte */
+static int sdhc_rx_u8(struct sdhc_data *data)
+{
+ u8_t buf[1];
+ int err = sdhc_rx_bytes(data, buf, sizeof(buf));
+
+ if (err != 0) {
+ return err;
+ }
+
+ return buf[0];
+}
+
+/* Transmits a block of bytes */
+static int sdhc_tx(struct sdhc_data *data, const u8_t *buf, int len)
+{
+ struct spi_buf spi_bufs[] = {
+ {
+ .buf = (u8_t *)buf,
+ .len = len
+ }
+ };
+
+ return sdhc_trace(data, 1, spi_write(&data->cfg, spi_bufs, 1), buf,
+ len);
+}
+
+/* Transmits the command and payload */
+static int sdhc_tx_cmd(struct sdhc_data *data, u8_t cmd, u32_t payload)
+{
+ u8_t buf[SDHC_CMD_SIZE];
+
+ SYS_LOG_DBG("cmd%d payload=%u", cmd, payload);
+ sdhc_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_tx(data, buf, sizeof(buf));
+}
+
+/* Reads until anything but `discard` is received */
+static int sdhc_skip(struct sdhc_data *data, int discard)
+{
+ int err;
+ struct sdhc_retry retry;
+
+ sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0);
+
+ do {
+ err = sdhc_rx_u8(data);
+ if (err != discard) {
+ return err;
+ }
+ } while (sdhc_retry_ok(&retry));
+
+ SYS_LOG_WRN("Timeout while waiting for !%d", discard);
+ return -ETIMEDOUT;
+}
+
+/* Reads until the first byte in a response is received */
+static int sdhc_skip_until_start(struct sdhc_data *data)
+{
+ struct sdhc_retry retry;
+ int status;
+
+ sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0);
+
+ do {
+ status = sdhc_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_skip_until_ready(struct sdhc_data *data)
+{
+ struct sdhc_retry retry;
+ int status;
+
+ sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0);
+
+ do {
+ status = sdhc_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_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_cmd_r1_raw(struct sdhc_data *data, u8_t cmd, u32_t payload)
+{
+ int err;
+
+ err = sdhc_tx_cmd(data, cmd, payload);
+ if (err != 0) {
+ return err;
+ }
+
+ err = sdhc_skip_until_start(data);
+
+ /* Ensure there's a idle byte between commands */
+ sdhc_rx_u8(data);
+
+ return err;
+}
+
+/* Sends a command and returns the mapped error code */
+static int sdhc_cmd_r1(struct sdhc_data *data, u8_t cmd, uint32_t payload)
+{
+ return sdhc_map_r1_status(sdhc_cmd_r1_raw(data, cmd, payload));
+}
+
+/* Sends a command in idle mode returns the mapped error code */
+static int sdhc_cmd_r1_idle(struct sdhc_data *data, u8_t cmd,
+ uint32_t payload)
+{
+ return sdhc_map_r1_idle_status(sdhc_cmd_r1_raw(data, cmd, payload));
+}
+
+/* Sends a command and returns the received multi-byte R2 status code */
+static int sdhc_cmd_r2(struct sdhc_data *data, u8_t cmd, uint32_t payload)
+{
+ int err;
+ int r1;
+ int r2;
+
+ err = sdhc_tx_cmd(data, cmd, payload);
+ if (err != 0) {
+ return err;
+ }
+
+ r1 = sdhc_map_r1_status(sdhc_skip_until_start(data));
+ /* Always read the rest of the reply */
+ r2 = sdhc_rx_u8(data);
+
+ /* Ensure there's a idle byte between commands */
+ sdhc_rx_u8(data);
+
+ if (r1 < 0) {
+ return r1;
+ }
+
+ return r2;
+}
+
+/* Sends a command and returns the received multi-byte status code */
+static int sdhc_cmd_r37_raw(struct sdhc_data *data, u8_t cmd, u32_t payload,
+ u32_t *reply)
+{
+ int err;
+ int status;
+ u8_t buf[sizeof(*reply)];
+
+ err = sdhc_tx_cmd(data, cmd, payload);
+ if (err != 0) {
+ return err;
+ }
+
+ status = sdhc_skip_until_start(data);
+
+ /* Always read the rest of the reply */
+ err = sdhc_rx_bytes(data, buf, sizeof(buf));
+ *reply = sys_get_be32(buf);
+
+ /* Ensure there's a idle byte between commands */
+ sdhc_rx_u8(data);
+
+ if (err != 0) {
+ return err;
+ }
+
+ return status;
+}
+
+/* Sends a command in idle mode returns the mapped error code */
+static int sdhc_cmd_r7_idle(struct sdhc_data *data, u8_t cmd, u32_t payload,
+ u32_t *reply)
+{
+ return sdhc_map_r1_idle_status(
+ sdhc_cmd_r37_raw(data, cmd, payload, reply));
+}
+
+/* Sends a command and returns the received multi-byte R3 error code */
+static int sdhc_cmd_r3(struct sdhc_data *data, u8_t cmd, uint32_t payload,
+ u32_t *reply)
+{
+ return sdhc_map_r1_status(
+ sdhc_cmd_r37_raw(data, cmd, payload, reply));
+}
+
+/* Receives a SDHC data block */
+static int sdhc_rx_block(struct sdhc_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_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
+ }
+ };
+
+ struct spi_buf rx_bufs[] = {
+ {
+ .buf = &buf[i],
+ .len = remain
+ }
+ };
+
+ err = sdhc_trace(data, -1, spi_transceive(&data->cfg, tx_bufs,
+ 1, rx_bufs, 1),
+ &buf[i], remain);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ err = sdhc_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_tx_block(struct sdhc_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_tx(data, buf, 1);
+ if (err != 0) {
+ return err;
+ }
+
+ /* Write the payload */
+ err = sdhc_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_tx(data, buf, sizeof(buf));
+ if (err != 0) {
+ return err;
+ }
+
+ return sdhc_map_data_status(sdhc_rx_u8(data));
+}
+
+static int sdhc_recover(struct sdhc_data *data)
+{
+ /* TODO(nzmichaelh): implement */
+ return sdhc_cmd_r1(data, SDHC_SEND_STATUS, 0);
+}
+
+/* Attempts to return the card to idle mode */
+static int sdhc_go_idle(struct sdhc_data *data)
+{
+ sdhc_set_cs(data, 1);
+
+ /* Write the initial >= 74 clocks */
+ sdhc_tx(data, sdhc_ones, 10);
+
+ sdhc_set_cs(data, 0);
+
+ return sdhc_cmd_r1_idle(data, SDHC_GO_IDLE_STATE, 0);
+}
+
+/* Checks the supported host volatage and basic protocol */
+static int sdhc_check_card(struct sdhc_data *data)
+{
+ u32_t cond;
+ int err;
+
+ /* Check that the current voltage is supported */
+ err = sdhc_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_detect(struct sdhc_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_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_go_idle(data);
+ if (err == 0) {
+ err = sdhc_check_card(data);
+ if (err == 0) {
+ break;
+ }
+ }
+
+ if (!sdhc_retry_ok(&retry)) {
+ return -ENOENT;
+ }
+ } while (true);
+
+ /* Enable CRC mode */
+ err = sdhc_cmd_r1_idle(data, SDHC_CRC_ON_OFF, 1);
+ if (err != 0) {
+ return err;
+ }
+
+ /* Wait for the card to leave idle state */
+ do {
+ sdhc_cmd_r1_raw(data, SDHC_APP_CMD, 0);
+
+ err = sdhc_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_cmd_r3(data, SDHC_READ_OCR, 0, &ocr);
+ if (err != 0) {
+ return err;
+ }
+
+ if ((ocr & SDHC_CCS) == 0) {
+ /* A 'SDSC' card */
+ return -ENOTSUP;
+ }
+
+ /* Read the CSD */
+ err = sdhc_cmd_r1(data, SDHC_SEND_CSD, 0);
+ if (err != 0) {
+ return err;
+ }
+
+ err = sdhc_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 / SDHC_SECTOR_SIZE);
+
+ SYS_LOG_INF("Found a ~%u MiB SDHC card.",
+ data->sector_count / (1024 * 1024 / SDHC_SECTOR_SIZE));
+
+ /* Read the CID */
+ err = sdhc_cmd_r1(data, SDHC_SEND_CID, 0);
+ if (err != 0) {
+ return err;
+ }
+
+ err = sdhc_rx_block(data, buf, sizeof(buf));
+ if (err != 0) {
+ return err;
+ }
+
+ SYS_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_SPEED;
+ data->status = DISK_STATUS_OK;
+
+ return 0;
+}
+
+static int sdhc_read(struct sdhc_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_set_cs(data, 0);
+
+ /* Send the start read command */
+ err = sdhc_cmd_r1(data, SDHC_READ_MULTIPLE_BLOCK, sector);
+ if (err != 0) {
+ goto error;
+ }
+
+ /* Read the sectors */
+ for (; count != 0; count--) {
+ err = sdhc_rx_block(data, buf, SDHC_SECTOR_SIZE);
+ if (err != 0) {
+ goto error;
+ }
+
+ buf += SDHC_SECTOR_SIZE;
+ }
+
+ /* Ignore the error as STOP_TRANSMISSION always returns 0x7F */
+ sdhc_cmd_r1(data, SDHC_STOP_TRANSMISSION, 0);
+
+ /* Wait until the card becomes ready */
+ err = sdhc_skip_until_ready(data);
+
+error:
+ sdhc_set_cs(data, 1);
+
+ return err;
+}
+
+static int sdhc_write(struct sdhc_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_set_cs(data, 0);
+
+ /* Write the blocks one-by-one */
+ for (; count != 0; count--) {
+ err = sdhc_cmd_r1(data, SDHC_WRITE_BLOCK, sector);
+ if (err < 0) {
+ goto error;
+ }
+
+ err = sdhc_tx_block(data, (u8_t *)buf, SDHC_SECTOR_SIZE);
+ if (err != 0) {
+ goto error;
+ }
+
+ /* Wait for the card to finish programming */
+ err = sdhc_skip_until_ready(data);
+ if (err != 0) {
+ goto error;
+ }
+
+ err = sdhc_cmd_r2(data, SDHC_SEND_STATUS, 0);
+ if (err != 0) {
+ goto error;
+ }
+
+ buf += SDHC_SECTOR_SIZE;
+ sector++;
+ }
+
+ err = 0;
+error:
+ sdhc_set_cs(data, 1);
+
+ return err;
+}
+
+static int sdhc_init(struct device *dev)
+{
+ struct sdhc_data *data = dev->driver_data;
+
+ data->cfg.dev = device_get_binding(DT_DISK_SDHC0_CS_GPIOS_CONTROLLER);
+ __ASSERT_NO_MSG(data->cfg.dev != NULL);
+
+ data->cfg.frequency = SDHC_INITIAL_SPEED;
+ data->cfg.operation = SPI_WORD_SET(8) | SPI_HOLD_ON_CS;
+
+ data->cs = device_get_binding(DT_DISK_SDHC0_CS_GPIOS_CONTROLLER);
+ __ASSERT_NO_MSG(data->cs != NULL);
+
+ data->pin = DT_DISK_SDHC0_CS_GPIOS_PIN;
+
+ return gpio_pin_configure(data->cs, data->pin, GPIO_DIR_OUT);
+}
+
+static struct device *sdhc_get_device(void) { return DEVICE_GET(sdhc_0); }
+
+int disk_access_status(void)
+{
+ struct device *dev = sdhc_get_device();
+ struct sdhc_data *data = dev->driver_data;
+
+ return data->status;
+}
+
+int disk_access_read(u8_t *buf, u32_t sector, u32_t count)
+{
+ struct device *dev = sdhc_get_device();
+ struct sdhc_data *data = dev->driver_data;
+ int err;
+
+ SYS_LOG_DBG("sector=%u count=%u", sector, count);
+
+ err = sdhc_read(data, buf, sector, count);
+ if (err != 0 && sdhc_is_retryable(err)) {
+ sdhc_recover(data);
+ err = sdhc_read(data, buf, sector, count);
+ }
+
+ return err;
+}
+
+int disk_access_write(const u8_t *buf, u32_t sector, u32_t count)
+{
+ struct device *dev = sdhc_get_device();
+ struct sdhc_data *data = dev->driver_data;
+ int err;
+
+ SYS_LOG_DBG("sector=%u count=%u", sector, count);
+
+ err = sdhc_write(data, buf, sector, count);
+ if (err != 0 && sdhc_is_retryable(err)) {
+ sdhc_recover(data);
+ err = sdhc_write(data, buf, sector, count);
+ }
+
+ return err;
+}
+
+int disk_access_ioctl(u8_t cmd, void *buf)
+{
+ struct device *dev = sdhc_get_device();
+ struct sdhc_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 = SDHC_SECTOR_SIZE;
+ break;
+ case DISK_IOCTL_GET_ERASE_BLOCK_SZ:
+ *(u32_t *)buf = SDHC_SECTOR_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int disk_access_init(void)
+{
+ struct device *dev = sdhc_get_device();
+ struct sdhc_data *data = dev->driver_data;
+ int err;
+
+ if (data->status == DISK_STATUS_OK) {
+ /* Called twice, don't re-init. */
+ return 0;
+ }
+
+ err = sdhc_detect(data);
+ sdhc_set_cs(data, 1);
+
+ return err;
+}
+
+static struct sdhc_data sdhc_data_0;
+
+DEVICE_AND_API_INIT(sdhc_0, "sdhc_0", sdhc_init, &sdhc_data_0, NULL,
+ APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL);