summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/nand
diff options
context:
space:
mode:
authorMiquel Raynal <miquel.raynal@bootlin.com>2020-05-29 13:13:15 +0200
committerMiquel Raynal <miquel.raynal@bootlin.com>2020-06-26 08:35:07 +0200
commitb5b39f640c1f5621ed4ff6418e74ee35ff5d988e (patch)
treed616cbada102ca2c84d0c8cc6016f3b962e1f3a1 /drivers/mtd/nand
parent42a9ad050e6f1f6e909e2117e7a99f54f5336939 (diff)
mtd: rawnand: Introduce nand_choose_best_sdr_timings()
Extract the logic out of nand_choose_interface_config() to create a public helper that can be reused by manufacturer drivers. Add the possibility to provide a specific set of timings. Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com> Link: https://lore.kernel.org/linux-mtd/20200529111322.7184-22-miquel.raynal@bootlin.com
Diffstat (limited to 'drivers/mtd/nand')
-rw-r--r--drivers/mtd/nand/raw/internals.h3
-rw-r--r--drivers/mtd/nand/raw/nand_base.c94
2 files changed, 61 insertions, 36 deletions
diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h
index 63c5af436901..5ebfbb89e572 100644
--- a/drivers/mtd/nand/raw/internals.h
+++ b/drivers/mtd/nand/raw/internals.h
@@ -90,6 +90,9 @@ void onfi_fill_interface_config(struct nand_chip *chip,
unsigned int timing_mode);
unsigned int
onfi_find_closest_sdr_mode(const struct nand_sdr_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_get_features(struct nand_chip *chip, int addr, u8 *subfeature_param);
int nand_set_features(struct nand_chip *chip, int addr, u8 *subfeature_param);
int nand_read_page_raw_notsupp(struct nand_chip *chip, u8 *buf,
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index 3bfd71d589cf..742d099df5c6 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -1006,6 +1006,62 @@ err_reset_chip:
}
/**
+ * nand_choose_best_sdr_timings - Pick up the best SDR 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, try to retrieve
+ * supported timing modes from ONFI information. Finally, if the NAND chip does
+ * not follow the ONFI specification, rely on the ->default_timing_mode
+ * specified in the nand_ids table.
+ */
+int nand_choose_best_sdr_timings(struct nand_chip *chip,
+ struct nand_interface_config *iface,
+ struct nand_sdr_timings *spec_timings)
+{
+ const struct nand_controller_ops *ops = chip->controller->ops;
+ int best_mode = 0, mode, ret;
+
+ iface->type = NAND_SDR_IFACE;
+
+ if (spec_timings) {
+ iface->timings.sdr = *spec_timings;
+ iface->timings.mode = onfi_find_closest_sdr_mode(spec_timings);
+
+ /* Verify the controller supports the requested interface */
+ ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY,
+ iface);
+ if (!ret)
+ return ret;
+
+ /* Fallback to slower modes */
+ best_mode = iface->timings.mode;
+ } else {
+ if (chip->parameters.onfi) {
+ unsigned int onfi_modes;
+
+ onfi_modes = chip->parameters.onfi->async_timing_mode;
+ best_mode = fls(onfi_modes) - 1;
+ } else {
+ best_mode = chip->onfi_timing_mode_default;
+ }
+ }
+
+ for (mode = best_mode; mode >= 0; mode--) {
+ onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, mode);
+
+ ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY,
+ iface);
+ if (!ret)
+ return 0;
+ }
+
+ return 0;
+}
+
+/**
* nand_choose_interface_config - find the best data interface and timings
* @chip: The NAND chip
*
@@ -1016,48 +1072,14 @@ err_reset_chip:
* ->onfi_timing_mode_default specified in the nand_ids table. After this
* function nand_chip->interface_ is initialized with the best timing mode
* available.
- *
- * Returns 0 for success or negative error code otherwise.
*/
static int nand_choose_interface_config(struct nand_chip *chip)
{
- int modes, mode, ret;
-
if (!nand_controller_can_setup_interface(chip))
return 0;
- /*
- * First try to identify the best timings from ONFI parameters and
- * if the NAND does not support ONFI, fallback to the default ONFI
- * timing mode.
- */
- if (chip->parameters.onfi) {
- modes = chip->parameters.onfi->async_timing_mode;
- } else {
- if (!chip->onfi_timing_mode_default)
- return 0;
-
- modes = GENMASK(chip->onfi_timing_mode_default, 0);
- }
-
- for (mode = fls(modes) - 1; mode >= 0; mode--) {
- onfi_fill_interface_config(chip, &chip->interface_config,
- NAND_SDR_IFACE, mode);
-
- /*
- * Pass NAND_DATA_IFACE_CHECK_ONLY to only check if the
- * controller supports the requested timings.
- */
- ret = chip->controller->ops->setup_interface(chip,
- NAND_DATA_IFACE_CHECK_ONLY,
- &chip->interface_config);
- if (!ret) {
- chip->onfi_timing_mode_default = mode;
- break;
- }
- }
-
- return 0;
+ return nand_choose_best_sdr_timings(chip, &chip->interface_config,
+ NULL);
}
/**