| /* |
| * Copyright (c) 2017 Google LLC. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT zephyr_mmc_spi_slot |
| |
| #include <logging/log.h> |
| |
| LOG_MODULE_REGISTER(sdmmc_spi, CONFIG_SDMMC_LOG_LEVEL); |
| |
| #include <drivers/disk.h> |
| #include <drivers/gpio.h> |
| #include <sys/byteorder.h> |
| #include <drivers/spi.h> |
| #include <sys/crc.h> |
| #include "sdmmc_sdhc.h" |
| |
| /* Clock speed used during initialisation */ |
| #define SDHC_SPI_INIT_SPEED KHZ(400) |
| /* Maximum clock speed used after initialisation (actual speed set in DTS). |
| * SD Specifications Part 1 Physical layer states 25MHz maximum. |
| */ |
| #define SDHC_SPI_MAX_OPER_SPEED MHZ(25) |
| |
| #define SPI_SDHC_NODE DT_DRV_INST(0) |
| |
| #if !DT_NODE_HAS_STATUS(SPI_SDHC_NODE, okay) |
| #warning NO SDHC slot specified on board |
| #else |
| struct sdhc_spi_data { |
| const struct device *spi; |
| const struct spi_config *spi_cfg; |
| bool high_capacity; |
| uint32_t sector_count; |
| uint8_t status; |
| #if LOG_LEVEL >= LOG_LEVEL_DBG |
| int trace_dir; |
| #endif |
| }; |
| |
| struct sdhc_spi_config { |
| struct spi_config init_cfg; |
| struct spi_config oper_cfg; |
| #if DT_SPI_DEV_HAS_CS_GPIOS(SPI_SDHC_NODE) |
| struct spi_cs_control cs; |
| #endif |
| }; |
| |
| static void sdhc_spi_set_status(const struct device *dev, uint8_t status) |
| { |
| struct sdhc_spi_data *data = dev->data; |
| const struct sdhc_spi_config *cfg = dev->config; |
| |
| data->status = status; |
| if (status == DISK_STATUS_UNINIT) { |
| data->spi_cfg = &cfg->init_cfg; |
| } else if (status == DISK_STATUS_OK) { |
| data->spi_cfg = &cfg->oper_cfg; |
| } |
| } |
| |
| /* Traces card traffic for LOG_LEVEL_DBG */ |
| static int sdhc_spi_trace(struct sdhc_spi_data *data, int dir, int err, |
| const uint8_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; |
| } |
| |
| /* Receives a fixed number of bytes */ |
| static int sdhc_spi_rx_bytes(struct sdhc_spi_data *data, uint8_t *buf, int len) |
| { |
| struct spi_buf tx_bufs[] = { |
| { |
| .buf = (uint8_t *)sdhc_ones, |
| .len = len |
| } |
| }; |
| |
| const struct spi_buf_set tx = { |
| .buffers = tx_bufs, |
| .count = 1, |
| }; |
| |
| struct spi_buf rx_bufs[] = { |
| { |
| .buf = buf, |
| .len = len |
| } |
| }; |
| |
| const struct spi_buf_set rx = { |
| .buffers = rx_bufs, |
| .count = 1, |
| }; |
| |
| return sdhc_spi_trace(data, -1, |
| spi_transceive(data->spi, data->spi_cfg, &tx, &rx), |
| buf, len); |
| } |
| |
| /* Receives and returns a single byte */ |
| static int sdhc_spi_rx_u8(struct sdhc_spi_data *data) |
| { |
| uint8_t buf[1]; |
| int err = sdhc_spi_rx_bytes(data, buf, sizeof(buf)); |
| |
| if (err != 0) { |
| return err; |
| } |
| |
| return buf[0]; |
| } |
| |
| /* Transmits a block of bytes */ |
| static int sdhc_spi_tx(struct sdhc_spi_data *data, const uint8_t *buf, int len) |
| { |
| struct spi_buf spi_bufs[] = { |
| { |
| .buf = (uint8_t *)buf, |
| .len = len |
| } |
| }; |
| |
| const struct spi_buf_set tx = { |
| .buffers = spi_bufs, |
| .count = 1 |
| }; |
| |
| return sdhc_spi_trace(data, 1, |
| spi_write(data->spi, data->spi_cfg, &tx), buf, |
| len); |
| } |
| |
| /* Transmits the command and payload */ |
| static int sdhc_spi_tx_cmd(struct sdhc_spi_data *data, uint8_t cmd, uint32_t payload) |
| { |
| uint8_t buf[SDHC_CMD_SIZE]; |
| |
| LOG_DBG("cmd%d payload=%u", cmd, payload); |
| sdhc_spi_trace(data, 0, 0, NULL, 0); |
| |
| /* Encode the command */ |
| buf[0] = SDHC_TX | (cmd & ~SDHC_START); |
| sys_put_be32(payload, &buf[1]); |
| |
| /* Add CRC and set LSB as 'end bit' */ |
| buf[SDHC_CMD_BODY_SIZE] = crc7_be(0, buf, SDHC_CMD_BODY_SIZE) | 0x01; |
| |
| return sdhc_spi_tx(data, buf, sizeof(buf)); |
| } |
| |
| /* Reads until anything but `discard` is received */ |
| static int sdhc_spi_skip(struct sdhc_spi_data *data, int discard) |
| { |
| int err; |
| struct sdhc_retry retry; |
| |
| sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0); |
| |
| do { |
| err = sdhc_spi_rx_u8(data); |
| if (err != discard) { |
| return err; |
| } |
| } while (sdhc_retry_ok(&retry)); |
| |
| LOG_WRN("Timeout while waiting for !%d", discard); |
| return -ETIMEDOUT; |
| } |
| |
| /* Reads until the first byte in a response is received */ |
| static int sdhc_spi_skip_until_start(struct sdhc_spi_data *data) |
| { |
| struct sdhc_retry retry; |
| int status; |
| |
| sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0); |
| |
| do { |
| status = sdhc_spi_rx_u8(data); |
| if (status < 0) { |
| return status; |
| } |
| |
| if ((status & SDHC_START) == 0) { |
| return status; |
| } |
| } while (sdhc_retry_ok(&retry)); |
| |
| return -ETIMEDOUT; |
| } |
| |
| /* Reads until the bus goes high */ |
| static int sdhc_spi_skip_until_ready(struct sdhc_spi_data *data) |
| { |
| struct sdhc_retry retry; |
| int status; |
| |
| sdhc_retry_init(&retry, SDHC_READY_TIMEOUT, 0); |
| |
| do { |
| status = sdhc_spi_rx_u8(data); |
| if (status < 0) { |
| return status; |
| } |
| |
| if (status == 0) { |
| /* Card is still busy */ |
| continue; |
| } |
| |
| if (status == 0xFF) { |
| return 0; |
| } |
| |
| /* Got something else. Some cards release MISO part |
| * way through the transfer. Read another and see if |
| * MISO went high. |
| */ |
| status = sdhc_spi_rx_u8(data); |
| if (status < 0) { |
| return status; |
| } |
| |
| if (status == 0xFF) { |
| return 0; |
| } |
| |
| return -EPROTO; |
| } while (sdhc_retry_ok(&retry)); |
| |
| return -ETIMEDOUT; |
| } |
| |
| /* Sends a command and returns the received R1 status code */ |
| static int sdhc_spi_cmd_r1_raw(struct sdhc_spi_data *data, |
| uint8_t cmd, uint32_t payload) |
| { |
| int err; |
| |
| err = sdhc_spi_tx_cmd(data, cmd, payload); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = sdhc_spi_skip_until_start(data); |
| |
| /* Ensure there's a idle byte between commands */ |
| if (cmd != SDHC_SEND_CSD && cmd != SDHC_SEND_CID && |
| cmd != SDHC_READ_SINGLE_BLOCK && cmd != SDHC_READ_MULTIPLE_BLOCK && |
| cmd != SDHC_WRITE_BLOCK && cmd != SDHC_WRITE_MULTIPLE_BLOCK) { |
| sdhc_spi_rx_u8(data); |
| } |
| |
| return err; |
| } |
| |
| /* Sends a command and returns the mapped error code */ |
| static int sdhc_spi_cmd_r1(struct sdhc_spi_data *data, |
| uint8_t cmd, uint32_t payload) |
| { |
| return sdhc_map_r1_status(sdhc_spi_cmd_r1_raw(data, cmd, payload)); |
| } |
| |
| /* Sends a command in idle mode returns the mapped error code */ |
| static int sdhc_spi_cmd_r1_idle(struct sdhc_spi_data *data, uint8_t cmd, |
| uint32_t payload) |
| { |
| return sdhc_map_r1_idle_status(sdhc_spi_cmd_r1_raw(data, cmd, payload)); |
| } |
| |
| /* Sends a command and returns the received multi-byte R2 status code */ |
| static int sdhc_spi_cmd_r2(struct sdhc_spi_data *data, |
| uint8_t cmd, uint32_t payload) |
| { |
| int err; |
| int r1; |
| int r2; |
| |
| err = sdhc_spi_tx_cmd(data, cmd, payload); |
| if (err != 0) { |
| return err; |
| } |
| |
| r1 = sdhc_map_r1_status(sdhc_spi_skip_until_start(data)); |
| /* Always read the rest of the reply */ |
| r2 = sdhc_spi_rx_u8(data); |
| |
| /* Ensure there's a idle byte between commands */ |
| sdhc_spi_rx_u8(data); |
| |
| if (r1 < 0) { |
| return r1; |
| } |
| |
| return r2; |
| } |
| |
| /* Sends a command and returns the received multi-byte status code */ |
| static int sdhc_spi_cmd_r37_raw(struct sdhc_spi_data *data, |
| uint8_t cmd, uint32_t payload, uint32_t *reply) |
| { |
| int err; |
| int status; |
| uint8_t buf[sizeof(*reply)]; |
| |
| err = sdhc_spi_tx_cmd(data, cmd, payload); |
| if (err != 0) { |
| return err; |
| } |
| |
| status = sdhc_spi_skip_until_start(data); |
| |
| /* Always read the rest of the reply */ |
| err = sdhc_spi_rx_bytes(data, buf, sizeof(buf)); |
| *reply = sys_get_be32(buf); |
| |
| /* Ensure there's a idle byte between commands */ |
| sdhc_spi_rx_u8(data); |
| |
| if (err != 0) { |
| return err; |
| } |
| |
| return status; |
| } |
| |
| /* Sends a command in idle mode returns the mapped error code */ |
| static int sdhc_spi_cmd_r7_idle(struct sdhc_spi_data *data, |
| uint8_t cmd, uint32_t payload, uint32_t *reply) |
| { |
| return sdhc_map_r1_idle_status( |
| sdhc_spi_cmd_r37_raw(data, cmd, payload, reply)); |
| } |
| |
| /* Sends a command and returns the received multi-byte R3 error code */ |
| static int sdhc_spi_cmd_r3(struct sdhc_spi_data *data, |
| uint8_t cmd, uint32_t payload, uint32_t *reply) |
| { |
| return sdhc_map_r1_status( |
| sdhc_spi_cmd_r37_raw(data, cmd, payload, reply)); |
| } |
| |
| /* Receives a SDHC data block */ |
| static int sdhc_spi_rx_block(struct sdhc_spi_data *data, |
| uint8_t *buf, int len) |
| { |
| int err; |
| int token; |
| int i; |
| /* Note the one extra byte to ensure there's an idle byte |
| * between commands. |
| */ |
| uint8_t crc[SDHC_CRC16_SIZE + 1]; |
| |
| token = sdhc_spi_skip(data, 0xFF); |
| if (token < 0) { |
| return token; |
| } |
| |
| if (token != SDHC_TOKEN_SINGLE) { |
| /* No start token */ |
| return -EIO; |
| } |
| |
| /* Read the data in batches */ |
| for (i = 0; i < len; i += sizeof(sdhc_ones)) { |
| int remain = MIN(sizeof(sdhc_ones), len - i); |
| |
| struct spi_buf tx_bufs[] = { |
| { |
| .buf = (uint8_t *)sdhc_ones, |
| .len = remain |
| } |
| }; |
| |
| const struct spi_buf_set tx = { |
| .buffers = tx_bufs, |
| .count = 1, |
| }; |
| |
| struct spi_buf rx_bufs[] = { |
| { |
| .buf = &buf[i], |
| .len = remain |
| } |
| }; |
| |
| const struct spi_buf_set rx = { |
| .buffers = rx_bufs, |
| .count = 1, |
| }; |
| |
| err = sdhc_spi_trace(data, -1, |
| spi_transceive(data->spi, data->spi_cfg, |
| &tx, &rx), |
| &buf[i], remain); |
| if (err != 0) { |
| return err; |
| } |
| } |
| |
| err = sdhc_spi_rx_bytes(data, crc, sizeof(crc)); |
| if (err != 0) { |
| return err; |
| } |
| |
| if (sys_get_be16(crc) != crc16_itu_t(0, buf, len)) { |
| /* Bad CRC */ |
| return -EILSEQ; |
| } |
| |
| return 0; |
| } |
| |
| /* Transmits a SDHC data block */ |
| static int sdhc_spi_tx_block(struct sdhc_spi_data *data, |
| uint8_t *send, int len) |
| { |
| uint8_t buf[SDHC_CRC16_SIZE]; |
| int err; |
| |
| /* Start the block */ |
| buf[0] = SDHC_TOKEN_SINGLE; |
| err = sdhc_spi_tx(data, buf, 1); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Write the payload */ |
| err = sdhc_spi_tx(data, send, len); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Build and write the trailing CRC */ |
| sys_put_be16(crc16_itu_t(0, send, len), buf); |
| |
| err = sdhc_spi_tx(data, buf, sizeof(buf)); |
| if (err != 0) { |
| return err; |
| } |
| |
| return sdhc_map_data_status(sdhc_spi_rx_u8(data)); |
| } |
| |
| static int sdhc_spi_recover(struct sdhc_spi_data *data) |
| { |
| /* TODO(nzmichaelh): implement */ |
| return sdhc_spi_cmd_r1(data, SDHC_SEND_STATUS, 0); |
| } |
| |
| /* Attempts to return the card to idle mode */ |
| static int sdhc_spi_go_idle(struct sdhc_spi_data *data) |
| { |
| /* Write the initial >= 74 clocks */ |
| sdhc_spi_tx(data, sdhc_ones, 10); |
| spi_release(data->spi, data->spi_cfg); |
| |
| return sdhc_spi_cmd_r1_idle(data, SDHC_GO_IDLE_STATE, 0); |
| } |
| |
| /* Checks the supported host voltage and basic protocol of a SDHC card */ |
| static int sdhc_spi_check_interface(struct sdhc_spi_data *data) |
| { |
| uint32_t cond; |
| int err; |
| |
| /* Check that the current voltage is supported */ |
| err = sdhc_spi_cmd_r7_idle(data, SDHC_SEND_IF_COND, |
| SDHC_VHS_3V3 | SDHC_CHECK, &cond); |
| if (err != 0) { |
| return err; |
| } |
| |
| if ((cond & 0xFF) != SDHC_CHECK) { |
| /* Card returned a different check pattern */ |
| return -ENOENT; |
| } |
| |
| if ((cond & SDHC_VHS_MASK) != SDHC_VHS_3V3) { |
| /* Card doesn't support this voltage */ |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| /* Detect and initialise the card */ |
| static int sdhc_spi_detect(const struct device *dev) |
| { |
| struct sdhc_spi_data *data = dev->data; |
| |
| int err; |
| uint32_t ocr; |
| struct sdhc_retry retry; |
| uint8_t structure; |
| uint8_t readbllen; |
| uint32_t csize; |
| uint8_t csizemult; |
| uint8_t buf[SDHC_CSD_SIZE]; |
| bool is_v2; |
| |
| sdhc_spi_set_status(dev, DISK_STATUS_UNINIT); |
| |
| sdhc_retry_init(&retry, SDHC_INIT_TIMEOUT, SDHC_RETRY_DELAY); |
| |
| /* Synchronise with the card by sending it to idle */ |
| do { |
| err = sdhc_spi_go_idle(data); |
| if (err == 0) { |
| err = sdhc_spi_check_interface(data); |
| is_v2 = (err == 0) ? true : false; |
| break; |
| } |
| |
| if (!sdhc_retry_ok(&retry)) { |
| return -ENOENT; |
| } |
| } while (true); |
| |
| /* Enable CRC mode */ |
| err = sdhc_spi_cmd_r1_idle(data, SDHC_CRC_ON_OFF, 1); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Wait for the card to leave idle state */ |
| do { |
| sdhc_spi_cmd_r1_raw(data, SDHC_APP_CMD, 0); |
| |
| /* Set HCS only if card conforms to specification v2.00 (cf. 4.2.3) */ |
| err = sdhc_spi_cmd_r1(data, SDHC_SEND_OP_COND, is_v2 ? SDHC_HCS : 0); |
| if (err == 0) { |
| break; |
| } |
| } while (sdhc_retry_ok(&retry)); |
| |
| if (err != 0) { |
| /* Card never exited idle */ |
| return -ETIMEDOUT; |
| } |
| |
| ocr = 0; |
| if (is_v2) { |
| do { |
| /* Read OCR to check if this is a SDSC or SDHC card. |
| * CCS bit is valid after BUSY bit is set. |
| */ |
| err = sdhc_spi_cmd_r3(data, SDHC_READ_OCR, 0, &ocr); |
| if (err != 0) { |
| return err; |
| } |
| if ((ocr & SDHC_BUSY) != 0U) { |
| break; |
| } |
| } while (sdhc_retry_ok(&retry)); |
| } |
| |
| if ((ocr & SDHC_CCS) != 0U) { |
| data->high_capacity = true; |
| } else { |
| /* A 'SDSC' card: Set block length to 512 bytes. */ |
| data->high_capacity = false; |
| err = sdhc_spi_cmd_r1(data, SDHC_SET_BLOCK_SIZE, SDMMC_DEFAULT_BLOCK_SIZE); |
| if (err != 0) { |
| return err; |
| } |
| } |
| |
| /* Read the CSD */ |
| err = sdhc_spi_cmd_r1(data, SDHC_SEND_CSD, 0); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = sdhc_spi_rx_block(data, buf, sizeof(buf)); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Bits 126..127 are the structure version */ |
| structure = (buf[0] >> 6); |
| switch (structure) { |
| case SDHC_CSD_V1: |
| /* The maximum read data block length is given by bits 80..83 raised |
| * to the power of 2. Possible values are 9, 10 and 11 for 512, 1024 |
| * and 2048 bytes, respectively. This driver does not make use of block |
| * lengths greater than 512 bytes, but forces 512 byte block transfers |
| * instead. |
| */ |
| readbllen = buf[5] & ((1 << 4) - 1); |
| if ((readbllen < 9) || (readbllen > 11)) { |
| /* Invalid maximum read data block length (cf. section 5.3.2) */ |
| return -ENOTSUP; |
| } |
| /* The capacity of the card is given by bits 62..73 plus 1 multiplied |
| * by bits 47..49 plus 2 raised to the power of 2 in maximum read data |
| * blocks. |
| */ |
| csize = (sys_get_be32(&buf[6]) >> 14) & ((1 << 12) - 1); |
| csizemult = (uint8_t) ((sys_get_be16(&buf[9]) >> 7) & ((1 << 3) - 1)); |
| data->sector_count = ((csize + 1) << (csizemult + 2 + readbllen - 9)); |
| break; |
| case SDHC_CSD_V2: |
| /* 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 (cf. section 5.3.3) */ |
| return -ENOTSUP; |
| } |
| data->sector_count = (csize + 1) * |
| (512 * 1024 / SDMMC_DEFAULT_BLOCK_SIZE); |
| break; |
| default: |
| /* Unsupported CSD format */ |
| return -ENOTSUP; |
| } |
| |
| LOG_INF("Found a ~%u MiB SDHC card.", |
| data->sector_count / (1024 * 1024 / SDMMC_DEFAULT_BLOCK_SIZE)); |
| |
| /* Read the CID */ |
| err = sdhc_spi_cmd_r1(data, SDHC_SEND_CID, 0); |
| if (err != 0) { |
| return err; |
| } |
| |
| err = sdhc_spi_rx_block(data, buf, sizeof(buf)); |
| if (err != 0) { |
| return err; |
| } |
| |
| LOG_INF("Manufacturer ID=%d OEM='%c%c' Name='%c%c%c%c%c' " |
| "Revision=0x%x Serial=0x%x", |
| buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], |
| buf[7], buf[8], sys_get_be32(&buf[9])); |
| |
| /* Initialisation complete */ |
| sdhc_spi_set_status(dev, DISK_STATUS_OK); |
| |
| return 0; |
| } |
| |
| static int sdhc_spi_read(struct sdhc_spi_data *data, |
| uint8_t *buf, uint32_t sector, uint32_t count) |
| { |
| int err; |
| uint32_t addr; |
| |
| err = sdhc_map_disk_status(data->status); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Translate sector number to data address. |
| * SDSC cards use byte addressing, SDHC cards use block addressing. |
| */ |
| if (data->high_capacity) { |
| addr = sector; |
| } else { |
| addr = sector * SDMMC_DEFAULT_BLOCK_SIZE; |
| } |
| |
| /* Send the start read command */ |
| err = sdhc_spi_cmd_r1(data, SDHC_READ_MULTIPLE_BLOCK, addr); |
| if (err != 0) { |
| goto error; |
| } |
| |
| /* Read the sectors */ |
| for (; count != 0U; count--) { |
| err = sdhc_spi_rx_block(data, buf, SDMMC_DEFAULT_BLOCK_SIZE); |
| if (err != 0) { |
| goto error; |
| } |
| |
| buf += SDMMC_DEFAULT_BLOCK_SIZE; |
| } |
| |
| /* Ignore the error as STOP_TRANSMISSION always returns 0x7F */ |
| sdhc_spi_cmd_r1(data, SDHC_STOP_TRANSMISSION, 0); |
| |
| /* Wait until the card becomes ready */ |
| err = sdhc_spi_skip_until_ready(data); |
| |
| error: |
| spi_release(data->spi, data->spi_cfg); |
| |
| return err; |
| } |
| |
| static int sdhc_spi_write(struct sdhc_spi_data *data, |
| const uint8_t *buf, uint32_t sector, uint32_t count) |
| { |
| int err; |
| uint32_t addr; |
| |
| err = sdhc_map_disk_status(data->status); |
| if (err != 0) { |
| return err; |
| } |
| |
| /* Write the blocks one-by-one */ |
| for (; count != 0U; count--) { |
| /* Translate sector number to data address. |
| * SDSC cards use byte addressing, SDHC cards use block addressing. |
| */ |
| if (data->high_capacity) { |
| addr = sector; |
| } else { |
| addr = sector * SDMMC_DEFAULT_BLOCK_SIZE; |
| } |
| |
| err = sdhc_spi_cmd_r1(data, SDHC_WRITE_BLOCK, addr); |
| if (err < 0) { |
| goto error; |
| } |
| |
| err = sdhc_spi_tx_block(data, (uint8_t *)buf, |
| SDMMC_DEFAULT_BLOCK_SIZE); |
| if (err != 0) { |
| goto error; |
| } |
| |
| /* Wait for the card to finish programming */ |
| err = sdhc_spi_skip_until_ready(data); |
| if (err != 0) { |
| goto error; |
| } |
| |
| err = sdhc_spi_cmd_r2(data, SDHC_SEND_STATUS, 0); |
| if (err != 0) { |
| goto error; |
| } |
| |
| buf += SDMMC_DEFAULT_BLOCK_SIZE; |
| sector++; |
| } |
| |
| err = 0; |
| error: |
| spi_release(data->spi, data->spi_cfg); |
| |
| return err; |
| } |
| |
| /* this function is optimized to write multiple blocks */ |
| static int sdhc_spi_write_multi(struct sdhc_spi_data *data, |
| const uint8_t *buf, uint32_t sector, uint32_t count) |
| { |
| int err; |
| uint32_t addr; |
| uint8_t block[SDHC_CRC16_SIZE]; |
| |
| err = sdhc_map_disk_status(data->status); |
| if (err != 0) { |
| return err; |
| } |
| |
| if (data->high_capacity) { |
| addr = sector; |
| } else { |
| addr = sector * SDMMC_DEFAULT_BLOCK_SIZE; |
| } |
| |
| err = sdhc_spi_cmd_r1(data, SDHC_WRITE_MULTIPLE_BLOCK, addr); |
| if (err < 0) { |
| goto exit; |
| } |
| |
| /* Write the blocks */ |
| for (; count != 0U; count--) { |
| /* Start the block */ |
| block[0] = SDHC_TOKEN_MULTI_WRITE; |
| err = sdhc_spi_tx(data, block, 1); |
| if (err != 0) { |
| goto exit; |
| } |
| |
| /* Write the payload */ |
| err = sdhc_spi_tx(data, buf, SDMMC_DEFAULT_BLOCK_SIZE); |
| if (err != 0) { |
| goto exit; |
| } |
| |
| /* Build and write the trailing CRC */ |
| sys_put_be16(crc16_itu_t(0, buf, SDMMC_DEFAULT_BLOCK_SIZE), |
| block); |
| |
| err = sdhc_spi_tx(data, block, sizeof(block)); |
| if (err != 0) { |
| goto exit; |
| } |
| |
| err = sdhc_map_data_status(sdhc_spi_rx_u8(data)); |
| if (err != 0) { |
| goto exit; |
| } |
| |
| /* Wait for the card to finish programming */ |
| err = sdhc_spi_skip_until_ready(data); |
| if (err != 0) { |
| goto exit; |
| } |
| |
| buf += SDMMC_DEFAULT_BLOCK_SIZE; |
| sector++; |
| } |
| |
| /* Stop the transmission */ |
| sdhc_spi_tx_cmd(data, SDHC_STOP_TRANSMISSION, 0); |
| |
| /* Wait for the card to finish operation */ |
| err = sdhc_spi_skip_until_ready(data); |
| if (err != 0) { |
| goto exit; |
| } |
| |
| err = 0; |
| exit: |
| spi_release(data->spi, data->spi_cfg); |
| |
| return err; |
| } |
| |
| static int disk_spi_sdhc_init(const struct device *dev); |
| |
| static int sdhc_spi_init(const struct device *dev) |
| { |
| struct sdhc_spi_data *data = dev->data; |
| |
| data->spi = device_get_binding(DT_BUS_LABEL(SPI_SDHC_NODE)); |
| |
| disk_spi_sdhc_init(dev); |
| |
| return 0; |
| } |
| |
| static int disk_spi_sdhc_access_status(struct disk_info *disk) |
| { |
| const struct device *dev = disk->dev; |
| struct sdhc_spi_data *data = dev->data; |
| |
| return data->status; |
| } |
| |
| static int disk_spi_sdhc_access_read(struct disk_info *disk, |
| uint8_t *buf, uint32_t sector, uint32_t count) |
| { |
| const struct device *dev = disk->dev; |
| struct sdhc_spi_data *data = dev->data; |
| int err; |
| |
| LOG_DBG("sector=%u count=%u", sector, count); |
| |
| err = sdhc_spi_read(data, buf, sector, count); |
| if (err != 0 && sdhc_is_retryable(err)) { |
| sdhc_spi_recover(data); |
| err = sdhc_spi_read(data, buf, sector, count); |
| } |
| |
| return err; |
| } |
| |
| static int disk_spi_sdhc_access_write(struct disk_info *disk, |
| const uint8_t *buf, uint32_t sector, uint32_t count) |
| { |
| const struct device *dev = disk->dev; |
| struct sdhc_spi_data *data = dev->data; |
| int err; |
| |
| /* for more than 2 blocks the multiple block is preferred */ |
| if (count > 2) { |
| LOG_DBG("multi block sector=%u count=%u", sector, count); |
| |
| err = sdhc_spi_write_multi(data, buf, sector, count); |
| if (err != 0 && sdhc_is_retryable(err)) { |
| sdhc_spi_recover(data); |
| err = sdhc_spi_write_multi(data, buf, sector, count); |
| } |
| } else { |
| LOG_DBG("sector=%u count=%u", sector, count); |
| |
| err = sdhc_spi_write(data, buf, sector, count); |
| if (err != 0 && sdhc_is_retryable(err)) { |
| sdhc_spi_recover(data); |
| err = sdhc_spi_write(data, buf, sector, count); |
| } |
| } |
| return err; |
| } |
| |
| static int disk_spi_sdhc_access_ioctl(struct disk_info *disk, |
| uint8_t cmd, void *buf) |
| { |
| const struct device *dev = disk->dev; |
| struct sdhc_spi_data *data = dev->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: |
| *(uint32_t *)buf = data->sector_count; |
| break; |
| case DISK_IOCTL_GET_SECTOR_SIZE: |
| *(uint32_t *)buf = SDMMC_DEFAULT_BLOCK_SIZE; |
| break; |
| case DISK_IOCTL_GET_ERASE_BLOCK_SZ: |
| *(uint32_t *)buf = SDMMC_DEFAULT_BLOCK_SIZE; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int disk_spi_sdhc_access_init(struct disk_info *disk) |
| { |
| const struct device *dev = disk->dev; |
| struct sdhc_spi_data *data = dev->data; |
| int err; |
| |
| err = sdhc_spi_detect(dev); |
| spi_release(data->spi, data->spi_cfg); |
| |
| return err; |
| } |
| |
| static const struct disk_operations spi_sdhc_disk_ops = { |
| .init = disk_spi_sdhc_access_init, |
| .status = disk_spi_sdhc_access_status, |
| .read = disk_spi_sdhc_access_read, |
| .write = disk_spi_sdhc_access_write, |
| .ioctl = disk_spi_sdhc_access_ioctl, |
| }; |
| |
| static struct disk_info spi_sdhc_disk = { |
| .name = CONFIG_SDMMC_VOLUME_NAME, |
| .ops = &spi_sdhc_disk_ops, |
| }; |
| |
| static int disk_spi_sdhc_init(const struct device *dev) |
| { |
| sdhc_spi_set_status(dev, DISK_STATUS_UNINIT); |
| |
| spi_sdhc_disk.dev = dev; |
| |
| return disk_access_register(&spi_sdhc_disk); |
| } |
| |
| static struct sdhc_spi_data sdhc_spi_data_0; |
| |
| static const struct sdhc_spi_config sdhc_spi_cfg_0 = { |
| .init_cfg = { |
| .frequency = SDHC_SPI_INIT_SPEED, |
| .operation = SPI_WORD_SET(8) | SPI_HOLD_ON_CS, |
| .slave = DT_REG_ADDR(SPI_SDHC_NODE), |
| #if DT_SPI_DEV_HAS_CS_GPIOS(SPI_SDHC_NODE) |
| .cs = &sdhc_spi_cfg_0.cs, |
| #endif |
| }, |
| .oper_cfg = { |
| .frequency = MIN(SDHC_SPI_MAX_OPER_SPEED, |
| DT_INST_PROP(0, spi_max_frequency)), |
| .operation = SPI_WORD_SET(8) | SPI_HOLD_ON_CS, |
| .slave = DT_REG_ADDR(SPI_SDHC_NODE), |
| #if DT_SPI_DEV_HAS_CS_GPIOS(SPI_SDHC_NODE) |
| .cs = &sdhc_spi_cfg_0.cs, |
| #endif |
| }, |
| #if DT_SPI_DEV_HAS_CS_GPIOS(SPI_SDHC_NODE) |
| .cs = { |
| .gpio_dev = DEVICE_DT_GET(DT_SPI_DEV_CS_GPIOS_CTLR(SPI_SDHC_NODE)), |
| .gpio_pin = DT_SPI_DEV_CS_GPIOS_PIN(SPI_SDHC_NODE), |
| .gpio_dt_flags = DT_SPI_DEV_CS_GPIOS_FLAGS(SPI_SDHC_NODE), |
| }, |
| #endif |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, sdhc_spi_init, NULL, |
| &sdhc_spi_data_0, &sdhc_spi_cfg_0, |
| POST_KERNEL, CONFIG_SDMMC_INIT_PRIORITY, NULL); |
| #endif |