配置 xxx_spinand_table
xxx_spinand_table
数据结构为第二级索引,通过 SPINAND_INFO 宏管理,描述厂商的系列器件信息,是 SPI NAND
驱动的核心。参数名称 | 描述 |
---|---|
SPINAND_ID | SPINAND_ID 宏,需要传入 2 到 3 个参数,具体需参照器件 DATASHEET 9Fh 命令之后的波形进行选择。 |
NAND_MEMORG | 描述芯片存储结构,可在器件 DATASHEET 中查找参数定义。 |
NAND_ECCREQ | NAND_ECCREQ 宏,描述每个 stp byte 包含多少位的内部 ECC,需要传入 2 个参数:
|
SPINAND_INFO_OP_VARIANTS | 定义不同总线宽度下的缓存操作接口,包括读取缓存、写入缓存和更新缓存的操作:
|
SPINAND_ECCINFO | 分为两个部分:
|
static const struct spinand_info winbond_spinand_table[] = {
SPINAND_INFO("W25M02GV",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2),
NAND_ECCREQ(1, 512),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
0,
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
SPINAND_SELECT_TARGET(w25m02gv_select_target)),
/* W25N02KV 新增 */
SPINAND_INFO("W25N02KV",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
NAND_ECCREQ(8, 512),
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
&write_cache_variants,
&update_cache_variants),
0,
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
};
static const struct spinand_info gigadevice_spinand_table[] = {
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)),
/* GD5F1GM7UE 新增 */
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)),
};
#define SPINAND_INFO(__model, __id, __memorg, __eccreq, __op_variants, \
__flags, ...)
{
.model = __model, // 对应器件型号,描述字符,不进行具体匹配
.devid = __id, // 对应器件 DID(DeviceID),是该器件的唯一标识
.memorg = __memorg, // 器件存储结构
.eccreq = __eccreq, // 请求ECC的参数
.op_variants = __op_variants, // 读写函数操作集合地址
.flags = __flags, // 功能标识
__VA_ARGS__
}
配置 SPINAND_ID
SPINAND_ID 可选项 | 描述 |
---|---|
SPINAND_READID_METHOD_OPCODE | 颗粒接收到 9Fh 命令之后立刻返回 MID。 |
SPINAND_READID_METHOD_OPCODE_ADDR | 颗粒接收到 9Fh 命令之后,先返回1 byte 地址,再返回 MID。 |
SPINAND_READID_METHOD_OPCODE_DUMMY | 颗粒接收到 9Fh 命令之后,先经过 8 Bit Dummy Clocks,再返回 MID。 |
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22),

如W25N02KV SPINAND_ID 配置所示,接收到 9Fh 命令之后,先经过了8 Bit Dummy Clocks,再返回 MID,则 SPINAND_ID 应填写为 SPINAND_READID_METHOD_OPCODE_DUMMY。

