[PATCH 4/7] mtd: nand: automate NAND timings selection
From: s.hauer@pengutronix.de (Sascha Hauer)
Date: 2016-09-06 15:04:58
On Tue, Sep 06, 2016 at 04:50:04PM +0200, Boris Brezillon wrote:
On Tue, 6 Sep 2016 16:08:17 +0200 Sascha Hauer [off-list ref] wrote:quoted
On Tue, Sep 06, 2016 at 01:58:07PM +0200, Boris Brezillon wrote:quoted
On Tue, 6 Sep 2016 12:39:12 +0200 Sascha Hauer [off-list ref] wrote:quoted
From: Boris Brezillon <redacted> The NAND framework provides several helpers to query timing modes supported by a NAND chip, but this implies that all NAND controller drivers have to implement the same timings selection dance. Also currently NAND devices can be resetted at arbitrary places which also resets the timing for ONFI chips to timing mode 0. Provide a common logic to select the best timings based on ONFI or ->onfi_timing_mode_default information. Hook this into nand_reset() to make sure the new timing is applied each time during a reset. NAND controller willing to support timings adjustment should just implement the ->setup_data_interface() method. Signed-off-by: Boris Brezillon <redacted> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> --- drivers/mtd/nand/nand_base.c | 112 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mtd/nand.h | 14 ++++-- 2 files changed, 122 insertions(+), 4 deletions(-)diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 20151fc..37852e9 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c@@ -955,8 +955,63 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip) */ int nand_reset(struct mtd_info *mtd) { + struct nand_chip *chip = mtd_to_nand(mtd); + int ret; + + if (chip->setup_data_interface) { + struct nand_data_interface conf = { + .type = NAND_SDR_IFACE, + .timings.sdr = *onfi_async_timing_mode_to_sdr_timings(0), + }; +Let's try to avoid putting such a huge structure on the stack.I could do that by doing const struct nand_data_interface *conf = onfi_async_timing_mode_to_data_interface(0); But you just discussed this function away ;) I need two different timings, first the ONFI timing mode 0 and then the real timing. I wanted to avoid re-initializing the same timing struct instance multiple times (twice in nand_reset() and two times again for each additional chip in an array).quoted
quoted
+ /* + * The ONFI specification says: + * " + * To transition from NV-DDR or NV-DDR2 to the SDR data + * interface, the host shall use the Reset (FFh) command + * using SDR timing mode 0. A device in any timing mode is + * required to recognize Reset (FFh) command issued in SDR + * timing mode 0. + * " + * + * Configure the data interface in SDR mode and set the + * timings to timing mode 0. + */ + + ret = chip->setup_data_interface(mtd, &conf, false); + if (ret) { + pr_err("Failed to configure data interface to SDR timing mode 0\n"); + return ret; + } + }Can you put this code in a separate function? I'd like to keep the nand_reset() function as small as possible. How about nand_reset_data_interface()?Yes, can do. In that case I would move the test if setting the data interface is supported to that function aswell. Are you okay with that?Sure.quoted
quoted
quoted
+ chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); + /* + * Setup the NAND interface (interface type + timings). + */ + if (chip->data_iface) { + uint8_t tmode_param[ONFI_SUBFEATURE_PARAM_LEN] = { + chip->onfi_timing_mode_default, + }; + + /* + * Ensure the timing mode has be changed on the chip side^ beenquoted
+ * before changing timings on the controller side. + */ + if (chip->onfi_version) { + ret = chip->onfi_set_features(mtd, chip, + ONFI_FEATURE_ADDR_TIMING_MODE, + tmode_param); + if (ret) + return ret; + } + + ret = chip->setup_data_interface(mtd, chip->data_iface, false); + if (ret) + return ret; + } +Ditto: nand_setup_data_interface()?quoted
return 0; }@@ -3335,6 +3390,54 @@ static void nand_onfi_detect_micron(struct nand_chip *chip, chip->setup_read_retry = nand_setup_read_retry_micron; } +/** + * nand_find_data_interface - Find the best data interface and timings + * @mtd: MTD device structure + * + * Try to find the best data interface and NAND timings supported by the + * chip and the driver. + * First tries to retrieve supported timing modes from ONFI information, + * and if the NAND chip does not support ONFI, relies on the + * ->onfi_timing_mode_default specified in the nand_ids table. + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_find_data_interface(struct mtd_info *mtd)How about nand_init_data_interface() or nand_init_data_iface_config()?quoted
+{ + struct nand_chip *chip = mtd_to_nand(mtd); + int modes, mode, ret; + const struct nand_data_interface *conf; + + /* + * 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. + */ + modes = onfi_get_async_timing_mode(chip); + if (modes == ONFI_TIMING_MODE_UNKNOWN) + modes = GENMASK(chip->onfi_timing_mode_default, 0); + + ret = -EINVAL; + for (mode = fls(modes) - 1; mode >= 0; mode--) { + conf = onfi_async_timing_mode_to_data_interface(mode);I'd still prefer to have conf allocated at the beginning of the function and timings copied from onfi_async_timing_mode_to_sdr_timings(mode), but maybe you can convince me otherwise.Let me ask the other way round: If we need struct nand_data_interface to fully describe a timing, why don't we keep an array of these in the kernel? Having an array of struct nand_sdr_timings() means we always have to copy it to a bigger struct to make it usable.Actually, the plan is to let vendor specific code tweak the timings if needed. Some NANDs that do not support ONFI have to pick timing mode 0 because one of their timing is not matching the ONFI spec. I'd like to let the door to fined-grained timing tweaking open, and this is only possible if the chip has its own nand_data_interface object (not the const one defined in nand_timings.c). Also note that some timings are not statically defined (like tPROG), and are extracted from another ONFI field, and I'd like to add them to the nand_sdr_timings struct, which again, is only possible if the nand_chip has its own nand_data_interface instance.
Hm, in the current series the nand_chip has it's own nand_data_interface instance, it's allocated in nand_find_data_interface().
quoted
quoted
quoted
@@ -759,6 +759,10 @@ struct nand_chip { int (*onfi_get_features)(struct mtd_info *mtd, struct nand_chip *chip, int feature_addr, uint8_t *subfeature_para); int (*setup_read_retry)(struct mtd_info *mtd, int retry_mode); + int (*setup_data_interface)(struct mtd_info *mtd, + const struct nand_data_interface *conf, + bool check_only); + int chip_delay; unsigned int options;@@ -788,6 +792,8 @@ struct nand_chip { struct nand_jedec_params jedec_params; }; + const struct nand_data_interface *data_iface; +How about making this field non-const so that you only allocate it once and modify it when you switch from one mode to another.As said above, I need two different timings. If we modify this nand_data_interface instance twice during reset there's not much point in storing it in struct nand_chip at all. That was one variant I tried: Always calculcate the timing from the supported ONFI modes when we need it in nand_reset(). I stepped away from this variant because of the overhead.Yes, your device will be configured twice (first mode 0, then the highest supported timing mode), but that does not mean you need to have 2 instances of nand_data_interface. ->data_iface should always be assigned to the current data interface config. If you reset the chip and go back to timing 0, then chip->data_iface should be set to sdr mode timing zero, and once a new timing mode is applied, it should be updated. And yes, there's a small overhead (copying the nand_sdr_timings data twice), but I'm pretty sure it's negligible compared to the whole NAND chip init overhead. And it's not like nand_reset() is called so regularly that it's useful to optimize this kind of things.
I haven't really thought about overhead in terms of burnt CPU cycles but more about how easy it is to follow the code. Anyway, I do as you wish, expect a new series tomorrow ;) Sascha -- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |