| /* |
| * Copyright (C) 2014 Imagination Technologies |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; version 2 of the License. |
| * |
| */ |
| |
| #include "nand.h" |
| #include "spi_nand.h" |
| #include "base/container_of.h" |
| |
| #include <strings.h> |
| |
| /* SPI NAND commands */ |
| #define SPI_NAND_WRITE_ENABLE 0x06 |
| #define SPI_NAND_WRITE_DISABLE 0x04 |
| #define SPI_NAND_GET_FEATURE 0x0f |
| #define SPI_NAND_SET_FEATURE 0x1f |
| #define SPI_NAND_PAGE_READ 0x13 |
| #define SPI_NAND_READ_CACHE 0x03 |
| #define SPI_NAND_FAST_READ_CACHE 0x0b |
| #define SPI_NAND_READ_CACHE_X2 0x3b |
| #define SPI_NAND_READ_CACHE_X4 0x6b |
| #define SPI_NAND_READ_CACHE_DUAL_IO 0xbb |
| #define SPI_NAND_READ_CACHE_QUAD_IO 0xeb |
| #define SPI_NAND_READ_ID 0x9f |
| #define SPI_NAND_PROGRAM_LOAD 0x02 |
| #define SPI_NAND_PROGRAM_LOAD4 0x32 |
| #define SPI_NAND_PROGRAM_EXEC 0x10 |
| #define SPI_NAND_PROGRAM_LOAD_RANDOM 0x84 |
| #define SPI_NAND_PROGRAM_LOAD_RANDOM4 0xc4 |
| #define SPI_NAND_BLOCK_ERASE 0xd8 |
| #define SPI_NAND_RESET 0xff |
| |
| /* Registers common to all devices */ |
| #define SPI_NAND_LOCK_REG 0xa0 |
| #define SPI_NAND_PROT_UNLOCK_ALL 0x0 |
| |
| #define SPI_NAND_FEATURE_REG 0xb0 |
| #define SPI_NAND_ECC_EN (1 << 4) |
| |
| #define SPI_NAND_STATUS_REG 0xc0 |
| #define SPI_NAND_STATUS_REG_ECC_MASK 0x3 |
| #define SPI_NAND_STATUS_REG_ECC_SHIFT 4 |
| #define SPI_NAND_STATUS_REG_PROG_FAIL (1 << 3) |
| #define SPI_NAND_STATUS_REG_ERASE_FAIL (1 << 2) |
| #define SPI_NAND_STATUS_REG_WREN (1 << 1) |
| #define SPI_NAND_STATUS_REG_BUSY (1 << 0) |
| |
| #define SPI_NAND_GD5F_ECC_MASK ((1 << 0) | (1 << 1) | (1 << 2)) |
| #define SPI_NAND_GD5F_ECC_UNCORR ((1 << 0) | (1 << 1) | (1 << 2)) |
| #define SPI_NAND_GD5F_ECC_SHIFT 4 |
| |
| #define NAND_READY_TIMEOUT 100000 /* 1 SEC */ |
| |
| #define NAND_ID_MAN(id) ((id) & 0xFF) |
| #define NAND_ID_DEV(id) (((id) >> 8) & 0xFF) |
| #define SPI_NAND_READID_LEN 2 |
| #define MTD_SPI_NAND_DEV(mtd) ((struct spi_nand_dev *)((mtd)->priv)) |
| |
| /* Debug macros */ |
| #define SNAND_DEBUG 0 |
| #define snand_debug(...) do { if (SNAND_DEBUG) printf(__VA_ARGS__); } while (0) |
| |
| /* |
| * Given Depthcharge's MTD testing options are very limited, let's |
| * add an option to poison the read buffers. |
| */ |
| /*#define SNAND_DEBUG_POISON_READ_BUFFER */ |
| #define SNAND_POISON_BYTE 0x5a |
| |
| struct spi_nand_flash_dev { |
| char *name; |
| uint8_t id; |
| unsigned int pagesize; |
| unsigned int chipsize; |
| unsigned int erasesize; |
| unsigned int oobsize; |
| }; |
| |
| struct spi_nand_cmd { |
| /* Command and address. I/O errors have been observed if a |
| * separate spi_transfer is used for command and address, |
| * so keep them together |
| */ |
| uint32_t n_cmd; |
| uint8_t cmd[4]; |
| |
| /* Tx data */ |
| uint32_t n_tx; |
| uint8_t *tx_buf; |
| |
| /* Rx data */ |
| uint32_t n_rx; |
| uint8_t *rx_buf; |
| }; |
| |
| struct spi_nand_dev { |
| SpiOps *spi; |
| struct spi_nand_cmd cmd; |
| |
| unsigned char *buffers; |
| unsigned char *pad_dat; |
| unsigned char *pad_oob; |
| unsigned char *zero_page; |
| unsigned char *zero_oob; |
| |
| unsigned int oob_per_page; |
| |
| /* Fields from nand_chip */ |
| unsigned page_shift; |
| unsigned phys_erase_shift; |
| }; |
| |
| static struct spi_nand_flash_dev spi_nand_flash_ids[] = { |
| { |
| .name = "SPI NAND 512MiB 3,3V", |
| .id = 0xb4, |
| .chipsize = 512, |
| .pagesize = 4 * 1024, |
| .erasesize = 256 * 1024, |
| .oobsize = 256, |
| }, |
| { |
| .name = "SPI NAND 512MiB 1,8V", |
| .id = 0xa4, |
| .chipsize = 512, |
| .pagesize = 4 * 1024, |
| .erasesize = 256 * 1024, |
| .oobsize = 256, |
| }, |
| }; |
| |
| static void spi_nand_debug_poison_buf(uint8_t *buf, unsigned int len) |
| { |
| #ifdef SNAND_DEBUG_POISON_READ_BUFFER |
| memset(buf, SNAND_POISON_BYTE, len); |
| #endif |
| } |
| |
| static int spi_nand_transfer(SpiOps *spi, struct spi_nand_cmd *cmd) |
| { |
| if (spi->start(spi)) { |
| printf("%s: failed to start flash transaction.\n", __func__); |
| return -EIO; |
| } |
| |
| if (!cmd->n_cmd) { |
| printf("%s: failed to send empty command\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (cmd->n_tx && cmd->n_rx) { |
| printf("%s: cannot send and receive data at the same time\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| /* Command and address */ |
| if (spi->transfer(spi, NULL, cmd->cmd, cmd->n_cmd)) { |
| printf("%s: failed to send command and address\n", __func__); |
| spi->stop(spi); |
| return -EIO; |
| } |
| |
| /* Data to be transmitted */ |
| if (cmd->n_tx && spi->transfer(spi, NULL, cmd->tx_buf, cmd->n_tx)) { |
| printf("%s: failed to send data\n", __func__); |
| spi->stop(spi); |
| return -EIO; |
| } |
| |
| /* Data to be received */ |
| if (cmd->n_rx && spi->transfer(spi, cmd->rx_buf, NULL, cmd->n_rx)) { |
| printf("%s: failed to receive data\n", __func__); |
| spi->stop(spi); |
| return -EIO; |
| } |
| |
| if (spi->stop(spi)) { |
| printf("%s: Failed to stop transaction.\n", __func__); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int spi_nand_write_enable(struct spi_nand_dev *dev) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 1; |
| cmd->cmd[0] = SPI_NAND_WRITE_ENABLE; |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| static int spi_nand_read_reg(struct spi_nand_dev *dev, uint8_t opcode, |
| uint8_t *buf) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 2; |
| cmd->cmd[0] = SPI_NAND_GET_FEATURE; |
| cmd->cmd[1] = opcode; |
| cmd->n_rx = 1; |
| cmd->rx_buf = buf; |
| |
| snand_debug("read reg 0x%x\n", opcode); |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| static int spi_nand_write_reg(struct spi_nand_dev *dev, uint8_t opcode, |
| uint8_t *buf) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 2; |
| cmd->cmd[0] = SPI_NAND_SET_FEATURE; |
| cmd->cmd[1] = opcode; |
| cmd->n_tx = 1; |
| cmd->tx_buf = buf; |
| |
| snand_debug("write reg 0x%x\n", opcode); |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| static int spi_nand_load_page(struct spi_nand_dev *dev, unsigned int page_addr) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 4; |
| cmd->cmd[0] = SPI_NAND_PAGE_READ; |
| cmd->cmd[1] = (uint8_t)((page_addr & 0xff0000) >> 16); |
| cmd->cmd[2] = (uint8_t)((page_addr & 0xff00) >> 8); |
| cmd->cmd[3] = (uint8_t)(page_addr & 0xff); |
| |
| snand_debug("%s: page 0x%x\n", __func__, page_addr); |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| static int spi_nand_read_cache(struct spi_nand_dev *dev, |
| unsigned int page_offset, |
| unsigned int length, uint8_t *read_buf) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 4; |
| cmd->cmd[0] = SPI_NAND_READ_CACHE; |
| cmd->cmd[1] = 0; |
| cmd->cmd[2] = (uint8_t)((page_offset & 0xff00) >> 8); |
| cmd->cmd[3] = (uint8_t)(page_offset & 0xff); |
| cmd->n_rx = length; |
| cmd->rx_buf = read_buf; |
| |
| snand_debug("%s: %d bytes\n", __func__, length); |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| static int spi_nand_write_from_cache(struct spi_nand_dev *dev, |
| unsigned int page_addr) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 4; |
| cmd->cmd[0] = SPI_NAND_PROGRAM_EXEC; |
| cmd->cmd[1] = (uint8_t)((page_addr & 0xff0000) >> 16); |
| cmd->cmd[2] = (uint8_t)((page_addr & 0xff00) >> 8); |
| cmd->cmd[3] = (uint8_t)(page_addr & 0xff); |
| |
| snand_debug("%s: page 0x%x\n", __func__, page_addr); |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| /* |
| * The SPI_NAND_PROGRAM_LOAD is used to write to the chip page cache, |
| * prior to programming the chip's page. |
| * Notice that it supports an offset, which might be useful to write only |
| * the OOB (e.g. in spi_nand_block_markbad). |
| */ |
| static int spi_nand_store_cache(struct spi_nand_dev *dev, unsigned int length, |
| uint8_t *write_buf) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 3; |
| cmd->cmd[0] = SPI_NAND_PROGRAM_LOAD; |
| cmd->cmd[1] = 0; |
| cmd->cmd[2] = 0; |
| cmd->n_tx = length; |
| cmd->tx_buf = write_buf; |
| |
| snand_debug("%s: %d bytes\n", __func__, length); |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| static int spi_nand_ecc_enable(struct spi_nand_dev *dev) |
| { |
| int ret; |
| uint8_t val; |
| |
| ret = spi_nand_read_reg(dev, SPI_NAND_FEATURE_REG, &val); |
| if (ret < 0) |
| return ret; |
| |
| val |= SPI_NAND_ECC_EN; |
| ret = spi_nand_write_reg(dev, SPI_NAND_FEATURE_REG, &val); |
| if (ret < 0) |
| return ret; |
| |
| snand_debug("ecc enabled\n"); |
| |
| return 0; |
| } |
| |
| static int spi_nand_ecc_disable(struct spi_nand_dev *dev) |
| { |
| int ret; |
| uint8_t val; |
| |
| ret = spi_nand_read_reg(dev, SPI_NAND_FEATURE_REG, &val); |
| if (ret < 0) |
| return ret; |
| |
| val &= ~SPI_NAND_ECC_EN; |
| ret = spi_nand_write_reg(dev, SPI_NAND_FEATURE_REG, &val); |
| if (ret < 0) |
| return ret; |
| |
| snand_debug("ecc disabled\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * Wait until the status register busy bit is cleared. |
| * Returns a negatie errno on error or time out, and a non-negative status |
| * value if the device is ready. |
| */ |
| static int spi_nand_wait_till_ready(struct spi_nand_dev *dev) |
| { |
| uint8_t status; |
| int ret, count = 0; |
| |
| while (count < NAND_READY_TIMEOUT) { |
| ret = spi_nand_read_reg(dev, SPI_NAND_STATUS_REG, &status); |
| if (ret < 0) { |
| printf("%s: error reading status register\n", __func__); |
| return ret; |
| } else if (!(status & SPI_NAND_STATUS_REG_BUSY)) { |
| return status; |
| } |
| |
| udelay(10); |
| count++; |
| } |
| |
| printf("%s: operation timed out\n", __func__); |
| |
| return -ETIMEDOUT; |
| } |
| |
| /* This is GD5F specific */ |
| static void spi_nand_get_ecc_status(unsigned int status, |
| unsigned int *corrected, |
| unsigned int *ecc_error) |
| { |
| unsigned int ecc_status = (status >> SPI_NAND_GD5F_ECC_SHIFT) & |
| SPI_NAND_GD5F_ECC_MASK; |
| |
| *ecc_error = (ecc_status == SPI_NAND_GD5F_ECC_UNCORR) ? 1 : 0; |
| if (*ecc_error == 0) |
| *corrected = 2 + ecc_status; |
| } |
| |
| |
| static int spi_nand_write_page(MtdDev *mtd, int pageno, unsigned int length, |
| uint8_t *write_buf) |
| { |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| int ret; |
| |
| /* Store the page to cache */ |
| ret = spi_nand_store_cache(dev, length, write_buf); |
| if (ret < 0) { |
| printf("spi_nand: error %d storing page %d to cache\n", |
| ret, pageno); |
| return ret; |
| } |
| |
| ret = spi_nand_write_enable(dev); |
| if (ret < 0) { |
| printf("spi_nand: write enable on page program failed\n"); |
| return ret; |
| } |
| |
| ret = spi_nand_write_from_cache(dev, pageno << dev->page_shift); |
| if (ret < 0) { |
| printf("spi_nand; error %d writing to page %d\n", |
| ret, pageno); |
| return ret; |
| } |
| |
| ret = spi_nand_wait_till_ready(dev); |
| if (ret < 0) |
| return ret; |
| if (ret & SPI_NAND_STATUS_REG_PROG_FAIL) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| /* |
| * Read a page worth of data and oob. |
| */ |
| static int spi_nand_read_page(MtdDev *mtd, int pageno, |
| unsigned int page_offset, |
| unsigned int length, |
| uint8_t *read_buf, int raw) |
| { |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| unsigned int corrected = 0, ecc_error = 0; |
| int ret; |
| |
| /* Load a page into the cache register */ |
| ret = spi_nand_load_page(dev, pageno << dev->page_shift); |
| if (ret < 0) { |
| printf("spi_nand: error %d loading page %d to cache\n", |
| ret, pageno); |
| return ret; |
| } |
| |
| ret = spi_nand_wait_till_ready(dev); |
| if (ret < 0) |
| return ret; |
| |
| if (!raw) { |
| spi_nand_get_ecc_status(ret, &corrected, &ecc_error); |
| mtd->ecc_stats.corrected += corrected; |
| |
| /* |
| * If there's an ECC error, print a message and notify MTD |
| * about it. Then complete the read, to load actual data on |
| * the buffer (instead of the status result). |
| */ |
| if (ecc_error) { |
| printf("spi_nand: ECC error reading page %d\n", |
| pageno); |
| mtd->ecc_stats.failed++; |
| } |
| } |
| |
| /* Get page from the device cache into our internal buffer */ |
| ret = spi_nand_read_cache(dev, page_offset, length, read_buf); |
| if (ret < 0) { |
| printf("spi_nand: error %d reading page %d from cache\n", |
| ret, pageno); |
| return ret; |
| } |
| return 0; |
| } |
| |
| /* |
| * Estimate the no. of pages to read, based on the data length and oob |
| * length. |
| */ |
| static int spi_nand_get_read_page_count(MtdDev *mtd, struct mtd_oob_ops *ops) |
| { |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| if (ops->datbuf != NULL) { |
| return (ops->len + mtd->writesize - 1) >> dev->page_shift; |
| } else { |
| if (dev->oob_per_page == 0) |
| return 0; |
| |
| return (ops->ooblen + dev->oob_per_page - 1) |
| / dev->oob_per_page; |
| } |
| } |
| |
| /* |
| * Copy the OOB data from the internal buffer, to the user buffer, if |
| * the internal buffer was used for the read. |
| */ |
| static void spi_nand_read_oobcopy(MtdDev *mtd, struct mtd_oob_ops *ops) |
| { |
| unsigned int ooblen, read_ooblen; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| if (ops->oobbuf == NULL) |
| return; |
| |
| read_ooblen = ops->ooblen - ops->oobretlen; |
| ooblen = MIN(read_ooblen, dev->oob_per_page); |
| memcpy(ops->oobbuf + ops->oobretlen, dev->pad_oob, ooblen); |
| |
| ops->oobretlen += ooblen; |
| } |
| |
| /* |
| * Copy the in-band data from the internal buffer, to the user buffer, |
| * if the internal buffer was used for the read. |
| */ |
| static void spi_nand_read_datcopy(MtdDev *mtd, struct mtd_oob_ops *ops) |
| { |
| unsigned int datlen, read_datlen; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| if (ops->datbuf == NULL) |
| return; |
| |
| read_datlen = ops->len - ops->retlen; |
| datlen = MIN(read_datlen, mtd->writesize); |
| memcpy(ops->datbuf + ops->retlen, dev->pad_dat, datlen); |
| |
| ops->retlen += datlen; |
| } |
| |
| static int spi_nand_read_oob(MtdDev *mtd, uint64_t from, |
| struct mtd_oob_ops *ops) |
| { |
| int start, pages, i; |
| uint32_t corrected; |
| unsigned read_len, read_off; |
| uint8_t *read_buf; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| int ret = 0; |
| |
| /* We don't support MTD_OOB_PLACE as of yet. */ |
| if (ops->mode == MTD_OOB_PLACE) |
| return -ENOSYS; |
| |
| /* Check for reads past end of device */ |
| if (ops->datbuf && (from + ops->len) > mtd->size) |
| return -EINVAL; |
| |
| if (from & (mtd->writesize - 1)) |
| return -EINVAL; |
| |
| if (ops->ooboffs != 0) |
| return -EINVAL; |
| |
| if (ops->mode == MTD_OOB_RAW) |
| spi_nand_ecc_disable(dev); |
| |
| start = from >> dev->page_shift; |
| pages = spi_nand_get_read_page_count(mtd, ops); |
| |
| read_len = 0; |
| if (ops->datbuf) |
| read_len += mtd->writesize; |
| if (ops->oobbuf) |
| read_len += dev->oob_per_page; |
| |
| if (ops->datbuf) { |
| read_buf = dev->pad_dat; |
| read_off = 0; |
| } else { |
| read_buf = dev->pad_oob; |
| read_off = mtd->writesize; |
| } |
| |
| snand_debug("Start of page: %d\n", start); |
| snand_debug("No of pages to read: %d\n", pages); |
| snand_debug("Page offset: 0x%x\n", read_off); |
| snand_debug("Read size %u bytes\n", read_len); |
| |
| corrected = mtd->ecc_stats.corrected; |
| |
| for (i = start; i < (start + pages); i++) { |
| |
| spi_nand_debug_poison_buf(read_buf, read_len); |
| |
| /* |
| * Read into the internal buffers. Depending on the request |
| * this reads either: page data plus OOB on pad_dat, page data |
| * only on pad_dat, or OOB only on pad_oob. |
| */ |
| ret = spi_nand_read_page(mtd, i, read_off, read_len, read_buf, |
| (ops->mode == MTD_OOB_RAW)); |
| if (ret < 0) |
| goto done; |
| |
| spi_nand_read_datcopy(mtd, ops); |
| spi_nand_read_oobcopy(mtd, ops); |
| } |
| |
| if (mtd->ecc_stats.corrected != corrected) |
| ret = -EUCLEAN; |
| |
| done: |
| if (ops->mode == MTD_OOB_RAW) |
| spi_nand_ecc_enable(dev); |
| return ret; |
| } |
| |
| static int spi_nand_read(MtdDev *mtd, uint64_t from, size_t len, |
| size_t *retlen, unsigned char *buf) |
| { |
| int ret; |
| struct mtd_oob_ops ops; |
| |
| ops.mode = MTD_OOB_AUTO; |
| ops.len = len; |
| ops.retlen = 0; |
| ops.ooblen = 0; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = (uint8_t *)buf; |
| ops.oobbuf = NULL; |
| |
| ret = spi_nand_read_oob(mtd, from, &ops); |
| *retlen = ops.retlen; |
| |
| return ret; |
| } |
| |
| static int spi_nand_write_oob(MtdDev *mtd, uint64_t to, struct mtd_oob_ops *ops) |
| { |
| uint8_t *write_buf; |
| unsigned int write_len; |
| int i, start, pages, ret = 0; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| /* We don't support MTD_OOB_PLACE as of yet. */ |
| if (ops->mode == MTD_OOB_PLACE) |
| return -ENOSYS; |
| |
| /* Check for writes past end of device. */ |
| if ((to + ops->len) > mtd->size) |
| return -EINVAL; |
| |
| if (to & (mtd->writesize - 1)) |
| return -EINVAL; |
| |
| if (ops->len & (mtd->writesize - 1)) |
| return -EINVAL; |
| |
| if (ops->ooboffs != 0) |
| return -EINVAL; |
| |
| if (ops->datbuf == NULL) |
| return -EINVAL; |
| |
| start = to >> dev->page_shift; |
| pages = ops->len >> dev->page_shift; |
| |
| /* Only support a single page OOB write request */ |
| if (ops->oobbuf && pages > 1) |
| return -EINVAL; |
| /* Only support a contiguous buffer for data and OOB */ |
| if (ops->oobbuf && ops->oobbuf != (ops->datbuf + ops->len + 1)) |
| return -EINVAL; |
| |
| if (ops->mode == MTD_OOB_RAW) |
| spi_nand_ecc_disable(dev); |
| |
| /* |
| * This currently supports two modes of writing: |
| * 1. Data only (no OOB), on a per-page basis. |
| * 2. Data plus OOB. |
| * |
| * Therefore, the write buffer always starts at the data buffer |
| * of the mtd_oob_ops. The write length is at least a chip page. |
| */ |
| write_buf = ops->datbuf; |
| write_len = mtd->writesize; |
| if (ops->oobbuf) |
| write_len += dev->oob_per_page; |
| ops->retlen = 0; |
| ops->oobretlen = 0; |
| |
| snand_debug("Start of page: %d\n", start); |
| snand_debug("No of pages to write: %d\n", pages); |
| |
| for (i = start; i < (start + pages); i++) { |
| |
| ret = spi_nand_write_page(mtd, i, write_len, write_buf); |
| if (ret < 0) |
| goto done; |
| |
| ops->retlen += mtd->writesize; |
| write_buf += mtd->writesize; |
| |
| if (ops->oobbuf) { |
| ops->oobretlen += dev->oob_per_page; |
| write_buf += dev->oob_per_page; |
| } |
| } |
| |
| done: |
| if (ops->mode == MTD_OOB_RAW) |
| spi_nand_ecc_enable(dev); |
| return ret; |
| } |
| |
| static int spi_nand_write(MtdDev *mtd, uint64_t to, size_t len, |
| size_t *retlen, const unsigned char *buf) |
| { |
| int ret; |
| struct mtd_oob_ops ops; |
| |
| ops.mode = MTD_OOB_AUTO; |
| ops.len = len; |
| ops.retlen = 0; |
| ops.ooblen = 0; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = (uint8_t *)buf; |
| ops.oobbuf = NULL; |
| |
| ret = spi_nand_write_oob(mtd, to, &ops); |
| *retlen = ops.retlen; |
| |
| return ret; |
| } |
| |
| /* Read the first byte of the out-of-band region. |
| * If this byte is clean 0xff, the block is good. |
| * Otherwise, it's been marked as bad. |
| */ |
| static int spi_nand_block_isbad(MtdDev *mtd, uint64_t offs) |
| { |
| int ret; |
| uint8_t oobbuf; |
| struct mtd_oob_ops ops; |
| |
| /* Check for out of bounds or unaligned offset */ |
| if ((offs > mtd->size) || |
| (offs & (mtd->erasesize - 1))) |
| return -EINVAL; |
| |
| ops.mode = MTD_OOB_RAW; |
| ops.len = 0; |
| ops.retlen = 0; |
| ops.ooblen = 1; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = NULL; |
| ops.oobbuf = &oobbuf; |
| |
| ret = spi_nand_read_oob(mtd, offs, &ops); |
| if (ret < 0) |
| return ret; |
| |
| return oobbuf != 0xFF; |
| } |
| |
| static int spi_nand_block_markbad(MtdDev *mtd, uint64_t offs) |
| { |
| int ret; |
| struct mtd_oob_ops ops; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| /* Check for invalid offset */ |
| if (offs > mtd->size) |
| return -EINVAL; |
| |
| if (offs & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| ops.mode = MTD_OOB_RAW; |
| ops.len = mtd->writesize; |
| ops.retlen = 0; |
| ops.ooblen = mtd->oobsize; |
| ops.oobretlen = 0; |
| ops.ooboffs = 0; |
| ops.datbuf = dev->zero_page; |
| ops.oobbuf = dev->zero_oob; |
| |
| ret = spi_nand_write_oob(mtd, offs, &ops); |
| if (!ret) |
| mtd->ecc_stats.badblocks++; |
| |
| return ret; |
| } |
| |
| /* |
| * Erase the specified block. |
| */ |
| static int spi_nand_erase_block(MtdDev *mtd, int blockno) |
| { |
| int ret; |
| uint64_t offs; |
| uint32_t page_addr; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| offs = blockno << dev->phys_erase_shift; |
| page_addr = offs >> dev->page_shift; |
| |
| snand_debug("erasing block %d (page 0x%x)\n", blockno, page_addr); |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 4; |
| cmd->cmd[0] = SPI_NAND_BLOCK_ERASE; |
| cmd->cmd[1] = (uint8_t)((page_addr & 0xff0000) >> 16); |
| cmd->cmd[2] = (uint8_t)((page_addr & 0xff00) >> 8); |
| cmd->cmd[3] = (uint8_t)(page_addr & 0xff); |
| |
| ret = spi_nand_transfer(dev->spi, cmd); |
| if (ret < 0) |
| return ret; |
| |
| ret = spi_nand_wait_till_ready(dev); |
| if (ret < 0) |
| return ret; |
| if (ret & SPI_NAND_STATUS_REG_ERASE_FAIL) |
| return -EIO; |
| return 0; |
| } |
| |
| static int spi_nand_erase(MtdDev *mtd, struct erase_info *instr) |
| { |
| int i; |
| int blocks; |
| int start; |
| uint64_t offs; |
| int ret = 0; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| /* Check for erase past end of device. */ |
| if ((instr->addr + instr->len) > mtd->size) |
| return -EINVAL; |
| |
| if (instr->addr & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| if (instr->len & (mtd->erasesize - 1)) |
| return -EINVAL; |
| |
| start = instr->addr >> dev->phys_erase_shift; |
| blocks = instr->len >> dev->phys_erase_shift; |
| snand_debug("number of blks to erase: %d\n", blocks); |
| |
| for (i = start; i < (start + blocks); i++) { |
| offs = i << dev->phys_erase_shift; |
| |
| if (!instr->scrub && spi_nand_block_isbad(mtd, offs)) { |
| printf("spi_nand: attempt to erase a bad block"); |
| return -EIO; |
| } |
| |
| ret = spi_nand_write_enable(dev); |
| if (ret < 0) { |
| printf("%s: write enable on block %d erase failed\n", |
| __func__, i); |
| return ret; |
| } |
| |
| ret = spi_nand_erase_block(mtd, i); |
| if (ret < 0) { |
| instr->fail_addr = offs; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * Read the ID from the flash device. |
| */ |
| static int spi_nand_readid(struct spi_nand_dev *dev, uint32_t *id) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 1; |
| cmd->cmd[0] = SPI_NAND_READ_ID; |
| cmd->n_rx = SPI_NAND_READID_LEN; |
| cmd->rx_buf = (uint8_t *)id; |
| |
| return spi_nand_transfer(dev->spi, cmd); |
| } |
| |
| /* |
| * Retreive the flash info entry using the device ID. |
| */ |
| static const struct spi_nand_flash_dev *flash_get_dev(uint8_t dev_id) |
| { |
| int i; |
| |
| for (i = 0; spi_nand_flash_ids[i].id; i++) { |
| if (spi_nand_flash_ids[i].id == dev_id) |
| return &spi_nand_flash_ids[i]; |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Populate flash parameters for non-ONFI devices. |
| */ |
| static int nand_get_info(MtdDev *mtd, uint32_t flash_id) |
| { |
| uint8_t man_id; |
| uint8_t dev_id; |
| const struct spi_nand_flash_dev *flash_dev; |
| |
| man_id = NAND_ID_MAN(flash_id); |
| dev_id = NAND_ID_DEV(flash_id); |
| |
| printf("Manufacturer ID: %x\n", man_id); |
| printf("Device ID: %x\n", dev_id); |
| |
| flash_dev = flash_get_dev(dev_id); |
| if (!flash_dev) { |
| printf("spi_nand: unknown NAND device!\n"); |
| return -ENOENT; |
| } |
| |
| mtd->size = (uint64_t)flash_dev->chipsize * MiB; |
| mtd->writesize = flash_dev->pagesize; |
| mtd->erasesize = flash_dev->erasesize; |
| mtd->oobsize = flash_dev->oobsize; |
| |
| return 0; |
| } |
| |
| /* |
| * Read the device ID, and populate the MTD callbacks and the device |
| * parameters. |
| */ |
| int spi_nand_scan(MtdDev *mtd) |
| { |
| int ret; |
| uint32_t nand_id1 = 0; |
| uint32_t nand_id2 = 0; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| |
| ret = spi_nand_readid(dev, &nand_id1); |
| if (ret < 0) |
| return ret; |
| |
| ret = spi_nand_readid(dev, &nand_id2); |
| if (ret < 0) |
| return ret; |
| |
| if (nand_id1 != nand_id2) { |
| /* |
| * Bus-hold or other interface concerns can cause |
| * random data to appear. If the two results do not |
| * match, we are reading garbage. |
| */ |
| |
| printf("spi_nand: device ID mismatch (0x%02x - 0x%02x)\n", |
| nand_id1, nand_id2); |
| return -ENODEV; |
| } |
| |
| ret = nand_get_info(mtd, nand_id1); |
| if (ret < 0) |
| return ret; |
| |
| mtd->erase = spi_nand_erase; |
| mtd->read = spi_nand_read; |
| mtd->write = spi_nand_write; |
| mtd->read_oob = spi_nand_read_oob; |
| mtd->write_oob = spi_nand_write_oob; |
| mtd->block_isbad = spi_nand_block_isbad; |
| mtd->block_markbad = spi_nand_block_markbad; |
| |
| dev->page_shift = ffs(mtd->writesize) - 1; |
| dev->phys_erase_shift = ffs(mtd->erasesize) - 1; |
| |
| return 0; |
| } |
| |
| /* |
| * Setup the hardware and the driver state. Called after the scan and |
| * is passed in the results of the scan. |
| */ |
| int spi_nand_post_scan_init(MtdDev *mtd) |
| { |
| size_t alloc_size; |
| struct spi_nand_dev *dev = MTD_SPI_NAND_DEV(mtd); |
| uint8_t *buf; |
| |
| alloc_size = (mtd->writesize /* For dev->pad_dat */ |
| + mtd->oobsize /* For dev->pad_oob */ |
| + mtd->writesize /* For dev->zero_page */ |
| + mtd->oobsize); /* For dev->zero_oob */ |
| |
| dev->buffers = malloc(alloc_size); |
| if (dev->buffers == NULL) { |
| printf("spi_nand: failed to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| buf = dev->buffers; |
| |
| dev->pad_dat = buf; |
| buf += mtd->writesize; |
| |
| dev->pad_oob = buf; |
| buf += mtd->oobsize; |
| |
| dev->zero_page = buf; |
| buf += mtd->writesize; |
| |
| dev->zero_oob = buf; |
| buf += mtd->oobsize; |
| |
| memset(dev->zero_page, 0x0, mtd->writesize); |
| memset(dev->zero_oob, 0x0, mtd->oobsize); |
| |
| printf("erase size: %d\n", mtd->erasesize); |
| printf("page size: %d\n", mtd->writesize); |
| printf("OOB size: %d\n", mtd->oobsize); |
| |
| return 0; |
| } |
| |
| static int spi_nand_reset(struct spi_nand_dev *dev) |
| { |
| struct spi_nand_cmd *cmd = &dev->cmd; |
| int ret; |
| |
| memset(cmd, 0, sizeof(struct spi_nand_cmd)); |
| cmd->n_cmd = 1; |
| cmd->cmd[0] = SPI_NAND_RESET; |
| |
| ret = spi_nand_transfer(dev->spi, cmd); |
| if (ret < 0) |
| return ret; |
| |
| ret = spi_nand_wait_till_ready(dev); |
| if (ret < 0) |
| return ret; |
| return 0; |
| } |
| |
| /* |
| * Initialize controller and register as an MTD device. |
| */ |
| int spi_nand_init(SpiOps *spi, MtdDev **mtd_out) |
| { |
| int ret; |
| uint8_t val; |
| MtdDev *mtd; |
| struct spi_nand_dev *dev; |
| |
| printf("Initializing SPI NAND\n"); |
| |
| mtd = xzalloc(sizeof(*mtd)); |
| dev = xzalloc(sizeof(*dev)); |
| |
| dev->spi = spi; |
| mtd->priv = dev; |
| |
| /* Reset Flash Memory */ |
| ret = spi_nand_reset(dev); |
| if (ret < 0) { |
| printf("spi_nand: flash reset timedout\n"); |
| return ret; |
| } |
| |
| /* Identify the NAND device. */ |
| ret = spi_nand_scan(mtd); |
| if (ret < 0) { |
| printf("spi_nand: failed to identify device\n"); |
| return ret; |
| } |
| |
| ret = spi_nand_post_scan_init(mtd); |
| if (ret < 0) |
| return ret; |
| |
| /* |
| * The device was detected so we need to unlock the entire device |
| * at this point to allow erase and write operations to work. |
| */ |
| val = SPI_NAND_PROT_UNLOCK_ALL; |
| ret = spi_nand_write_reg(dev, SPI_NAND_LOCK_REG, &val); |
| if (ret < 0) |
| return ret; |
| |
| *mtd_out = mtd; |
| return 0; |
| } |
| |
| typedef struct { |
| MtdDevCtrlr ctrlr; |
| SpiOps *spiops; |
| } SpiNandDevCtrlr; |
| |
| static int spi_nand_update(MtdDevCtrlr *ctrlr) |
| { |
| if (ctrlr->dev) |
| return 0; |
| |
| MtdDev *mtd; |
| SpiNandDevCtrlr *spi_ctrlr = container_of(ctrlr, |
| SpiNandDevCtrlr, ctrlr); |
| int ret = spi_nand_init(spi_ctrlr->spiops, &mtd); |
| if (ret) |
| return ret; |
| ctrlr->dev = mtd; |
| return 0; |
| } |
| |
| /* External entrypoint for lazy NAND initialization */ |
| MtdDevCtrlr *new_spi_nand(SpiOps *spiops) |
| { |
| SpiNandDevCtrlr *ctrlr = xzalloc(sizeof(*ctrlr)); |
| ctrlr->ctrlr.update = spi_nand_update; |
| ctrlr->spiops = spiops; |
| return &ctrlr->ctrlr; |
| } |