[PATCH 05/11] nand: spi: Add write function support

Peter Pan
2017-02-21T07:49:57Z

Add write/writeoob support for SPI NAND. Will be
registered under struct mtd_info later.

Signed-off-by: Peter Pan <peterpandong@micron.com>
---
drivers/mtd/nand/spi/spi-nand-base.c | 228 +++++++++++++++++++++++++++++++++++
1 file changed, 228 insertions(+)

diff --git a/drivers/mtd/nand/spi/spi-nand-base.c b/drivers/mtd/nand/spi/spi-nand-base.c
index ee94eb8..d0cf552 100644
--- a/drivers/mtd/nand/spi/spi-nand-base.c
+++ b/drivers/mtd/nand/spi/spi-nand-base.c
@@ -497,6 +497,44 @@ static int spi_nand_do_read_page(struct mtd_info *mtd, u32 page_addr,
}

/**
+ * spi_nand_do_write_page - write data from buffer to flash
+ * @mtd: MTD device structure
+ * @page_addr: page address/raw address
+ * @column: column address
+ * @buf: data buffer
+ * @len: data length to write
+ * @clr_cache: clear cache register with 0xFF or not
+ */
+static int spi_nand_do_write_page(struct mtd_info *mtd, u32 page_addr,
+ bool oob_only)
+{
+ struct spi_nand_chip *chip = mtd_to_spi_nand(mtd);
+ struct nand_device *nand = mtd_to_nand(mtd);
+ u8 status;
+ int ret = 0;
+
+ spi_nand_write_enable(chip);
+ if (!oob_only)
+ spi_nand_program_data_to_cache(chip, page_addr, 0,
+ nand_page_size(nand) + nand_per_page_oobsize(nand), chip->buf);
+ else
+ spi_nand_program_data_to_cache(chip, page_addr, nand_page_size(nand),
+ nand_per_page_oobsize(nand), chip->oobbuf);
+ spi_nand_program_execute(chip, page_addr);
+ ret = spi_nand_wait(chip, &status);
+ if (ret < 0) {
+ pr_err("error %d reading page 0x%x from cache\n",
+ ret, page_addr);
+ return ret;
+ }
+ if ((status & STATUS_P_FAIL_MASK) == STATUS_P_FAIL) {
+ pr_err("program page 0x%x failed\n", page_addr);
+ ret = -EIO;
+ }
+ return ret;
+}
+
+/**
* spi_nand_transfer_oob - transfer oob to client buffer
* @chip: SPI-NAND device structure
* @oob: oob destination address
@@ -528,6 +566,39 @@ static void spi_nand_transfer_oob(struct spi_nand_chip *chip, u8 *oob,
}

/**
+ * spi_nand_fill_oob - transfer client buffer to oob
+ * @chip: SPI-NAND device structure
+ * @oob: oob data buffer
+ * @len: oob data write length
+ * @ops: oob ops structure
+ */
+static void spi_nand_fill_oob(struct spi_nand_chip *chip, uint8_t *oob,
+ size_t len, struct mtd_oob_ops *ops)
+{
+ struct mtd_info *mtd = spi_nand_to_mtd(chip);
+ struct nand_device *nand = mtd_to_nand(mtd);
+ int ret;
+
+ memset(chip->oobbuf, 0xff, nand_per_page_oobsize(nand));
+ switch (ops->mode) {
+
+ case MTD_OPS_PLACE_OOB:
+ case MTD_OPS_RAW:
+ memcpy(chip->oobbuf + ops->ooboffs, oob, len);
+ return;
+
+ case MTD_OPS_AUTO_OOB:
+ ret = mtd_ooblayout_set_databytes(mtd, oob, chip->oobbuf,
+ ops->ooboffs, len);
+ BUG_ON(ret);
+ return;
+
+ default:
+ BUG();
+ }
+}
+
+/**
* spi_nand_read_pages - read data from flash to buffer
* @mtd: MTD device structure
* @from: offset to read from
@@ -625,6 +696,102 @@ static int spi_nand_do_read_ops(struct mtd_info *mtd, loff_t from,
}

/**
+ * spi_nand_write_pages - write data from buffer to flash
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @ops: oob operations description structure
+ * @max_bitflips: maximum bitflip count
+ * Description:
+ * Normal read function, read one page to buffer before issue
+ * another.
+ */
+static int spi_nand_write_pages(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ struct spi_nand_chip *chip = mtd_to_spi_nand(mtd);
+ struct nand_device *nand = mtd_to_nand(mtd);
+ int page_addr, page_offset, size, ret;
+ int writelen = ops->len;
+ int oobwritelen = ops->ooblen;
+ int ooblen = ops->mode == MTD_OPS_AUTO_OOB ?
+ mtd->oobavail : mtd->oobsize;
+ bool oob_only = ops->datbuf == NULL;
+
+ page_addr = nand_offs_to_page(nand, to);
+ page_offset = to & (nand_page_size(nand) - 1);
+ ops->retlen = 0;
+
+ while (1) {
+ memset(chip->buf, 0xff, nand_page_size(nand) + nand_per_page_oobsize(nand));
+ if (ops->oobbuf) {
+ size = min(oobwritelen, ooblen);
+ spi_nand_fill_oob(chip, ops->oobbuf + ops->oobretlen,
+ size, ops);
+ ops->oobretlen += size;
+ oobwritelen -= size;
+ }
+ if (ops->datbuf) {
+ size = min_t(int, writelen, nand_page_size(nand) - page_offset);
+ memcpy(chip->buf + page_offset,
+ ops->datbuf + ops->retlen, size);
+ ops->retlen += size;
+ writelen -= size;
+ }
+ ret = spi_nand_do_write_page(mtd, page_addr, oob_only);
+ if (ret) {
+ pr_err("error %d writing page 0x%x\n",
+ ret, page_addr);
+ return ret;
+ }
+ page_offset = 0;
+ if (!writelen && !oobwritelen)
+ break;
+ page_addr++;
+ }
+
+ return ret;
+}
+
+/**
+ * spi_nand_do_write_ops - write data from buffer to flash
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @ops: oob operations description structure
+ * Description:
+ * Disable internal ECC before writing when MTD_OPS_RAW set.
+ */
+static int spi_nand_do_write_ops(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ struct spi_nand_chip *chip = mtd_to_spi_nand(mtd);
+ struct nand_device *nand = mtd_to_nand(mtd);
+ int page_addr, page_offset;
+ int oobwritelen = ops->ooblen;
+ int ret = 0;
+ int ooblen = ops->mode == MTD_OPS_AUTO_OOB ?
+ mtd->oobavail : mtd->oobsize;
+ bool ecc_off = ops->mode == MTD_OPS_RAW;
+
+ page_addr = nand_offs_to_page(nand, to);
+ page_offset = to & (nand_page_size(nand) - 1);
+ ops->retlen = 0;
+
+ /* for oob */
+ if (oobwritelen > 0) {
+ ooblen -= ops->ooboffs;
+ ops->oobretlen = 0;
+ }
+
+ if (ecc_off)
+ spi_nand_disable_ecc(chip);
+ ret = spi_nand_write_pages(mtd, to, ops);
+ if (ecc_off)
+ spi_nand_enable_ecc(chip);
+
+ return ret;
+}
+
+/**
* spi_nand_read - [MTD Interface] SPI-NAND read
* @mtd: MTD device structure
* @from: offset to read from
@@ -654,6 +821,35 @@ static int spi_nand_read(struct mtd_info *mtd, loff_t from, size_t len,
}

/**
+ * spi_nand_write - [MTD Interface] SPI-NAND write
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @len: number of bytes to write
+ * @retlen: pointer to variable to store the number of written bytes
+ * @buf: the data to write
+ */
+static int spi_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u8 *buf)
+{
+ struct mtd_oob_ops ops;
+ int ret;
+
+ spi_nand_get_device(mtd, FL_WRITING);
+
+ memset(&ops, 0, sizeof(ops));
+ ops.len = len;
+ ops.datbuf = (uint8_t *)buf;
+ ops.mode = MTD_OPS_PLACE_OOB;
+ ret = spi_nand_do_write_ops(mtd, to, &ops);
+
+ *retlen = ops.retlen;
+
+ spi_nand_release_device(mtd);
+
+ return ret;
+}
+
+/**
* spi_nand_read_oob - [MTD Interface] read data and/or out-of-band
* @mtd: MTD device structure
* @from: offset to read from
@@ -684,6 +880,38 @@ static int spi_nand_read_oob(struct mtd_info *mtd, loff_t from,

return ret;
}
+
+/**
+ * spi_nand_write_oob - [MTD Interface] write data and/or out-of-band
+ * @mtd: MTD device structure
+ * @to: offset to write to
+ * @ops: oob operation description structure
+ */
+static int spi_nand_write_oob(struct mtd_info *mtd, loff_t to,
+ struct mtd_oob_ops *ops)
+{
+ int ret = -ENOTSUPP;
+
+ ops->retlen = 0;
+ spi_nand_get_device(mtd, FL_WRITING);
+
+ switch (ops->mode) {
+ case MTD_OPS_PLACE_OOB:
+ case MTD_OPS_AUTO_OOB:
+ case MTD_OPS_RAW:
+ break;
+
+ default:
+ goto out;
+ }
+
+ ret = spi_nand_do_write_ops(mtd, to, ops);
+
+out:
+ spi_nand_release_device(mtd);
+
+ return ret;
+}
MODULE_DESCRIPTION("SPI NAND framework");
MODULE_AUTHOR("Peter Pan<peterpandong@micron.com>");
MODULE_LICENSE("GPL v2");
--
1.9.1