[PATCH 07/11] mtd: spi-nor: Migrate stacked/parallel part to new modules
Takahiro Kuwano via B4 Relay
devnull+Takahiro.Kuwano.infineon.com at kernel.org
Wed Nov 19 06:13:06 CET 2025
From: Takahiro Kuwano <Takahiro.Kuwano at infineon.com>
Add new modules named stacked.c and parallel.c. Those are compiled along
with spi-nor-core in case CONFIG_SPI_STACKED_PARALLEL is enabled.
Copy erase/program/read and write_bar functions from core to both
modules. Some helper functions inn core are exposed.
Introduce new fixup functions, spi_nor_stacked_post_init_fixups() and
spi_nor_parallel_post_init_fixups() in each module. Those implement
ofnode property check performed in spi_nor_init_params() in core, update
size parameters with replacing params->*, and assign erase/program/read
to mtd_info. The fixups will be called from core after spi_nor_init()
call.
In stacked mode, spi_nor_init() is called twice to init upper page.
spi_nor_stacked_post_init_fixups() performs the second call of
spi_nor_init().
In parallel mode, there are size updates in spi_nor_select_erase(),
spi_nor_set_4byte_opcodes(), and spi_nor_scan(). Those are not moved to
parallel module as size update can be consolidated into one place,
spi_nor_parallel_post_init_fixups(). Besides, write_bar call performed
in read_bar() is moved to fixup as read_bar() is called once before
spi_nor_init() and shouldn't be a problem if it is called after
spi_nor_init().
At this point, keep spi-nor-core as it is, reverting stacked/parallel
related changes will be done at once in another patch.
Signed-off-by: Takahiro Kuwano <Takahiro.Kuwano at infineon.com>
---
drivers/mtd/spi/Makefile | 4 +
drivers/mtd/spi/parallel.c | 470 +++++++++++++++++++++++++++++++++++++++++
drivers/mtd/spi/sf_internal.h | 15 ++
drivers/mtd/spi/spi-nor-core.c | 22 +-
drivers/mtd/spi/stacked.c | 453 +++++++++++++++++++++++++++++++++++++++
5 files changed, 953 insertions(+), 11 deletions(-)
diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile
index 44e67cd913a..563f61bd1aa 100644
--- a/drivers/mtd/spi/Makefile
+++ b/drivers/mtd/spi/Makefile
@@ -17,6 +17,10 @@ else
spi-nor-y += spi-nor-core.o
endif
+ifneq (,$(findstring spi-nor-core, $(spi-nor-y)))
+spi-nor-$(CONFIG_SPI_STACKED_PARALLEL) += stacked.o parallel.o
+endif
+
obj-$(CONFIG_SPI_FLASH) += spi-nor.o
obj-$(CONFIG_SPI_FLASH_DATAFLASH) += sf_dataflash.o
obj-$(CONFIG_$(PHASE_)SPI_FLASH_MTD) += sf_mtd.o
diff --git a/drivers/mtd/spi/parallel.c b/drivers/mtd/spi/parallel.c
new file mode 100644
index 00000000000..d884eaefcd0
--- /dev/null
+++ b/drivers/mtd/spi/parallel.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2025 Infineon Technologies
+ *
+ * SPI NOR driver for parallel-memories configuration.
+ */
+
+#include <dm/device_compat.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/sizes.h>
+#include <linux/mtd/spi-nor.h>
+#include <spi.h>
+
+#include "sf_internal.h"
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+static int write_bar(struct spi_nor *nor, u32 offset)
+{
+ u8 cmd, bank_sel, upage_curr;
+ int ret;
+ struct mtd_info *mtd = &nor->mtd;
+
+ /* Wait until previous write command is finished */
+ if (spi_nor_wait_till_ready(nor))
+ return 1;
+
+ if (nor->flags & (SNOR_F_HAS_PARALLEL | SNOR_F_HAS_STACKED) &&
+ mtd->size <= SZ_32M)
+ return 0;
+
+ if (mtd->size <= SZ_16M)
+ return 0;
+
+ offset = offset % (u32)mtd->size;
+ bank_sel = offset >> 24;
+
+ upage_curr = nor->spi->flags & SPI_XFER_U_PAGE;
+
+ if (!(nor->flags & SNOR_F_HAS_STACKED) && bank_sel == nor->bank_curr)
+ return 0;
+ else if (upage_curr == nor->upage_prev && bank_sel == nor->bank_curr)
+ return 0;
+
+ nor->upage_prev = upage_curr;
+
+ cmd = nor->bank_write_cmd;
+ write_enable(nor);
+ ret = nor->write_reg(nor, cmd, &bank_sel, 1);
+ if (ret < 0) {
+ debug("SF: fail to write bank register\n");
+ return ret;
+ }
+
+ nor->bank_curr = bank_sel;
+
+ return write_disable(nor);
+}
+#endif
+
+static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ u32 addr, len, rem, offset, max_size;
+ bool addr_known = false;
+ int ret, err;
+
+ dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
+ (long long)instr->len);
+
+ div_u64_rem(instr->len, mtd->erasesize, &rem);
+ if (rem) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ addr = instr->addr;
+ len = instr->len;
+ max_size = instr->len;
+
+ instr->state = MTD_ERASING;
+ addr_known = true;
+
+ while (len) {
+ schedule();
+ if (!IS_ENABLED(CONFIG_XPL_BUILD) && ctrlc()) {
+ addr_known = false;
+ ret = -EINTR;
+ goto erase_err;
+ }
+ offset = addr;
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ offset /= 2;
+
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ if (offset >= (mtd->size / 2))
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ else
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+ }
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = write_bar(nor, offset);
+ if (ret < 0)
+ goto erase_err;
+#endif
+ ret = write_enable(nor);
+ if (ret < 0)
+ goto erase_err;
+
+ if (len == mtd->size &&
+ !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
+ ret = spi_nor_erase_chip(nor);
+ } else {
+ ret = spi_nor_erase_sector(nor, offset);
+ }
+ if (ret < 0)
+ goto erase_err;
+
+ addr += ret;
+ len -= ret;
+
+ if (max_size == mtd->size &&
+ !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
+ ret = spi_nor_erase_chip_wait_till_ready(nor, mtd->size);
+ } else {
+ ret = spi_nor_wait_till_ready(nor);
+ }
+
+ if (ret)
+ goto erase_err;
+ }
+
+ addr_known = false;
+erase_err:
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ err = clean_bar(nor);
+ if (!ret)
+ ret = err;
+#endif
+ err = write_disable(nor);
+ if (!ret)
+ ret = err;
+
+err:
+ if (ret) {
+ instr->fail_addr = addr_known ? addr : MTD_FAIL_ADDR_UNKNOWN;
+ instr->state = MTD_ERASE_FAILED;
+ } else {
+ instr->state = MTD_ERASE_DONE;
+ }
+
+ return ret;
+}
+
+static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ loff_t offset = from;
+ u32 rem_bank_len = 0;
+ u32 stack_shift = 0;
+ size_t read_len;
+ u8 bank;
+ int ret;
+ bool is_ofst_odd = false;
+
+ dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);
+
+ if ((nor->flags & SNOR_F_HAS_PARALLEL) && (offset & 1)) {
+ /* We can hit this case when we use file system like ubifs */
+ from--;
+ len++;
+ is_ofst_odd = true;
+ }
+
+ while (len) {
+ read_len = len;
+ offset = from;
+
+ if (CONFIG_IS_ENABLED(SPI_FLASH_BAR)) {
+ bank = (u32)from / SZ_16M;
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ bank /= 2;
+ }
+ rem_bank_len = SZ_16M * (bank + 1);
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ rem_bank_len *= 2;
+ }
+ rem_bank_len -= from;
+ }
+
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ stack_shift = 1;
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ } else {
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+ }
+ }
+
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ offset /= 2;
+ }
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = write_bar(nor, offset);
+ if (ret < 0)
+ return log_ret(ret);
+ if (len < rem_bank_len)
+ read_len = len;
+ else
+ read_len = rem_bank_len;
+#endif
+
+ if (read_len == 0)
+ return -EIO;
+
+ ret = nor->read(nor, offset, read_len, buf);
+ if (ret == 0) {
+ /* We shouldn't see 0-length reads */
+ ret = -EIO;
+ goto read_err;
+ }
+ if (ret < 0)
+ goto read_err;
+
+ if (is_ofst_odd == true) {
+ memmove(buf, (buf + 1), (len - 1));
+ *retlen += (ret - 1);
+ buf += ret - 1;
+ is_ofst_odd = false;
+ } else {
+ *retlen += ret;
+ buf += ret;
+ }
+ from += ret;
+ len -= ret;
+ }
+ ret = 0;
+
+read_err:
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = clean_bar(nor);
+#endif
+ return ret;
+}
+
+static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ size_t page_offset, page_remain, i;
+ ssize_t ret;
+ u32 offset;
+
+#ifdef CONFIG_SPI_FLASH_SST
+ /* sst nor chips use AAI word program */
+ if (nor->info->flags & SST_WRITE)
+ return sst_write(mtd, to, len, retlen, buf);
+#endif
+
+ dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
+
+ if (!len)
+ return 0;
+
+ /*
+ * Cannot write to odd offset in parallel mode,
+ * so write 2 bytes first
+ */
+ if ((nor->flags & SNOR_F_HAS_PARALLEL) && (to & 1)) {
+ u8 two[2] = {0xff, buf[0]};
+ size_t local_retlen;
+
+ ret = spi_nor_write(mtd, to & ~1, 2, &local_retlen, two);
+ if (ret < 0)
+ return ret;
+
+ *retlen += 1; /* We've written only one actual byte */
+ buf++;
+ len--;
+ to++;
+ }
+
+ for (i = 0; i < len; ) {
+ ssize_t written;
+ loff_t addr = to + i;
+ schedule();
+
+ /*
+ * If page_size is a power of two, the offset can be quickly
+ * calculated with an AND operation. On the other cases we
+ * need to do a modulus operation (more expensive).
+ */
+ if (is_power_of_2(nor->page_size)) {
+ page_offset = addr & (nor->page_size - 1);
+ } else {
+ u64 aux = addr;
+
+ page_offset = do_div(aux, nor->page_size);
+ }
+ offset = to + i;
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ offset /= 2;
+
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ } else {
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+ }
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = write_bar(nor, offset);
+ if (ret < 0)
+ return ret;
+#endif
+
+ /* the size of data remaining on the first page */
+ page_remain = min_t(size_t,
+ nor->page_size - page_offset, len - i);
+
+ write_enable(nor);
+ /*
+ * On DTR capable flashes like Micron Xcella the writes cannot
+ * start or end at an odd address in DTR mode. So we need to
+ * append or prepend extra 0xff bytes to make sure the start
+ * address and end address are even.
+ */
+ if (spi_nor_protocol_is_dtr(nor->write_proto) &&
+ ((offset | page_remain) & 1)) {
+ u_char *tmp;
+ size_t extra_bytes = 0;
+
+ tmp = kmalloc(nor->page_size, 0);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto write_err;
+ }
+
+ /* Prepend a 0xff byte if the start address is odd. */
+ if (offset & 1) {
+ tmp[0] = 0xff;
+ memcpy(tmp + 1, buf + i, page_remain);
+ offset--;
+ page_remain++;
+ extra_bytes++;
+ } else {
+ memcpy(tmp, buf + i, page_remain);
+ }
+
+ /* Append a 0xff byte if the end address is odd. */
+ if ((offset + page_remain) & 1) {
+ tmp[page_remain + extra_bytes] = 0xff;
+ extra_bytes++;
+ page_remain++;
+ }
+
+ ret = nor->write(nor, offset, page_remain, tmp);
+
+ kfree(tmp);
+
+ if (ret < 0)
+ goto write_err;
+
+ /*
+ * We write extra bytes but they are not part of the
+ * original write.
+ */
+ written = ret - extra_bytes;
+ } else {
+ ret = nor->write(nor, offset, page_remain, buf + i);
+ if (ret < 0)
+ goto write_err;
+ written = ret;
+ }
+
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ goto write_err;
+
+ *retlen += written;
+ i += written;
+ }
+
+write_err:
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = clean_bar(nor);
+#endif
+ return ret;
+}
+
+int spi_nor_parallel_post_init_fixups(struct spi_nor *nor)
+{
+ struct mtd_info *mtd = &nor->mtd;
+
+#if CONFIG_IS_ENABLED(DM_SPI)
+ u64 flash_size[SNOR_FLASH_CNT_MAX] = { 0 };
+ struct udevice *dev = nor->spi->dev;
+ u32 idx = 0, i = 0;
+ int rc;
+
+ while (i < SNOR_FLASH_CNT_MAX) {
+ rc = ofnode_read_u64_index(dev_ofnode(dev), "parallel-memories",
+ idx, &flash_size[i]);
+ if (rc == -EINVAL) {
+ break;
+ } else if (rc == -EOVERFLOW) {
+ idx++;
+ } else {
+ idx++;
+ i++;
+ if (!(nor->flags & SNOR_F_HAS_PARALLEL))
+ nor->flags |= SNOR_F_HAS_PARALLEL;
+ }
+ }
+
+ if (nor->flags & (SNOR_F_HAS_STACKED | SNOR_F_HAS_PARALLEL)) {
+ mtd->size = 0;
+ for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++)
+ mtd->size += flash_size[idx];
+ }
+
+ /*
+ * In parallel-memories the erase operation is
+ * performed on both the flashes simultaneously
+ * so, double the erasesize.
+ */
+ if (nor->flags & SNOR_F_HAS_PARALLEL) {
+ nor->mtd.erasesize <<= 1;
+ nor->page_size <<= 1;
+ nor->mtd.writebufsize <<= 1;
+ }
+#endif
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ /* Make sure both chips use the same BAR */
+ if (nor->flags & SNOR_F_HAS_PARALLEL) {
+ int ret;
+
+ write_enable(nor);
+ ret = nor->write_reg(nor, nor->bank_write_cmd, &nor->bank_curr,
+ 1);
+ if (ret)
+ return ret;
+
+ ret = write_disable(nor);
+ if (ret)
+ return ret;
+ }
+#endif
+
+ if (nor->flags & SNOR_F_HAS_PARALLEL) {
+ mtd->_erase = spi_nor_erase;
+ mtd->_read = spi_nor_read;
+ mtd->_write = spi_nor_write;
+ }
+
+ return 0;
+}
diff --git a/drivers/mtd/spi/sf_internal.h b/drivers/mtd/spi/sf_internal.h
index 8d2249ce354..8b1a48f045a 100644
--- a/drivers/mtd/spi/sf_internal.h
+++ b/drivers/mtd/spi/sf_internal.h
@@ -93,4 +93,19 @@ static inline void spi_flash_mtd_unregister(struct spi_flash *flash)
}
#endif
+#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
+int spi_nor_init(struct spi_nor *nor);
+int write_enable(struct spi_nor *nor);
+int write_disable(struct spi_nor *nor);
+int spi_nor_wait_till_ready(struct spi_nor *nor);
+struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd);
+#endif
+
+int spi_nor_erase_chip_wait_till_ready(struct spi_nor *nor, unsigned long size);
+int spi_nor_erase_chip(struct spi_nor *nor);
+int spi_nor_erase_sector(struct spi_nor *nor, u32 addr);
+int clean_bar(struct spi_nor *nor);
+int sst_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
+ const u_char *buf);
+
#endif /* _SF_INTERNAL_H_ */
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c
index 76c33b24368..9695a706d0b 100644
--- a/drivers/mtd/spi/spi-nor-core.c
+++ b/drivers/mtd/spi/spi-nor-core.c
@@ -637,7 +637,7 @@ static int write_sr3(struct spi_nor *nor, u8 val)
* Set write enable latch with Write Enable command.
* Returns negative if error occurred.
*/
-static int write_enable(struct spi_nor *nor)
+int write_enable(struct spi_nor *nor)
{
return nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0);
}
@@ -645,12 +645,12 @@ static int write_enable(struct spi_nor *nor)
/*
* Send write disable instruction to the chip.
*/
-static int write_disable(struct spi_nor *nor)
+int write_disable(struct spi_nor *nor)
{
return nor->write_reg(nor, SPINOR_OP_WRDI, NULL, 0);
}
-static struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
+struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
{
return mtd->priv;
}
@@ -910,13 +910,13 @@ static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
return -ETIMEDOUT;
}
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
+int spi_nor_wait_till_ready(struct spi_nor *nor)
{
return spi_nor_wait_till_ready_with_timeout(nor,
DEFAULT_READY_WAIT_JIFFIES);
}
-static int spi_nor_erase_chip_wait_till_ready(struct spi_nor *nor, unsigned long size)
+int spi_nor_erase_chip_wait_till_ready(struct spi_nor *nor, unsigned long size)
{
/*
* Scale the timeout linearly with the size of the flash, with
@@ -941,7 +941,7 @@ static int spi_nor_erase_chip_wait_till_ready(struct spi_nor *nor, unsigned long
* Otherwise, the BA24 bit may be left set and then after reset, the
* ROM would read/write/erase SPL from 16 MiB * bank_sel address.
*/
-static int clean_bar(struct spi_nor *nor)
+int clean_bar(struct spi_nor *nor)
{
u8 cmd, bank_sel = 0;
int ret;
@@ -1053,7 +1053,7 @@ static int read_bar(struct spi_nor *nor, const struct flash_info *info)
*
* Return: 0 on success, -errno otherwise.
*/
-static int spi_nor_erase_chip(struct spi_nor *nor)
+int spi_nor_erase_chip(struct spi_nor *nor)
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0),
@@ -1075,7 +1075,7 @@ static int spi_nor_erase_chip(struct spi_nor *nor)
* Initiate the erasure of a single sector. Returns the number of bytes erased
* on success, a negative error code on error.
*/
-static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
+int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
{
struct spi_mem_op op =
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->erase_opcode, 0),
@@ -1885,8 +1885,8 @@ sst_write_err:
return ret;
}
-static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
- size_t *retlen, const u_char *buf)
+int sst_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
+ const u_char *buf)
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
struct spi_slave *spi = nor->spi;
@@ -4278,7 +4278,7 @@ static int spi_nor_octal_dtr_enable(struct spi_nor *nor)
return 0;
}
-static int spi_nor_init(struct spi_nor *nor)
+int spi_nor_init(struct spi_nor *nor)
{
int err;
diff --git a/drivers/mtd/spi/stacked.c b/drivers/mtd/spi/stacked.c
new file mode 100644
index 00000000000..e005aea1053
--- /dev/null
+++ b/drivers/mtd/spi/stacked.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2025 Infineon Technologies
+ *
+ * SPI NOR driver for stacked-memories configuration.
+ */
+
+#include <dm/device_compat.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/sizes.h>
+#include <linux/mtd/spi-nor.h>
+#include <spi.h>
+
+#include "sf_internal.h"
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+static int write_bar(struct spi_nor *nor, u32 offset)
+{
+ u8 cmd, bank_sel, upage_curr;
+ int ret;
+ struct mtd_info *mtd = &nor->mtd;
+
+ /* Wait until previous write command is finished */
+ if (spi_nor_wait_till_ready(nor))
+ return 1;
+
+ if (nor->flags & (SNOR_F_HAS_PARALLEL | SNOR_F_HAS_STACKED) &&
+ mtd->size <= SZ_32M)
+ return 0;
+
+ if (mtd->size <= SZ_16M)
+ return 0;
+
+ offset = offset % (u32)mtd->size;
+ bank_sel = offset >> 24;
+
+ upage_curr = nor->spi->flags & SPI_XFER_U_PAGE;
+
+ if (!(nor->flags & SNOR_F_HAS_STACKED) && bank_sel == nor->bank_curr)
+ return 0;
+ else if (upage_curr == nor->upage_prev && bank_sel == nor->bank_curr)
+ return 0;
+
+ nor->upage_prev = upage_curr;
+
+ cmd = nor->bank_write_cmd;
+ write_enable(nor);
+ ret = nor->write_reg(nor, cmd, &bank_sel, 1);
+ if (ret < 0) {
+ debug("SF: fail to write bank register\n");
+ return ret;
+ }
+
+ nor->bank_curr = bank_sel;
+
+ return write_disable(nor);
+}
+#endif
+
+static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ u32 addr, len, rem, offset, max_size;
+ bool addr_known = false;
+ int ret, err;
+
+ dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
+ (long long)instr->len);
+
+ div_u64_rem(instr->len, mtd->erasesize, &rem);
+ if (rem) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ addr = instr->addr;
+ len = instr->len;
+ max_size = instr->len;
+
+ instr->state = MTD_ERASING;
+ addr_known = true;
+
+ while (len) {
+ schedule();
+ if (!IS_ENABLED(CONFIG_XPL_BUILD) && ctrlc()) {
+ addr_known = false;
+ ret = -EINTR;
+ goto erase_err;
+ }
+ offset = addr;
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ offset /= 2;
+
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ if (offset >= (mtd->size / 2))
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ else
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+ }
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = write_bar(nor, offset);
+ if (ret < 0)
+ goto erase_err;
+#endif
+ ret = write_enable(nor);
+ if (ret < 0)
+ goto erase_err;
+
+ if (len == mtd->size &&
+ !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
+ ret = spi_nor_erase_chip(nor);
+ } else {
+ ret = spi_nor_erase_sector(nor, offset);
+ }
+ if (ret < 0)
+ goto erase_err;
+
+ addr += ret;
+ len -= ret;
+
+ if (max_size == mtd->size &&
+ !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
+ ret = spi_nor_erase_chip_wait_till_ready(nor, mtd->size);
+ } else {
+ ret = spi_nor_wait_till_ready(nor);
+ }
+
+ if (ret)
+ goto erase_err;
+ }
+
+ addr_known = false;
+erase_err:
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ err = clean_bar(nor);
+ if (!ret)
+ ret = err;
+#endif
+ err = write_disable(nor);
+ if (!ret)
+ ret = err;
+
+err:
+ if (ret) {
+ instr->fail_addr = addr_known ? addr : MTD_FAIL_ADDR_UNKNOWN;
+ instr->state = MTD_ERASE_FAILED;
+ } else {
+ instr->state = MTD_ERASE_DONE;
+ }
+
+ return ret;
+}
+
+static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ loff_t offset = from;
+ u32 rem_bank_len = 0;
+ u32 stack_shift = 0;
+ size_t read_len;
+ u8 bank;
+ int ret;
+ bool is_ofst_odd = false;
+
+ dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);
+
+ if ((nor->flags & SNOR_F_HAS_PARALLEL) && (offset & 1)) {
+ /* We can hit this case when we use file system like ubifs */
+ from--;
+ len++;
+ is_ofst_odd = true;
+ }
+
+ while (len) {
+ read_len = len;
+ offset = from;
+
+ if (CONFIG_IS_ENABLED(SPI_FLASH_BAR)) {
+ bank = (u32)from / SZ_16M;
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ bank /= 2;
+ }
+ rem_bank_len = SZ_16M * (bank + 1);
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ rem_bank_len *= 2;
+ }
+ rem_bank_len -= from;
+ }
+
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ stack_shift = 1;
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ } else {
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+ }
+ }
+
+ if (CONFIG_IS_ENABLED(SPI_STACKED_PARALLEL)) {
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ offset /= 2;
+ }
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = write_bar(nor, offset);
+ if (ret < 0)
+ return log_ret(ret);
+ if (len < rem_bank_len)
+ read_len = len;
+ else
+ read_len = rem_bank_len;
+#endif
+
+ if (read_len == 0)
+ return -EIO;
+
+ ret = nor->read(nor, offset, read_len, buf);
+ if (ret == 0) {
+ /* We shouldn't see 0-length reads */
+ ret = -EIO;
+ goto read_err;
+ }
+ if (ret < 0)
+ goto read_err;
+
+ if (is_ofst_odd == true) {
+ memmove(buf, (buf + 1), (len - 1));
+ *retlen += (ret - 1);
+ buf += ret - 1;
+ is_ofst_odd = false;
+ } else {
+ *retlen += ret;
+ buf += ret;
+ }
+ from += ret;
+ len -= ret;
+ }
+ ret = 0;
+
+read_err:
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = clean_bar(nor);
+#endif
+ return ret;
+}
+
+static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
+ size_t page_offset, page_remain, i;
+ ssize_t ret;
+ u32 offset;
+
+#ifdef CONFIG_SPI_FLASH_SST
+ /* sst nor chips use AAI word program */
+ if (nor->info->flags & SST_WRITE)
+ return sst_write(mtd, to, len, retlen, buf);
+#endif
+
+ dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
+
+ if (!len)
+ return 0;
+
+ /*
+ * Cannot write to odd offset in parallel mode,
+ * so write 2 bytes first
+ */
+ if ((nor->flags & SNOR_F_HAS_PARALLEL) && (to & 1)) {
+ u8 two[2] = {0xff, buf[0]};
+ size_t local_retlen;
+
+ ret = spi_nor_write(mtd, to & ~1, 2, &local_retlen, two);
+ if (ret < 0)
+ return ret;
+
+ *retlen += 1; /* We've written only one actual byte */
+ buf++;
+ len--;
+ to++;
+ }
+
+ for (i = 0; i < len; ) {
+ ssize_t written;
+ loff_t addr = to + i;
+ schedule();
+
+ /*
+ * If page_size is a power of two, the offset can be quickly
+ * calculated with an AND operation. On the other cases we
+ * need to do a modulus operation (more expensive).
+ */
+ if (is_power_of_2(nor->page_size)) {
+ page_offset = addr & (nor->page_size - 1);
+ } else {
+ u64 aux = addr;
+
+ page_offset = do_div(aux, nor->page_size);
+ }
+ offset = to + i;
+ if (nor->flags & SNOR_F_HAS_PARALLEL)
+ offset /= 2;
+
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ if (offset >= (mtd->size / 2)) {
+ offset = offset - (mtd->size / 2);
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ } else {
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+ }
+
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = write_bar(nor, offset);
+ if (ret < 0)
+ return ret;
+#endif
+
+ /* the size of data remaining on the first page */
+ page_remain = min_t(size_t,
+ nor->page_size - page_offset, len - i);
+
+ write_enable(nor);
+ /*
+ * On DTR capable flashes like Micron Xcella the writes cannot
+ * start or end at an odd address in DTR mode. So we need to
+ * append or prepend extra 0xff bytes to make sure the start
+ * address and end address are even.
+ */
+ if (spi_nor_protocol_is_dtr(nor->write_proto) &&
+ ((offset | page_remain) & 1)) {
+ u_char *tmp;
+ size_t extra_bytes = 0;
+
+ tmp = kmalloc(nor->page_size, 0);
+ if (!tmp) {
+ ret = -ENOMEM;
+ goto write_err;
+ }
+
+ /* Prepend a 0xff byte if the start address is odd. */
+ if (offset & 1) {
+ tmp[0] = 0xff;
+ memcpy(tmp + 1, buf + i, page_remain);
+ offset--;
+ page_remain++;
+ extra_bytes++;
+ } else {
+ memcpy(tmp, buf + i, page_remain);
+ }
+
+ /* Append a 0xff byte if the end address is odd. */
+ if ((offset + page_remain) & 1) {
+ tmp[page_remain + extra_bytes] = 0xff;
+ extra_bytes++;
+ page_remain++;
+ }
+
+ ret = nor->write(nor, offset, page_remain, tmp);
+
+ kfree(tmp);
+
+ if (ret < 0)
+ goto write_err;
+
+ /*
+ * We write extra bytes but they are not part of the
+ * original write.
+ */
+ written = ret - extra_bytes;
+ } else {
+ ret = nor->write(nor, offset, page_remain, buf + i);
+ if (ret < 0)
+ goto write_err;
+ written = ret;
+ }
+
+ ret = spi_nor_wait_till_ready(nor);
+ if (ret)
+ goto write_err;
+
+ *retlen += written;
+ i += written;
+ }
+
+write_err:
+#if CONFIG_IS_ENABLED(SPI_FLASH_BAR)
+ ret = clean_bar(nor);
+#endif
+ return ret;
+}
+
+int spi_nor_stacked_post_init_fixups(struct spi_nor *nor)
+{
+ struct mtd_info *mtd = &nor->mtd;
+ int ret;
+
+#if CONFIG_IS_ENABLED(DM_SPI)
+ u64 flash_size[SNOR_FLASH_CNT_MAX] = { 0 };
+ struct udevice *dev = nor->spi->dev;
+ u32 idx = 0, i = 0;
+ int rc;
+
+ while (i < SNOR_FLASH_CNT_MAX) {
+ rc = ofnode_read_u64_index(dev_ofnode(dev), "stacked-memories",
+ idx, &flash_size[i]);
+ if (rc == -EINVAL) {
+ break;
+ } else if (rc == -EOVERFLOW) {
+ idx++;
+ } else {
+ idx++;
+ i++;
+ if (!(nor->flags & SNOR_F_HAS_STACKED))
+ nor->flags |= SNOR_F_HAS_STACKED;
+ if (!(nor->spi->flags & SPI_XFER_STACKED))
+ nor->spi->flags |= SPI_XFER_STACKED;
+ }
+ }
+
+ if (nor->flags & (SNOR_F_HAS_STACKED | SNOR_F_HAS_PARALLEL)) {
+ mtd->size = 0;
+ for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++)
+ mtd->size += flash_size[idx];
+ }
+#endif
+
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ nor->spi->flags |= SPI_XFER_U_PAGE;
+ ret = spi_nor_init(nor);
+ if (ret)
+ return ret;
+ nor->spi->flags &= ~SPI_XFER_U_PAGE;
+ }
+
+ if (nor->flags & SNOR_F_HAS_STACKED) {
+ mtd->_erase = spi_nor_erase;
+ mtd->_read = spi_nor_read;
+ mtd->_write = spi_nor_write;
+ }
+
+ return 0;
+}
--
2.34.1
More information about the U-Boot
mailing list