mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-15 00:04:15 +08:00
f6ca3fb697
Both the JEDEC and ONFI specification say that read cache sequential
support is an optional command. This means that we not only need to
check whether the individual controller supports the command, we also
need to check the parameter pages for both ONFI and JEDEC NAND flashes
before enabling sequential cache reads.
This fixes support for NAND flashes which don't support enabling cache
reads, i.e. Samsung K9F4G08U0F or Toshiba TC58NVG0S3HTA00.
Sequential cache reads are now only available for ONFI and JEDEC
devices, if individual vendors implement this, it needs to be enabled
per vendor.
Tested on i.MX6Q with a Samsung NAND flash chip that doesn't support
sequential reads.
Fixes: 003fe4b954
("mtd: rawnand: Support for sequential cache reads")
Cc: stable@vger.kernel.org
Signed-off-by: Rouven Czerwinski <r.czerwinski@pengutronix.de>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20230922141717.35977-1-r.czerwinski@pengutronix.de
340 lines
8.6 KiB
C
340 lines
8.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
|
|
* 2002-2006 Thomas Gleixner (tglx@linutronix.de)
|
|
*
|
|
* Credits:
|
|
* David Woodhouse for adding multichip support
|
|
*
|
|
* Aleph One Ltd. and Toby Churchill Ltd. for supporting the
|
|
* rework for 2K page size chips
|
|
*
|
|
* This file contains all ONFI helpers.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include "internals.h"
|
|
|
|
#define ONFI_PARAM_PAGES 3
|
|
|
|
u16 onfi_crc16(u16 crc, u8 const *p, size_t len)
|
|
{
|
|
int i;
|
|
while (len--) {
|
|
crc ^= *p++ << 8;
|
|
for (i = 0; i < 8; i++)
|
|
crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/* Parse the Extended Parameter Page. */
|
|
static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
|
|
struct nand_onfi_params *p)
|
|
{
|
|
struct nand_device *base = &chip->base;
|
|
struct nand_ecc_props requirements;
|
|
struct onfi_ext_param_page *ep;
|
|
struct onfi_ext_section *s;
|
|
struct onfi_ext_ecc_info *ecc;
|
|
uint8_t *cursor;
|
|
int ret;
|
|
int len;
|
|
int i;
|
|
|
|
len = le16_to_cpu(p->ext_param_page_length) * 16;
|
|
ep = kmalloc(len, GFP_KERNEL);
|
|
if (!ep)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Use the Change Read Column command to skip the ONFI param pages and
|
|
* ensure we read at the right location.
|
|
*/
|
|
ret = nand_change_read_column_op(chip,
|
|
sizeof(*p) * p->num_of_param_pages,
|
|
ep, len, true);
|
|
if (ret)
|
|
goto ext_out;
|
|
|
|
ret = -EINVAL;
|
|
if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2)
|
|
!= le16_to_cpu(ep->crc))) {
|
|
pr_debug("fail in the CRC.\n");
|
|
goto ext_out;
|
|
}
|
|
|
|
/*
|
|
* Check the signature.
|
|
* Do not strictly follow the ONFI spec, maybe changed in future.
|
|
*/
|
|
if (strncmp(ep->sig, "EPPS", 4)) {
|
|
pr_debug("The signature is invalid.\n");
|
|
goto ext_out;
|
|
}
|
|
|
|
/* find the ECC section. */
|
|
cursor = (uint8_t *)(ep + 1);
|
|
for (i = 0; i < ONFI_EXT_SECTION_MAX; i++) {
|
|
s = ep->sections + i;
|
|
if (s->type == ONFI_SECTION_TYPE_2)
|
|
break;
|
|
cursor += s->length * 16;
|
|
}
|
|
if (i == ONFI_EXT_SECTION_MAX) {
|
|
pr_debug("We can not find the ECC section.\n");
|
|
goto ext_out;
|
|
}
|
|
|
|
/* get the info we want. */
|
|
ecc = (struct onfi_ext_ecc_info *)cursor;
|
|
|
|
if (!ecc->codeword_size) {
|
|
pr_debug("Invalid codeword size\n");
|
|
goto ext_out;
|
|
}
|
|
|
|
requirements.strength = ecc->ecc_bits;
|
|
requirements.step_size = 1 << ecc->codeword_size;
|
|
nanddev_set_ecc_requirements(base, &requirements);
|
|
|
|
ret = 0;
|
|
|
|
ext_out:
|
|
kfree(ep);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Recover data with bit-wise majority
|
|
*/
|
|
static void nand_bit_wise_majority(const void **srcbufs,
|
|
unsigned int nsrcbufs,
|
|
void *dstbuf,
|
|
unsigned int bufsize)
|
|
{
|
|
int i, j, k;
|
|
|
|
for (i = 0; i < bufsize; i++) {
|
|
u8 val = 0;
|
|
|
|
for (j = 0; j < 8; j++) {
|
|
unsigned int cnt = 0;
|
|
|
|
for (k = 0; k < nsrcbufs; k++) {
|
|
const u8 *srcbuf = srcbufs[k];
|
|
|
|
if (srcbuf[i] & BIT(j))
|
|
cnt++;
|
|
}
|
|
|
|
if (cnt > nsrcbufs / 2)
|
|
val |= BIT(j);
|
|
}
|
|
|
|
((u8 *)dstbuf)[i] = val;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check if the NAND chip is ONFI compliant, returns 1 if it is, 0 otherwise.
|
|
*/
|
|
int nand_onfi_detect(struct nand_chip *chip)
|
|
{
|
|
struct nand_device *base = &chip->base;
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
struct nand_memory_organization *memorg;
|
|
struct nand_onfi_params *p = NULL, *pbuf;
|
|
struct onfi_params *onfi;
|
|
bool use_datain = false;
|
|
int onfi_version = 0;
|
|
char id[4];
|
|
int i, ret, val;
|
|
u16 crc;
|
|
|
|
memorg = nanddev_get_memorg(&chip->base);
|
|
|
|
/* Try ONFI for unknown chip or LP */
|
|
ret = nand_readid_op(chip, 0x20, id, sizeof(id));
|
|
if (ret || strncmp(id, "ONFI", 4))
|
|
return 0;
|
|
|
|
/* ONFI chip: allocate a buffer to hold its parameter page */
|
|
pbuf = kzalloc((sizeof(*pbuf) * ONFI_PARAM_PAGES), GFP_KERNEL);
|
|
if (!pbuf)
|
|
return -ENOMEM;
|
|
|
|
if (!nand_has_exec_op(chip) || chip->controller->supported_op.data_only_read)
|
|
use_datain = true;
|
|
|
|
for (i = 0; i < ONFI_PARAM_PAGES; i++) {
|
|
if (!i)
|
|
ret = nand_read_param_page_op(chip, 0, &pbuf[i],
|
|
sizeof(*pbuf));
|
|
else if (use_datain)
|
|
ret = nand_read_data_op(chip, &pbuf[i], sizeof(*pbuf),
|
|
true, false);
|
|
else
|
|
ret = nand_change_read_column_op(chip, sizeof(*pbuf) * i,
|
|
&pbuf[i], sizeof(*pbuf),
|
|
true);
|
|
if (ret) {
|
|
ret = 0;
|
|
goto free_onfi_param_page;
|
|
}
|
|
|
|
crc = onfi_crc16(ONFI_CRC_BASE, (u8 *)&pbuf[i], 254);
|
|
if (crc == le16_to_cpu(pbuf[i].crc)) {
|
|
p = &pbuf[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ONFI_PARAM_PAGES) {
|
|
const void *srcbufs[ONFI_PARAM_PAGES];
|
|
unsigned int j;
|
|
|
|
for (j = 0; j < ONFI_PARAM_PAGES; j++)
|
|
srcbufs[j] = pbuf + j;
|
|
|
|
pr_warn("Could not find a valid ONFI parameter page, trying bit-wise majority to recover it\n");
|
|
nand_bit_wise_majority(srcbufs, ONFI_PARAM_PAGES, pbuf,
|
|
sizeof(*pbuf));
|
|
|
|
crc = onfi_crc16(ONFI_CRC_BASE, (u8 *)pbuf, 254);
|
|
if (crc != le16_to_cpu(pbuf->crc)) {
|
|
pr_err("ONFI parameter recovery failed, aborting\n");
|
|
goto free_onfi_param_page;
|
|
}
|
|
p = pbuf;
|
|
}
|
|
|
|
if (chip->manufacturer.desc && chip->manufacturer.desc->ops &&
|
|
chip->manufacturer.desc->ops->fixup_onfi_param_page)
|
|
chip->manufacturer.desc->ops->fixup_onfi_param_page(chip, p);
|
|
|
|
/* Check version */
|
|
val = le16_to_cpu(p->revision);
|
|
if (val & ONFI_VERSION_2_3)
|
|
onfi_version = 23;
|
|
else if (val & ONFI_VERSION_2_2)
|
|
onfi_version = 22;
|
|
else if (val & ONFI_VERSION_2_1)
|
|
onfi_version = 21;
|
|
else if (val & ONFI_VERSION_2_0)
|
|
onfi_version = 20;
|
|
else if (val & ONFI_VERSION_1_0)
|
|
onfi_version = 10;
|
|
|
|
if (!onfi_version) {
|
|
pr_info("unsupported ONFI version: %d\n", val);
|
|
goto free_onfi_param_page;
|
|
}
|
|
|
|
sanitize_string(p->manufacturer, sizeof(p->manufacturer));
|
|
sanitize_string(p->model, sizeof(p->model));
|
|
chip->parameters.model = kstrdup(p->model, GFP_KERNEL);
|
|
if (!chip->parameters.model) {
|
|
ret = -ENOMEM;
|
|
goto free_onfi_param_page;
|
|
}
|
|
|
|
memorg->pagesize = le32_to_cpu(p->byte_per_page);
|
|
mtd->writesize = memorg->pagesize;
|
|
|
|
/*
|
|
* pages_per_block and blocks_per_lun may not be a power-of-2 size
|
|
* (don't ask me who thought of this...). MTD assumes that these
|
|
* dimensions will be power-of-2, so just truncate the remaining area.
|
|
*/
|
|
memorg->pages_per_eraseblock =
|
|
1 << (fls(le32_to_cpu(p->pages_per_block)) - 1);
|
|
mtd->erasesize = memorg->pages_per_eraseblock * memorg->pagesize;
|
|
|
|
memorg->oobsize = le16_to_cpu(p->spare_bytes_per_page);
|
|
mtd->oobsize = memorg->oobsize;
|
|
|
|
memorg->luns_per_target = p->lun_count;
|
|
memorg->planes_per_lun = 1 << p->interleaved_bits;
|
|
|
|
/* See erasesize comment */
|
|
memorg->eraseblocks_per_lun =
|
|
1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1);
|
|
memorg->max_bad_eraseblocks_per_lun = le32_to_cpu(p->blocks_per_lun);
|
|
memorg->bits_per_cell = p->bits_per_cell;
|
|
|
|
if (le16_to_cpu(p->features) & ONFI_FEATURE_16_BIT_BUS)
|
|
chip->options |= NAND_BUSWIDTH_16;
|
|
|
|
if (p->ecc_bits != 0xff) {
|
|
struct nand_ecc_props requirements = {
|
|
.strength = p->ecc_bits,
|
|
.step_size = 512,
|
|
};
|
|
|
|
nanddev_set_ecc_requirements(base, &requirements);
|
|
} else if (onfi_version >= 21 &&
|
|
(le16_to_cpu(p->features) & ONFI_FEATURE_EXT_PARAM_PAGE)) {
|
|
|
|
/*
|
|
* The nand_flash_detect_ext_param_page() uses the
|
|
* Change Read Column command which maybe not supported
|
|
* by the chip->legacy.cmdfunc. So try to update the
|
|
* chip->legacy.cmdfunc now. We do not replace user supplied
|
|
* command function.
|
|
*/
|
|
nand_legacy_adjust_cmdfunc(chip);
|
|
|
|
/* The Extended Parameter Page is supported since ONFI 2.1. */
|
|
if (nand_flash_detect_ext_param_page(chip, p))
|
|
pr_warn("Failed to detect ONFI extended param page\n");
|
|
} else {
|
|
pr_warn("Could not retrieve ONFI ECC requirements\n");
|
|
}
|
|
|
|
/* Save some parameters from the parameter page for future use */
|
|
if (le16_to_cpu(p->opt_cmd) & ONFI_OPT_CMD_SET_GET_FEATURES) {
|
|
chip->parameters.supports_set_get_features = true;
|
|
bitmap_set(chip->parameters.get_feature_list,
|
|
ONFI_FEATURE_ADDR_TIMING_MODE, 1);
|
|
bitmap_set(chip->parameters.set_feature_list,
|
|
ONFI_FEATURE_ADDR_TIMING_MODE, 1);
|
|
}
|
|
|
|
if (le16_to_cpu(p->opt_cmd) & ONFI_OPT_CMD_READ_CACHE)
|
|
chip->parameters.supports_read_cache = true;
|
|
|
|
onfi = kzalloc(sizeof(*onfi), GFP_KERNEL);
|
|
if (!onfi) {
|
|
ret = -ENOMEM;
|
|
goto free_model;
|
|
}
|
|
|
|
onfi->version = onfi_version;
|
|
onfi->tPROG = le16_to_cpu(p->t_prog);
|
|
onfi->tBERS = le16_to_cpu(p->t_bers);
|
|
onfi->tR = le16_to_cpu(p->t_r);
|
|
onfi->tCCS = le16_to_cpu(p->t_ccs);
|
|
onfi->fast_tCAD = le16_to_cpu(p->nvddr_nvddr2_features) & BIT(0);
|
|
onfi->sdr_timing_modes = le16_to_cpu(p->sdr_timing_modes);
|
|
if (le16_to_cpu(p->features) & ONFI_FEATURE_NV_DDR)
|
|
onfi->nvddr_timing_modes = le16_to_cpu(p->nvddr_timing_modes);
|
|
onfi->vendor_revision = le16_to_cpu(p->vendor_revision);
|
|
memcpy(onfi->vendor, p->vendor, sizeof(p->vendor));
|
|
chip->parameters.onfi = onfi;
|
|
|
|
/* Identification done, free the full ONFI parameter page and exit */
|
|
kfree(pbuf);
|
|
|
|
return 1;
|
|
|
|
free_model:
|
|
kfree(chip->parameters.model);
|
|
free_onfi_param_page:
|
|
kfree(pbuf);
|
|
|
|
return ret;
|
|
}
|