Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+9
View File
@@ -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.
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
spinand-objs := core.o otp.o
spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o
spinand-objs += macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
@@ -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_1S_4S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(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,
};
+86
View File
@@ -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_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(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,
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Author: Ahmed Naseef <naseefkm@gmail.com>
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#define SPINAND_MFR_DOSILICON 0xE5
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int ds35xx_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
region->offset = 8 + (section * 16);
region->length = 8;
return 0;
}
static int ds35xx_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
if (section == 0) {
/* reserve 2 bytes for the BBM */
region->offset = 2;
region->length = 6;
} else {
region->offset = section * 16;
region->length = 8;
}
return 0;
}
static const struct mtd_ooblayout_ops ds35xx_ooblayout = {
.ecc = ds35xx_ooblayout_ecc,
.free = ds35xx_ooblayout_free,
};
static const struct spinand_info dosilicon_spinand_table[] = {
SPINAND_INFO("DS35Q1GA",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71),
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(&ds35xx_ooblayout, NULL)),
SPINAND_INFO("DS35M1GA",
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,
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&ds35xx_ooblayout, NULL)),
};
static const struct spinand_manufacturer_ops dosilicon_spinand_manuf_ops = {
};
const struct spinand_manufacturer dosilicon_spinand_manufacturer = {
.id = SPINAND_MFR_DOSILICON,
.name = "Dosilicon",
.chips = dosilicon_spinand_table,
.nchips = ARRAY_SIZE(dosilicon_spinand_table),
.ops = &dosilicon_spinand_manuf_ops,
};
+257
View File
@@ -0,0 +1,257 @@
// 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>
#include <linux/spi/spi-mem.h>
/* ESMT uses GigaDevice 0xc8 JECDEC ID on some SPI NANDs */
#define SPINAND_MFR_ESMT_C8 0xc8
#define SPINAND_MFR_ESMT_8C 0x8c
#define ESMT_F50L1G41LB_CFG_OTP_PROTECT BIT(7)
#define ESMT_F50L1G41LB_CFG_OTP_LOCK \
(CFG_OTP_ENABLE | ESMT_F50L1G41LB_CFG_OTP_PROTECT)
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(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 int f50l1g41lb_otp_info(struct spinand_device *spinand, size_t len,
struct otp_info *buf, size_t *retlen, bool user)
{
if (len < sizeof(*buf))
return -EINVAL;
buf->locked = 0;
buf->start = 0;
buf->length = user ? spinand_user_otp_size(spinand) :
spinand_fact_otp_size(spinand);
*retlen = sizeof(*buf);
return 0;
}
static int f50l1g41lb_fact_otp_info(struct spinand_device *spinand, size_t len,
struct otp_info *buf, size_t *retlen)
{
return f50l1g41lb_otp_info(spinand, len, buf, retlen, false);
}
static int f50l1g41lb_user_otp_info(struct spinand_device *spinand, size_t len,
struct otp_info *buf, size_t *retlen)
{
return f50l1g41lb_otp_info(spinand, len, buf, retlen, true);
}
static int f50l1g41lb_otp_lock(struct spinand_device *spinand, loff_t from,
size_t len)
{
struct spi_mem_op write_op = SPINAND_OP(spinand, wr_en);
struct spi_mem_op exec_op = SPINAND_OP(spinand, prog_exec, 0);
u8 status;
int ret;
ret = spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK,
ESMT_F50L1G41LB_CFG_OTP_LOCK);
if (!ret)
return ret;
ret = spi_mem_exec_op(spinand->spimem, &write_op);
if (!ret)
goto out;
ret = spi_mem_exec_op(spinand->spimem, &exec_op);
if (!ret)
goto out;
ret = spinand_wait(spinand,
SPINAND_WRITE_INITIAL_DELAY_US,
SPINAND_WRITE_POLL_DELAY_US,
&status);
if (!ret && (status & STATUS_PROG_FAILED))
ret = -EIO;
out:
if (spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK, 0)) {
dev_warn(&spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
return ret;
}
static const struct spinand_user_otp_ops f50l1g41lb_user_otp_ops = {
.info = f50l1g41lb_user_otp_info,
.lock = f50l1g41lb_otp_lock,
.read = spinand_user_otp_read,
.write = spinand_user_otp_write,
};
static const struct spinand_fact_otp_ops f50l1g41lb_fact_otp_ops = {
.info = f50l1g41lb_fact_otp_info,
.read = spinand_fact_otp_read,
};
static const struct spinand_info esmt_8c_spinand_table[] = {
SPINAND_INFO("F50L1G41LC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x2C),
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_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops),
SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)),
};
static const struct spinand_info esmt_c8_spinand_table[] = {
SPINAND_INFO("F50L1G41LB",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01, 0x7f,
0x7f, 0x7f),
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_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops),
SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)),
SPINAND_INFO("F50D1G41LB",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x11, 0x7f,
0x7f, 0x7f),
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_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops),
SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)),
SPINAND_INFO("F50D2G41KA",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51, 0x7f,
0x7f, 0x7f),
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_8c_spinand_manufacturer = {
.id = SPINAND_MFR_ESMT_8C,
.name = "ESMT",
.chips = esmt_8c_spinand_table,
.nchips = ARRAY_SIZE(esmt_8c_spinand_table),
.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,
};
+146
View File
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd.
*
* Author: Dingqiang Lin <jon.lin@rock-chips.com>
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#define FM25S01BI3_STATUS_ECC_MASK (7 << 4)
#define FM25S01BI3_STATUS_ECC_NO_BITFLIPS (0 << 4)
#define FM25S01BI3_STATUS_ECC_1_3_BITFLIPS (1 << 4)
#define FM25S01BI3_STATUS_ECC_UNCOR_ERROR (2 << 4)
#define FM25S01BI3_STATUS_ECC_4_6_BITFLIPS (3 << 4)
#define FM25S01BI3_STATUS_ECC_7_8_BITFLIPS (5 << 4)
#define SPINAND_MFR_FMSH 0xA1
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
return -ERANGE;
}
static int fm25s01a_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section)
return -ERANGE;
region->offset = 2;
region->length = 62;
return 0;
}
static int fm25s01bi3_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
switch (status & FM25S01BI3_STATUS_ECC_MASK) {
case FM25S01BI3_STATUS_ECC_NO_BITFLIPS:
return 0;
case FM25S01BI3_STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
case FM25S01BI3_STATUS_ECC_1_3_BITFLIPS:
return 3;
case FM25S01BI3_STATUS_ECC_4_6_BITFLIPS:
return 6;
case FM25S01BI3_STATUS_ECC_7_8_BITFLIPS:
return 8;
default:
break;
}
return -EINVAL;
}
static int fm25s01bi3_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 fm25s01bi3_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
region->offset = (16 * section) + 4;
region->length = 12;
return 0;
}
static const struct mtd_ooblayout_ops fm25s01a_ooblayout = {
.ecc = fm25s01a_ooblayout_ecc,
.free = fm25s01a_ooblayout_free,
};
static const struct mtd_ooblayout_ops fm25s01bi3_ooblayout = {
.ecc = fm25s01bi3_ooblayout_ecc,
.free = fm25s01bi3_ooblayout_free,
};
static const struct spinand_info fmsh_spinand_table[] = {
SPINAND_INFO("FM25S01A",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
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(&fm25s01a_ooblayout, NULL)),
SPINAND_INFO("FM25S01BI3",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd4),
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(&fm25s01bi3_ooblayout,
fm25s01bi3_ecc_get_status)),
};
static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = {
};
const struct spinand_manufacturer fmsh_spinand_manufacturer = {
.id = SPINAND_MFR_FMSH,
.name = "Fudan Micro",
.chips = fmsh_spinand_table,
.nchips = ARRAY_SIZE(fmsh_spinand_table),
.ops = &fmsh_spinand_manuf_ops,
};
+139
View File
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2023, SberDevices. All Rights Reserved.
*
* Author: Martin Kurbanov <mmkurbanov@salutedevices.com>
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#define SPINAND_MFR_FORESEE 0xCD
#define F35SQB002G_STATUS_ECC_MASK (7 << 4)
#define F35SQB002G_STATUS_ECC_NO_BITFLIPS (0 << 4)
#define F35SQB002G_STATUS_ECC_1_3_BITFLIPS (1 << 4)
#define F35SQB002G_STATUS_ECC_UNCOR_ERROR (7 << 4)
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int f35sqa002g_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
return -ERANGE;
}
static int f35sqa002g_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 f35sqa002g_ooblayout = {
.ecc = f35sqa002g_ooblayout_ecc,
.free = f35sqa002g_ooblayout_free,
};
static int f35sqa002g_ecc_get_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
return 0;
case STATUS_ECC_HAS_BITFLIPS:
return nanddev_get_ecc_conf(nand)->strength;
default:
break;
}
/* More than 1-bit error was detected in one or more sectors and
* cannot be corrected.
*/
return -EBADMSG;
}
static int f35sqb002g_ecc_get_status(struct spinand_device *spinand, u8 status)
{
switch (status & F35SQB002G_STATUS_ECC_MASK) {
case F35SQB002G_STATUS_ECC_NO_BITFLIPS:
return 0;
case F35SQB002G_STATUS_ECC_1_3_BITFLIPS:
return 3;
case F35SQB002G_STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
default: /* (2 << 4) through (6 << 4) are 4-8 corrected errors */
return ((status & F35SQB002G_STATUS_ECC_MASK) >> 4) + 2;
}
return -EINVAL;
}
static const struct spinand_info foresee_spinand_table[] = {
SPINAND_INFO("F35SQA002G",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x72, 0x72),
NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 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(&f35sqa002g_ooblayout,
f35sqa002g_ecc_get_status)),
SPINAND_INFO("F35SQA001G",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71, 0x71),
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(&f35sqa002g_ooblayout,
f35sqa002g_ecc_get_status)),
SPINAND_INFO("F35SQB002G",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52, 0x52),
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(&f35sqa002g_ooblayout,
f35sqb002g_ecc_get_status)),
};
static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = {
};
const struct spinand_manufacturer foresee_spinand_manufacturer = {
.id = SPINAND_MFR_FORESEE,
.name = "FORESEE",
.chips = foresee_spinand_table,
.nchips = ARRAY_SIZE(foresee_spinand_table),
.ops = &foresee_spinand_manuf_ops,
};
@@ -0,0 +1,670 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Author:
* Chuanhong Guo <gch981213@gmail.com>
*/
#include <linux/bitfield.h>
#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)
/* Feature bit definitions */
#define GD_FEATURE_NR BIT(3) /* Normal Read(1=normal,0=continuous) */
#define GD_FEATURE_CRDC BIT(2) /* Continuous Read Dummy */
/* ECC status extraction helpers */
#define GD_ECCSR_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr)
#define GD_ECCSR_ACCUMULATED(eccsr) FIELD_GET(GENMASK(7, 4), eccsr)
struct gigadevice_priv {
bool continuous_read;
};
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants_f,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_3A_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_3A_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_3A_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_3A_1S_1S_1S_OP(0, 0, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants_1gq5,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants_2gq5,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 4, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int gd5fxgm9_get_eccsr(struct spinand_device *spinand, u8 *eccsr)
{
struct gigadevice_priv *priv = spinand->priv;
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;
ret = spi_mem_exec_op(spinand->spimem, &op);
if (ret)
return ret;
if (priv->continuous_read)
*eccsr = GD_ECCSR_ACCUMULATED(*eccsr);
else
*eccsr = GD_ECCSR_LAST_PAGE(*eccsr);
return 0;
}
static int gd5fxgm9_ecc_get_status(struct spinand_device *spinand, u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
u8 eccsr;
int ret;
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
return 0;
case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS:
ret = gd5fxgm9_get_eccsr(spinand, spinand->scratchbuf);
if (ret)
return nanddev_get_ecc_conf(nand)->strength;
eccsr = *spinand->scratchbuf;
if (WARN_ON(!eccsr || eccsr > nanddev_get_ecc_conf(nand)->strength))
return nanddev_get_ecc_conf(nand)->strength;
return eccsr;
case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS:
return 8;
case STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
default:
return -EINVAL;
}
}
static int gd5fxgm9_set_continuous_read(struct spinand_device *spinand, bool enable)
{
struct gigadevice_priv *priv = spinand->priv;
int ret;
ret = spinand_upd_cfg(spinand, GD_FEATURE_NR,
enable ? 0 : GD_FEATURE_NR);
if (ret)
return ret;
priv->continuous_read = enable;
return 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_OP(spinand, get_feature,
GD5FXGQXXEXXG_REG_STATUS2, spinand->scratchbuf);
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 */
status2 = *(spinand->scratchbuf);
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_OP(spinand, get_feature,
GD5FXGQXXEXXG_REG_STATUS2, spinand->scratchbuf);
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 */
status2 = *(spinand->scratchbuf);
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)),
SPINAND_INFO("GD5F1GM9UExxG",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91, 0x01),
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,
gd5fxgm9_ecc_get_status),
SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)),
SPINAND_INFO("GD5F1GM9RExxG",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81, 0x01),
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,
gd5fxgm9_ecc_get_status),
SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)),
};
static int gd5fxgm9_spinand_init(struct spinand_device *spinand)
{
struct gigadevice_priv *priv;
priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
spinand->priv = priv;
return 0;
}
static void gd5fxgm9_spinand_cleanup(struct spinand_device *spinand)
{
kfree(spinand->priv);
}
static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
.init = gd5fxgm9_spinand_init,
.cleanup = gd5fxgm9_spinand_cleanup,
};
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,
};
+545
View File
@@ -0,0 +1,545 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 Macronix
*
* Author: Boris Brezillon <boris.brezillon@bootlin.com>
*/
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#define SPINAND_MFR_MACRONIX 0xC2
#define MACRONIX_ECCSR_BF_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr)
#define MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(eccsr) FIELD_GET(GENMASK(7, 4), eccsr)
#define MACRONIX_CFG_CONT_READ BIT(2)
#define MACRONIX_CFG_RANDOMIZER_EN BIT(1)
#define MACRONIX_FEATURE_ADDR_RANDOMIZER 0x10
#define MACRONIX_FEATURE_ADDR_READ_RETRY 0x70
#define MACRONIX_NUM_READ_RETRY_MODES 5
#define STATUS_ECC_HAS_BITFLIPS_THRESHOLD (3 << 4)
/* Bitflip theshold configuration register */
#define REG_CFG_BFT 0x10
#define CFG_BFT(x) FIELD_PREP(GENMASK(7, 4), (x))
struct macronix_priv {
bool cont_read;
};
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
#define SPINAND_MACRONIX_READ_ECCSR_1S_0_1S(buf) \
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, buf, 1))
static SPINAND_OP_VARIANTS(macronix_ops,
SPINAND_MACRONIX_READ_ECCSR_1S_0_1S(NULL));
static struct spi_mem_op
spinand_fill_macronix_read_eccsr_op(struct spinand_device *spinand, void *valptr)
{
WARN_ON_ONCE(spinand->bus_iface != SSDR);
return (struct spi_mem_op)SPINAND_MACRONIX_READ_ECCSR_1S_0_1S(valptr);
}
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 macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr)
{
struct macronix_priv *priv = spinand->priv;
struct spi_mem_op op = SPINAND_OP(spinand, macronix_read_eccsr, eccsr);
int ret;
ret = spi_mem_exec_op(spinand->spimem, &op);
if (ret)
return ret;
/*
* ECCSR exposes the number of bitflips for the last read page in bits [3:0].
* Continuous read compatible chips also expose the maximum number of
* bitflips for the whole (continuous) read operation in bits [7:4].
*/
if (!priv->cont_read)
*eccsr = MACRONIX_ECCSR_BF_LAST_PAGE(*eccsr);
else
*eccsr = MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(*eccsr);
return 0;
}
static int macronix_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 (macronix_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 int macronix_set_cont_read(struct spinand_device *spinand, bool enable)
{
struct macronix_priv *priv = spinand->priv;
int ret;
ret = spinand_upd_cfg(spinand, MACRONIX_CFG_CONT_READ,
enable ? MACRONIX_CFG_CONT_READ : 0);
if (ret)
return ret;
priv->cont_read = enable;
return 0;
}
/**
* macronix_set_read_retry - Set the retry mode
* @spinand: SPI NAND device
* @retry_mode: Specify which retry mode to set
*
* Return: 0 on success, a negative error code otherwise.
*/
static int macronix_set_read_retry(struct spinand_device *spinand,
unsigned int retry_mode)
{
struct spi_mem_op op = SPINAND_OP(spinand, set_feature,
MACRONIX_FEATURE_ADDR_READ_RETRY, spinand->scratchbuf);
*spinand->scratchbuf = retry_mode;
return spi_mem_exec_op(spinand->spimem, &op);
}
static int macronix_set_randomizer(struct spinand_device *spinand, bool enable)
{
return spinand_write_reg_op(spinand, MACRONIX_FEATURE_ADDR_RANDOMIZER,
enable ? MACRONIX_CFG_RANDOMIZER_EN : 0);
}
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_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_HAS_PROG_PLANE_SELECT_BIT |
SPINAND_HAS_READ_PLANE_SELECT_BIT,
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
SPINAND_INFO("MX35LF2GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x26, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry)),
SPINAND_INFO("MX35LF4GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry)),
SPINAND_INFO("MX35LF1G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14, 0x03),
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_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF2G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24, 0x03),
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_HAS_PROG_PLANE_SELECT_BIT,
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF2G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x64, 0x03),
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, NULL),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF4G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35, 0x03),
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_HAS_PROG_PLANE_SELECT_BIT,
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF4G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x75, 0x03),
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, NULL),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_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_HAS_PROG_PLANE_SELECT_BIT |
SPINAND_HAS_READ_PLANE_SELECT_BIT,
SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35UF4G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb5, 0x03),
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_HAS_PROG_PLANE_SELECT_BIT,
SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF4G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xf5, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF4GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry)),
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_HAS_PROG_PLANE_SELECT_BIT |
SPINAND_HAS_READ_PLANE_SELECT_BIT,
SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35UF2G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa4, 0x03),
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_HAS_PROG_PLANE_SELECT_BIT,
SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF2G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe4, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF2GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry)),
SPINAND_INFO("MX35UF2GE4AC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2, 0x01),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read)),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35UF1G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x94, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry),
SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF1GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96, 0x03),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read),
SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
macronix_set_read_retry)),
SPINAND_INFO("MX35UF1GE4AC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92, 0x01),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read)),
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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_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_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
};
static int macronix_spinand_init(struct spinand_device *spinand)
{
struct macronix_priv *priv;
priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
spinand->priv = priv;
return 0;
}
static void macronix_spinand_cleanup(struct spinand_device *spinand)
{
kfree(spinand->priv);
}
static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = {
.init = macronix_spinand_init,
.cleanup = macronix_spinand_cleanup,
};
const struct spinand_manufacturer macronix_spinand_manufacturer = {
.id = SPINAND_MFR_MACRONIX,
.name = "Macronix",
.chips = macronix_spinand_table,
.nchips = ARRAY_SIZE(macronix_spinand_table),
.ops = &macronix_spinand_manuf_ops,
};
+442
View File
@@ -0,0 +1,442 @@
// 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>
#include <linux/spi/spi-mem.h>
#include <linux/string.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)
#define MICRON_MT29F2G01ABAGD_CFG_OTP_STATE BIT(7)
#define MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK \
(CFG_OTP_ENABLE | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE)
static SPINAND_OP_VARIANTS(quadio_read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(x4_write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(x4_update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
/* Micron MT29F2G01AAAED Device */
static SPINAND_OP_VARIANTS(x4_read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(x1_write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(x1_update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_1S_OP(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_OP(spinand, set_feature,
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 int mt29f2g01abagd_otp_is_locked(struct spinand_device *spinand)
{
size_t bufsize = spinand_otp_page_size(spinand);
size_t retlen;
u8 *buf;
int ret;
buf = kmalloc(bufsize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = spinand_upd_cfg(spinand,
MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
MICRON_MT29F2G01ABAGD_CFG_OTP_STATE);
if (ret)
goto free_buf;
ret = spinand_user_otp_read(spinand, 0, bufsize, &retlen, buf);
if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
0)) {
dev_warn(&spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
if (ret)
goto free_buf;
/* If all zeros, then the OTP area is locked. */
if (mem_is_zero(buf, bufsize))
ret = 1;
free_buf:
kfree(buf);
return ret;
}
static int mt29f2g01abagd_otp_info(struct spinand_device *spinand, size_t len,
struct otp_info *buf, size_t *retlen,
bool user)
{
int locked;
if (len < sizeof(*buf))
return -EINVAL;
locked = mt29f2g01abagd_otp_is_locked(spinand);
if (locked < 0)
return locked;
buf->locked = locked;
buf->start = 0;
buf->length = user ? spinand_user_otp_size(spinand) :
spinand_fact_otp_size(spinand);
*retlen = sizeof(*buf);
return 0;
}
static int mt29f2g01abagd_fact_otp_info(struct spinand_device *spinand,
size_t len, struct otp_info *buf,
size_t *retlen)
{
return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, false);
}
static int mt29f2g01abagd_user_otp_info(struct spinand_device *spinand,
size_t len, struct otp_info *buf,
size_t *retlen)
{
return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, true);
}
static int mt29f2g01abagd_otp_lock(struct spinand_device *spinand, loff_t from,
size_t len)
{
struct spi_mem_op write_op = SPINAND_OP(spinand, wr_en);
struct spi_mem_op exec_op = SPINAND_OP(spinand, prog_exec, 0);
u8 status;
int ret;
ret = spinand_upd_cfg(spinand,
MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK);
if (!ret)
return ret;
ret = spi_mem_exec_op(spinand->spimem, &write_op);
if (!ret)
goto out;
ret = spi_mem_exec_op(spinand->spimem, &exec_op);
if (!ret)
goto out;
ret = spinand_wait(spinand,
SPINAND_WRITE_INITIAL_DELAY_US,
SPINAND_WRITE_POLL_DELAY_US,
&status);
if (!ret && (status & STATUS_PROG_FAILED))
ret = -EIO;
out:
if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, 0)) {
dev_warn(&spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
return ret;
}
static const struct spinand_user_otp_ops mt29f2g01abagd_user_otp_ops = {
.info = mt29f2g01abagd_user_otp_info,
.lock = mt29f2g01abagd_otp_lock,
.read = spinand_user_otp_read,
.write = spinand_user_otp_write,
};
static const struct spinand_fact_otp_ops mt29f2g01abagd_fact_otp_ops = {
.info = mt29f2g01abagd_fact_otp_info,
.read = spinand_fact_otp_read,
};
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(&micron_8_ooblayout,
micron_8_ecc_get_status),
SPINAND_USER_OTP_INFO(12, 2, &mt29f2g01abagd_user_otp_ops),
SPINAND_FACT_OTP_INFO(2, 0, &mt29f2g01abagd_fact_otp_ops)),
/* 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(&micron_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(&micron_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(&micron_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(&micron_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(&micron_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(&micron_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(&micron_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(&micron_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(&micron_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 = &micron_spinand_manuf_ops,
};
+362
View File
@@ -0,0 +1,362 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025, SaluteDevices. All Rights Reserved.
*
* Author: Martin Kurbanov <mmkurbanov@salutedevices.com>
*/
#include <linux/mtd/mtd.h>
#include <linux/mtd/spinand.h>
/**
* spinand_otp_page_size() - Get SPI-NAND OTP page size
* @spinand: the spinand device
*
* Return: the OTP page size.
*/
size_t spinand_otp_page_size(struct spinand_device *spinand)
{
struct nand_device *nand = spinand_to_nand(spinand);
return nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
}
static size_t spinand_otp_size(struct spinand_device *spinand,
const struct spinand_otp_layout *layout)
{
return layout->npages * spinand_otp_page_size(spinand);
}
/**
* spinand_fact_otp_size() - Get SPI-NAND factory OTP area size
* @spinand: the spinand device
*
* Return: the OTP size.
*/
size_t spinand_fact_otp_size(struct spinand_device *spinand)
{
return spinand_otp_size(spinand, &spinand->fact_otp->layout);
}
/**
* spinand_user_otp_size() - Get SPI-NAND user OTP area size
* @spinand: the spinand device
*
* Return: the OTP size.
*/
size_t spinand_user_otp_size(struct spinand_device *spinand)
{
return spinand_otp_size(spinand, &spinand->user_otp->layout);
}
static int spinand_otp_check_bounds(struct spinand_device *spinand, loff_t ofs,
size_t len,
const struct spinand_otp_layout *layout)
{
if (ofs < 0 || ofs + len > spinand_otp_size(spinand, layout))
return -EINVAL;
return 0;
}
static int spinand_user_otp_check_bounds(struct spinand_device *spinand,
loff_t ofs, size_t len)
{
return spinand_otp_check_bounds(spinand, ofs, len,
&spinand->user_otp->layout);
}
static int spinand_otp_rw(struct spinand_device *spinand, loff_t ofs,
size_t len, size_t *retlen, u8 *buf, bool is_write,
const struct spinand_otp_layout *layout)
{
struct nand_page_io_req req = {};
unsigned long long page;
size_t copied = 0;
size_t otp_pagesize = spinand_otp_page_size(spinand);
int ret;
if (!len)
return 0;
ret = spinand_otp_check_bounds(spinand, ofs, len, layout);
if (ret)
return ret;
ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, CFG_OTP_ENABLE);
if (ret)
return ret;
page = ofs;
req.dataoffs = do_div(page, otp_pagesize);
req.pos.page = page + layout->start_page;
req.type = is_write ? NAND_PAGE_WRITE : NAND_PAGE_READ;
req.mode = MTD_OPS_RAW;
req.databuf.in = buf;
while (copied < len) {
req.datalen = min_t(unsigned int,
otp_pagesize - req.dataoffs,
len - copied);
if (is_write)
ret = spinand_write_page(spinand, &req);
else
ret = spinand_read_page(spinand, &req);
if (ret < 0)
break;
req.databuf.in += req.datalen;
req.pos.page++;
req.dataoffs = 0;
copied += req.datalen;
}
*retlen = copied;
if (spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0)) {
dev_warn(&spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
return ret;
}
/**
* spinand_fact_otp_read() - Read from OTP area
* @spinand: the spinand device
* @ofs: the offset to read
* @len: the number of data bytes to read
* @retlen: the pointer to variable to store the number of read bytes
* @buf: the buffer to store the read data
*
* Return: 0 on success, an error code otherwise.
*/
int spinand_fact_otp_read(struct spinand_device *spinand, loff_t ofs,
size_t len, size_t *retlen, u8 *buf)
{
return spinand_otp_rw(spinand, ofs, len, retlen, buf, false,
&spinand->fact_otp->layout);
}
/**
* spinand_user_otp_read() - Read from OTP area
* @spinand: the spinand device
* @ofs: the offset to read
* @len: the number of data bytes to read
* @retlen: the pointer to variable to store the number of read bytes
* @buf: the buffer to store the read data
*
* Return: 0 on success, an error code otherwise.
*/
int spinand_user_otp_read(struct spinand_device *spinand, loff_t ofs,
size_t len, size_t *retlen, u8 *buf)
{
return spinand_otp_rw(spinand, ofs, len, retlen, buf, false,
&spinand->user_otp->layout);
}
/**
* spinand_user_otp_write() - Write to OTP area
* @spinand: the spinand device
* @ofs: the offset to write to
* @len: the number of bytes to write
* @retlen: the pointer to variable to store the number of written bytes
* @buf: the buffer with data to write
*
* Return: 0 on success, an error code otherwise.
*/
int spinand_user_otp_write(struct spinand_device *spinand, loff_t ofs,
size_t len, size_t *retlen, const u8 *buf)
{
return spinand_otp_rw(spinand, ofs, len, retlen, (u8 *)buf, true,
&spinand->user_otp->layout);
}
static int spinand_mtd_otp_info(struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf,
bool is_fact)
{
struct spinand_device *spinand = mtd_to_spinand(mtd);
int ret;
*retlen = 0;
mutex_lock(&spinand->lock);
if (is_fact)
ret = spinand->fact_otp->ops->info(spinand, len, buf, retlen);
else
ret = spinand->user_otp->ops->info(spinand, len, buf, retlen);
mutex_unlock(&spinand->lock);
return ret;
}
static int spinand_mtd_fact_otp_info(struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf)
{
return spinand_mtd_otp_info(mtd, len, retlen, buf, true);
}
static int spinand_mtd_user_otp_info(struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf)
{
return spinand_mtd_otp_info(mtd, len, retlen, buf, false);
}
static int spinand_mtd_otp_read(struct mtd_info *mtd, loff_t ofs, size_t len,
size_t *retlen, u8 *buf, bool is_fact)
{
struct spinand_device *spinand = mtd_to_spinand(mtd);
int ret;
*retlen = 0;
if (!len)
return 0;
ret = spinand_otp_check_bounds(spinand, ofs, len,
is_fact ? &spinand->fact_otp->layout :
&spinand->user_otp->layout);
if (ret)
return ret;
mutex_lock(&spinand->lock);
if (is_fact)
ret = spinand->fact_otp->ops->read(spinand, ofs, len, retlen,
buf);
else
ret = spinand->user_otp->ops->read(spinand, ofs, len, retlen,
buf);
mutex_unlock(&spinand->lock);
return ret;
}
static int spinand_mtd_fact_otp_read(struct mtd_info *mtd, loff_t ofs,
size_t len, size_t *retlen, u8 *buf)
{
return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, true);
}
static int spinand_mtd_user_otp_read(struct mtd_info *mtd, loff_t ofs,
size_t len, size_t *retlen, u8 *buf)
{
return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, false);
}
static int spinand_mtd_user_otp_write(struct mtd_info *mtd, loff_t ofs,
size_t len, size_t *retlen, const u8 *buf)
{
struct spinand_device *spinand = mtd_to_spinand(mtd);
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
int ret;
*retlen = 0;
if (!len)
return 0;
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
if (ret)
return ret;
mutex_lock(&spinand->lock);
ret = ops->write(spinand, ofs, len, retlen, buf);
mutex_unlock(&spinand->lock);
return ret;
}
static int spinand_mtd_user_otp_erase(struct mtd_info *mtd, loff_t ofs,
size_t len)
{
struct spinand_device *spinand = mtd_to_spinand(mtd);
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
int ret;
if (!len)
return 0;
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
if (ret)
return ret;
mutex_lock(&spinand->lock);
ret = ops->erase(spinand, ofs, len);
mutex_unlock(&spinand->lock);
return ret;
}
static int spinand_mtd_user_otp_lock(struct mtd_info *mtd, loff_t ofs,
size_t len)
{
struct spinand_device *spinand = mtd_to_spinand(mtd);
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
int ret;
if (!len)
return 0;
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
if (ret)
return ret;
mutex_lock(&spinand->lock);
ret = ops->lock(spinand, ofs, len);
mutex_unlock(&spinand->lock);
return ret;
}
/**
* spinand_set_mtd_otp_ops() - Setup OTP methods
* @spinand: the spinand device
*
* Setup OTP methods.
*
* Return: 0 on success, a negative error code otherwise.
*/
int spinand_set_mtd_otp_ops(struct spinand_device *spinand)
{
struct mtd_info *mtd = spinand_to_mtd(spinand);
const struct spinand_fact_otp_ops *fact_ops = spinand->fact_otp->ops;
const struct spinand_user_otp_ops *user_ops = spinand->user_otp->ops;
if (!user_ops && !fact_ops)
return -EINVAL;
if (user_ops) {
if (user_ops->info)
mtd->_get_user_prot_info = spinand_mtd_user_otp_info;
if (user_ops->read)
mtd->_read_user_prot_reg = spinand_mtd_user_otp_read;
if (user_ops->write)
mtd->_write_user_prot_reg = spinand_mtd_user_otp_write;
if (user_ops->lock)
mtd->_lock_user_prot_reg = spinand_mtd_user_otp_lock;
if (user_ops->erase)
mtd->_erase_user_prot_reg = spinand_mtd_user_otp_erase;
}
if (fact_ops) {
if (fact_ops->info)
mtd->_get_fact_prot_info = spinand_mtd_fact_otp_info;
if (fact_ops->read)
mtd->_read_fact_prot_reg = spinand_mtd_fact_otp_read;
}
return 0;
}
+131
View File
@@ -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_1S_4S_4S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(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 = &paragon_spinand_manuf_ops,
};
+147
View File
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2024 SkyHigh Memory Limited
*
* Author: Takahiro Kuwano <takahiro.kuwano@infineon.com>
* Co-Author: KR Kim <kr.kim@skyhighmemory.com>
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#define SPINAND_MFR_SKYHIGH 0x01
#define SKYHIGH_STATUS_ECC_1TO2_BITFLIPS (1 << 4)
#define SKYHIGH_STATUS_ECC_3TO6_BITFLIPS (2 << 4)
#define SKYHIGH_STATUS_ECC_UNCOR_ERROR (3 << 4)
#define SKYHIGH_CONFIG_PROTECT_EN BIT(1)
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 4, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int skyhigh_spinand_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
/* ECC bytes are stored in hidden area. */
return -ERANGE;
}
static int skyhigh_spinand_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section)
return -ERANGE;
/* ECC bytes are stored in hidden area. Reserve 2 bytes for the BBM. */
region->offset = 2;
region->length = mtd->oobsize - 2;
return 0;
}
static const struct mtd_ooblayout_ops skyhigh_spinand_ooblayout = {
.ecc = skyhigh_spinand_ooblayout_ecc,
.free = skyhigh_spinand_ooblayout_free,
};
static int skyhigh_spinand_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
return 0;
case SKYHIGH_STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
case SKYHIGH_STATUS_ECC_1TO2_BITFLIPS:
return 2;
case SKYHIGH_STATUS_ECC_3TO6_BITFLIPS:
return 6;
default:
break;
}
return -EINVAL;
}
static const struct spinand_info skyhigh_spinand_table[] = {
SPINAND_INFO("S35ML01G301",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
NAND_ECCREQ(6, 32),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
SPINAND_NO_RAW_ACCESS,
SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
skyhigh_spinand_ecc_get_status)),
SPINAND_INFO("S35ML01G300",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14),
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
NAND_ECCREQ(6, 32),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
SPINAND_NO_RAW_ACCESS,
SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
skyhigh_spinand_ecc_get_status)),
SPINAND_INFO("S35ML02G300",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
NAND_ECCREQ(6, 32),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
SPINAND_NO_RAW_ACCESS,
SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
skyhigh_spinand_ecc_get_status)),
SPINAND_INFO("S35ML04G300",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35),
NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 2, 1, 1),
NAND_ECCREQ(6, 32),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
SPINAND_NO_RAW_ACCESS,
SPINAND_ECCINFO(&skyhigh_spinand_ooblayout,
skyhigh_spinand_ecc_get_status)),
};
static int skyhigh_spinand_init(struct spinand_device *spinand)
{
/*
* Config_Protect_En (bit 1 in Block Lock register) must be set to 1
* before writing other bits. Do it here before core unlocks all blocks
* by writing block protection bits.
*/
return spinand_write_reg_op(spinand, REG_BLOCK_LOCK,
SKYHIGH_CONFIG_PROTECT_EN);
}
static const struct spinand_manufacturer_ops skyhigh_spinand_manuf_ops = {
.init = skyhigh_spinand_init,
};
const struct spinand_manufacturer skyhigh_spinand_manufacturer = {
.id = SPINAND_MFR_SKYHIGH,
.name = "SkyHigh",
.chips = skyhigh_spinand_table,
.nchips = ARRAY_SIZE(skyhigh_spinand_table),
.ops = &skyhigh_spinand_manuf_ops,
};
+314
View File
@@ -0,0 +1,314 @@
// 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_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_x4_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_x4_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(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_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_1S_OP(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_OP(spinand, get_feature,
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,
};
+814
View File
@@ -0,0 +1,814 @@
// 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>
#include <linux/units.h>
#include <linux/delay.h>
#define SPINAND_MFR_WINBOND 0xEF
#define WINBOND_CFG_HFREQ BIT(0)
#define WINBOND_CFG_BUF_READ BIT(3)
#define W25N04KV_STATUS_ECC_5_8_BITFLIPS (3 << 4)
#define W25W35NXXJW_STATUS_ECC_MULT_UNCOR (3 << 4)
#define W25N0XJW_SR4 0xD0
#define W25N0XJW_SR4_HS BIT(2)
#define W35N01JW_VCR_IO_MODE_REG 0x00
#define W35N01JW_VCR_IO_MODE_SINGLE_SDR 0xFF
#define W35N01JW_VCR_IO_MODE_OCTAL_SDR 0xDF
#define W35N01JW_VCR_IO_MODE_OCTAL_DDR_DS 0xE7
#define W35N01JW_VCR_IO_MODE_OCTAL_DDR 0xC7
#define W35N01JW_VCR_DUMMY_CLOCK_REG 0x01
/*
* Winbond chips ignore the address bytes during continuous reads, and
* because the dummy cycles are enough they indicate dropping the
* address cycles from the continuous read from cache variants. This is
* very poorly supported by SPI controller drivers which are "wired" to
* always at least provide the column. Keep using address cycles, but
* reduce the number of dummy cycles accordingly.
*/
#define WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x0b, 1), \
SPI_MEM_OP_ADDR(1, 0, 1), \
SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
SPI_MEM_OP_DATA_IN(len, buf, 1), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_1D_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x0d, 1), \
SPI_MEM_DTR_OP_ADDR(2, 0, 1), \
SPI_MEM_DTR_OP_DUMMY(ndummy, 1), \
SPI_MEM_DTR_OP_DATA_IN(len, buf, 1), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \
SPI_MEM_OP_ADDR(1, 0, 1), \
SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
SPI_MEM_OP_DATA_IN(len, buf, 2), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \
SPI_MEM_OP_ADDR(1, 0, 2), \
SPI_MEM_OP_DUMMY(ndummy - 1, 2), \
SPI_MEM_OP_DATA_IN(len, buf, 2), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_2D_2D_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0xbd, 1), \
SPI_MEM_DTR_OP_ADDR(1, 0, 2), \
SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 2), \
SPI_MEM_DTR_OP_DATA_IN(len, buf, 2), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \
SPI_MEM_OP_ADDR(1, 0, 1), \
SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
SPI_MEM_OP_DATA_IN(len, buf, 4), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_4D_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x6d, 1), \
SPI_MEM_DTR_OP_ADDR(1, 0, 1), \
SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 1), \
SPI_MEM_DTR_OP_DATA_IN(len, buf, 4), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \
SPI_MEM_OP_ADDR(1, 0, 4), \
SPI_MEM_OP_DUMMY(ndummy - 1, 4), \
SPI_MEM_OP_DATA_IN(len, buf, 4), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_4D_4D_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0xed, 1), \
SPI_MEM_DTR_OP_ADDR(1, 0, 4), \
SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 4), \
SPI_MEM_DTR_OP_DATA_IN(len, buf, 4), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x8b, 1), \
SPI_MEM_OP_ADDR(1, 0, 1), \
SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
SPI_MEM_OP_DATA_IN(len, buf, 8), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x9d, 1), \
SPI_MEM_DTR_OP_ADDR(1, 0, 1), \
SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 1), \
SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0xcb, 1), \
SPI_MEM_OP_ADDR(1, 0, 8), \
SPI_MEM_OP_DUMMY(ndummy - 1, 8), \
SPI_MEM_OP_DATA_IN(len, buf, 8), \
SPI_MEM_OP_MAX_FREQ(freq))
#define WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(ndummy, buf, len, freq) \
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x9d, 8), \
SPI_MEM_DTR_OP_ADDR(2, 0, 8), \
SPI_MEM_DTR_OP_DUMMY(ndummy - 2, 8), \
SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \
SPI_MEM_OP_MAX_FREQ(freq))
/*
* "X2" in the core is equivalent to "dual output" in the datasheets,
* "X4" in the core is equivalent to "quad output" in the datasheets.
* Quad and octal capable chips feature an absolute maximum frequency of 166MHz.
*/
static SPINAND_OP_VARIANTS(read_cache_octal_variants,
SPINAND_PAGE_READ_FROM_CACHE_8D_8D_8D_OP(0, 24, NULL, 0, 120 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_8D_8D_8D_OP(0, 16, NULL, 0, 86 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 3, NULL, 0, 120 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 2, NULL, 0, 105 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 20, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 16, NULL, 0, 162 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 12, NULL, 0, 124 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 8, NULL, 0, 86 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 1, NULL, 0, 133 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(cont_read_cache_octal_variants,
WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(24, NULL, 0, 120 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(16, NULL, 0, 86 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(3, NULL, 0, 120 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(2, NULL, 0, 105 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(20, NULL, 0, 0),
WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(16, NULL, 0, 162 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(12, NULL, 0, 124 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(8, NULL, 0, 86 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(2, NULL, 0, 0),
WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(1, NULL, 0, 133 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_octal_variants,
SPINAND_PROG_LOAD_8D_8D_8D_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_8S_8S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_8S_OP(0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_octal_variants,
SPINAND_PROG_LOAD_8D_8D_8D_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_8S_8S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static SPINAND_OP_VARIANTS(read_cache_dual_quad_dtr_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4D_4D_OP(0, 8, NULL, 0, 80 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_4D_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 4, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 104 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2D_2D_OP(0, 4, NULL, 0, 80 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_2D_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 104 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1D_1D_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 54 * HZ_PER_MHZ));
static SPINAND_OP_VARIANTS(cont_read_cache_dual_quad_dtr_variants,
WINBOND_CONT_READ_FROM_CACHE_1S_4D_4D_OP(11, NULL, 0, 80 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_1D_4D_OP(5, NULL, 0, 80 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(7, NULL, 0, 0),
WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(6, NULL, 0, 104 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(4, NULL, 0, 0),
WINBOND_CONT_READ_FROM_CACHE_1S_2D_2D_OP(6, NULL, 0, 80 * HZ_PER_MHZ),
/* The 1S_1D_2D variant would require 4.5 dummy bytes, this is not possible */
WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(5, NULL, 0, 0),
WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(4, NULL, 0, 104 * HZ_PER_MHZ),
WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(4, NULL, 0, 0),
/* The 1S_1D_1D variant would require 4.5 dummy bytes, this is not possible */
WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(4, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
#define SPINAND_WINBOND_WRITE_VCR_1S_1S_1S(reg, buf) \
SPI_MEM_OP(SPI_MEM_OP_CMD(0x81, 1), \
SPI_MEM_OP_ADDR(3, reg, 1), \
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_OP_DATA_OUT(1, buf, 1))
#define SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(reg, buf) \
SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x81, 8), \
SPI_MEM_DTR_OP_ADDR(4, reg << 8, 8), \
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_DTR_OP_DATA_OUT(2, buf, 8))
static SPINAND_OP_VARIANTS(winbond_w35_ops,
SPINAND_WINBOND_WRITE_VCR_1S_1S_1S(0, NULL),
SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(0, NULL));
static struct spi_mem_op
spinand_fill_winbond_write_vcr_op(struct spinand_device *spinand, u8 reg, void *valptr)
{
return (spinand->bus_iface == SSDR) ?
(struct spi_mem_op)SPINAND_WINBOND_WRITE_VCR_1S_1S_1S(reg, valptr) :
(struct spi_mem_op)SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(reg, valptr);
}
#define SPINAND_WINBOND_SELECT_TARGET_1S_0_1S(buf) \
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, buf, 1))
static SPINAND_OP_VARIANTS(winbond_w25_ops,
SPINAND_WINBOND_SELECT_TARGET_1S_0_1S(NULL));
static struct spi_mem_op
spinand_fill_winbond_select_target_op(struct spinand_device *spinand, void *valptr)
{
WARN_ON_ONCE(spinand->bus_iface != SSDR);
return (struct spi_mem_op)SPINAND_WINBOND_SELECT_TARGET_1S_0_1S(valptr);
}
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 = SPINAND_OP(spinand, winbond_select_target,
spinand->scratchbuf);
*spinand->scratchbuf = target;
return spi_mem_exec_op(spinand->spimem, &op);
}
static int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
region->offset = 64 + (8 * section);
region->length = 7;
return 0;
}
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 w25n01kv_ooblayout = {
.ecc = w25n01kv_ooblayout_ecc,
.free = w25n02kv_ooblayout_free,
};
static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
.ecc = w25n02kv_ooblayout_ecc,
.free = w25n02kv_ooblayout_free,
};
static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
region->offset = (16 * section) + 12;
region->length = 4;
return 0;
}
static int w25n01jw_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 3)
return -ERANGE;
region->offset = (16 * section);
region->length = 12;
/* Extract BBM */
if (!section) {
region->offset += 2;
region->length -= 2;
}
return 0;
}
static int w35n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 7)
return -ERANGE;
region->offset = (16 * section) + 12;
region->length = 4;
return 0;
}
static int w35n01jw_ooblayout_free(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
{
if (section > 7)
return -ERANGE;
region->offset = 16 * section;
region->length = 12;
/* Extract BBM */
if (!section) {
region->offset += 2;
region->length -= 2;
}
return 0;
}
static const struct mtd_ooblayout_ops w25n01jw_ooblayout = {
.ecc = w25n01jw_ooblayout_ecc,
.free = w25n01jw_ooblayout_free,
};
static const struct mtd_ooblayout_ops w35n01jw_ooblayout = {
.ecc = w35n01jw_ooblayout_ecc,
.free = w35n01jw_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_OP(spinand, get_feature,
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 W25N04KV_STATUS_ECC_5_8_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 int w25w35nxxjw_ecc_get_status(struct spinand_device *spinand, u8 status)
{
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
return 0;
case STATUS_ECC_HAS_BITFLIPS:
return 1;
case STATUS_ECC_UNCOR_ERROR:
case W25W35NXXJW_STATUS_ECC_MULT_UNCOR:
return -EBADMSG;
default:
break;
}
return -EINVAL;
}
static int w25n0xjw_set_sr4_hs(struct spinand_device *spinand, bool enable)
{
int ret;
u8 sr4;
ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4);
if (ret)
return ret;
if (enable)
sr4 |= W25N0XJW_SR4_HS;
else
sr4 &= ~W25N0XJW_SR4_HS;
return spinand_write_reg_op(spinand, W25N0XJW_SR4, sr4);
}
/*
* SDR dual and quad I/O operations over 104MHz require the HS bit to
* enable a few more dummy cycles.
*/
static bool w25n0xjw_op_needs_hs(const struct spi_mem_op *op)
{
if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
return false;
else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1)
return false;
else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ)
return false;
return true;
}
static int w25n0xjw_hs_cfg(struct spinand_device *spinand,
enum spinand_bus_interface iface)
{
const struct spi_mem_op *op;
if (iface != SSDR)
return -EOPNOTSUPP;
/*
* At this stage, we do not yet know the continuous read template, nor
* if there is going to be one. Let's assume the continuous read
* template will be selected with the same heuristics as the buffered
* read variant, as there cannot be a HS configuration mismatch between
* them.
*/
op = spinand->op_templates->read_cache;
return w25n0xjw_set_sr4_hs(spinand, w25n0xjw_op_needs_hs(op));
}
static int w25n0xjw_set_cont_read(struct spinand_device *spinand, bool enable)
{
u8 mask = enable ? 0 : WINBOND_CFG_BUF_READ;
return spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, mask);
}
static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val)
{
struct spi_mem_op op = SPINAND_OP(spinand, winbond_write_vcr,
reg, spinand->scratchbuf);
int ret;
*spinand->scratchbuf = val;
ret = spinand_write_enable_op(spinand);
if (ret)
return ret;
ret = spi_mem_exec_op(spinand->spimem, &op);
if (ret)
return ret;
/*
* Write VCR operation doesn't set the busy bit in SR, which means we
* cannot perform a status poll. Minimum time of 50ns is needed to
* complete the write.
*/
ndelay(50);
return 0;
}
static int w35n0xjw_vcr_cfg(struct spinand_device *spinand,
enum spinand_bus_interface iface)
{
const struct spi_mem_op *ref_op;
unsigned int dummy_cycles;
bool dtr, single;
u8 io_mode;
int ret;
switch (iface) {
case SSDR:
ref_op = spinand->ssdr_op_templates.read_cache;
break;
case ODTR:
ref_op = spinand->odtr_op_templates.read_cache;
break;
default:
return -EOPNOTSUPP;
}
dummy_cycles = ((ref_op->dummy.nbytes * 8) / ref_op->dummy.buswidth) /
(ref_op->dummy.dtr ? 2 : 1);
switch (dummy_cycles) {
case 8:
case 12:
case 16:
case 20:
case 24:
case 28:
break;
default:
return -EINVAL;
}
ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_DUMMY_CLOCK_REG, dummy_cycles);
if (ret)
return ret;
single = (ref_op->cmd.buswidth == 1 &&
ref_op->addr.buswidth == 1 &&
ref_op->data.buswidth == 1);
dtr = (ref_op->cmd.dtr && ref_op->addr.dtr && ref_op->data.dtr);
if (single && !dtr)
io_mode = W35N01JW_VCR_IO_MODE_SINGLE_SDR;
else if (!single && !dtr)
io_mode = W35N01JW_VCR_IO_MODE_OCTAL_SDR;
else if (!single && dtr)
io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR;
else
return -EINVAL;
ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_IO_MODE_REG, io_mode);
if (ret)
return ret;
return 0;
}
static int w35n0xjw_set_cont_read(struct spinand_device *spinand, bool enable)
{
const struct spi_mem_op *cont_op = spinand->op_templates->cont_read_cache;
u8 mask = enable ? 0 : WINBOND_CFG_BUF_READ;
if (cont_op && enable && spinand_op_is_odtr(cont_op) &&
cont_op->max_freq >= 90 * HZ_PER_MHZ)
mask |= WINBOND_CFG_HFREQ;
return spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ | WINBOND_CFG_HFREQ, mask);
}
static const struct spinand_info winbond_spinand_table[] = {
/* 512M-bit densities */
SPINAND_INFO("W25N512GW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x20),
NAND_MEMORG(1, 2048, 64, 64, 512, 10, 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)),
/* 1G-bit densities */
SPINAND_INFO("W25N01GV", /* 3.3V */
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("W25N01GW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 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("W25N01JW", /* high-speed 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbc, 0x21),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_dual_quad_dtr_variants,
&write_cache_variants,
&update_cache_variants,
&cont_read_cache_dual_quad_dtr_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&w25n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg),
SPINAND_CONT_READ(w25n0xjw_set_cont_read)),
SPINAND_INFO("W25N01KV", /* 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae, 0x21),
NAND_MEMORG(1, 2048, 96, 64, 1024, 20, 1, 1, 1),
NAND_ECCREQ(4, 512),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
0,
SPINAND_ECCINFO(&w25n01kv_ooblayout, w25n02kv_ecc_get_status)),
SPINAND_INFO("W35N01JW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc, 0x21),
NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 1, 1),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants,
&write_cache_octal_variants,
&update_cache_octal_variants,
&cont_read_cache_octal_variants),
0,
SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
SPINAND_CONT_READ(w35n0xjw_set_cont_read)),
/* 2G-bit densities */
SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */
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_INFO_VENDOR_OPS(&winbond_w25_ops),
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
SPINAND_SELECT_TARGET(w25m02gv_select_target)),
SPINAND_INFO("W25N02JW", /* high-speed 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbf, 0x22),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 2, 1),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_dual_quad_dtr_variants,
&write_cache_variants,
&update_cache_variants,
&cont_read_cache_dual_quad_dtr_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&w25m02gv_ooblayout, w25w35nxxjw_ecc_get_status),
SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg),
SPINAND_CONT_READ(w25n0xjw_set_cont_read)),
SPINAND_INFO("W25N02KV", /* 3.3V */
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)),
SPINAND_INFO("W25N02KW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 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)),
SPINAND_INFO("W35N02JW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22),
NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants,
&write_cache_octal_variants,
&update_cache_octal_variants,
&cont_read_cache_octal_variants),
SPINAND_ODTR_PACKED_PAGE_READ,
SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
SPINAND_CONT_READ(w35n0xjw_set_cont_read)),
/* 4G-bit densities */
SPINAND_INFO("W25N04KV", /* 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23),
NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 2, 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)),
SPINAND_INFO("W25N04KW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x23),
NAND_MEMORG(1, 2048, 128, 64, 4096, 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)),
SPINAND_INFO("W35N04JW", /* 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants,
&write_cache_octal_variants,
&update_cache_octal_variants,
&cont_read_cache_octal_variants),
SPINAND_ODTR_PACKED_PAGE_READ,
SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
SPINAND_CONT_READ(w35n0xjw_set_cont_read)),
};
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,
};
+263
View File
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Author:
* Felix Matouschek <felix@matouschek.org>
*/
#include <linux/bitfield.h>
#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)
#define XT26XXXD_STATUS_ECC3_ECC2_MASK GENMASK(7, 6)
#define XT26XXXD_STATUS_ECC_NO_DETECTED (0)
#define XT26XXXD_STATUS_ECC_1_7_CORRECTED (1)
#define XT26XXXD_STATUS_ECC_8_CORRECTED (3)
#define XT26XXXD_STATUS_ECC_UNCOR_ERROR (2)
static SPINAND_OP_VARIANTS(read_cache_variants,
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
SPINAND_PROG_LOAD_1S_1S_1S_OP(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 int xt26xxxd_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 xt26xxxd_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 - 2;
return 0;
}
static const struct mtd_ooblayout_ops xt26xxxd_ooblayout = {
.ecc = xt26xxxd_ooblayout_ecc,
.free = xt26xxxd_ooblayout_free,
};
static int xt26xxxd_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
switch (FIELD_GET(STATUS_ECC_MASK, status)) {
case XT26XXXD_STATUS_ECC_NO_DETECTED:
return 0;
case XT26XXXD_STATUS_ECC_UNCOR_ERROR:
return -EBADMSG;
case XT26XXXD_STATUS_ECC_1_7_CORRECTED:
return 4 + FIELD_GET(XT26XXXD_STATUS_ECC3_ECC2_MASK, status);
case XT26XXXD_STATUS_ECC_8_CORRECTED:
return 8;
default:
break;
}
return -EINVAL;
}
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)),
SPINAND_INFO("XT26G01D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x31),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26G11D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x34),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26Q01D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26G02D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x32),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26G12D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x35),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26Q02D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x52),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26G04D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x33),
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(&xt26xxxd_ooblayout,
xt26xxxd_ecc_get_status)),
SPINAND_INFO("XT26Q04D",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x53),
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(&xt26xxxd_ooblayout,
xt26xxxd_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,
};