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
This commit is contained in:
Miquel Raynal 2020-05-29 13:13:15 +02:00
parent 42a9ad050e
commit b5b39f640c
2 changed files with 61 additions and 36 deletions

View File

@ -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,

View File

@ -1005,6 +1005,62 @@ err_reset_chip:
return ret;
}
/**
* 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);
}
/**