如GD5F1GM7UE SPINAND_ID 配置所示,接收到 9Fh 命令之后,先经过了8 Bit Dummy Clocks,再返回 MID,则 SPINAND_ID 应填写为 SPINAND_READID_METHOD_OPCODE_DUMMY:
SPINAND_ID 中第 2 到 3 个参数会传入颗粒的 DID,部分颗粒的 DID 只有 1 个 Byte,部分颗粒的有2 个 Bytes。直接填入 DID 数据即可,SPINAND_ID 宏会对其进行处理。
配置 NAND_MEMORG
memorg
通过 NAND_MEMORG 结构描述。一般情况下,器件数据手册会在开头描述
memorg
信息。NAND_MEMORG
的参数示例描述如下:#define NAND_MEMORG(bpc, ps, os, ppe, epl, mbb, ppl, lpt, nt)
{
.bits_per_cell = (bpc), // Cell 是 NAND 的最小单元,一般只能存储 1bit,少有其它值
.pagesize = (ps), // 页大小,大部分的器件的页通过 (N + Mbytes)的方式描述,N 为页大小,M 为 oob
.oobsize = (os), //界外大小,一般用于存放 ECC 校验数据或其它数据,和 pagesize 共同描述
.pages_per_eraseblock = (ppe), // 一个擦除块有多少个页
.eraseblocks_per_lun = (epl), // 一个 lun(die)有多少个擦除块,1Gb/(64 x 2048 x 8) = 1024
.max_bad_eraseblocks_per_lun = (mbb), //器件出厂的最大坏块数,一般在数据手册中通过 bad blocks 查找到
.planes_per_lun = (ppl), //一般设置为 1,单 die
.luns_per_target = (lpt), //一般设置为 1
.ntargets = (nt), //一般设置为 1
}
参数名称 | 描述 |
---|---|
bits_per_cell (bpc) | 一个 cell 所能存储的 bit 大小,一般只能存储 1 bit。 Cell 是 NAND 的最小单元。 |
pagesize (ps) | 页大小,多数器件的页通过 (N + Mbytes)的方式描述,其中 N 为页大小,M 为 oob。 |
oobsize (os) | 界外大小,一般用于存放 ECC 校验数据或其它数据,和 pagesize 共同描述。 |
pages_per_eraseblock (ppe) | 一个擦除块的页数量。 |
eraseblocks_per_lun (epl) | 一个 lun(die)所含的擦除块 数量,1Gb/(64 x 2048 x 8) = 1024 |
max_bad_eraseblocks_per_lun (mbb) | 器件出厂的最大坏块数,一般在数据手册中通过搜索 bad blocks 查找到。 |
planes_per_lun (ppl) | 一般设置为 1,单 die |
luns_per_target (lpt) | 一般设置为 1 |
ntargets (nt) | 一般设置为 1 |
配置 W25N02KV 中的 NAND_MEMORG
对于 W25N02KV,按照下列描述配置参数 NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1)。
在 DATASHEET 中搜索 bits per cell
可以看到在Parameter Page Data
Definitions 中有定义:
-
Number of bits per cell: 值为 01h,则 bpc 设置为 1
-
Number of data bytes per page: 值为 00h, 08h,则 ps 设置为 2048(0x800)
-
Number of spare bytes per page: 值为 80h, 00h,则 OS 设置为 128(0x80)
-
Number of pages per block: 值为 40h, 00h,则 ppe 设置为 64(0x40)
-
Number of blocks per logical unit: 值为 00h, 08h,则 epl 设置为 2048(0x800)
-
Bad blocks maximum per unit:值为 28h, 00h,则 mbb 设置为 40(0x28)
-
Number of plane address bits:lpt,值为 00h,则 ppl 设置为 1。不支持多 plane
-
lpt 和 nt 默认设置为1

配置 GD5F1GM7UE 中的 NAND_MEMORG
对于 GD5F1GM7UE,按照下列描述配置参数 NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1)。
在 DATASHEET 中搜索 bits per cell
可以看到在Parameter Page Data
Definitions 中有定义:
-
Number of bits per cell: 值为 01h,则 bpc 设置为 1。
-
Number of data bytes per page: 值为 00h, 08h,则 ps 设置为 2048(0x800)。
-
Number of spare bytes per page: 值为 80h, 00h,则 OS 设置为 128(0x80)。
-
Number of pages per block: 值为 40h, 00h,则 ppe 设置为 64(0x40)。
-
Number of blocks per logical unit: 值为 00h, 04h,则 epl 设置为 1024(0x400)。
-
Bad blocks maximum per unit:值为 14h, 00h,则 mbb 设置为 20(0x14)。
-
logical planes per:值为 01h,则 ppl 设置为 1。
-
lpt 和 nt 默认设置为1。

配置 NAND_ECCREQ
Error Correcting Code (ECC),可以在数据手册中搜索 ECC
字样获得。 NAND_ECCREQ
参数用来描述每个 stp byte 包含多少位的内部 ECC。
NAND_ECCREQ(参数1, 参数2),
- 参数 1:用于存储每次 ECC 值的 Bit 数量。
- 参数2:每次 ECC 计算的存储区的数据 Byte 数量。
NAND_ECCREQ 配置示例——W25N02KV
ECC
,可以获得如下参数信息:NAND_ECCREQ(8, 512),
- 参数 1:用于存储每次 ECC 计算值为 8 Bit。
- 参数 1:其 Page Size 为 2048 Bytes,每个Page 分 4 个 Sector 进行,故每次计算 512 Bytes。

