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>
This commit is contained in:
parent
bbafc36b1c
commit
42accf2e64
6 changed files with 1074 additions and 0 deletions
54
doc/subsystems/disk/sdhc.rst
Normal file
54
doc/subsystems/disk/sdhc.rst
Normal file
|
@ -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()`.
|
|
@ -23,3 +23,4 @@ to applications.
|
|||
usb/usb.rst
|
||||
settings/settings.rst
|
||||
nvs/nvs.rst
|
||||
disk/sdhc.rst
|
||||
|
|
19
dts/bindings/mmc/mmc-spi-slot.yaml
Normal file
19
dts/bindings/mmc/mmc-spi-slot.yaml
Normal file
|
@ -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"
|
||||
|
||||
...
|
|
@ -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)
|
||||
|
|
|
@ -36,6 +36,13 @@ config DISK_ACCESS_FLASH
|
|||
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 @@ config DISK_VOLUME_SIZE
|
|||
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
|
||||
|
|
981
subsys/disk/disk_access_sdhc.c
Normal file
981
subsys/disk/disk_access_sdhc.c
Normal file
|
@ -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);
|
Loading…
Add table
Add a link
Reference in a new issue