mtd: spinand: gigadevice: Add continuous read support

GD5F1GM9 supports a high-speed Continuous Read (CR) mode which allows
the entire memory array to be read out by a single read command
without re-issuing the 13h “Page Read to Cache” command for every page.

Datasheet summary
Feature register B0[3] (NR bit)
1 = Normal Read (default)
0 = Continuous Read (CR)

ECC status register 7Ch
Normal read: bits [3:0] → ECC status of last page
Continuous read: bits [7:4] → accumulated max bit-flips over the
whole sequential read operation.

Datasheet:
https://github.com/WT-886/DATASHEET/blob/main/GD5F1GM9-v1.0.pdf

Signed-off-by: Teng Wu <gigadevice2025@gmail.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
Teng Wu 2025-08-18 21:52:27 +08:00 committed by Miquel Raynal
parent 5b5dc7db5a
commit 49b9254217

View File

@ -4,6 +4,7 @@
* Chuanhong Guo <gch981213@gmail.com>
*/
#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
@ -23,6 +24,18 @@
#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),
@ -63,6 +76,74 @@ 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)
{
@ -542,7 +623,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
gd5fxgq4uexxg_ecc_get_status)),
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),
@ -552,10 +634,31 @@ static const struct spinand_info gigadevice_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
gd5fxgq4uexxg_ecc_get_status)),
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(sizeof(*priv), GFP_KERNEL);
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 = {