NAND_ECCREQ 配置示例——GD5F1GM7UE
ECC
,可以获得如下信息:NAND_ECCREQ(8, 512),

配置 SPINAND_INFO_OP_VARIANTS
#define SPINAND_INFO_OP_VARIANTS(__read, __write, __update) \
{ \
.read_cache = __read, \
.write_cache = __write, \
.update_cache = __update, \
}
- read_cache_variants:描述 SPI Master 从 颗粒的 CACHE 中读取数据时的协议格式。可在器件 DATASHEET
的相关章节找到各个命令所需的协议格式。
static SPINAND_OP_VARIANTS(read_cache_variants, SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), //command id:0xeb SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), //command id:0x6b SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), //command id:0xbb SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), //command id:0x3b SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), //command id:0x0b SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); //command id:0x03
- write_cache_variants:所有器件的写缓存操作设置都一致,不需要根据数据手册更改:
static SPINAND_OP_VARIANTS(update_cache_variants, SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), SPINAND_PROG_LOAD(false, 0, NULL, 0));
- update_cache_variants
所有器件的更新缓存操作设置都一致,不需要根据数据手册更改:
static SPINAND_OP_VARIANTS(update_cache_variants, SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), SPINAND_PROG_LOAD(false, 0, NULL, 0));
参数示例 | 值 | 描述 |
---|---|---|
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP 以 (0, 1, NULL, 0) 为例进行说明 |
0 | 地址,后续赋值,以此设为固定值 0。 |
1 | dummy 的 bite 长度,大部分为 1,少数部分为 2,可在数据手册中查找获得。 如果器件对 dummy 没有明确标识,则设置为 1。 注: 在 (0, 1, NULL, 0) 代表的四个参数中,仅需从数据手册中去获取 dummy 的长度即可,其它均赋固定值。 |
|
NULL | 读取数据的 buf ,后续赋值,此处固定为 NULL。 | |
0 | 读取数据的长度参数,后续赋值,设置为固定值 0。 | |
SPINAND_PAGE_READ_FROM_CACHE_OP 以 (true, 0, 1, NULL, 0) 为例进行说明 |
- |
参数与其他宏增加了第一个参数用于指定是否使能 fast read 模式,是则使用 0bh 命令, 否则使用 03h。二者需要同时存在;后面参数相比其他宏依次后延。 |
配置 read_cache_variants —— W25N02KV
根据数据手册中的命令列表,为不同的总线宽度配置读取缓存操作。可以在数据手册中 Instruction Set Table 1 (Buffer Read,
BUF = 1, default)
章节获取各个命令需要的协议格式。
-
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0)
- 在 (0, 1, NULL, 0) 代表的四个参数中,仅需从数据手册中去获取 dummy 的长度即可,其它均赋固定值。
- 如果有些器件对 0xeb 的 dummy 没有明确标识,则设置为 1。
- 如果器件支持的命令较少,需要去掉部分不支持的操作。可根据 variants 的 command id 来判断。
- SPINAND_PAGE_READ_FROM_CACHE_OP
配置 read_cache_variants —— GD5F1GM7UE
根据数据手册中的命令列表,为不同的总线宽度配置读取缓存操作。可以在数据手册中 Instruction Set Table 1 (Buffer Read,
BUF = 1, default)
章节获取各个命令需要的协议格式。
-
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0)
- 在 (0, 1, NULL, 0) 代表的四个参数中,仅需从数据手册中去获取 dummy 的长度即可,其它均赋固定值。
- 如果有些器件对 0xeb 的 dummy 没有明确标识,则设置为 1。
- 如果器件支持的命令较少,需要去掉部分不支持的操作。可根据 variants 的 command id 来判断。
- SPINAND_PAGE_READ_FROM_CACHE_OP
配置 SPINAND_ECCINFO
- ooblayout:用于区分 OOB 区域专供硬件 ECC 使用和可供软件使用的区域划分。一般情况下,ooblayout 分为下列三个区域:注:各个器件的区域名称可能不完全一致。
- Main:可分为 4 个 sector。
- User:除 OOB 首 1-2 字节用于记录坏块标记(通用标准)外,剩余空间可供软件使用。
- Parity:一般用于存储硬件 ECC 校验值。
可在器件 DATASHEET 中查找 Page 区域划分明细。
- get_status :用于解析每次读/写操作时,ECC 校验的结果。
SPI NAND Flash 因为其物理特性,在存储过程中可能会发生比特翻转,每次读/写操作后颗粒固件都会对数据进行ECC 校验操作,发现比特翻转会在本身能力范围内将数据修复回来,并上报翻转的比特数,若超出其自身修复能力,则返回错误。 软件需要根据ECC 的状态做相应的处理。
在 DATASHEET 中搜索 ECC Status,找到 ECC0, ECC1 在状态寄存器中的位置。两个寄存器位的状态组合分别代表 ECC 校验的状态。对照 DATASHEET 中的描述编写编写 get_status 函数。
配置 SPINAND_ECCINFO —— W25N02KV
以下是 W25N02KV 的 ECCINFO 的配置示例。
在 DATASHEET 中搜索 ECC Status,找到 ECC-0 和 ECC-1 在状态寄存器中的位置。2 个寄存器位的状态组合分别代表ECC 校验的状态。对照 DATASHEET 中的描述编写编写 get_status 函数。


