From: Venkatesh Yadav Abbarapu Date: Thu, 26 Sep 2024 04:55:02 +0000 (+0530) Subject: mtd: spi-nor: Add parallel and stacked memories support X-Git-Url: http://git.dujemihanovic.xyz/img/static/git-logo.png?a=commitdiff_plain;h=5d40b3d384dc536ec26ce9b76b20b0b84749d2d1;p=u-boot.git mtd: spi-nor: Add parallel and stacked memories support In parallel mode, the current implementation assumes that a maximum of two flashes are connected. The QSPI controller splits the data evenly between both the flashes so, both the flashes that are connected in parallel mode should be identical. During each operation SPI-NOR sets 0th bit for CS0 & 1st bit for CS1 in nor->flags. In stacked mode the current implementation assumes that a maximum of two flashes are connected and both the flashes are of same make but can differ in sizes. So, except the sizes all other flash parameters of both the flashes are identical Spi-nor will pass on the appropriate flash select flag to low level driver, and it will select pass all the data to that particular flash. Write operation in parallel mode are performed in page size * 2 chunks as each write operation results in writing both the flashes. For doubling the address space each operation is performed at addr/2 flash offset, where addr is the address specified by the user. Similarly for read and erase operations it will read from both flashes, so size and offset are divided by 2 and send to flash. Adding the config option SPI_ADVANCE for non SPL code. Signed-off-by: Ashok Reddy Soma Signed-off-by: Venkatesh Yadav Abbarapu --- diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 8f7a77e716..8ba3a9bd60 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -668,12 +668,17 @@ static u8 spi_nor_convert_3to4_erase(u8 opcode) static void spi_nor_set_4byte_opcodes(struct spi_nor *nor, const struct flash_info *info) { + bool shift = 0; + + if (nor->flags & SNOR_F_HAS_PARALLEL) + shift = 1; + /* Do some manufacturer fixups first */ switch (JEDEC_MFR(info)) { case SNOR_MFR_SPANSION: /* No small sector erase for 4-byte command set */ nor->erase_opcode = SPINOR_OP_SE; - nor->mtd.erasesize = info->sector_size; + nor->mtd.erasesize = info->sector_size << shift; break; default: @@ -1008,8 +1013,8 @@ static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr) 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; - u32 addr, len, rem, max_size; int ret, err; dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr, @@ -1035,6 +1040,18 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr) ret = -EINTR; goto erase_err; } + offset = addr; + 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; + } + } #ifdef CONFIG_SPI_FLASH_BAR ret = write_bar(nor, addr); if (ret < 0) @@ -1446,6 +1463,9 @@ static const struct flash_info *spi_nor_read_id(struct spi_nor *nor) u8 id[SPI_NOR_MAX_ID_LEN]; const struct flash_info *info; + if (nor->flags & SNOR_F_HAS_PARALLEL) + nor->spi->flags |= SPI_XFER_LOWER; + tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN); if (tmp < 0) { dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp); @@ -1470,28 +1490,67 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, { struct spi_nor *nor = mtd_to_spi_nor(mtd); int ret; + loff_t offset = from; + u32 read_len = 0; + u32 rem_bank_len = 0; + u8 bank; + 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) { - loff_t addr = from; - size_t read_len = len; + if (nor->addr_width == 3) { + if (nor->flags & SNOR_F_HAS_PARALLEL) { + bank = (u32)from / (SZ_16M << 0x01); + rem_bank_len = ((SZ_16M << 0x01) * + (bank + 1)) - from; + } else { + bank = (u32)from / SZ_16M; + rem_bank_len = (SZ_16M * (bank + 1)) - from; + } + } + offset = from; -#ifdef CONFIG_SPI_FLASH_BAR - u32 remain_len; + 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; + } + } - ret = write_bar(nor, addr); - if (ret < 0) - return log_ret(ret); - remain_len = (SZ_16M * (nor->bank_curr + 1)) - addr; + if (nor->flags & SNOR_F_HAS_PARALLEL) + offset /= 2; - if (len < remain_len) + if (nor->addr_width == 3) { +#ifdef CONFIG_SPI_FLASH_BAR + ret = write_bar(nor, offset); + if (ret < 0) + return log_ret(ret); +#endif + } + + if (len < rem_bank_len) read_len = len; else - read_len = remain_len; -#endif + read_len = rem_bank_len; + + if (read_len == 0) + return -EIO; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto read_err; - ret = nor->read(nor, addr, read_len, buf); + ret = nor->read(nor, offset, read_len, buf); if (ret == 0) { /* We shouldn't see 0-length reads */ ret = -EIO; @@ -1500,8 +1559,15 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len, if (ret < 0) goto read_err; - *retlen += ret; - buf += ret; + 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; } @@ -1796,6 +1862,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, 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 */ @@ -1805,6 +1872,27 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, 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; @@ -1822,18 +1910,35 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, 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 (nor->addr_width == 3) { +#ifdef CONFIG_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); -#ifdef CONFIG_SPI_FLASH_BAR - ret = write_bar(nor, addr); - if (ret < 0) - return ret; -#endif + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto write_err; 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 @@ -1841,7 +1946,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, * address and end address are even. */ if (spi_nor_protocol_is_dtr(nor->write_proto) && - ((addr | page_remain) & 1)) { + ((offset | page_remain) & 1)) { u_char *tmp; size_t extra_bytes = 0; @@ -1852,10 +1957,10 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, } /* Prepend a 0xff byte if the start address is odd. */ - if (addr & 1) { + if (offset & 1) { tmp[0] = 0xff; memcpy(tmp + 1, buf + i, page_remain); - addr--; + offset--; page_remain++; extra_bytes++; } else { @@ -1863,13 +1968,13 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, } /* Append a 0xff byte if the end address is odd. */ - if ((addr + page_remain) & 1) { + if ((offset + page_remain) & 1) { tmp[page_remain + extra_bytes] = 0xff; extra_bytes++; page_remain++; } - ret = nor->write(nor, addr, page_remain, tmp); + ret = nor->write(nor, offset, page_remain, tmp); kfree(tmp); @@ -1882,7 +1987,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, */ written = ret - extra_bytes; } else { - ret = nor->write(nor, addr, page_remain, buf + i); + ret = nor->write(nor, offset, page_remain, buf + i); if (ret < 0) goto write_err; written = ret; @@ -1891,6 +1996,11 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len, ret = spi_nor_wait_till_ready(nor); if (ret) goto write_err; + + ret = write_disable(nor); + if (ret) + goto write_err; + *retlen += written; i += written; } @@ -1931,6 +2041,10 @@ static int macronix_quad_enable(struct spi_nor *nor) if (ret) return ret; + ret = write_disable(nor); + if (ret) + return ret; + ret = read_sr(nor); if (!(ret > 0 && (ret & SR_QUAD_EN_MX))) { dev_err(nor->dev, "Macronix Quad bit not set\n"); @@ -1992,7 +2106,7 @@ static int spansion_quad_enable_volatile(struct spi_nor *nor, u32 addr_base, return -EINVAL; } - return 0; + return write_disable(nor); } #endif @@ -2168,6 +2282,10 @@ static int spi_nor_read_sfdp(struct spi_nor *nor, u32 addr, nor->read_dummy = 8; while (len) { + /* Both chips are identical, so should be the SFDP data */ + if (nor->flags & SNOR_F_HAS_PARALLEL) + nor->spi->flags |= SPI_XFER_LOWER; + ret = nor->read(nor, addr, len, (u8 *)buf); if (!ret || ret > len) { ret = -EIO; @@ -2862,6 +2980,13 @@ static int spi_nor_init_params(struct spi_nor *nor, const struct flash_info *info, struct spi_nor_flash_parameter *params) { +#if CONFIG_IS_ENABLED(DM_SPI) && CONFIG_IS_ENABLED(SPI_ADVANCE) + struct udevice *dev = nor->spi->dev; + u64 flash_size[SNOR_FLASH_CNT_MAX] = {0}; + u32 idx = 0, i = 0; + int rc; +#endif + /* Set legacy flash parameters as default. */ memset(params, 0, sizeof(*params)); @@ -2979,7 +3104,62 @@ static int spi_nor_init_params(struct spi_nor *nor, memcpy(params, &sfdp_params, sizeof(*params)); } } +#if CONFIG_IS_ENABLED(DM_SPI) && CONFIG_IS_ENABLED(SPI_ADVANCE) + /* + * The flashes that are connected in stacked mode should be of same make. + * Except the flash size all other properties are identical for all the + * flashes connected in stacked mode. + * The flashes that are connected in parallel mode should be identical. + */ + 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; + } + } + + i = 0; + idx = 0; + 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)) { + params->size = 0; + for (idx = 0; idx < SNOR_FLASH_CNT_MAX; idx++) + params->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; + params->page_size <<= 1; + } +#endif spi_nor_post_sfdp_fixups(nor, params); return 0; @@ -3294,16 +3474,54 @@ static int spi_nor_select_erase(struct spi_nor *nor, /* prefer "small sector" erase if possible */ if (info->flags & SECT_4K) { nor->erase_opcode = SPINOR_OP_BE_4K; - mtd->erasesize = 4096; + /* + * In parallel-memories the erase operation is + * performed on both the flashes simultaneously + * so, double the erasesize. + */ + if (nor->flags & SNOR_F_HAS_PARALLEL) + mtd->erasesize = 4096 * 2; + else + mtd->erasesize = 4096; } else if (info->flags & SECT_4K_PMC) { nor->erase_opcode = SPINOR_OP_BE_4K_PMC; - mtd->erasesize = 4096; + /* + * In parallel-memories the erase operation is + * performed on both the flashes simultaneously + * so, double the erasesize. + */ + if (nor->flags & SNOR_F_HAS_PARALLEL) + mtd->erasesize = 4096 * 2; + else + mtd->erasesize = 4096; } else #endif { nor->erase_opcode = SPINOR_OP_SE; - mtd->erasesize = info->sector_size; + /* + * In parallel-memories the erase operation is + * performed on both the flashes simultaneously + * so, double the erasesize. + */ + if (nor->flags & SNOR_F_HAS_PARALLEL) + mtd->erasesize = info->sector_size * 2; + else + mtd->erasesize = info->sector_size; + } + + if ((JEDEC_MFR(info) == SNOR_MFR_SST) && info->flags & SECT_4K) { + nor->erase_opcode = SPINOR_OP_BE_4K; + /* + * In parallel-memories the erase operation is + * performed on both the flashes simultaneously + * so, double the erasesize. + */ + if (nor->flags & SNOR_F_HAS_PARALLEL) + mtd->erasesize = 4096 * 2; + else + mtd->erasesize = 4096; } + return 0; } @@ -3925,6 +4143,9 @@ static int spi_nor_init(struct spi_nor *nor) { int err; + if (nor->flags & SNOR_F_HAS_PARALLEL) + nor->spi->flags |= SPI_NOR_ENABLE_MULTI_CS; + err = spi_nor_octal_dtr_enable(nor); if (err) { dev_dbg(nor->dev, "Octal DTR mode not supported\n"); @@ -4091,6 +4312,7 @@ int spi_nor_scan(struct spi_nor *nor) struct spi_slave *spi = nor->spi; int ret; int cfi_mtd_nb = 0; + bool shift = 0; #ifdef CONFIG_FLASH_CFI_MTD cfi_mtd_nb = CFI_FLASH_BANKS; @@ -4228,7 +4450,9 @@ int spi_nor_scan(struct spi_nor *nor) nor->addr_width = 3; } - if (nor->addr_width == 3 && mtd->size > SZ_16M) { + if (nor->flags & (SNOR_F_HAS_PARALLEL | SNOR_F_HAS_STACKED)) + shift = 1; + if (nor->addr_width == 3 && (mtd->size >> shift) > SZ_16M) { #ifndef CONFIG_SPI_FLASH_BAR /* enable 4-byte addressing if the device exceeds 16MiB */ nor->addr_width = 4; @@ -4238,6 +4462,7 @@ int spi_nor_scan(struct spi_nor *nor) #else /* Configure the BAR - discover bank cmds and read current bank */ nor->addr_width = 3; + set_4byte(nor, info, 0); ret = read_bar(nor, info); if (ret < 0) return ret; @@ -4255,6 +4480,14 @@ int spi_nor_scan(struct spi_nor *nor) if (ret) return ret; + 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; + } + nor->rdsr_dummy = params.rdsr_dummy; nor->rdsr_addr_nbytes = params.rdsr_addr_nbytes; nor->name = info->name; diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index cd785aefd5..18dfd1796f 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -20,6 +20,12 @@ menuconfig SPI if SPI +config SPI_ADVANCE + bool "Enable the advance feature" + help + Enable the SPI advance feature support. By default this is disabled. + If you intend to use the advance feature support you should enable. + config DM_SPI bool "Enable Driver Model for SPI drivers" depends on DM diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h index d1dbf3eadb..d5f4faf0a6 100644 --- a/include/linux/mtd/spi-nor.h +++ b/include/linux/mtd/spi-nor.h @@ -13,6 +13,9 @@ #include #include +/* In parallel configuration enable multiple CS */ +#define SPI_NOR_ENABLE_MULTI_CS (BIT(0) | BIT(1)) + /* * Manufacturer IDs * @@ -177,6 +180,12 @@ /* Status Register 2 bits. */ #define SR2_QUAD_EN_BIT7 BIT(7) +/* + * Maximum number of flashes that can be connected + * in stacked/parallel configuration + */ +#define SNOR_FLASH_CNT_MAX 2 + /* For Cypress flash. */ #define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */ #define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */ @@ -294,6 +303,13 @@ enum spi_nor_option_flags { SNOR_F_BROKEN_RESET = BIT(6), SNOR_F_SOFT_RESET = BIT(7), SNOR_F_IO_MODE_EN_VOLATILE = BIT(8), +#if defined(CONFIG_SPI_ADVANCE) + SNOR_F_HAS_STACKED = BIT(9), + SNOR_F_HAS_PARALLEL = BIT(10), +#else + SNOR_F_HAS_STACKED = 0, + SNOR_F_HAS_PARALLEL = 0, +#endif }; struct spi_nor; @@ -551,6 +567,7 @@ struct spi_nor { u8 bank_read_cmd; u8 bank_write_cmd; u8 bank_curr; + u8 upage_prev; #endif enum spi_nor_protocol read_proto; enum spi_nor_protocol write_proto; diff --git a/include/spi.h b/include/spi.h index 9e9851284c..6e8e0cce7f 100644 --- a/include/spi.h +++ b/include/spi.h @@ -38,6 +38,15 @@ #define SPI_DEFAULT_WORDLEN 8 +/* SPI transfer flags */ +#define SPI_XFER_STRIPE (1 << 6) +#define SPI_XFER_MASK (3 << 8) +#define SPI_XFER_LOWER (1 << 8) +#define SPI_XFER_UPPER (2 << 8) + +/* Max no. of CS supported per spi device */ +#define SPI_CS_CNT_MAX 2 + /** * struct dm_spi_bus - SPI bus info * @@ -155,6 +164,8 @@ struct spi_slave { #define SPI_XFER_BEGIN BIT(0) /* Assert CS before transfer */ #define SPI_XFER_END BIT(1) /* Deassert CS after transfer */ #define SPI_XFER_ONCE (SPI_XFER_BEGIN | SPI_XFER_END) +#define SPI_XFER_U_PAGE BIT(4) +#define SPI_XFER_STACKED BIT(5) }; /**