From: Takahiro Kuwano <[email protected]> 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 <[email protected]> --- 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

