]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
mtd: spinand: Use secondary ops for continuous reads
authorMiquel Raynal <miquel.raynal@bootlin.com>
Wed, 29 Apr 2026 17:56:43 +0000 (19:56 +0200)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Mon, 4 May 2026 13:15:47 +0000 (15:15 +0200)
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 <miquel.raynal@bootlin.com>
drivers/mtd/nand/spi/core.c
include/linux/mtd/spinand.h

index 786e3fb4874d8c0a9910cf81ac04ed251747d5d1..99a2494554ef1461b3338da6130032e8d29cc5f3 100644 (file)
@@ -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;
        }
 
index f53f5f0499b4870746a82daff2fbb79df3fc4e6b..44f4347104d661f9d48b0786afb44b91ba9b5c95 100644 (file)
@@ -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;
 };
 
 /**