Edit online

配置 xxx_spinand_table

xxx_spinand_table 数据结构为第二级索引,通过 SPINAND_INFO 宏管理,描述厂商的系列器件信息,是 SPI NAND 驱动的核心。
1. spinand_table 配置描述
参数名称 描述
SPINAND_ID SPINAND_ID 宏,需要传入 2 到 3 个参数,具体需参照器件 DATASHEET 9Fh 命令之后的波形进行选择。
NAND_MEMORG 描述芯片存储结构,可在器件 DATASHEET 中查找参数定义。
NAND_ECCREQ NAND_ECCREQ 宏,描述每个 stp byte 包含多少位的内部 ECC,需要传入 2 个参数:
  • 用于存储每次 ECC 值的 Bit 数量
  • 每次 ECC 计算的存储区的数据 Byte 数量
SPINAND_INFO_OP_VARIANTS 定义不同总线宽度下的缓存操作接口,包括读取缓存、写入缓存和更新缓存的操作:
  • read_cache_variants:描述 SPI Master 从 颗粒的 CACHE 中读取数据时的协议格式。可在器件 DATASHEET 的相关章节找到各个命令所需的协议格式。
  • write_cache_variants:所有器件的写缓存操作设置都一致,不需要根据数据手册更改。
  • update_cache_variants:所有器件的更新缓存操作设置都一致,不需要根据数据手册更改。
SPINAND_ECCINFO 分为两个部分:
  • ooblayout:ooblayot 操作函数集合,用于区分 OOB 区域专供硬件 ECC使用和可供软件使用的区域划分。
  • get_status:获取 ECC 状态,用于解析每次读/写操作时,ECC 校验的结果。
winbond_spinand_table 示例:
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)),
};
gigadevice_spinand_table 示例:
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__
}
Edit online

配置 SPINAND_ID

SPINAND_ID 宏,需要传入2-3 个参数,具体需参照器件 DATASHEET 9Fh 命令之后的波形进行选择。可选项如下:
2. 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),


1. W25N02KV SPINAND_ID 配置

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



2. GD5F1GM7UE SPINAND_ID 配置

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 宏会对其进行处理。

Edit online

配置 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
    }
3. NAND_MEMORG 配置描述
参数名称 描述
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


FM25S01A-1

3. W25N02KV 数据手册描述示例

配置 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。


F35SQA-1

4. GD5F1GM7UE 数据手册描述示例
Edit online

配置 NAND_ECCREQ

Error Correcting Code (ECC),可以在数据手册中搜索 ECC 字样获得。 NAND_ECCREQ 参数用来描述每个 stp byte 包含多少位的内部 ECC。

NAND_ECCREQ 宏需要传入下列两个参数:
NAND_ECCREQ(参数1, 参数2),
  • 参数 1:用于存储每次 ECC 值的 Bit 数量。
  • 参数2:每次 ECC 计算的存储区的数据 Byte 数量。

NAND_ECCREQ 配置示例——W25N02KV

如图所示,在 W25N02KV 数据手册中搜索 ECC,可以获得如下参数信息:
NAND_ECCREQ(8, 512),
  • 参数 1:用于存储每次 ECC 计算值为 8 Bit。
  • 参数 1:其 Page Size 为 2048 Bytes,每个Page 分 4 个 Sector 进行,故每次计算 512 Bytes。


5. W25N02KV ECC 信息

NAND_ECCREQ 配置示例——GD5F1GM7UE

如图所示,在 GD5F1GM7UE 数据手册中搜索 ECC,可以获得如下信息:
NAND_ECCREQ(8, 512),


6. GD5F1GM7UE ECC 信息
Edit online

配置 SPINAND_INFO_OP_VARIANTS

在配置 SPI NAND 设备时,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_INFO_OP_VARIANTS,即配置 read_cache_variants 参数,详细配置说明如下所示:
4. read_cache_variants 配置描述
参数示例 描述
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) 章节获取各个命令需要的协议格式。

以下是如何根据数据手册设置缓存操作的详细步骤:
注:
关于下列参数的详细描述,可查看read_cache_variants 配置描述
  • 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


Edit online

配置 SPINAND_ECCINFO

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 函数。


toshiba-ecc

在检测到发生比特翻转之后,为了获取准确的翻转数量, W25N02KV 还提供了 0fh + 地址 30h 的命令格式,参考下图:


#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 函数。


gd-ecc

如下图所示,当 ECCS0 为 1 且 ECCS1 为 0 时,代表有 1 到 7 个 bit 发生了翻转, GD5F1GM7UE 提供了ECCSE0和 ECCSE1 2 个额外的 bit 用来确定翻转的 bit 数。


#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;
}