#define STATUS_ECC_MASK GENMASK(5, 4) /* ECC 状态位在状态寄存器的Bit4 和 Bit5。 */
#define STATUS_ECC_NO_BITFLIPS (0 << 4) /* 2 个Bit 都为0时表示没有比特翻转发生。 */
#define STATUS_ECC_HAS_BITFLIPS (1 << 4) /* 有比特翻转发生,且ECC已经完成修复,数据正确。 */
#define STATUS_ECC_UNCOR_ERROR (2 << 4) /* 有比特翻转发生,且超出ECC修复能力,数据不正确。 */
static int w25n02kv_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
u8 mbf = 0;
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
return 0;
case STATUS_ECC_UNCOR_ERROR: /* 比特翻转超出ECC修复能力,返回ECC错误。*/
return -EBADMSG;
case STATUS_ECC_HAS_BITFLIPS:
/*
* Let's try to retrieve the real maximum number of bitflips
* in order to avoid forcing the wear-leveling layer to move
* data around if it's not necessary.
*/
if (spi_mem_exec_op(spinand->spimem, &op)) /* 使用0fh 命令 + 30h 地址可以查看确切的比特翻转数量。 */
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;
}
配置 SPINAND_ECCINFO —— GD5F1GM7UE
以下是 GD5F1GM7UE 的 ECCINFO 的配置示例。
在 DATASHEET 中搜索 Status register,找到 ECCS0 和 ECCS1 在状态寄存器中的位置。两个寄存器位的状态组合分别代表 ECC 校验的状态。对照 DATASHEET 中的描述编写编写 get_status 函数。


#define STATUS_ECC_MASK GENMASK(5, 4) /* ECC 状态位在状态寄存器的Bit4 和 Bit5。 */
#define STATUS_ECC_NO_BITFLIPS (0 << 4) /* 没有比特翻转发生。 */
#define STATUS_ECC_UNCOR_ERROR (2 << 4) /* 有比特翻转发生,且超出ECC修复能力,数据不正确。 */
#define GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS BIT(4) /* 有1-7 个Bit 发生翻转,且已修复,数据正确。 */
#define GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS (3 << 4) /* 有8 个Bit 发生翻转,且已修复,数据正确。 */
#define GD5FXGQXXEXXG_REG_STATUS2 0xf0
static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
u8 status2;
struct spi_mem_op op = SPINAND_GET_FEATURE_OP(GD5FXGQXXEXXG_REG_STATUS2, /* 获取ECCSE0, ECCSE1 的命令是 f0h */
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;
}