diff options
Diffstat (limited to 'drivers/mtd/nand/spi')
-rw-r--r-- | drivers/mtd/nand/spi/Kconfig | 9 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/Makefile | 4 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/alliancememory.c | 153 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/ato.c | 86 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/core.c | 1406 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/esmt.c | 144 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/gigadevice.c | 545 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/macronix.c | 333 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/micron.c | 309 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/paragon.c | 131 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/toshiba.c | 313 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/winbond.c | 202 | ||||
-rw-r--r-- | drivers/mtd/nand/spi/xtx.c | 129 |
13 files changed, 3764 insertions, 0 deletions
diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig new file mode 100644 index 0000000000..3d7649a2dd --- /dev/null +++ b/drivers/mtd/nand/spi/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig MTD_SPI_NAND + tristate "SPI NAND device Support" + select MTD_NAND_CORE + select MTD_NAND_ECC + depends on SPI_MASTER + select SPI_MEM + help + This is the framework for the SPI NAND device drivers. diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile new file mode 100644 index 0000000000..cd8b66bf77 --- /dev/null +++ b/drivers/mtd/nand/spi/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +spinand-objs := core.o alliancememory.o ato.o esmt.o gigadevice.o macronix.o +spinand-objs += micron.o paragon.o toshiba.o winbond.o xtx.o +obj-$(CONFIG_MTD_SPI_NAND) += spinand.o diff --git a/drivers/mtd/nand/spi/alliancememory.c b/drivers/mtd/nand/spi/alliancememory.c new file mode 100644 index 0000000000..7936ea546b --- /dev/null +++ b/drivers/mtd/nand/spi/alliancememory.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Mario Kicherer <dev@kicherer.org> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_ALLIANCEMEMORY 0x52 + +#define AM_STATUS_ECC_BITMASK (3 << 4) + +#define AM_STATUS_ECC_NONE_DETECTED (0 << 4) +#define AM_STATUS_ECC_CORRECTED (1 << 4) +#define AM_STATUS_ECC_ERRORED (2 << 4) +#define AM_STATUS_ECC_MAX_CORRECTED (3 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int am_get_eccsize(struct mtd_info *mtd) +{ + if (mtd->oobsize == 64) + return 0x20; + else if (mtd->oobsize == 128) + return 0x38; + else if (mtd->oobsize == 256) + return 0x70; + else + return -EINVAL; +} + +static int am_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + int ecc_bytes; + + ecc_bytes = am_get_eccsize(mtd); + if (ecc_bytes < 0) + return ecc_bytes; + + region->offset = mtd->oobsize - ecc_bytes; + region->length = ecc_bytes; + + return 0; +} + +static int am_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + int ecc_bytes; + + if (section) + return -ERANGE; + + ecc_bytes = am_get_eccsize(mtd); + if (ecc_bytes < 0) + return ecc_bytes; + + /* + * It is unclear how many bytes are used for the bad block marker. We + * reserve the common two bytes here. + * + * The free area in this kind of flash is divided into chunks where the + * first 4 bytes of each chunk are unprotected. The number of chunks + * depends on the specific model. The models with 4096+256 bytes pages + * have 8 chunks, the others 4 chunks. + */ + + region->offset = 2; + region->length = mtd->oobsize - 2 - ecc_bytes; + + return 0; +} + +static const struct mtd_ooblayout_ops am_ooblayout = { + .ecc = am_ooblayout_ecc, + .free = am_ooblayout_free, +}; + +static int am_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + switch (status & AM_STATUS_ECC_BITMASK) { + case AM_STATUS_ECC_NONE_DETECTED: + return 0; + + case AM_STATUS_ECC_CORRECTED: + /* + * use oobsize to determine the flash model and the maximum of + * correctable errors and return maximum - 1 by convention + */ + if (spinand->base.mtd.oobsize == 64) + return 3; + else + return 7; + + case AM_STATUS_ECC_ERRORED: + return -EBADMSG; + + case AM_STATUS_ECC_MAX_CORRECTED: + /* + * use oobsize to determine the flash model and the maximum of + * correctable errors + */ + if (spinand->base.mtd.oobsize == 64) + return 4; + else + return 8; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info alliancememory_spinand_table[] = { + SPINAND_INFO("AS5F34G04SND", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2f), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&am_ooblayout, + am_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops alliancememory_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer alliancememory_spinand_manufacturer = { + .id = SPINAND_MFR_ALLIANCEMEMORY, + .name = "AllianceMemory", + .chips = alliancememory_spinand_table, + .nchips = ARRAY_SIZE(alliancememory_spinand_table), + .ops = &alliancememory_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/ato.c b/drivers/mtd/nand/spi/ato.c new file mode 100644 index 0000000000..82b377c068 --- /dev/null +++ b/drivers/mtd/nand/spi/ato.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Aidan MacDonald + * + * Author: Aidan MacDonald <aidanmacdonald.0x0@gmail.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + + +#define SPINAND_MFR_ATO 0x9b + + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + + +static int ato25d1ga_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + return 0; +} + +static int ato25d1ga_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + if (section) { + region->offset = (16 * section); + region->length = 8; + } else { + /* first byte of section 0 is reserved for the BBM */ + region->offset = 1; + region->length = 7; + } + + return 0; +} + +static const struct mtd_ooblayout_ops ato25d1ga_ooblayout = { + .ecc = ato25d1ga_ooblayout_ecc, + .free = ato25d1ga_ooblayout_free, +}; + + +static const struct spinand_info ato_spinand_table[] = { + SPINAND_INFO("ATO25D1GA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x12), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&ato25d1ga_ooblayout, NULL)), +}; + +static const struct spinand_manufacturer_ops ato_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer ato_spinand_manufacturer = { + .id = SPINAND_MFR_ATO, + .name = "ATO", + .chips = ato_spinand_table, + .nchips = ARRAY_SIZE(ato_spinand_table), + .ops = &ato_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c new file mode 100644 index 0000000000..393ff37f0d --- /dev/null +++ b/drivers/mtd/nand/spi/core.c @@ -0,0 +1,1406 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan <peterpandong@micron.com> + * Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#define pr_fmt(fmt) "spi-nand: " fmt + +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mtd/spinand.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi-mem.h> + +static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val) +{ + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg, + spinand->scratchbuf); + int ret; + + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + *val = *spinand->scratchbuf; + return 0; +} + +static int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) +{ + struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg, + spinand->scratchbuf); + + *spinand->scratchbuf = val; + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_read_status(struct spinand_device *spinand, u8 *status) +{ + return spinand_read_reg_op(spinand, REG_STATUS, status); +} + +static int spinand_get_cfg(struct spinand_device *spinand, u8 *cfg) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + if (WARN_ON(spinand->cur_target < 0 || + spinand->cur_target >= nand->memorg.ntargets)) + return -EINVAL; + + *cfg = spinand->cfg_cache[spinand->cur_target]; + return 0; +} + +static int spinand_set_cfg(struct spinand_device *spinand, u8 cfg) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + if (WARN_ON(spinand->cur_target < 0 || + spinand->cur_target >= nand->memorg.ntargets)) + return -EINVAL; + + if (spinand->cfg_cache[spinand->cur_target] == cfg) + return 0; + + ret = spinand_write_reg_op(spinand, REG_CFG, cfg); + if (ret) + return ret; + + spinand->cfg_cache[spinand->cur_target] = cfg; + return 0; +} + +/** + * spinand_upd_cfg() - Update the configuration register + * @spinand: the spinand device + * @mask: the mask encoding the bits to update in the config reg + * @val: the new value to apply + * + * Update the configuration register. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val) +{ + int ret; + u8 cfg; + + ret = spinand_get_cfg(spinand, &cfg); + if (ret) + return ret; + + cfg &= ~mask; + cfg |= val; + + return spinand_set_cfg(spinand, cfg); +} + +/** + * spinand_select_target() - Select a specific NAND target/die + * @spinand: the spinand device + * @target: the target/die to select + * + * Select a new target/die. If chip only has one die, this function is a NOOP. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_select_target(struct spinand_device *spinand, unsigned int target) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + if (WARN_ON(target >= nand->memorg.ntargets)) + return -EINVAL; + + if (spinand->cur_target == target) + return 0; + + if (nand->memorg.ntargets == 1) { + spinand->cur_target = target; + return 0; + } + + ret = spinand->select_target(spinand, target); + if (ret) + return ret; + + spinand->cur_target = target; + return 0; +} + +static int spinand_read_cfg(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int target; + int ret; + + for (target = 0; target < nand->memorg.ntargets; target++) { + ret = spinand_select_target(spinand, target); + if (ret) + return ret; + + /* + * We use spinand_read_reg_op() instead of spinand_get_cfg() + * here to bypass the config cache. + */ + ret = spinand_read_reg_op(spinand, REG_CFG, + &spinand->cfg_cache[target]); + if (ret) + return ret; + } + + return 0; +} + +static int spinand_init_cfg_cache(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct device *dev = &spinand->spimem->spi->dev; + + spinand->cfg_cache = devm_kcalloc(dev, + nand->memorg.ntargets, + sizeof(*spinand->cfg_cache), + GFP_KERNEL); + if (!spinand->cfg_cache) + return -ENOMEM; + + return 0; +} + +static int spinand_init_quad_enable(struct spinand_device *spinand) +{ + bool enable = false; + + if (!(spinand->flags & SPINAND_HAS_QE_BIT)) + return 0; + + if (spinand->op_templates.read_cache->data.buswidth == 4 || + spinand->op_templates.write_cache->data.buswidth == 4 || + spinand->op_templates.update_cache->data.buswidth == 4) + enable = true; + + return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, + enable ? CFG_QUAD_ENABLE : 0); +} + +static int spinand_ecc_enable(struct spinand_device *spinand, + bool enable) +{ + return spinand_upd_cfg(spinand, CFG_ECC_ENABLE, + enable ? CFG_ECC_ENABLE : 0); +} + +static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + if (spinand->eccinfo.get_status) + return spinand->eccinfo.get_status(spinand, status); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * We have no way to know exactly how many bitflips have been + * fixed, so let's return the maximum possible value so that + * wear-leveling layers move the data immediately. + */ + return nanddev_get_ecc_conf(nand)->strength; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = 62; + + return 0; +} + +static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = { + .ecc = spinand_noecc_ooblayout_ecc, + .free = spinand_noecc_ooblayout_free, +}; + +static int spinand_ondie_ecc_init_ctx(struct nand_device *nand) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct spinand_ondie_ecc_conf *engine_conf; + + nand->ecc.ctx.conf.engine_type = NAND_ECC_ENGINE_TYPE_ON_DIE; + nand->ecc.ctx.conf.step_size = nand->ecc.requirements.step_size; + nand->ecc.ctx.conf.strength = nand->ecc.requirements.strength; + + engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL); + if (!engine_conf) + return -ENOMEM; + + nand->ecc.ctx.priv = engine_conf; + + if (spinand->eccinfo.ooblayout) + mtd_set_ooblayout(mtd, spinand->eccinfo.ooblayout); + else + mtd_set_ooblayout(mtd, &spinand_noecc_ooblayout); + + return 0; +} + +static void spinand_ondie_ecc_cleanup_ctx(struct nand_device *nand) +{ + kfree(nand->ecc.ctx.priv); +} + +static int spinand_ondie_ecc_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + bool enable = (req->mode != MTD_OPS_RAW); + + memset(spinand->oobbuf, 0xff, nanddev_per_page_oobsize(nand)); + + /* Only enable or disable the engine */ + return spinand_ecc_enable(spinand, enable); +} + +static int spinand_ondie_ecc_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct spinand_ondie_ecc_conf *engine_conf = nand->ecc.ctx.priv; + struct spinand_device *spinand = nand_to_spinand(nand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + int ret; + + if (req->mode == MTD_OPS_RAW) + return 0; + + /* Nothing to do when finishing a page write */ + if (req->type == NAND_PAGE_WRITE) + return 0; + + /* Finish a page read: check the status, report errors/bitflips */ + ret = spinand_check_ecc_status(spinand, engine_conf->status); + if (ret == -EBADMSG) + mtd->ecc_stats.failed++; + else if (ret > 0) + mtd->ecc_stats.corrected += ret; + + return ret; +} + +static struct nand_ecc_engine_ops spinand_ondie_ecc_engine_ops = { + .init_ctx = spinand_ondie_ecc_init_ctx, + .cleanup_ctx = spinand_ondie_ecc_cleanup_ctx, + .prepare_io_req = spinand_ondie_ecc_prepare_io_req, + .finish_io_req = spinand_ondie_ecc_finish_io_req, +}; + +static struct nand_ecc_engine spinand_ondie_ecc_engine = { + .ops = &spinand_ondie_ecc_engine_ops, +}; + +static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status) +{ + struct spinand_ondie_ecc_conf *engine_conf = nand->ecc.ctx.priv; + + if (nand->ecc.ctx.conf.engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE && + engine_conf) + engine_conf->status = status; +} + +static int spinand_write_enable_op(struct spinand_device *spinand) +{ + struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_load_page_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, &req->pos); + struct spi_mem_op op = SPINAND_PAGE_READ_OP(row); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_read_from_cache_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct spi_mem_dirmap_desc *rdesc; + unsigned int nbytes = 0; + void *buf = NULL; + u16 column = 0; + ssize_t ret; + + if (req->datalen) { + buf = spinand->databuf; + nbytes = nanddev_page_size(nand); + column = 0; + } + + if (req->ooblen) { + nbytes += nanddev_per_page_oobsize(nand); + if (!buf) { + buf = spinand->oobbuf; + column = nanddev_page_size(nand); + } + } + + if (req->mode == MTD_OPS_RAW) + rdesc = spinand->dirmaps[req->pos.plane].rdesc; + else + rdesc = spinand->dirmaps[req->pos.plane].rdesc_ecc; + + while (nbytes) { + ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf); + if (ret < 0) + return ret; + + if (!ret || ret > nbytes) + return -EIO; + + nbytes -= ret; + column += ret; + buf += ret; + } + + if (req->datalen) + memcpy(req->databuf.in, spinand->databuf + req->dataoffs, + req->datalen); + + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_get_databytes(mtd, req->oobbuf.in, + spinand->oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs, + req->ooblen); + } + + return 0; +} + +static int spinand_write_to_cache_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct spi_mem_dirmap_desc *wdesc; + unsigned int nbytes, column = 0; + void *buf = spinand->databuf; + ssize_t ret; + + /* + * Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset + * the cache content to 0xFF (depends on vendor implementation), so we + * must fill the page cache entirely even if we only want to program + * the data portion of the page, otherwise we might corrupt the BBM or + * user data previously programmed in OOB area. + * + * Only reset the data buffer manually, the OOB buffer is prepared by + * ECC engines ->prepare_io_req() callback. + */ + nbytes = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); + memset(spinand->databuf, 0xff, nanddev_page_size(nand)); + + if (req->datalen) + memcpy(spinand->databuf + req->dataoffs, req->databuf.out, + req->datalen); + + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_set_databytes(mtd, req->oobbuf.out, + spinand->oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(spinand->oobbuf + req->ooboffs, req->oobbuf.out, + req->ooblen); + } + + if (req->mode == MTD_OPS_RAW) + wdesc = spinand->dirmaps[req->pos.plane].wdesc; + else + wdesc = spinand->dirmaps[req->pos.plane].wdesc_ecc; + + while (nbytes) { + ret = spi_mem_dirmap_write(wdesc, column, nbytes, buf); + if (ret < 0) + return ret; + + if (!ret || ret > nbytes) + return -EIO; + + nbytes -= ret; + column += ret; + buf += ret; + } + + return 0; +} + +static int spinand_program_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, &req->pos); + struct spi_mem_op op = SPINAND_PROG_EXEC_OP(row); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_erase_op(struct spinand_device *spinand, + const struct nand_pos *pos) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, pos); + struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_wait(struct spinand_device *spinand, + unsigned long initial_delay_us, + unsigned long poll_delay_us, + u8 *s) +{ + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(REG_STATUS, + spinand->scratchbuf); + u8 status; + int ret; + + ret = spi_mem_poll_status(spinand->spimem, &op, STATUS_BUSY, 0, + initial_delay_us, + poll_delay_us, + SPINAND_WAITRDY_TIMEOUT_MS); + if (ret) + return ret; + + status = *spinand->scratchbuf; + if (!(status & STATUS_BUSY)) + goto out; + + /* + * Extra read, just in case the STATUS_READY bit has changed + * since our last check + */ + ret = spinand_read_status(spinand, &status); + if (ret) + return ret; + +out: + if (s) + *s = status; + + return status & STATUS_BUSY ? -ETIMEDOUT : 0; +} + +static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr, + u8 ndummy, u8 *buf) +{ + struct spi_mem_op op = SPINAND_READID_OP( + naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN); + int ret; + + ret = spi_mem_exec_op(spinand->spimem, &op); + if (!ret) + memcpy(buf, spinand->scratchbuf, SPINAND_MAX_ID_LEN); + + return ret; +} + +static int spinand_reset_op(struct spinand_device *spinand) +{ + struct spi_mem_op op = SPINAND_RESET_OP; + int ret; + + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + return spinand_wait(spinand, + SPINAND_RESET_INITIAL_DELAY_US, + SPINAND_RESET_POLL_DELAY_US, + NULL); +} + +static int spinand_lock_block(struct spinand_device *spinand, u8 lock) +{ + return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock); +} + +static int spinand_read_page(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 status; + int ret; + + ret = nand_ecc_prepare_io_req(nand, (struct nand_page_io_req *)req); + if (ret) + return ret; + + ret = spinand_load_page_op(spinand, req); + if (ret) + return ret; + + ret = spinand_wait(spinand, + SPINAND_READ_INITIAL_DELAY_US, + SPINAND_READ_POLL_DELAY_US, + &status); + if (ret < 0) + return ret; + + spinand_ondie_ecc_save_status(nand, status); + + ret = spinand_read_from_cache_op(spinand, req); + if (ret) + return ret; + + return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req); +} + +static int spinand_write_page(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 status; + int ret; + + ret = nand_ecc_prepare_io_req(nand, (struct nand_page_io_req *)req); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_write_to_cache_op(spinand, req); + if (ret) + return ret; + + ret = spinand_program_op(spinand, req); + if (ret) + return ret; + + ret = spinand_wait(spinand, + SPINAND_WRITE_INITIAL_DELAY_US, + SPINAND_WRITE_POLL_DELAY_US, + &status); + if (!ret && (status & STATUS_PROG_FAILED)) + return -EIO; + + return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req); +} + +static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct mtd_ecc_stats old_stats; + unsigned int max_bitflips = 0; + struct nand_io_iter iter; + bool disable_ecc = false; + bool ecc_failed = false; + int ret = 0; + + if (ops->mode == MTD_OPS_RAW || !spinand->eccinfo.ooblayout) + disable_ecc = true; + + mutex_lock(&spinand->lock); + + old_stats = mtd->ecc_stats; + + nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) { + if (disable_ecc) + iter.req.mode = MTD_OPS_RAW; + + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + break; + + ret = spinand_read_page(spinand, &iter.req); + if (ret < 0 && ret != -EBADMSG) + break; + + if (ret == -EBADMSG) + ecc_failed = true; + else + max_bitflips = max_t(unsigned int, max_bitflips, ret); + + ret = 0; + ops->retlen += iter.req.datalen; + ops->oobretlen += iter.req.ooblen; + } + + if (ops->stats) { + ops->stats->uncorrectable_errors += + mtd->ecc_stats.failed - old_stats.failed; + ops->stats->corrected_bitflips += + mtd->ecc_stats.corrected - old_stats.corrected; + } + + mutex_unlock(&spinand->lock); + + if (ecc_failed && !ret) + ret = -EBADMSG; + + return ret ? ret : max_bitflips; +} + +static int spinand_mtd_write(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_io_iter iter; + bool disable_ecc = false; + int ret = 0; + + if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout) + disable_ecc = true; + + mutex_lock(&spinand->lock); + + nanddev_io_for_each_page(nand, NAND_PAGE_WRITE, to, ops, &iter) { + if (disable_ecc) + iter.req.mode = MTD_OPS_RAW; + + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + break; + + ret = spinand_write_page(spinand, &iter.req); + if (ret) + break; + + ops->retlen += iter.req.datalen; + ops->oobretlen += iter.req.ooblen; + } + + mutex_unlock(&spinand->lock); + + return ret; +} + +static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 marker[2] = { }; + struct nand_page_io_req req = { + .pos = *pos, + .ooblen = sizeof(marker), + .ooboffs = 0, + .oobbuf.in = marker, + .mode = MTD_OPS_RAW, + }; + + spinand_select_target(spinand, pos->target); + spinand_read_page(spinand, &req); + if (marker[0] != 0xff || marker[1] != 0xff) + return true; + + return false; +} + +static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); + mutex_lock(&spinand->lock); + ret = nanddev_isbad(nand, &pos); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 marker[2] = { }; + struct nand_page_io_req req = { + .pos = *pos, + .ooboffs = 0, + .ooblen = sizeof(marker), + .oobbuf.out = marker, + .mode = MTD_OPS_RAW, + }; + int ret; + + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + return spinand_write_page(spinand, &req); +} + +static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); + mutex_lock(&spinand->lock); + ret = nanddev_markbad(nand, &pos); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 status; + int ret; + + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_erase_op(spinand, pos); + if (ret) + return ret; + + ret = spinand_wait(spinand, + SPINAND_ERASE_INITIAL_DELAY_US, + SPINAND_ERASE_POLL_DELAY_US, + &status); + + if (!ret && (status & STATUS_ERASE_FAILED)) + ret = -EIO; + + return ret; +} + +static int spinand_mtd_erase(struct mtd_info *mtd, + struct erase_info *einfo) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + mutex_lock(&spinand->lock); + ret = nanddev_mtd_erase(mtd, einfo); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); + mutex_lock(&spinand->lock); + ret = nanddev_isreserved(nand, &pos); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_create_dirmap(struct spinand_device *spinand, + unsigned int plane) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct spi_mem_dirmap_info info = { + .length = nanddev_page_size(nand) + + nanddev_per_page_oobsize(nand), + }; + struct spi_mem_dirmap_desc *desc; + + /* The plane number is passed in MSB just above the column address */ + info.offset = plane << fls(nand->memorg.pagesize); + + info.op_tmpl = *spinand->op_templates.update_cache; + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, &info); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + spinand->dirmaps[plane].wdesc = desc; + + info.op_tmpl = *spinand->op_templates.read_cache; + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, &info); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + spinand->dirmaps[plane].rdesc = desc; + + if (nand->ecc.engine->integration != NAND_ECC_ENGINE_INTEGRATION_PIPELINED) { + spinand->dirmaps[plane].wdesc_ecc = spinand->dirmaps[plane].wdesc; + spinand->dirmaps[plane].rdesc_ecc = spinand->dirmaps[plane].rdesc; + + return 0; + } + + info.op_tmpl = *spinand->op_templates.update_cache; + info.op_tmpl.data.ecc = true; + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, &info); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + spinand->dirmaps[plane].wdesc_ecc = desc; + + info.op_tmpl = *spinand->op_templates.read_cache; + info.op_tmpl.data.ecc = true; + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, &info); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + spinand->dirmaps[plane].rdesc_ecc = desc; + + return 0; +} + +static int spinand_create_dirmaps(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int i, ret; + + spinand->dirmaps = devm_kzalloc(&spinand->spimem->spi->dev, + sizeof(*spinand->dirmaps) * + nand->memorg.planes_per_lun, + GFP_KERNEL); + if (!spinand->dirmaps) + return -ENOMEM; + + for (i = 0; i < nand->memorg.planes_per_lun; i++) { + ret = spinand_create_dirmap(spinand, i); + if (ret) + return ret; + } + + return 0; +} + +static const struct nand_ops spinand_ops = { + .erase = spinand_erase, + .markbad = spinand_markbad, + .isbad = spinand_isbad, +}; + +static const struct spinand_manufacturer *spinand_manufacturers[] = { + &alliancememory_spinand_manufacturer, + &ato_spinand_manufacturer, + &esmt_c8_spinand_manufacturer, + &gigadevice_spinand_manufacturer, + ¯onix_spinand_manufacturer, + µn_spinand_manufacturer, + ¶gon_spinand_manufacturer, + &toshiba_spinand_manufacturer, + &winbond_spinand_manufacturer, + &xtx_spinand_manufacturer, +}; + +static int spinand_manufacturer_match(struct spinand_device *spinand, + enum spinand_readid_method rdid_method) +{ + u8 *id = spinand->id.data; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) { + const struct spinand_manufacturer *manufacturer = + spinand_manufacturers[i]; + + if (id[0] != manufacturer->id) + continue; + + ret = spinand_match_and_init(spinand, + manufacturer->chips, + manufacturer->nchips, + rdid_method); + if (ret < 0) + continue; + + spinand->manufacturer = manufacturer; + return 0; + } + return -ENOTSUPP; +} + +static int spinand_id_detect(struct spinand_device *spinand) +{ + u8 *id = spinand->id.data; + int ret; + + ret = spinand_read_id_op(spinand, 0, 0, id); + if (ret) + return ret; + ret = spinand_manufacturer_match(spinand, SPINAND_READID_METHOD_OPCODE); + if (!ret) + return 0; + + ret = spinand_read_id_op(spinand, 1, 0, id); + if (ret) + return ret; + ret = spinand_manufacturer_match(spinand, + SPINAND_READID_METHOD_OPCODE_ADDR); + if (!ret) + return 0; + + ret = spinand_read_id_op(spinand, 0, 1, id); + if (ret) + return ret; + ret = spinand_manufacturer_match(spinand, + SPINAND_READID_METHOD_OPCODE_DUMMY); + + return ret; +} + +static int spinand_manufacturer_init(struct spinand_device *spinand) +{ + if (spinand->manufacturer->ops->init) + return spinand->manufacturer->ops->init(spinand); + + return 0; +} + +static void spinand_manufacturer_cleanup(struct spinand_device *spinand) +{ + /* Release manufacturer private data */ + if (spinand->manufacturer->ops->cleanup) + return spinand->manufacturer->ops->cleanup(spinand); +} + +static const struct spi_mem_op * +spinand_select_op_variant(struct spinand_device *spinand, + const struct spinand_op_variants *variants) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + for (i = 0; i < variants->nops; i++) { + struct spi_mem_op op = variants->ops[i]; + unsigned int nbytes; + int ret; + + nbytes = nanddev_per_page_oobsize(nand) + + nanddev_page_size(nand); + + while (nbytes) { + op.data.nbytes = nbytes; + ret = spi_mem_adjust_op_size(spinand->spimem, &op); + if (ret) + break; + + if (!spi_mem_supports_op(spinand->spimem, &op)) + break; + + nbytes -= op.data.nbytes; + } + + if (!nbytes) + return &variants->ops[i]; + } + + return NULL; +} + +/** + * spinand_match_and_init() - Try to find a match between a device ID and an + * entry in a spinand_info table + * @spinand: SPI NAND object + * @table: SPI NAND device description table + * @table_size: size of the device description table + * @rdid_method: read id method to match + * + * Match between a device ID retrieved through the READ_ID command and an + * entry in the SPI NAND description table. If a match is found, the spinand + * object will be initialized with information provided by the matching + * spinand_info entry. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_match_and_init(struct spinand_device *spinand, + const struct spinand_info *table, + unsigned int table_size, + enum spinand_readid_method rdid_method) +{ + u8 *id = spinand->id.data; + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + for (i = 0; i < table_size; i++) { + const struct spinand_info *info = &table[i]; + const struct spi_mem_op *op; + + if (rdid_method != info->devid.method) + continue; + + if (memcmp(id + 1, info->devid.id, info->devid.len)) + continue; + + nand->memorg = table[i].memorg; + nanddev_set_ecc_requirements(nand, &table[i].eccreq); + spinand->eccinfo = table[i].eccinfo; + spinand->flags = table[i].flags; + spinand->id.len = 1 + table[i].devid.len; + spinand->select_target = table[i].select_target; + + op = spinand_select_op_variant(spinand, + info->op_variants.read_cache); + if (!op) + return -ENOTSUPP; + + spinand->op_templates.read_cache = op; + + op = spinand_select_op_variant(spinand, + info->op_variants.write_cache); + if (!op) + return -ENOTSUPP; + + spinand->op_templates.write_cache = op; + + op = spinand_select_op_variant(spinand, + info->op_variants.update_cache); + spinand->op_templates.update_cache = op; + + return 0; + } + + return -ENOTSUPP; +} + +static int spinand_detect(struct spinand_device *spinand) +{ + struct device *dev = &spinand->spimem->spi->dev; + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + ret = spinand_reset_op(spinand); + if (ret) + return ret; + + ret = spinand_id_detect(spinand); + if (ret) { + dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN, + spinand->id.data); + return ret; + } + + if (nand->memorg.ntargets > 1 && !spinand->select_target) { + dev_err(dev, + "SPI NANDs with more than one die must implement ->select_target()\n"); + return -EINVAL; + } + + dev_info(&spinand->spimem->spi->dev, + "%s SPI NAND was found.\n", spinand->manufacturer->name); + dev_info(&spinand->spimem->spi->dev, + "%llu MiB, block size: %zu KiB, page size: %zu, OOB size: %u\n", + nanddev_size(nand) >> 20, nanddev_eraseblock_size(nand) >> 10, + nanddev_page_size(nand), nanddev_per_page_oobsize(nand)); + + return 0; +} + +static int spinand_init_flash(struct spinand_device *spinand) +{ + struct device *dev = &spinand->spimem->spi->dev; + struct nand_device *nand = spinand_to_nand(spinand); + int ret, i; + + ret = spinand_read_cfg(spinand); + if (ret) + return ret; + + ret = spinand_init_quad_enable(spinand); + if (ret) + return ret; + + ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0); + if (ret) + return ret; + + ret = spinand_manufacturer_init(spinand); + if (ret) { + dev_err(dev, + "Failed to initialize the SPI NAND chip (err = %d)\n", + ret); + return ret; + } + + /* After power up, all blocks are locked, so unlock them here. */ + for (i = 0; i < nand->memorg.ntargets; i++) { + ret = spinand_select_target(spinand, i); + if (ret) + break; + + ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED); + if (ret) + break; + } + + if (ret) + spinand_manufacturer_cleanup(spinand); + + return ret; +} + +static void spinand_mtd_resume(struct mtd_info *mtd) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + ret = spinand_reset_op(spinand); + if (ret) + return; + + ret = spinand_init_flash(spinand); + if (ret) + return; + + spinand_ecc_enable(spinand, false); +} + +static int spinand_init(struct spinand_device *spinand) +{ + struct device *dev = &spinand->spimem->spi->dev; + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct nand_device *nand = mtd_to_nanddev(mtd); + int ret; + + /* + * We need a scratch buffer because the spi_mem interface requires that + * buf passed in spi_mem_op->data.buf be DMA-able. + */ + spinand->scratchbuf = kzalloc(SPINAND_MAX_ID_LEN, GFP_KERNEL); + if (!spinand->scratchbuf) + return -ENOMEM; + + ret = spinand_detect(spinand); + if (ret) + goto err_free_bufs; + + /* + * Use kzalloc() instead of devm_kzalloc() here, because some drivers + * may use this buffer for DMA access. + * Memory allocated by devm_ does not guarantee DMA-safe alignment. + */ + spinand->databuf = kzalloc(nanddev_page_size(nand) + + nanddev_per_page_oobsize(nand), + GFP_KERNEL); + if (!spinand->databuf) { + ret = -ENOMEM; + goto err_free_bufs; + } + + spinand->oobbuf = spinand->databuf + nanddev_page_size(nand); + + ret = spinand_init_cfg_cache(spinand); + if (ret) + goto err_free_bufs; + + ret = spinand_init_flash(spinand); + if (ret) + goto err_free_bufs; + + ret = nanddev_init(nand, &spinand_ops, THIS_MODULE); + if (ret) + goto err_manuf_cleanup; + + /* SPI-NAND default ECC engine is on-die */ + nand->ecc.defaults.engine_type = NAND_ECC_ENGINE_TYPE_ON_DIE; + nand->ecc.ondie_engine = &spinand_ondie_ecc_engine; + + spinand_ecc_enable(spinand, false); + ret = nanddev_ecc_engine_init(nand); + if (ret) + goto err_cleanup_nanddev; + + mtd->_read_oob = spinand_mtd_read; + mtd->_write_oob = spinand_mtd_write; + mtd->_block_isbad = spinand_mtd_block_isbad; + mtd->_block_markbad = spinand_mtd_block_markbad; + mtd->_block_isreserved = spinand_mtd_block_isreserved; + mtd->_erase = spinand_mtd_erase; + mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks; + mtd->_resume = spinand_mtd_resume; + + if (nand->ecc.engine) { + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + goto err_cleanup_ecc_engine; + } + + mtd->oobavail = ret; + + /* Propagate ECC information to mtd_info */ + mtd->ecc_strength = nanddev_get_ecc_conf(nand)->strength; + mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size; + + ret = spinand_create_dirmaps(spinand); + if (ret) { + dev_err(dev, + "Failed to create direct mappings for read/write operations (err = %d)\n", + ret); + goto err_cleanup_ecc_engine; + } + + return 0; + +err_cleanup_ecc_engine: + nanddev_ecc_engine_cleanup(nand); + +err_cleanup_nanddev: + nanddev_cleanup(nand); + +err_manuf_cleanup: + spinand_manufacturer_cleanup(spinand); + +err_free_bufs: + kfree(spinand->databuf); + kfree(spinand->scratchbuf); + return ret; +} + +static void spinand_cleanup(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + nanddev_cleanup(nand); + spinand_manufacturer_cleanup(spinand); + kfree(spinand->databuf); + kfree(spinand->scratchbuf); +} + +static int spinand_probe(struct spi_mem *mem) +{ + struct spinand_device *spinand; + struct mtd_info *mtd; + int ret; + + spinand = devm_kzalloc(&mem->spi->dev, sizeof(*spinand), + GFP_KERNEL); + if (!spinand) + return -ENOMEM; + + spinand->spimem = mem; + spi_mem_set_drvdata(mem, spinand); + spinand_set_of_node(spinand, mem->spi->dev.of_node); + mutex_init(&spinand->lock); + mtd = spinand_to_mtd(spinand); + mtd->dev.parent = &mem->spi->dev; + + ret = spinand_init(spinand); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + goto err_spinand_cleanup; + + return 0; + +err_spinand_cleanup: + spinand_cleanup(spinand); + + return ret; +} + +static int spinand_remove(struct spi_mem *mem) +{ + struct spinand_device *spinand; + struct mtd_info *mtd; + int ret; + + spinand = spi_mem_get_drvdata(mem); + mtd = spinand_to_mtd(spinand); + + ret = mtd_device_unregister(mtd); + if (ret) + return ret; + + spinand_cleanup(spinand); + + return 0; +} + +static const struct spi_device_id spinand_ids[] = { + { .name = "spi-nand" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(spi, spinand_ids); + +#ifdef CONFIG_OF +static const struct of_device_id spinand_of_ids[] = { + { .compatible = "spi-nand" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, spinand_of_ids); +#endif + +static struct spi_mem_driver spinand_drv = { + .spidrv = { + .id_table = spinand_ids, + .driver = { + .name = "spi-nand", + .of_match_table = of_match_ptr(spinand_of_ids), + }, + }, + .probe = spinand_probe, + .remove = spinand_remove, +}; +module_spi_mem_driver(spinand_drv); + +MODULE_DESCRIPTION("SPI NAND framework"); +MODULE_AUTHOR("Peter Pan<peterpandong@micron.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/spi/esmt.c b/drivers/mtd/nand/spi/esmt.c new file mode 100644 index 0000000000..31c439a557 --- /dev/null +++ b/drivers/mtd/nand/spi/esmt.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Chuanhong Guo <gch981213@gmail.com> - the main driver logic + * Martin Kurbanov <mmkurbanov@sberdevices.ru> - OOB layout + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +/* ESMT uses GigaDevice 0xc8 JECDEC ID on some SPI NANDs */ +#define SPINAND_MFR_ESMT_C8 0xc8 + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +/* + * OOB spare area map (64 bytes) + * + * Bad Block Markers + * filled by HW and kernel Reserved + * | +-----------------------+-----------------------+ + * | | | | + * | | OOB free data Area |non ECC protected | + * | +-------------|-----+-----------------|-----+-----------------|-----+ + * | | | | | | | | + * +-|---|----------+--|-----|--------------+--|-----|--------------+--|-----|--------------+ + * | | | section0 | | | section1 | | | section2 | | | section3 | + * +-v-+-v-+---+----+--v--+--v--+-----+-----+--v--+--v--+-----+-----+--v--+--v--+-----+-----+ + * | | | | | | | | | | | | | | | | | + * |0:1|2:3|4:7|8:15|16:17|18:19|20:23|24:31|32:33|34:35|36:39|40:47|48:49|50:51|52:55|56:63| + * | | | | | | | | | | | | | | | | | + * +---+---+-^-+--^-+-----+-----+--^--+--^--+-----+-----+--^--+--^--+-----+-----+--^--+--^--+ + * | | | | | | | | + * | +----------------|-----+-----------------|-----+-----------------|-----+ + * | ECC Area|(Main + Spare) - filled|by ESMT NAND HW | + * | | | | + * +---------------------+-----------------------+-----------------------+ + * OOB ECC protected Area - not used due to + * partial programming from some filesystems + * (like JFFS2 with cleanmarkers) + */ + +#define ESMT_OOB_SECTION_COUNT 4 +#define ESMT_OOB_SECTION_SIZE(nand) \ + (nanddev_per_page_oobsize(nand) / ESMT_OOB_SECTION_COUNT) +#define ESMT_OOB_FREE_SIZE(nand) \ + (ESMT_OOB_SECTION_SIZE(nand) / 2) +#define ESMT_OOB_ECC_SIZE(nand) \ + (ESMT_OOB_SECTION_SIZE(nand) - ESMT_OOB_FREE_SIZE(nand)) +#define ESMT_OOB_BBM_SIZE 2 + +static int f50l1g41lb_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + + if (section >= ESMT_OOB_SECTION_COUNT) + return -ERANGE; + + region->offset = section * ESMT_OOB_SECTION_SIZE(nand) + + ESMT_OOB_FREE_SIZE(nand); + region->length = ESMT_OOB_ECC_SIZE(nand); + + return 0; +} + +static int f50l1g41lb_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + + if (section >= ESMT_OOB_SECTION_COUNT) + return -ERANGE; + + /* + * Reserve space for bad blocks markers (section0) and + * reserved bytes (sections 1-3) + */ + region->offset = section * ESMT_OOB_SECTION_SIZE(nand) + 2; + + /* Use only 2 non-protected ECC bytes per each OOB section */ + region->length = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops f50l1g41lb_ooblayout = { + .ecc = f50l1g41lb_ooblayout_ecc, + .free = f50l1g41lb_ooblayout_free, +}; + +static const struct spinand_info esmt_c8_spinand_table[] = { + SPINAND_INFO("F50L1G41LB", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)), + SPINAND_INFO("F50D1G41LB", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x11), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)), + SPINAND_INFO("F50D2G41KA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)), +}; + +static const struct spinand_manufacturer_ops esmt_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer esmt_c8_spinand_manufacturer = { + .id = SPINAND_MFR_ESMT_C8, + .name = "ESMT", + .chips = esmt_c8_spinand_table, + .nchips = ARRAY_SIZE(esmt_c8_spinand_table), + .ops = &esmt_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/gigadevice.c b/drivers/mtd/nand/spi/gigadevice.c new file mode 100644 index 0000000000..987710e094 --- /dev/null +++ b/drivers/mtd/nand/spi/gigadevice.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Chuanhong Guo <gch981213@gmail.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_GIGADEVICE 0xC8 + +#define GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS (1 << 4) +#define GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS (3 << 4) + +#define GD5FXGQ5XE_STATUS_ECC_1_4_BITFLIPS (1 << 4) +#define GD5FXGQ5XE_STATUS_ECC_4_BITFLIPS (3 << 4) + +#define GD5FXGQXXEXXG_REG_STATUS2 0xf0 + +#define GD5FXGQ4UXFXXG_STATUS_ECC_MASK (7 << 4) +#define GD5FXGQ4UXFXXG_STATUS_ECC_NO_BITFLIPS (0 << 4) +#define GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS (1 << 4) +#define GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR (7 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(read_cache_variants_f, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP_3A(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP_3A(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP_3A(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP_3A(false, 0, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(read_cache_variants_1gq5, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(read_cache_variants_2gq5, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int gd5fxgq4xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + + return 0; +} + +static int gd5fxgq4xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + if (section) { + region->offset = 16 * section; + region->length = 8; + } else { + /* section 0 has one byte reserved for bad block mark */ + region->offset = 1; + region->length = 7; + } + return 0; +} + +static const struct mtd_ooblayout_ops gd5fxgq4xa_ooblayout = { + .ecc = gd5fxgq4xa_ooblayout_ecc, + .free = gd5fxgq4xa_ooblayout_free, +}; + +static int gd5fxgq4xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS: + /* 1-7 bits are flipped. return the maximum. */ + return 7; + + case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS: + return 8; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int gd5fxgqx_variant2_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 64; + region->length = 64; + + return 0; +} + +static int gd5fxgqx_variant2_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 1 bytes for the BBM. */ + region->offset = 1; + region->length = 63; + + return 0; +} + +/* Valid for Q4/Q5 and Q6 (untested) devices */ +static const struct mtd_ooblayout_ops gd5fxgqx_variant2_ooblayout = { + .ecc = gd5fxgqx_variant2_ooblayout_ecc, + .free = gd5fxgqx_variant2_ooblayout_free, +}; + +static int gd5fxgq4xc_ooblayout_256_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 128; + oobregion->length = 128; + + return 0; +} + +static int gd5fxgq4xc_ooblayout_256_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 1; + oobregion->length = 127; + + return 0; +} + +static const struct mtd_ooblayout_ops gd5fxgq4xc_oob_256_ops = { + .ecc = gd5fxgq4xc_ooblayout_256_ecc, + .free = gd5fxgq4xc_ooblayout_256_free, +}; + +static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + u8 status2; + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(GD5FXGQXXEXXG_REG_STATUS2, + &status2); + int ret; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS: + /* + * Read status2 register to determine a more fine grained + * bit error status + */ + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + /* + * 4 ... 7 bits are flipped (1..4 can't be detected, so + * report the maximum of 4 in this case + */ + /* bits sorted this way (3...0): ECCS1,ECCS0,ECCSE1,ECCSE0 */ + return ((status & STATUS_ECC_MASK) >> 2) | + ((status2 & STATUS_ECC_MASK) >> 4); + + case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS: + return 8; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int gd5fxgq5xexxg_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + u8 status2; + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(GD5FXGQXXEXXG_REG_STATUS2, + &status2); + int ret; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ5XE_STATUS_ECC_1_4_BITFLIPS: + /* + * Read status2 register to determine a more fine grained + * bit error status + */ + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + /* + * 1 ... 4 bits are flipped (and corrected) + */ + /* bits sorted this way (1...0): ECCSE1, ECCSE0 */ + return ((status2 & STATUS_ECC_MASK) >> 4) + 1; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int gd5fxgq4ufxxg_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & GD5FXGQ4UXFXXG_STATUS_ECC_MASK) { + case GD5FXGQ4UXFXXG_STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS: + return 3; + + case GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: /* (2 << 4) through (6 << 4) are 4-8 corrected errors */ + return ((status & GD5FXGQ4UXFXXG_STATUS_ECC_MASK) >> 4) + 2; + } + + return -EINVAL; +} + +static const struct spinand_info gigadevice_spinand_table[] = { + SPINAND_INFO("GD5F1GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf1), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout, + gd5fxgq4xa_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout, + gd5fxgq4xa_ecc_get_status)), + SPINAND_INFO("GD5F4GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf4), + NAND_MEMORG(1, 2048, 64, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout, + gd5fxgq4xa_ecc_get_status)), + SPINAND_INFO("GD5F4GQ4RC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xa4, 0x68), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xc_oob_256_ops, + gd5fxgq4ufxxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ4UC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb4, 0x68), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xc_oob_256_ops, + gd5fxgq4ufxxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4UFxxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4ufxxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x51), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x41), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x42), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ6UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x55), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ6RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x45), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GM7UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GM7RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GM7UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GM7RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x82), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GM8UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x95), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GM8RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x85), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5xExxH", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5RExxH", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x21), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4RExxH", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xc9), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer gigadevice_spinand_manufacturer = { + .id = SPINAND_MFR_GIGADEVICE, + .name = "GigaDevice", + .chips = gigadevice_spinand_table, + .nchips = ARRAY_SIZE(gigadevice_spinand_table), + .ops = &gigadevice_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c new file mode 100644 index 0000000000..3dfc7e1e52 --- /dev/null +++ b/drivers/mtd/nand/spi/macronix.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Macronix + * + * Author: Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_MACRONIX 0xC2 +#define MACRONIX_ECCSR_MASK 0x0F + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int mx35lfxge4ab_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int mx35lfxge4ab_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 2; + region->length = mtd->oobsize - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = { + .ecc = mx35lfxge4ab_ooblayout_ecc, + .free = mx35lfxge4ab_ooblayout_free, +}; + +static int mx35lf1ge4ab_get_eccsr(struct spinand_device *spinand, u8 *eccsr) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_DUMMY(1, 1), + SPI_MEM_OP_DATA_IN(1, eccsr, 1)); + + int ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + *eccsr &= MACRONIX_ECCSR_MASK; + return 0; +} + +static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 eccsr; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (mx35lf1ge4ab_get_eccsr(spinand, spinand->scratchbuf)) + return nanddev_get_ecc_conf(nand)->strength; + + eccsr = *spinand->scratchbuf; + if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || + !eccsr)) + return nanddev_get_ecc_conf(nand)->strength; + + return eccsr; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info macronix_spinand_table[] = { + SPINAND_INFO("MX35LF1GE4AB", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35LF2GE4AB", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_INFO("MX35LF2GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x26), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35LF4GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37), + NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35LF1G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_INFO("MX35LF2G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_INFO("MX35LF4G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_INFO("MX31LF1GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x1e), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX31UF1GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x9e), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + + SPINAND_INFO("MX35LF2G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x20), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF4G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb5), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF4GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa0), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa4), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF2GE4AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x90), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x94), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX35UF1GE4AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + + SPINAND_INFO("MX31LF2GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2e), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), + SPINAND_INFO("MX3UF2GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + mx35lf1ge4ab_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer macronix_spinand_manufacturer = { + .id = SPINAND_MFR_MACRONIX, + .name = "Macronix", + .chips = macronix_spinand_table, + .nchips = ARRAY_SIZE(macronix_spinand_table), + .ops = ¯onix_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c new file mode 100644 index 0000000000..12601bc422 --- /dev/null +++ b/drivers/mtd/nand/spi/micron.c @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan <peterpandong@micron.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_MICRON 0x2c + +#define MICRON_STATUS_ECC_MASK GENMASK(6, 4) +#define MICRON_STATUS_ECC_NO_BITFLIPS (0 << 4) +#define MICRON_STATUS_ECC_1TO3_BITFLIPS (1 << 4) +#define MICRON_STATUS_ECC_4TO6_BITFLIPS (3 << 4) +#define MICRON_STATUS_ECC_7TO8_BITFLIPS (5 << 4) + +#define MICRON_CFG_CR BIT(0) + +/* + * As per datasheet, die selection is done by the 6th bit of Die + * Select Register (Address 0xD0). + */ +#define MICRON_DIE_SELECT_REG 0xD0 + +#define MICRON_SELECT_DIE(x) ((x) << 6) + +static SPINAND_OP_VARIANTS(quadio_read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(x4_write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(x4_update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +/* Micron MT29F2G01AAAED Device */ +static SPINAND_OP_VARIANTS(x4_read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(x1_write_cache_variants, + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(x1_update_cache_variants, + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int micron_8_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int micron_8_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = (mtd->oobsize / 2) - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops micron_8_ooblayout = { + .ecc = micron_8_ooblayout_ecc, + .free = micron_8_ooblayout_free, +}; + +static int micron_4_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + + if (section >= spinand->base.memorg.pagesize / + mtd->ecc_step_size) + return -ERANGE; + + region->offset = (section * 16) + 8; + region->length = 8; + + return 0; +} + +static int micron_4_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + + if (section >= spinand->base.memorg.pagesize / + mtd->ecc_step_size) + return -ERANGE; + + if (section) { + region->offset = 16 * section; + region->length = 8; + } else { + /* section 0 has two bytes reserved for the BBM */ + region->offset = 2; + region->length = 6; + } + + return 0; +} + +static const struct mtd_ooblayout_ops micron_4_ooblayout = { + .ecc = micron_4_ooblayout_ecc, + .free = micron_4_ooblayout_free, +}; + +static int micron_select_target(struct spinand_device *spinand, + unsigned int target) +{ + struct spi_mem_op op = SPINAND_SET_FEATURE_OP(MICRON_DIE_SELECT_REG, + spinand->scratchbuf); + + if (target > 1) + return -EINVAL; + + *spinand->scratchbuf = MICRON_SELECT_DIE(target); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int micron_8_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & MICRON_STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case MICRON_STATUS_ECC_1TO3_BITFLIPS: + return 3; + + case MICRON_STATUS_ECC_4TO6_BITFLIPS: + return 6; + + case MICRON_STATUS_ECC_7TO8_BITFLIPS: + return 8; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info micron_spinand_table[] = { + /* M79A 2Gb 3.3V */ + SPINAND_INFO("MT29F2G01ABAGD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M79A 2Gb 1.8V */ + SPINAND_INFO("MT29F2G01ABBGD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M78A 1Gb 3.3V */ + SPINAND_INFO("MT29F1G01ABAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M78A 1Gb 1.8V */ + SPINAND_INFO("MT29F1G01ABAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M79A 4Gb 3.3V */ + SPINAND_INFO("MT29F4G01ADAGD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x36), + NAND_MEMORG(1, 2048, 128, 64, 2048, 80, 2, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status), + SPINAND_SELECT_TARGET(micron_select_target)), + /* M70A 4Gb 3.3V */ + SPINAND_INFO("MT29F4G01ABAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x34), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M70A 4Gb 1.8V */ + SPINAND_INFO("MT29F4G01ABBFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M70A 8Gb 3.3V */ + SPINAND_INFO("MT29F8G01ADAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x46), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status), + SPINAND_SELECT_TARGET(micron_select_target)), + /* M70A 8Gb 1.8V */ + SPINAND_INFO("MT29F8G01ADBFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x47), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status), + SPINAND_SELECT_TARGET(micron_select_target)), + /* M69A 2Gb 3.3V */ + SPINAND_INFO("MT29F2G01AAAED", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x9F), + NAND_MEMORG(1, 2048, 64, 64, 2048, 80, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&x4_read_cache_variants, + &x1_write_cache_variants, + &x1_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_4_ooblayout, NULL)), +}; + +static int micron_spinand_init(struct spinand_device *spinand) +{ + /* + * M70A device series enable Continuous Read feature at Power-up, + * which is not supported. Disable this bit to avoid any possible + * failure. + */ + if (spinand->flags & SPINAND_HAS_CR_FEAT_BIT) + return spinand_upd_cfg(spinand, MICRON_CFG_CR, 0); + + return 0; +} + +static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = { + .init = micron_spinand_init, +}; + +const struct spinand_manufacturer micron_spinand_manufacturer = { + .id = SPINAND_MFR_MICRON, + .name = "Micron", + .chips = micron_spinand_table, + .nchips = ARRAY_SIZE(micron_spinand_table), + .ops = µn_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/paragon.c b/drivers/mtd/nand/spi/paragon.c new file mode 100644 index 0000000000..519ade513c --- /dev/null +++ b/drivers/mtd/nand/spi/paragon.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Jeff Kletsky + * + * Author: Jeff Kletsky <git-commits@allycomm.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + + +#define SPINAND_MFR_PARAGON 0xa1 + + +#define PN26G0XA_STATUS_ECC_BITMASK (3 << 4) + +#define PN26G0XA_STATUS_ECC_NONE_DETECTED (0 << 4) +#define PN26G0XA_STATUS_ECC_1_7_CORRECTED (1 << 4) +#define PN26G0XA_STATUS_ECC_ERRORED (2 << 4) +#define PN26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) + + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + + +static int pn26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = 6 + (15 * section); /* 4 BBM + 2 user bytes */ + region->length = 13; + + return 0; +} + +static int pn26g0xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 4) + return -ERANGE; + + if (section == 4) { + region->offset = 64; + region->length = 64; + } else { + region->offset = 4 + (15 * section); + region->length = 2; + } + + return 0; +} + +static int pn26g0xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & PN26G0XA_STATUS_ECC_BITMASK) { + case PN26G0XA_STATUS_ECC_NONE_DETECTED: + return 0; + + case PN26G0XA_STATUS_ECC_1_7_CORRECTED: + return 7; /* Return upper limit by convention */ + + case PN26G0XA_STATUS_ECC_8_CORRECTED: + return 8; + + case PN26G0XA_STATUS_ECC_ERRORED: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static const struct mtd_ooblayout_ops pn26g0xa_ooblayout = { + .ecc = pn26g0xa_ooblayout_ecc, + .free = pn26g0xa_ooblayout_free, +}; + + +static const struct spinand_info paragon_spinand_table[] = { + SPINAND_INFO("PN26G01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&pn26g0xa_ooblayout, + pn26g0xa_ecc_get_status)), + SPINAND_INFO("PN26G02A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&pn26g0xa_ooblayout, + pn26g0xa_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer paragon_spinand_manufacturer = { + .id = SPINAND_MFR_PARAGON, + .name = "Paragon", + .chips = paragon_spinand_table, + .nchips = ARRAY_SIZE(paragon_spinand_table), + .ops = ¶gon_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/toshiba.c b/drivers/mtd/nand/spi/toshiba.c new file mode 100644 index 0000000000..bbbcaa87c0 --- /dev/null +++ b/drivers/mtd/nand/spi/toshiba.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 exceet electronics GmbH + * Copyright (c) 2018 Kontron Electronics GmbH + * + * Author: Frieder Schrempf <frieder.schrempf@kontron.de> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +/* Kioxia is new name of Toshiba memory. */ +#define SPINAND_MFR_TOSHIBA 0x98 +#define TOSH_STATUS_ECC_HAS_BITFLIPS_T (3 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_x4_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_x4_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +/* + * Backward compatibility for 1st generation Serial NAND devices + * which don't support Quad Program Load operation. + */ +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int tx58cxgxsxraix_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 0) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int tx58cxgxsxraix_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 0) + return -ERANGE; + + /* 2 bytes reserved for BBM */ + region->offset = 2; + region->length = (mtd->oobsize / 2) - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops tx58cxgxsxraix_ooblayout = { + .ecc = tx58cxgxsxraix_ooblayout_ecc, + .free = tx58cxgxsxraix_ooblayout_free, +}; + +static int tx58cxgxsxraix_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 mbf = 0; + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + case TOSH_STATUS_ECC_HAS_BITFLIPS_T: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (spi_mem_exec_op(spinand->spimem, &op)) + return nanddev_get_ecc_conf(nand)->strength; + + mbf = *(spinand->scratchbuf) >> 4; + + if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf)) + return nanddev_get_ecc_conf(nand)->strength; + + return mbf; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info toshiba_spinand_table[] = { + /* 3.3V 1Gb (1st generation) */ + SPINAND_INFO("TC58CVG0S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xC2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 2Gb (1st generation) */ + SPINAND_INFO("TC58CVG1S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 4Gb (1st generation) */ + SPINAND_INFO("TC58CVG2S0HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCD), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 1Gb (1st generation) */ + SPINAND_INFO("TC58CYG0S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xB2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 2Gb (1st generation) */ + SPINAND_INFO("TC58CYG1S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 4Gb (1st generation) */ + SPINAND_INFO("TC58CYG2S0HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBD), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + + /* + * 2nd generation serial nand has HOLD_D which is equivalent to + * QE_BIT. + */ + /* 3.3V 1Gb (2nd generation) */ + SPINAND_INFO("TC58CVG0S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 2Gb (2nd generation) */ + SPINAND_INFO("TC58CVG1S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xEB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 4Gb (2nd generation) */ + SPINAND_INFO("TC58CVG2S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xED), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 8Gb (2nd generation) */ + SPINAND_INFO("TH58CVG3S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4), + NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 1Gb (2nd generation) */ + SPINAND_INFO("TC58CYG0S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 2Gb (2nd generation) */ + SPINAND_INFO("TC58CYG1S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xDB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 4Gb (2nd generation) */ + SPINAND_INFO("TC58CYG2S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xDD), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 8Gb (2nd generation) */ + SPINAND_INFO("TH58CYG3S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD4), + NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 1Gb (1st generation) */ + SPINAND_INFO("TC58NYG0S3HBAI4", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 4Gb (1st generation) */ + SPINAND_INFO("TH58NYG2S3HBAI4", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAC), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 2, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 8Gb (1st generation) */ + SPINAND_INFO("TH58NYG3S0HBAI6", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA3), + NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops toshiba_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer toshiba_spinand_manufacturer = { + .id = SPINAND_MFR_TOSHIBA, + .name = "Toshiba", + .chips = toshiba_spinand_table, + .nchips = ARRAY_SIZE(toshiba_spinand_table), + .ops = &toshiba_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c new file mode 100644 index 0000000000..f507e37593 --- /dev/null +++ b/drivers/mtd/nand/spi/winbond.c @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 exceet electronics GmbH + * + * Authors: + * Frieder Schrempf <frieder.schrempf@exceet.de> + * Boris Brezillon <boris.brezillon@bootlin.com> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_WINBOND 0xEF + +#define WINBOND_CFG_BUF_READ BIT(3) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + + return 0; +} + +static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 2; + region->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops w25m02gv_ooblayout = { + .ecc = w25m02gv_ooblayout_ecc, + .free = w25m02gv_ooblayout_free, +}; + +static int w25m02gv_select_target(struct spinand_device *spinand, + unsigned int target) +{ + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(1, + spinand->scratchbuf, + 1)); + + *spinand->scratchbuf = target; + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = 64 + (16 * section); + region->length = 13; + + return 0; +} + +static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 2; + region->length = 14; + + return 0; +} + +static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { + .ecc = w25n02kv_ooblayout_ecc, + .free = w25n02kv_ooblayout_free, +}; + +static int w25n02kv_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 mbf = 0; + struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (spi_mem_exec_op(spinand->spimem, &op)) + return nanddev_get_ecc_conf(nand)->strength; + + mbf = *(spinand->scratchbuf) >> 4; + + if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf)) + return nanddev_get_ecc_conf(nand)->strength; + + return mbf; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info winbond_spinand_table[] = { + SPINAND_INFO("W25M02GV", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL), + SPINAND_SELECT_TARGET(w25m02gv_select_target)), + SPINAND_INFO("W25N01GV", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)), + SPINAND_INFO("W25N02KV", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), +}; + +static int winbond_spinand_init(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + /* + * Make sure all dies are in buffer read mode and not continuous read + * mode. + */ + for (i = 0; i < nand->memorg.ntargets; i++) { + spinand_select_target(spinand, i); + spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, + WINBOND_CFG_BUF_READ); + } + + return 0; +} + +static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { + .init = winbond_spinand_init, +}; + +const struct spinand_manufacturer winbond_spinand_manufacturer = { + .id = SPINAND_MFR_WINBOND, + .name = "Winbond", + .chips = winbond_spinand_table, + .nchips = ARRAY_SIZE(winbond_spinand_table), + .ops = &winbond_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/xtx.c b/drivers/mtd/nand/spi/xtx.c new file mode 100644 index 0000000000..3911520f71 --- /dev/null +++ b/drivers/mtd/nand/spi/xtx.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Felix Matouschek <felix@matouschek.org> + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/mtd/spinand.h> + +#define SPINAND_MFR_XTX 0x0B + +#define XT26G0XA_STATUS_ECC_MASK GENMASK(5, 2) +#define XT26G0XA_STATUS_ECC_NO_DETECTED (0 << 2) +#define XT26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) +#define XT26G0XA_STATUS_ECC_UNCOR_ERROR (2 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), + SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); + +static SPINAND_OP_VARIANTS(write_cache_variants, + SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), + SPINAND_PROG_LOAD(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), + SPINAND_PROG_LOAD(false, 0, NULL, 0)); + +static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 48; + region->length = 16; + + return 0; +} + +static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 1; + region->length = 47; + + return 0; +} + +static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = { + .ecc = xt26g0xa_ooblayout_ecc, + .free = xt26g0xa_ooblayout_free, +}; + +static int xt26g0xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + status = status & XT26G0XA_STATUS_ECC_MASK; + + switch (status) { + case XT26G0XA_STATUS_ECC_NO_DETECTED: + return 0; + case XT26G0XA_STATUS_ECC_8_CORRECTED: + return 8; + case XT26G0XA_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + default: + break; + } + + /* At this point values greater than (2 << 4) are invalid */ + if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR) + return -EINVAL; + + /* (1 << 2) through (7 << 2) are 1-7 corrected errors */ + return status >> 2; +} + +static const struct spinand_info xtx_spinand_table[] = { + SPINAND_INFO("XT26G01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G02A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G04A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3), + NAND_MEMORG(1, 2048, 64, 128, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer xtx_spinand_manufacturer = { + .id = SPINAND_MFR_XTX, + .name = "XTX", + .chips = xtx_spinand_table, + .nchips = ARRAY_SIZE(xtx_spinand_table), + .ops = &xtx_spinand_manuf_ops, +}; |