summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPratyush Yadav <p.yadav@ti.com>2024-03-09 01:33:55 +0530
committerPraneeth Bajjuri <praneeth@ti.com>2024-03-11 13:51:58 -0500
commitb1d5329addbbdd443a0cdea23a3c2148b70df7b1 (patch)
treee156164ebdfa85706c91b5ba1134896d3eb90980
parent1867806af3335d4c4c74fdcc9b0510db70d4a9bd (diff)
mtd: spi-nor: core: avoid odd length/address writes in 8D-8D-8D mode
On Octal DTR capable flashes like Micron Xcella the writes cannot start or end at an odd address in Octal DTR mode. Extra 0xff bytes need to be appended or prepended to make sure the start address and end address are even. 0xff is used because on NOR flashes a program operation can only flip bits from 1 to 0, not the other way round. 0 to 1 flip needs to happen via erases. Signed-off-by: Pratyush Yadav <p.yadav@ti.com> Signed-off-by: Vignesh Raghavendra <vigneshr@ti.com> Signed-off-by: Apurva Nandan <a-nandan@ti.com>
-rw-r--r--drivers/mtd/spi-nor/core.c72
1 files changed, 71 insertions, 1 deletions
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index c4b835e00183..5adf607b2a90 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1816,6 +1816,71 @@ read_err:
}
/*
+ * On Octal DTR capable flashes like Micron Xcella the writes cannot start or
+ * end at an odd address in Octal DTR mode. Extra 0xff bytes need to be appended
+ * or prepended to make sure the start address and end address are even. 0xff is
+ * used because on NOR flashes a program operation can only flip bits from 1 to
+ * 0, not the other way round. 0 to 1 flip needs to happen via erases.
+ */
+static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
+ const u8 *buf)
+{
+ u8 *tmp_buf;
+ size_t bytes_written;
+ loff_t start, end;
+ int ret;
+
+ if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
+ return spi_nor_write_data(nor, to, len, buf);
+
+ tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ memset(tmp_buf, 0xff, nor->params->page_size);
+
+ start = round_down(to, 2);
+ end = round_up(to + len, 2);
+
+ memcpy(tmp_buf + (to - start), buf, len);
+
+ ret = spi_nor_write_data(nor, start, end - start, tmp_buf);
+ if (ret == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ /*
+ * More bytes are written than actually requested, but that number can't
+ * be reported to the calling function or it will confuse its
+ * calculations. Calculate how many of the _requested_ bytes were
+ * written.
+ */
+ bytes_written = ret;
+
+ if (to != start)
+ ret -= to - start;
+
+ /*
+ * Only account for extra bytes at the end if they were actually
+ * written. For example, if for some reason the controller could only
+ * complete a partial write then the adjustment for the extra bytes at
+ * the end is not needed.
+ */
+ if (start + bytes_written == end)
+ ret -= end - (to + len);
+
+ if (ret < 0)
+ ret = -EIO;
+
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
+/*
* Write an address range to the nor chip. Data must be written in
* FLASH_PAGESIZE chunks. The address range may be any size provided
* it is within the physical boundaries.
@@ -1859,7 +1924,12 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
if (ret)
goto write_err;
- ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
+ if (nor->write_proto == SNOR_PROTO_8_8_8_DTR)
+ ret = spi_nor_octal_dtr_write(nor, addr, page_remain,
+ buf + i);
+ else
+ ret = spi_nor_write_data(nor, addr, page_remain,
+ buf + i);
if (ret < 0)
goto write_err;
written = ret;