From: Miquel Raynal Date: Wed, 29 Apr 2026 17:56:43 +0000 (+0200) Subject: mtd: spinand: Use secondary ops for continuous reads X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6eb7c193e751f057b2d75af9b174cfe5dd060696;p=thirdparty%2Flinux.git mtd: spinand: Use secondary ops for continuous reads In case a chip supports continuous reads, but uses a slightly different cache operation for these, it may provide a secondary operation template which will be used only during continuous cache read operations. From a vendor driver point of view, enabling this feature implies providing a new set of templates for these continuous read operations. The core will automatically pick the fastest variant, depending on the hardware capabilities. Signed-off-by: Miquel Raynal --- diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 786e3fb4874d8..99a2494554ef1 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -503,6 +503,11 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand, rdesc = spinand->dirmaps[req->pos.plane].rdesc; + if (spinand->op_templates->cont_read_cache && req->continuous) + rdesc->info.op_tmpl = &rdesc->info.secondary_op_tmpl; + else + rdesc->info.op_tmpl = &rdesc->info.primary_op_tmpl; + if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED && req->mode != MTD_OPS_RAW) rdesc->info.op_tmpl->data.ecc = true; @@ -1235,6 +1240,7 @@ static struct spi_mem_dirmap_desc *spinand_create_rdesc( * its spi controller, use regular reading */ spinand->cont_read_possible = false; + memset(&info->secondary_op_tmpl, 0, sizeof(info->secondary_op_tmpl)); info->length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); @@ -1251,11 +1257,24 @@ static int spinand_create_dirmap(struct spinand_device *spinand, struct nand_device *nand = spinand_to_nand(spinand); struct spi_mem_dirmap_info info = { 0 }; struct spi_mem_dirmap_desc *desc; - bool enable_ecc = false; + bool enable_ecc = false, secondary_op = false; if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED) enable_ecc = true; + if (spinand->cont_read_possible && spinand->op_templates->cont_read_cache) + secondary_op = true; + + /* + * Continuous read implies that only the main data is retrieved, backed + * by an on-die ECC engine. It is not possible to use a pipelind ECC + * engine with continuous read. + */ + if (enable_ecc && secondary_op) { + secondary_op = false; + spinand->cont_read_possible = false; + } + /* The plane number is passed in MSB just above the column address */ info.offset = plane << fls(nand->memorg.pagesize); @@ -1273,6 +1292,10 @@ static int spinand_create_dirmap(struct spinand_device *spinand, /* Read descriptor */ info.primary_op_tmpl = *spinand->op_templates->read_cache; info.primary_op_tmpl.data.ecc = enable_ecc; + if (secondary_op) { + info.secondary_op_tmpl = *spinand->op_templates->cont_read_cache; + info.secondary_op_tmpl.data.ecc = enable_ecc; + } desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) return PTR_ERR(desc); @@ -1622,6 +1645,33 @@ int spinand_match_and_init(struct spinand_device *spinand, if (ret) return ret; + if (info->op_variants.cont_read_cache) { + op = spinand_select_op_variant(spinand, SSDR, + info->op_variants.cont_read_cache); + if (op) { + const struct spi_mem_op *read_op; + + read_op = spinand->ssdr_op_templates.read_cache; + + /* + * Sometimes the fastest continuous read variant may not + * be supported. In this case, prefer to use the fastest + * read from cache variant and disable continuous reads. + */ + if (read_op->cmd.buswidth > op->cmd.buswidth || + (read_op->cmd.dtr && !op->cmd.dtr) || + read_op->addr.buswidth > op->addr.buswidth || + (read_op->addr.dtr && !op->addr.dtr) || + read_op->data.buswidth > op->data.buswidth || + (read_op->data.dtr && !op->data.dtr)) + spinand->cont_read_possible = false; + else + spinand->ssdr_op_templates.cont_read_cache = op; + } else { + spinand->cont_read_possible = false; + } + } + /* I/O variants selection with octo-spi DDR commands (optional) */ ret = spinand_init_odtr_instruction_set(spinand); @@ -1644,6 +1694,15 @@ int spinand_match_and_init(struct spinand_device *spinand, info->op_variants.update_cache); spinand->odtr_op_templates.update_cache = op; + if (info->op_variants.cont_read_cache) { + op = spinand_select_op_variant(spinand, ODTR, + info->op_variants.cont_read_cache); + if (op) + spinand->odtr_op_templates.cont_read_cache = op; + else + spinand->cont_read_possible = false; + } + return 0; } diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index f53f5f0499b48..44f4347104d66 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -583,6 +583,7 @@ enum spinand_bus_interface { * @op_variants.read_cache: variants of the read-cache operation * @op_variants.write_cache: variants of the write-cache operation * @op_variants.update_cache: variants of the update-cache operation + * @op_variants.cont_read_cache: variants of the continuous read-cache operation * @vendor_ops: vendor specific operations * @select_target: function used to select a target/die. Required only for * multi-die chips @@ -607,6 +608,7 @@ struct spinand_info { const struct spinand_op_variants *read_cache; const struct spinand_op_variants *write_cache; const struct spinand_op_variants *update_cache; + const struct spinand_op_variants *cont_read_cache; } op_variants; const struct spinand_op_variants *vendor_ops; int (*select_target)(struct spinand_device *spinand, @@ -636,6 +638,14 @@ struct spinand_info { .update_cache = __update, \ } +#define SPINAND_INFO_OP_VARIANTS_WITH_CONT(__read, __write, __update, __cont_read) \ + { \ + .read_cache = __read, \ + .write_cache = __write, \ + .update_cache = __update, \ + .cont_read_cache = __cont_read, \ + } + #define SPINAND_INFO_VENDOR_OPS(__ops) \ .vendor_ops = __ops @@ -707,6 +717,7 @@ struct spinand_dirmap { * @read_cache: read cache op template * @write_cache: write cache op template * @update_cache: update cache op template + * @cont_read_cache: continuous read cache op template (optional) */ struct spinand_mem_ops { struct spi_mem_op reset; @@ -721,6 +732,7 @@ struct spinand_mem_ops { const struct spi_mem_op *read_cache; const struct spi_mem_op *write_cache; const struct spi_mem_op *update_cache; + const struct spi_mem_op *cont_read_cache; }; /**