/* * Copyright (c) 2017 Google LLC. * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(sdhc_spi, CONFIG_DISK_LOG_LEVEL); #include #include #include #include #include #include "disk_access_sdhc.h" /* Clock speed used during initialisation */ #define SDHC_SPI_INITIAL_SPEED 400000 /* Clock speed used after initialisation */ #define SDHC_SPI_SPEED 4000000 #if !DT_NODE_HAS_STATUS(DT_INST(0, zephyr_mmc_spi_slot), okay) #warning NO SDHC slot specified on board #else struct sdhc_spi_data { struct device *spi; struct spi_config cfg; struct device *cs; uint32_t pin; gpio_dt_flags_t flags; bool high_capacity; uint32_t sector_count; uint8_t status; #if LOG_LEVEL >= LOG_LEVEL_DBG int trace_dir; #endif }; DEVICE_DECLARE(sdhc_spi_0); /* 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; } /* Asserts or deasserts chip select */ static void sdhc_spi_set_cs(struct sdhc_spi_data *data, int value) { gpio_pin_set(data->cs, data->pin, value); } /* 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->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->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]); buf[SDHC_CMD_BODY_SIZE] = crc7_be(0, buf, SDHC_CMD_BODY_SIZE); 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->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) { sdhc_spi_set_cs(data, 1); /* Write the initial >= 74 clocks */ sdhc_spi_tx(data, sdhc_ones, 10); sdhc_spi_set_cs(data, 0); 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(struct sdhc_spi_data *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; data->cfg.frequency = SDHC_SPI_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_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])); /* Initilisation complete */ data->cfg.frequency = SDHC_SPI_SPEED; data->status = 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; } sdhc_spi_set_cs(data, 0); /* 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: sdhc_spi_set_cs(data, 1); 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; } sdhc_spi_set_cs(data, 0); /* 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: sdhc_spi_set_cs(data, 1); return err; } static int disk_spi_sdhc_init(struct device *dev); static int sdhc_spi_init(struct device *dev) { struct sdhc_spi_data *data = dev->driver_data; data->spi = device_get_binding(DT_BUS_LABEL(DT_INST(0, zephyr_mmc_spi_slot))); data->cfg.frequency = SDHC_SPI_INITIAL_SPEED; data->cfg.operation = SPI_WORD_SET(8) | SPI_HOLD_ON_CS; data->cfg.slave = DT_REG_ADDR(DT_INST(0, zephyr_mmc_spi_slot)); data->cs = device_get_binding( DT_SPI_DEV_CS_GPIOS_LABEL(DT_INST(0, zephyr_mmc_spi_slot))); __ASSERT_NO_MSG(data->cs != NULL); data->pin = DT_SPI_DEV_CS_GPIOS_PIN(DT_INST(0, zephyr_mmc_spi_slot)); data->flags = DT_SPI_DEV_CS_GPIOS_FLAGS(DT_INST(0, zephyr_mmc_spi_slot)); disk_spi_sdhc_init(dev); return gpio_pin_configure(data->cs, data->pin, GPIO_OUTPUT_INACTIVE | data->flags); } static int disk_spi_sdhc_access_status(struct disk_info *disk) { struct device *dev = disk->dev; struct sdhc_spi_data *data = dev->driver_data; return data->status; } static int disk_spi_sdhc_access_read(struct disk_info *disk, uint8_t *buf, uint32_t sector, uint32_t count) { struct device *dev = disk->dev; struct sdhc_spi_data *data = dev->driver_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) { struct device *dev = disk->dev; struct sdhc_spi_data *data = dev->driver_data; int err; 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) { struct device *dev = disk->dev; struct sdhc_spi_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: *(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) { struct device *dev = disk->dev; struct sdhc_spi_data *data = dev->driver_data; int err; err = sdhc_spi_detect(data); sdhc_spi_set_cs(data, 1); 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_DISK_SDHC_VOLUME_NAME, .ops = &spi_sdhc_disk_ops, }; static int disk_spi_sdhc_init(struct device *dev) { struct sdhc_spi_data *data = dev->driver_data; data->status = DISK_STATUS_UNINIT; spi_sdhc_disk.dev = dev; return disk_access_register(&spi_sdhc_disk); } static struct sdhc_spi_data sdhc_spi_data_0; DEVICE_AND_API_INIT(sdhc_spi_0, DT_LABEL(DT_INST(0, zephyr_mmc_spi_slot)), sdhc_spi_init, &sdhc_spi_data_0, NULL, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, NULL); #endif