[PATCH 4/7] mtd: nand: automate NAND timings selection
Boris Brezillon
boris.brezillon at free-electrons.com
Tue Sep 6 04:58:07 PDT 2016
On Tue, 6 Sep 2016 12:39:12 +0200
Sascha Hauer <s.hauer at pengutronix.de> wrote:
> From: Boris Brezillon <boris.brezillon at free-electrons.com>
>
> 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 <boris.brezillon at free-electrons.com>
> Signed-off-by: Sascha Hauer <s.hauer at 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.
> + /*
> + * 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()?
> +
> 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
^ been
> + * 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()?
> 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()?
> +{
> + 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.
> +
> + ret = chip->setup_data_interface(mtd, conf, true);
> + if (!ret) {
> + chip->onfi_timing_mode_default = mode;
> + break;
> + }
> + }
> +
> + if (ret)
> + return ret;
> +
> + chip->data_iface = kmemdup(conf, sizeof(*conf), GFP_KERNEL);
> + if (!chip->data_iface)
> + return -ENOMEM;
> +
> + return 0;
> +}
> +
> /*
> * Check if the NAND chip is ONFI compliant, returns 1 if it is, 0 otherwise.
> */
> @@ -4168,6 +4271,12 @@ int nand_scan_ident(struct mtd_info *mtd, int maxchips,
> return PTR_ERR(type);
> }
>
> + if (chip->setup_data_interface) {
> + ret = nand_find_data_interface(mtd);
> + if (ret)
> + return ret;
> + }
> +
> chip->select_chip(mtd, -1);
>
> /* Check for a chip array */
> @@ -4627,6 +4736,9 @@ void nand_release(struct mtd_info *mtd)
>
> mtd_device_unregister(mtd);
>
> + /* Free interface config struct */
> + kfree(chip->data_iface);
> +
Can we hide that in an helper as well? nand_cleanup_data_interface()?
> /* Free bad block table memory */
> kfree(chip->bbt);
> if (!(chip->options & NAND_OWN_BUFFERS))
> diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h
> index a8bdf6c..5269357 100644
> --- a/include/linux/mtd/nand.h
> +++ b/include/linux/mtd/nand.h
> @@ -690,10 +690,9 @@ struct nand_data_interface {
> * also from the datasheet. It is the recommended ECC step
> * size, if known; if unknown, set to zero.
> * @onfi_timing_mode_default: [INTERN] default ONFI timing mode. This field is
> - * either deduced from the datasheet if the NAND
> - * chip is not ONFI compliant or set to 0 if it is
> - * (an ONFI chip is always configured in mode 0
> - * after a NAND reset)
> + * set to the actually used ONFI mode if the chip is
> + * ONFI compliant or deduced from the datasheet if
> + * the NAND chip is not ONFI compliant.
> * @numchips: [INTERN] number of physical chips
> * @chipsize: [INTERN] the size of one chip for multichip arrays
> * @pagemask: [INTERN] page number mask = number of (pages / chip) - 1
> @@ -713,6 +712,7 @@ struct nand_data_interface {
> * @read_retries: [INTERN] the number of read retry modes supported
> * @onfi_set_features: [REPLACEABLE] set the features for ONFI nand
> * @onfi_get_features: [REPLACEABLE] get the features for ONFI nand
> + * @setup_data_interface: [OPTIONAL] setup the data interface and timing
> * @bbt: [INTERN] bad block table pointer
> * @bbt_td: [REPLACEABLE] bad block table descriptor for flash
> * lookup.
> @@ -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.
> int read_retries;
>
> flstate_t state;
More information about the linux-arm-kernel
mailing list