| /* |
| * Copyright (c) 2017 Google LLC. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <logging/log.h> |
| |
| LOG_MODULE_REGISTER(sdhc, CONFIG_DISK_LOG_LEVEL); |
| |
| #include <disk_access.h> |
| #include <gpio.h> |
| #include <misc/byteorder.h> |
| #include <spi.h> |
| #include <crc.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 device *spi; |
| 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 LOG_LEVEL_DBG */ |
| static int sdhc_trace(struct sdhc_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; |
| } |
| |
| /* 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 != 0U; 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 != 0U) { |
| 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 |
| } |
| }; |
| |
| 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_trace(data, -1, |
| spi_transceive(data->spi, &data->cfg, &tx, &rx), |
| 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 |
| } |
| }; |
| |
| const struct spi_buf_set tx = { |
| .buffers = spi_bufs, |
| .count = 1 |
| }; |
| |
| return sdhc_trace(data, 1, spi_write(data->spi, &data->cfg, &tx), 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]; |
| |
| 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)); |
| |
| 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 */ |
| 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_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 |
| } |
| }; |
| |
| 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_trace(data, -1, spi_transceive(data->spi, &data->cfg, |
| &tx, &rx), |
| &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) == 0U) { |
| /* 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); |
| |
| 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; |
| } |
| |
| 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 != 0U; 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 != 0U; 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 disk_sdhc_init(struct device *dev); |
| |
| static int sdhc_init(struct device *dev) |
| { |
| struct sdhc_data *data = dev->driver_data; |
| |
| data->spi = device_get_binding(DT_ZEPHYR_MMC_SPI_SLOT_0_BUS_NAME); |
| |
| data->cfg.frequency = SDHC_INITIAL_SPEED; |
| data->cfg.operation = SPI_WORD_SET(8) | SPI_HOLD_ON_CS; |
| data->cfg.slave = DT_ZEPHYR_MMC_SPI_SLOT_0_BASE_ADDRESS; |
| data->cs = device_get_binding(DT_ZEPHYR_MMC_SPI_SLOT_0_CS_GPIO_CONTROLLER); |
| __ASSERT_NO_MSG(data->cs != NULL); |
| |
| data->pin = DT_ZEPHYR_MMC_SPI_SLOT_0_CS_GPIO_PIN; |
| |
| disk_sdhc_init(dev); |
| |
| return gpio_pin_configure(data->cs, data->pin, GPIO_DIR_OUT); |
| } |
| |
| static struct device *sdhc_get_device(void) { return DEVICE_GET(sdhc_0); } |
| |
| static int disk_sdhc_access_status(struct disk_info *disk) |
| { |
| struct device *dev = sdhc_get_device(); |
| struct sdhc_data *data = dev->driver_data; |
| |
| return data->status; |
| } |
| |
| static int disk_sdhc_access_read(struct disk_info *disk, u8_t *buf, |
| u32_t sector, u32_t count) |
| { |
| struct device *dev = sdhc_get_device(); |
| struct sdhc_data *data = dev->driver_data; |
| int err; |
| |
| 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; |
| } |
| |
| static int disk_sdhc_access_write(struct disk_info *disk, 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; |
| |
| 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; |
| } |
| |
| static int disk_sdhc_access_ioctl(struct disk_info *disk, 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; |
| } |
| |
| static int disk_sdhc_access_init(struct disk_info *disk) |
| { |
| 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 const struct disk_operations sdhc_disk_ops = { |
| .init = disk_sdhc_access_init, |
| .status = disk_sdhc_access_status, |
| .read = disk_sdhc_access_read, |
| .write = disk_sdhc_access_write, |
| .ioctl = disk_sdhc_access_ioctl, |
| }; |
| |
| static struct disk_info sdhc_disk = { |
| .name = CONFIG_DISK_SDHC_VOLUME_NAME, |
| .ops = &sdhc_disk_ops, |
| }; |
| |
| static int disk_sdhc_init(struct device *dev) |
| { |
| struct sdhc_data *data = dev->driver_data; |
| |
| data->status = DISK_STATUS_UNINIT; |
| |
| return disk_access_register(&sdhc_disk); |
| } |
| |
| 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); |