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);