[RFC PATCH 15/16] mtd: rawnand: Choose the best timings, NV-DDR included
Miquel Raynal
miquel.raynal at bootlin.com
Fri Apr 2 10:51:44 BST 2021
Now that the necessary peaces to support the NV-DDR interface type have
been contributed, let's add the relevant logic to make use of it. In
particular, the core does not choose the best SDR timings anymore but
calls a more generic helper instead.
This helper of course checks if NV-DDR is supported. If they are not,
then it fallback to SDR. Otherwise, it tries to find the best NV-DDR
supported mode, with a logic very close to what is being done on the
SDR side.
One singularity however: it looks like NV-DDR mode 0 is slower than SDR
mode 5. In the case we would be stuck at NV-DDR mode 0, let's try if SDR
mode 5 is supported. If yes, then we fallback to this mode, otherwise we
keep the original NV-DDR one.
Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
drivers/mtd/nand/raw/internals.h | 3 +
drivers/mtd/nand/raw/nand_base.c | 95 +++++++++++++++++++++++++++++++-
2 files changed, 97 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h
index c06bc2f1aaa2..7016e0f38398 100644
--- a/drivers/mtd/nand/raw/internals.h
+++ b/drivers/mtd/nand/raw/internals.h
@@ -95,6 +95,9 @@ onfi_find_closest_nvddr_mode(const struct nand_nvddr_timings *spec_timings);
int nand_choose_best_sdr_timings(struct nand_chip *chip,
struct nand_interface_config *iface,
struct nand_sdr_timings *spec_timings);
+int nand_choose_best_nvddr_timings(struct nand_chip *chip,
+ struct nand_interface_config *iface,
+ struct nand_nvddr_timings *spec_timings);
const struct nand_interface_config *nand_get_reset_interface_config(void);
int nand_get_features(struct nand_chip *chip, int addr, u8 *subfeature_param);
int nand_set_features(struct nand_chip *chip, int addr, u8 *subfeature_param);
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index e43ea20d7483..695d1db17ebd 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -911,6 +911,99 @@ int nand_choose_best_sdr_timings(struct nand_chip *chip,
return 0;
}
+/**
+ * nand_choose_best_nvddr_timings - Pick up the best NVDDR timings that both the
+ * NAND controller and the NAND chip support
+ * @chip: the NAND chip
+ * @iface: the interface configuration (can eventually be updated)
+ * @spec_timings: specific timings, when not fitting the ONFI specification
+ *
+ * If specific timings are provided, use them. Otherwise, retrieve supported
+ * timing modes from ONFI information.
+ */
+int nand_choose_best_nvddr_timings(struct nand_chip *chip,
+ struct nand_interface_config *iface,
+ struct nand_nvddr_timings *spec_timings)
+{
+ const struct nand_controller_ops *ops = chip->controller->ops;
+ int best_mode = 0, mode, ret;
+
+ iface->type = NAND_NVDDR_IFACE;
+
+ if (spec_timings) {
+ iface->timings.nvddr = *spec_timings;
+ iface->timings.mode = onfi_find_closest_nvddr_mode(spec_timings);
+
+ /* Verify the controller supports the requested interface */
+ ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY,
+ iface);
+ if (!ret) {
+ chip->best_interface_config = iface;
+ return ret;
+ }
+
+ /* Fallback to slower modes */
+ best_mode = iface->timings.mode;
+ } else if (chip->parameters.onfi) {
+ best_mode = fls(chip->parameters.onfi->nvddr_timing_modes) - 1;
+ }
+
+ for (mode = best_mode; mode >= 0; mode--) {
+ onfi_fill_interface_config(chip, iface, NAND_NVDDR_IFACE, mode);
+
+ ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY,
+ iface);
+ if (!ret)
+ break;
+ }
+
+ chip->best_interface_config = iface;
+
+ return 0;
+}
+
+/**
+ * nand_choose_best_timings - Pick up the best NVDDR or SDR timings that both
+ * NAND controller and the NAND chip support
+ * @chip: the NAND chip
+ * @iface: the interface configuration (can eventually be updated)
+ *
+ * If specific timings are provided, use them. Otherwise, retrieve supported
+ * timing modes from ONFI information.
+ */
+static int nand_choose_best_timings(struct nand_chip *chip,
+ struct nand_interface_config *iface)
+{
+ bool valid_nvddr_timings = false;
+ unsigned int best_sdr_mode;
+ int ret;
+
+ /* Try the fastest timings: NV-DDR */
+ ret = nand_choose_best_nvddr_timings(chip, iface, NULL);
+ if (!ret) {
+ if (chip->best_interface_config->timings.mode != 0)
+ return 0;
+
+ /*
+ * ONFI SDR mode 5 is faster than ONFI NV-DDR mode 0, so check
+ * if is supported before acking NV-DDR mode 0.
+ */
+ valid_nvddr_timings = true;
+ }
+
+ /* Fallback to SDR timings */
+ ret = nand_choose_best_sdr_timings(chip, iface, NULL);
+ if (ret)
+ return ret;
+
+ best_sdr_mode = chip->best_interface_config->timings.mode;
+ if (!valid_nvddr_timings || best_sdr_mode == 5)
+ return 0;
+
+ /* Eventually go back to faster NV-DDR mode 0 */
+ return nand_choose_best_nvddr_timings(chip, iface, NULL);
+}
+
/**
* nand_choose_interface_config - find the best data interface and timings
* @chip: The NAND chip
@@ -939,7 +1032,7 @@ static int nand_choose_interface_config(struct nand_chip *chip)
if (chip->ops.choose_interface_config)
ret = chip->ops.choose_interface_config(chip, iface);
else
- ret = nand_choose_best_sdr_timings(chip, iface, NULL);
+ ret = nand_choose_best_timings(chip, iface);
if (ret)
kfree(iface);
--
2.27.0
More information about the linux-mtd
mailing list