summaryrefslogtreecommitdiff
path: root/drivers/mtd
diff options
context:
space:
mode:
authorMiquel Raynal <miquel.raynal@bootlin.com>2026-01-09 18:18:24 +0100
committerMiquel Raynal <miquel.raynal@bootlin.com>2026-01-29 20:21:41 +0100
commit76b7dc76dd0e1af5a538e977e015ac95271b3b12 (patch)
tree597d52498a08067f5120519ff784152c7e60dd42 /drivers/mtd
parentf636d9216146fe11de754bf78207352507fb3b91 (diff)
downloadlwn-76b7dc76dd0e1af5a538e977e015ac95271b3b12.tar.gz
lwn-76b7dc76dd0e1af5a538e977e015ac95271b3b12.zip
mtd: spinand: Add octal DTR support
Create a new bus interface named ODTR for "octal DTR", which matches the following pattern: 8D-8D-8D. Add octal DTR support for all the existing core operations. Add a second set of templates for this bus interface. Give the possibility for drivers to register their read, write and update cache variants as well as their vendor specific operations. Check the SPI controller driver supports all the octal DTR commands that we might need before switching to the ODTR bus interface. Make the switch by calling ->configure_chip() with the ODTR parameter. Fallback in case this step fails. If someone ever attempts to suspend a chip in octal DTR mode, there are changes that it will loose its configuration at resume. Prevent any problem by explicitly switching back to SSDR while suspending. Note: there is a limitation in the current approach, page I/Os are not available as the dirmaps will be created for the ODTR bus interface if that option is supported and not switched back to SSDR during suspend. Switching them is possible but would be costly and would not bring anything as right after resuming we will switch again to ODTR. In case this capability is used for debug, developpers should mind to destroy and recreate suitable direct mappings. Finally, as a side effect, we increase the buffer for reading IDs to 6. No device at this point returns 6 bytes, but we support 5 bytes IDs, which means in octal DTR mode we have no other choice than reading an even number of bytes, hence 6. Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/nand/spi/core.c140
1 files changed, 139 insertions, 1 deletions
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index bbda60b39788..21a980e626eb 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -57,6 +57,9 @@ spinand_fill_set_feature_op(struct spinand_device *spinand, u64 reg, const void
{
struct spi_mem_op op = spinand->op_templates->set_feature;
+ if (op.cmd.dtr && op.cmd.buswidth == 8)
+ reg |= reg << 8;
+
op.addr.val = reg;
op.data.buf.out = valptr;
@@ -68,6 +71,9 @@ spinand_fill_get_feature_op(struct spinand_device *spinand, u64 reg, void *valpt
{
struct spi_mem_op op = spinand->op_templates->get_feature;
+ if (op.cmd.dtr && op.cmd.buswidth == 8)
+ reg |= reg << 8;
+
op.addr.val = reg;
op.data.buf.in = valptr;
@@ -1393,6 +1399,11 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
return spinand->manufacturer->ops->cleanup(spinand);
}
+static bool spinand_op_is_odtr(const struct spi_mem_op *op)
+{
+ return op->cmd.dtr && op->cmd.buswidth == 8;
+}
+
static void spinand_init_ssdr_templates(struct spinand_device *spinand)
{
struct spinand_mem_ops *tmpl = &spinand->ssdr_op_templates;
@@ -1425,6 +1436,10 @@ static int spinand_support_vendor_ops(struct spinand_device *spinand,
for (i = 0; i < info->vendor_ops->nops; i++) {
const struct spi_mem_op *op = &info->vendor_ops->ops[i];
+ if ((iface == SSDR && spinand_op_is_odtr(op)) ||
+ (iface == ODTR && !spinand_op_is_odtr(op)))
+ continue;
+
if (!spi_mem_supports_op(spinand->spimem, op))
return -EOPNOTSUPP;
}
@@ -1432,6 +1447,49 @@ static int spinand_support_vendor_ops(struct spinand_device *spinand,
return 0;
}
+static int spinand_init_odtr_instruction_set(struct spinand_device *spinand)
+{
+ struct spinand_mem_ops *tmpl = &spinand->odtr_op_templates;
+
+ tmpl->reset = (struct spi_mem_op)SPINAND_RESET_8D_0_0_OP;
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->reset))
+ return -EOPNOTSUPP;
+
+ tmpl->readid = (struct spi_mem_op)SPINAND_READID_8D_8D_8D_OP(0, 0, NULL, 0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->readid))
+ return -EOPNOTSUPP;
+
+ tmpl->wr_en = (struct spi_mem_op)SPINAND_WR_EN_8D_0_0_OP;
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_en))
+ return -EOPNOTSUPP;
+
+ tmpl->wr_dis = (struct spi_mem_op)SPINAND_WR_DIS_8D_0_0_OP;
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_dis))
+ return -EOPNOTSUPP;
+
+ tmpl->set_feature = (struct spi_mem_op)SPINAND_SET_FEATURE_8D_8D_8D_OP(0, NULL);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->set_feature))
+ return -EOPNOTSUPP;
+
+ tmpl->get_feature = (struct spi_mem_op)SPINAND_GET_FEATURE_8D_8D_8D_OP(0, NULL);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->get_feature))
+ return -EOPNOTSUPP;
+
+ tmpl->blk_erase = (struct spi_mem_op)SPINAND_BLK_ERASE_8D_8D_0_OP(0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->blk_erase))
+ return -EOPNOTSUPP;
+
+ tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_8D_8D_0_OP(0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->page_read))
+ return -EOPNOTSUPP;
+
+ tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_8D_8D_0_OP(0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->prog_exec))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
static const struct spi_mem_op *
spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_interface iface,
const struct spinand_op_variants *variants)
@@ -1447,6 +1505,10 @@ spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_inter
unsigned int nbytes;
int ret;
+ if ((iface == SSDR && spinand_op_is_odtr(&op)) ||
+ (iface == ODTR && !spinand_op_is_odtr(&op)))
+ continue;
+
nbytes = nanddev_per_page_oobsize(nand) +
nanddev_page_size(nand);
@@ -1523,6 +1585,8 @@ int spinand_match_and_init(struct spinand_device *spinand,
spinand->read_retries = table[i].read_retries;
spinand->set_read_retry = table[i].set_read_retry;
+ /* I/O variants selection with single-spi SDR commands */
+
op = spinand_select_op_variant(spinand, SSDR,
info->op_variants.read_cache);
if (!op)
@@ -1548,6 +1612,28 @@ int spinand_match_and_init(struct spinand_device *spinand,
if (ret)
return ret;
+ /* I/O variants selection with octo-spi DDR commands (optional) */
+
+ ret = spinand_init_odtr_instruction_set(spinand);
+ if (ret)
+ return 0;
+
+ ret = spinand_support_vendor_ops(spinand, info, ODTR);
+ if (ret)
+ return 0;
+
+ op = spinand_select_op_variant(spinand, ODTR,
+ info->op_variants.read_cache);
+ spinand->odtr_op_templates.read_cache = op;
+
+ op = spinand_select_op_variant(spinand, ODTR,
+ info->op_variants.write_cache);
+ spinand->odtr_op_templates.write_cache = op;
+
+ op = spinand_select_op_variant(spinand, ODTR,
+ info->op_variants.update_cache);
+ spinand->odtr_op_templates.update_cache = op;
+
return 0;
}
@@ -1589,9 +1675,34 @@ static int spinand_detect(struct spinand_device *spinand)
static int spinand_configure_chip(struct spinand_device *spinand)
{
- bool quad_enable = false;
+ bool odtr = false, quad_enable = false;
int ret;
+ if (spinand->odtr_op_templates.read_cache &&
+ spinand->odtr_op_templates.write_cache &&
+ spinand->odtr_op_templates.update_cache)
+ odtr = true;
+
+ if (odtr) {
+ if (!spinand->configure_chip)
+ goto try_ssdr;
+
+ /* ODTR bus interface configuration happens here */
+ ret = spinand->configure_chip(spinand, ODTR);
+ if (ret) {
+ spinand->odtr_op_templates.read_cache = NULL;
+ spinand->odtr_op_templates.write_cache = NULL;
+ spinand->odtr_op_templates.update_cache = NULL;
+ goto try_ssdr;
+ }
+
+ spinand->op_templates = &spinand->odtr_op_templates;
+ spinand->bus_iface = ODTR;
+
+ return 0;
+ }
+
+try_ssdr:
if (spinand->flags & SPINAND_HAS_QE_BIT) {
if (spinand->ssdr_op_templates.read_cache->data.buswidth == 4 ||
spinand->ssdr_op_templates.write_cache->data.buswidth == 4 ||
@@ -1673,6 +1784,32 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
spinand_ecc_enable(spinand, false);
}
+static int spinand_mtd_suspend(struct mtd_info *mtd)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ int ret;
+
+ /*
+ * Return to SSDR interface in the suspend path to make sure the
+ * reset operation is correctly processed upon resume.
+ *
+ * Note: Once back in SSDR mode, every operation but the page helpers
+ * (dirmap based I/O accessors) will work. Page accesses would require
+ * destroying and recreating the dirmaps twice to work, which would be
+ * impacting for no reason, as this is just a transitional state.
+ */
+ if (spinand->bus_iface == ODTR) {
+ ret = spinand->configure_chip(spinand, SSDR);
+ if (ret)
+ return ret;
+
+ spinand->op_templates = &spinand->ssdr_op_templates;
+ spinand->bus_iface = SSDR;
+ }
+
+ return 0;
+}
+
static int spinand_init(struct spinand_device *spinand)
{
struct device *dev = &spinand->spimem->spi->dev;
@@ -1742,6 +1879,7 @@ static int spinand_init(struct spinand_device *spinand)
mtd->_block_isreserved = spinand_mtd_block_isreserved;
mtd->_erase = spinand_mtd_erase;
mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
+ mtd->_suspend = spinand_mtd_suspend;
mtd->_resume = spinand_mtd_resume;
if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {