[PATCH v7 16/18] mtd: spinand: negotiate optimal controller operating point before dirmap creation
Santhosh Kumar K <[email protected]>
| Newsgroups | org.infradead.lists.linux-mtd,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-spi |
|---|---|
| Message-ID | <[email protected]> |
Add spinand_optimize_controller() which calls spi_mem_execute_tuning() to negotiate the best operating point for the SPI NAND device before creating dirmaps. The validated max_freq is then embedded into the dirmap op templates so the controller uses the optimised frequency for all subsequent page I/O. The function tries the pre-selected op variant first. If the controller signals that optimisation is not applicable for that specific op (ret == 0 with max_freq still zero), spinand_try_ranked_variant() iterates the remaining variants in descending performance order. For devices supporting both ODTR and SSDR interfaces, ODTR variants are tried first; if all ODTR variants fail, the bus is switched to SSDR and SSDR variants are tried. On full failure the device reverts to ODTR non-optimised mode. Add spinand_reset_max_freq_ops() to copy op templates with max_freq zeroed before each execute_tuning call, enforcing the invariant that a non-zero max_freq only results from a successful operation. Optimisation failure is never fatal; the device operates at the conservative base rate. When optimisation permanently selects SSDR (either because a ranked SSDR variant succeeds or because reverting to ODTR after exhausting all variants fails), clear the odtr_op_templates pointers. This prevents spinand_configure_chip() on resume from re-entering ODTR mode with dirmaps that were built for SSDR, which would cause data corruption. Reusing the existing NULL-check mechanism avoids the need for a separate flag and is consistent with how spinand_configure_chip() itself handles ODTR configure_chip() failure. Move spinand_create_dirmaps() from spinand_init() to spinand_probe() so the validated frequency is available at dirmap construction time. Signed-off-by: Santhosh Kumar K <[email protected]> --- drivers/mtd/nand/spi/core.c | 221 ++++++++++++++++++++++++++++++++++-- include/linux/mtd/spinand.h | 11 ++ 2 files changed, 223 insertions(+), 9 deletions(-) diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 7c3341f1fca0..dfc9d0871e91 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -1284,6 +1284,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand, info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); info.primary_op_tmpl = *spinand->op_templates->update_cache; info.primary_op_tmpl.data.ecc = enable_ecc; + info.primary_op_tmpl.max_freq = spinand->max_write_op.max_freq; desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, spinand->spimem, &info); if (IS_ERR(desc)) @@ -1294,9 +1295,11 @@ 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; + info.primary_op_tmpl.max_freq = spinand->max_read_op.max_freq; if (secondary_op) { info.secondary_op_tmpl = *spinand->op_templates->cont_read_cache; info.secondary_op_tmpl.data.ecc = enable_ecc; + info.secondary_op_tmpl.max_freq = spinand->max_read_op.max_freq; } desc = spinand_create_rdesc(spinand, &info); if (IS_ERR(desc)) @@ -1745,6 +1748,17 @@ int spinand_match_and_init(struct spinand_device *spinand, spinand->cont_read_possible = false; } + /* + * Save the full read variant list (ODTR and SSDR ops) for + * ranked controller optimization. Only saved when all ODTR + * templates are valid; spinand_optimize_controller() uses this + * to fall back to the next-best variant when needed. + */ + if (spinand->odtr_op_templates.read_cache && + spinand->odtr_op_templates.write_cache && + spinand->odtr_op_templates.update_cache) + spinand->all_read_variants = info->op_variants.read_cache; + return 0; } @@ -1923,7 +1937,6 @@ static int spinand_mtd_suspend(struct mtd_info *mtd) static int spinand_init(struct spinand_device *spinand) { - struct device *dev = &spinand->spimem->spi->dev; struct mtd_info *mtd = spinand_to_mtd(spinand); struct nand_device *nand = mtd_to_nanddev(mtd); int ret; @@ -2015,14 +2028,6 @@ static int spinand_init(struct spinand_device *spinand) mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size; mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4); - ret = spinand_create_dirmaps(spinand); - if (ret) { - dev_err(dev, - "Failed to create direct mappings for read/write operations (err = %d)\n", - ret); - goto err_cleanup_ecc_engine; - } - return 0; err_cleanup_ecc_engine: @@ -2051,6 +2056,190 @@ static void spinand_cleanup(struct spinand_device *spinand) kfree(spinand->scratchbuf); } +/* + * spinand_try_ranked_variant() - Try controller optimization on variants in + * performance order. + * @spinand: SPI NAND device + * @mem: SPI memory device + * @iface: bus interface to iterate (ODTR or SSDR) + * @tried_mask: bitmask of already-tried variant indices; updated on each try + * + * Iterates the full read variant list in descending performance order, + * skipping variants in @tried_mask, and calls execute_tuning on each until + * one succeeds. Ranked iteration finds the best available variant without + * re-trying already-attempted ones. + * + * On success, sets spinand->max_read_op and updates the matching + * odtr_op_templates.read_cache or ssdr_op_templates.read_cache. + */ +static bool spinand_try_ranked_variant(struct spinand_device *spinand, + struct spi_mem *mem, + enum spinand_bus_interface iface, + u32 *tried_mask) +{ + const struct spinand_op_variants *variants = spinand->all_read_variants; + const struct spi_mem_op *best; + int ret; + + if (!variants) + return false; + + while ((best = spinand_op_find_best_variant(spinand, variants, iface, + *tried_mask))) { + *tried_mask |= BIT(best - variants->ops); + spinand->max_read_op = *best; + spinand->max_read_op.max_freq = 0; + spinand->max_write_op.max_freq = 0; + ret = spi_mem_execute_tuning(mem, &spinand->max_read_op, + &spinand->max_write_op); + if (ret && ret != -EOPNOTSUPP) + dev_dbg(&mem->spi->dev, "%s optimization failed: %d\n", + iface == ODTR ? "ODTR" : "SSDR", ret); + if (!ret && spinand->max_read_op.max_freq) { + if (iface == ODTR) + spinand->odtr_op_templates.read_cache = best; + else + spinand->ssdr_op_templates.read_cache = best; + spinand->cont_read_possible = false; + return true; + } + } + return false; +} + +/* + * spinand_reset_max_freq_ops() - Copy op templates and zero max_freq on both. + * @spinand: SPI NAND device + * @templates: op template set to copy from + * + * Called before execute_tuning so max_freq starts at zero; execute_tuning sets + * it to the validated clock rate only on success. A non-zero max_freq means + * controller-optimized; zero means the base rate applies. + */ +static void spinand_reset_max_freq_ops(struct spinand_device *spinand, + struct spinand_mem_ops *templates) +{ + spinand->max_read_op = *templates->read_cache; + spinand->max_read_op.max_freq = 0; + spinand->max_write_op = *templates->write_cache; + spinand->max_write_op.max_freq = 0; +} + +/* + * spinand_optimize_controller() - Negotiate the optimal controller operating + * point for the SPI NAND device. + * @spinand: SPI NAND device + * @mem: SPI memory device + * + * Tries the pre-selected variant first. If the controller signals that + * optimization is not applicable for that specific op, iterates all remaining + * variants in performance order. For devices that support both DTR and SDR + * interfaces, DTR variants are tried first; if all fail the device is + * switched to SDR mode and SDR variants are tried. On full failure the + * device falls back to the best available non-optimized mode. Devices that + * support only SDR skip the DTR ranked pass entirely. + * + * Optimization failure is never fatal. + * + * Note: tried_mask is u32, supporting up to 32 variants total across both + * ODTR and SSDR. Flash devices with more than 32 read variants are not + * supported. + */ +static void spinand_optimize_controller(struct spinand_device *spinand, + struct spi_mem *mem) +{ + u32 tried_mask; + int ret; + + /* Skip entirely when no post-config target is configured. */ + if (!mem->spi->post_config_max_speed_hz) + return; + + spinand_reset_max_freq_ops(spinand, spinand->op_templates); + + ret = spi_mem_execute_tuning(mem, &spinand->max_read_op, + &spinand->max_write_op); + if (ret && ret != -EOPNOTSUPP) + dev_dbg(&mem->spi->dev, "Controller optimization failed: %d\n", + ret); + + /* + * Any non-zero return or a set max_freq means we are done (error, + * unsupported, or success). Fallback only for the op-specific "skip" + * signal: ret == 0 with max_freq still 0. + */ + if (ret || spinand->max_read_op.max_freq) + return; + + /* SSDR-only devices have no ranked ODTR fallback available. */ + if (spinand->bus_iface == SSDR || !spinand->all_read_variants) + return; + + if (WARN_ON(spinand->all_read_variants->nops > 32)) + return; + + /* Mark the pre-selected ODTR variant as already tried. */ + tried_mask = BIT(spinand->odtr_op_templates.read_cache - + spinand->all_read_variants->ops); + + dev_dbg(&mem->spi->dev, + "Optimization skipped for current op; searching for best variant\n"); + + /* Pass 1: try all remaining ODTR variants in performance order. */ + if (spinand_try_ranked_variant(spinand, mem, ODTR, &tried_mask)) + return; + + /* + * Pass 2: switch to SSDR and try all SSDR variants in performance + * order. configure_chip is guaranteed non-NULL here: reaching ODTR + * mode requires it. + */ + if (WARN_ON(!spinand->configure_chip)) + goto use_odtr_fallback; + + if (spinand->configure_chip(spinand, SSDR)) + goto use_odtr_fallback; + + spinand->op_templates = &spinand->ssdr_op_templates; + spinand->bus_iface = SSDR; + spinand->max_write_op = *spinand->ssdr_op_templates.write_cache; + spinand->max_write_op.max_freq = 0; + + /* + * Only ODTR variants were candidates in Pass 1; SSDR bits are clear. + * Clear ODTR templates on success so spinand_configure_chip() on + * resume does not re-enter ODTR with mismatched SSDR dirmaps. + */ + if (spinand_try_ranked_variant(spinand, mem, SSDR, &tried_mask)) { + spinand->odtr_op_templates.read_cache = NULL; + spinand->odtr_op_templates.write_cache = NULL; + spinand->odtr_op_templates.update_cache = NULL; + return; + } + + /* + * All attempts exhausted. Revert to ODTR for non-optimized DTR + * operation. If revert fails, stay in SSDR — a mode mismatch + * (ODTR op templates on SSDR-mode device) would corrupt data. + * Clear ODTR templates in either case to prevent resume from + * re-entering ODTR with mismatched SSDR dirmaps. + */ + if (spinand->configure_chip(spinand, ODTR)) { + dev_warn(&mem->spi->dev, + "Failed to revert to ODTR, staying in SSDR\n"); + spinand->odtr_op_templates.read_cache = NULL; + spinand->odtr_op_templates.write_cache = NULL; + spinand->odtr_op_templates.update_cache = NULL; + spinand_reset_max_freq_ops(spinand, &spinand->ssdr_op_templates); + return; + } + +use_odtr_fallback: + spinand->op_templates = &spinand->odtr_op_templates; + spinand->bus_iface = ODTR; + spinand_reset_max_freq_ops(spinand, &spinand->odtr_op_templates); +} + static int spinand_probe(struct spi_mem *mem) { struct spinand_device *spinand; @@ -2073,6 +2262,20 @@ static int spinand_probe(struct spi_mem *mem) if (ret) return ret; + /* + * Negotiate the best controller operating point before creating dirmaps + * so the validated frequency is available at dirmap construction time. + */ + spinand_optimize_controller(spinand, mem); + + ret = spinand_create_dirmaps(spinand); + if (ret) { + dev_err(&mem->spi->dev, + "Failed to create direct mappings for read/write operations (err = %d)\n", + ret); + goto err_spinand_cleanup; + } + ret = mtd_device_register(mtd, NULL, 0); if (ret) goto err_spinand_cleanup; diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 5f4c00ae72a7..7eacda949719 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -792,8 +792,19 @@ struct spinand_device { struct spinand_mem_ops *op_templates; enum spinand_bus_interface bus_iface; + /* + * Full read variant list (ODTR and SSDR ops together), saved when ODTR + * templates are valid. Used by spinand_optimize_controller() for ranked + * fallback when the pre-selected variant cannot be controller-optimized. + */ + const struct spinand_op_variants *all_read_variants; + struct spinand_dirmap *dirmaps; + /* Persistent op templates updated by execute_tuning with validated speed. */ + struct spi_mem_op max_read_op; + struct spi_mem_op max_write_op; + int (*select_target)(struct spinand_device *spinand, unsigned int target); unsigned int cur_target; -- 2.34.1 ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/