diff options
author | Tom Rini <trini@konsulko.com> | 2024-10-09 09:02:22 -0600 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2024-10-09 09:02:22 -0600 |
commit | f8efc68b30e29360f7a419fe4968432179d7368b (patch) | |
tree | f7e4ed2b5d65eff2c81a16bf937eba4900f40543 /drivers/mtd/spi/spi-nor-core.c | |
parent | fbe16bc28014dc1ed957f5fee7e53d6eee781aa9 (diff) | |
parent | 8be3beef44c29c70a9ec447a4f232f987e1b4c64 (diff) |
Merge patch series "spi-nor: Add parallel and stacked memories support"
Venkatesh Yadav Abbarapu <venkatesh.abbarapu@amd.com> says:
This series adds support for Xilinx qspi parallel and
stacked memeories.
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.
Diffstat (limited to 'drivers/mtd/spi/spi-nor-core.c')
-rw-r--r-- | drivers/mtd/spi/spi-nor-core.c | 404 |
1 files changed, 349 insertions, 55 deletions
diff --git a/drivers/mtd/spi/spi-nor-core.c b/drivers/mtd/spi/spi-nor-core.c index 8f7a77e7169..032e429d3fa 100644 --- a/drivers/mtd/spi/spi-nor-core.c +++ b/drivers/mtd/spi/spi-nor-core.c @@ -467,8 +467,9 @@ static ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len, } /* - * Read the status register, returning its value in the location - * Return the status register value. + * Return the status register value. If the chip is parallel, then the + * read will be striped, so we should read 2 bytes to get the sr + * register value from both of the parallel chips. * Returns negative if error occurred. */ static int read_sr(struct spi_nor *nor) @@ -500,18 +501,29 @@ static int read_sr(struct spi_nor *nor) if (spi_nor_protocol_is_dtr(nor->reg_proto)) op.data.nbytes = 2; - ret = spi_nor_read_write_reg(nor, &op, val); - if (ret < 0) { - pr_debug("error %d reading SR\n", (int)ret); - return ret; + if (nor->flags & SNOR_F_HAS_PARALLEL) { + op.data.nbytes = 2; + ret = spi_nor_read_write_reg(nor, &op, &val[0]); + if (ret < 0) { + pr_debug("error %d reading SR\n", (int)ret); + return ret; + } + val[0] |= val[1]; + } else { + ret = spi_nor_read_write_reg(nor, &op, &val[0]); + if (ret < 0) { + pr_debug("error %d reading SR\n", (int)ret); + return ret; + } } - return *val; + return val[0]; } /* - * Read the flag status register, returning its value in the location - * Return the status register value. + * Return the flag status register value. If the chip is parallel, then + * the read will be striped, so we should read 2 bytes to get the fsr + * register value from both of the parallel chips. * Returns negative if error occurred. */ static int read_fsr(struct spi_nor *nor) @@ -543,13 +555,23 @@ static int read_fsr(struct spi_nor *nor) if (spi_nor_protocol_is_dtr(nor->reg_proto)) op.data.nbytes = 2; - ret = spi_nor_read_write_reg(nor, &op, val); - if (ret < 0) { - pr_debug("error %d reading FSR\n", ret); - return ret; + if (nor->flags & SNOR_F_HAS_PARALLEL) { + op.data.nbytes = 2; + ret = spi_nor_read_write_reg(nor, &op, &val[0]); + if (ret < 0) { + pr_debug("error %d reading SR\n", (int)ret); + return ret; + } + val[0] &= val[1]; + } else { + ret = spi_nor_read_write_reg(nor, &op, &val[0]); + if (ret < 0) { + pr_debug("error %d reading FSR\n", ret); + return ret; + } } - return *val; + return val[0]; } /* @@ -668,12 +690,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: @@ -901,12 +928,32 @@ static int clean_bar(struct spi_nor *nor) static int write_bar(struct spi_nor *nor, u32 offset) { - u8 cmd, bank_sel; + 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; - bank_sel = offset / SZ_16M; - if (bank_sel == nor->bank_curr) - goto bar_end; + 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); @@ -916,15 +963,19 @@ static int write_bar(struct spi_nor *nor, u32 offset) return ret; } -bar_end: nor->bank_curr = bank_sel; - return nor->bank_curr; + + return write_disable(nor); } static int read_bar(struct spi_nor *nor, const struct flash_info *info) { u8 curr_bank = 0; int ret; + struct mtd_info *mtd = &nor->mtd; + + if (mtd->size <= SZ_16M) + return 0; switch (JEDEC_MFR(info)) { case SNOR_MFR_SPANSION: @@ -936,15 +987,30 @@ static int read_bar(struct spi_nor *nor, const struct flash_info *info) nor->bank_write_cmd = SPINOR_OP_WREAR; } + if (nor->flags & SNOR_F_HAS_PARALLEL) + nor->spi->flags |= SPI_XFER_LOWER; + ret = nor->read_reg(nor, nor->bank_read_cmd, - &curr_bank, 1); + &curr_bank, 1); if (ret) { debug("SF: fail to read bank addr register\n"); return ret; } nor->bank_curr = curr_bank; - return 0; + // Make sure both chips use the same BAR + if (nor->flags & SNOR_F_HAS_PARALLEL) { + write_enable(nor); + ret = nor->write_reg(nor, nor->bank_write_cmd, &curr_bank, 1); + if (ret) + return ret; + + ret = write_disable(nor); + if (ret) + return ret; + } + + return ret; } #endif @@ -1008,8 +1074,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 +1101,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 +1524,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 +1551,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 (nor->addr_width == 3) { +#ifdef CONFIG_SPI_FLASH_BAR + ret = write_bar(nor, offset); + if (ret < 0) + return log_ret(ret); +#endif + } - if (len < remain_len) + if (len < rem_bank_len) read_len = len; else - read_len = remain_len; -#endif + read_len = rem_bank_len; - ret = nor->read(nor, addr, read_len, buf); + if (read_len == 0) + return -EIO; + + ret = spi_nor_wait_till_ready(nor); + if (ret) + goto read_err; + + ret = nor->read(nor, offset, read_len, buf); if (ret == 0) { /* We shouldn't see 0-length reads */ ret = -EIO; @@ -1500,8 +1620,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 +1923,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 +1933,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 +1971,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 +2007,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 +2018,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 +2029,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 +2048,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 +2057,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 +2102,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 +2167,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 +2343,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 +3041,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 +3165,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 +3535,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 +4204,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 +4373,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 +4511,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 +4523,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 +4541,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; |