Linux学习笔记协议篇(六):SPI FLASH设备驱动

目录

一、设备树解析

二、SPI设备驱动代码分析

1、spi_nor_probe

2、spi_nor_scan

(1)协议配置

(2)初始化Flash参数(核心步骤)

(3)MTD子系统集成

[(3)配置 SPI 通信参数](#(3)配置 SPI 通信参数)

spi_nor_setup

spi_nor_set_addr_width

(4)初始化设备

3、spi_nor_create_read_dirmap

4、mtd_device_register

三、spi_nor_probe函数整体总结



根据 "Linux学习笔记协议篇(六):SPI控制器驱动":

SPI控制器驱动在与设备树匹配后,加载在platform总线上,生成相应SPI bus,随后扫秒该SPI控制器下挂的设备树子节点,将该子节点转化为设备信息

以/linux-5.14.10/arch/arm/boot/dts/imx6ul-14x14-evk.dtsi样板中的qspi设备为例:

一、设备树解析

复制代码
&qspi {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_qspi>;
	status = "okay";

	flash0: n25q256a@0 {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "micron,n25q256a", "jedec,spi-nor";
		spi-max-frequency = <29000000>;
		spi-rx-bus-width = <4>;
		spi-tx-bus-width = <4>;
		reg = <0>;
	};
};

flash0: n25q256a@0:定义了一个名为flash0的子节点

  • n25q256a@0表示这是Micron的n25q256a芯片,位于片选0
  • flash0是标签(label),可供其他节点引用

address-cells和size-cells

  • 都设置为1,表示地址和大小都用1个32位数表示

compatible

  • "micron,n25q256a": 精确匹配Micron的n25q256a芯片
  • "jedec,spi-nor": 作为后备,匹配任何符合JEDEC标准的SPI NOR Flash

SPI相关参数

  • spi-max-frequency = <29000000>: 最大SPI时钟频率29MHz
  • spi-rx-bus-width = <4>: 接收总线宽度为4线(Quad模式)
  • spi-tx-bus-width = <4>: 发送总线宽度为4线(Quad模式)

reg = <0>

  • 表示该设备使用片选信号0(CS0)

二、SPI设备驱动代码分析

在内核上电加载到该设备树子节点对应的设备驱动时,设备驱动会通过Compatible匹配与挂接在SPI控制器下子节点相关的设备,首先查找与n25q256a相适配的Compatible属性:

复制代码
/linux-5.14.10/drivers/mtd/spi-nor/micron-st.c

static const struct flash_info st_parts[] = {
    ... ...
    { "n25q256a",    INFO(0x20ba19, 0, 64 * 1024,  512, SECT_4K |
                  USE_FSR | SPI_NOR_DUAL_READ |
                  SPI_NOR_QUAD_READ) },
    ... ...
    }

const struct spi_nor_manufacturer spi_nor_st = {
	.name = "st",
	.parts = st_parts,
	.nparts = ARRAY_SIZE(st_parts),
	.fixups = &micron_st_fixups,
};

/linux-5.14.10/drivers/mtd/spi-nor/core.c
static const struct spi_nor_manufacturer *manufacturers[] = {
    ... ...
	&spi_nor_micron,
	&spi_nor_st,
    ... ...
};

代码中没有找到与n25q256a相适配的Compatible属性,因此查找 jedec,spi-nor 属性:

复制代码
static const struct of_device_id spi_nor_of_table[] = {
    ... ...
	{ .compatible = "jedec,spi-nor" },
	{ /* sentinel */ },
};

static struct spi_mem_driver spi_nor_driver = {
	.spidrv = {
		.driver = {
			.name = "spi-nor",
			.of_match_table = spi_nor_of_table,
			.dev_groups = spi_nor_sysfs_groups,
		},
		.id_table = spi_nor_dev_ids,
	},
	.probe = spi_nor_probe,
	.remove = spi_nor_remove,
	.shutdown = spi_nor_shutdown,
};
module_spi_mem_driver(spi_nor_driver);

.compatible = "jedec,spi-nor 表示该驱动支持所有兼容 JEDEC 标准的 SPI NOR 闪存,在设备树与驱动匹配后,会触发 spi_nor_probe 函数:

1、spi_nor_probe
复制代码
static int spi_nor_probe(struct spi_mem *spimem)
{
    // 获取底层 SPI 设备
    struct spi_device *spi = spimem->spi;
    // 获取平台数据(传统方式,非设备树)
    struct flash_platform_data *data = dev_get_platdata(&spi->dev);
    struct spi_nor *nor;  // SPI NOR 设备结构体
    /*
     * 默认启用所有硬件能力,核心会通过 spi_mem_supports_op() 
     * 检查实际支持的能力后进行屏蔽
     */
    const struct spi_nor_hwcaps hwcaps = { .mask = SNOR_HWCAPS_ALL };
    char *flash_name;  // Flash 名称
    int ret;          // 返回值

    /* 1. 分配并初始化 spi_nor 结构体 */
    // 使用设备资源管理的内存分配(自动释放)
    nor = devm_kzalloc(&spi->dev, sizeof(*nor), GFP_KERNEL);
    if (!nor)
        return -ENOMEM;  // 内存不足错误

    /* 2. 基本设备信息设置 */
    nor->spimem = spimem;       // 关联 SPI 内存设备
    nor->dev = &spi->dev;       // 关联设备结构
    spi_nor_set_flash_node(nor, spi->dev.of_node);  // 设置设备树节点

    // 将 nor 设置为驱动私有数据
    spi_mem_set_drvdata(spimem, nor);

    /* 3. 设置 MTD 设备名称 */
    if (data && data->name)
        nor->mtd.name = data->name;  // 使用平台数据中的名称

    if (!nor->mtd.name)
        nor->mtd.name = spi_mem_get_name(spimem);  // 获取 SPI 设备名称

    /*
     * 处理历史遗留的命名问题:
     * 许多平台在 flash_platform_data 中提供两个名称:
     * - "name": 通常设为 "m25p80"(通用名)
     * - "type": 实际芯片型号
     * 优先使用 "type",如果没有则尝试自动检测
     */
    if (data && data->type)
        flash_name = data->type;  // 使用平台数据中的实际型号
    else if (!strcmp(spi->modalias, "spi-nor"))
        flash_name = NULL;  // NULL 表示需要自动检测
    else
        flash_name = spi->modalias;  // 使用 SPI 设备别名

    /* 4. 扫描并识别 Flash 芯片 */
    ret = spi_nor_scan(nor, flash_name, &hwcaps);
    ...
    /*
     * 处理大页大小情况(安全防护):
     * 目前没有 NOR Flash 的页大小超过 PAGE_SIZE(通常 4KB),
     * 但为防止未来支持此类设备时出现缓冲区溢出,添加保护逻辑
     */
    if (nor->page_size > PAGE_SIZE) {
        nor->bouncebuf_size = nor->page_size;
        devm_kfree(nor->dev, nor->bouncebuf);  // 释放原有缓冲区
        // 重新分配足够大的缓冲区
        nor->bouncebuf = devm_kmalloc(nor->dev,
                          nor->bouncebuf_size,
                          GFP_KERNEL);
        if (!nor->bouncebuf)
            return -ENOMEM;  // 内存不足
    }

    /* 5. 创建直接读取映射 */
    ret = spi_nor_create_read_dirmap(nor);
    if (ret)
        return ret;

    /* 6. 创建直接写入映射 */
    ret = spi_nor_create_write_dirmap(nor);
    if (ret)
        return ret;

    /* 7. 注册 MTD 设备 */
    return mtd_device_register(&nor->mtd, 
             data ? data->parts : NULL,    // 分区信息
             data ? data->nr_parts : 0);  // 分区数量
}
2、spi_nor_scan
复制代码
int spi_nor_scan(struct spi_nor *nor, const char *name,
         const struct spi_nor_hwcaps *hwcaps)
{
    const struct flash_info *info;  // Flash芯片信息表
    struct device *dev = nor->dev;  // 关联的设备结构
    struct mtd_info *mtd = &nor->mtd;  // MTD接口结构
    struct device_node *np = spi_nor_get_flash_node(nor);  // 设备树节点
    int ret;
    int i;

    /* 1. 基础检查 */
    ret = spi_nor_check(nor);  // 验证nor结构有效性
    if (ret)
        return ret;

    /* 2. 初始化默认SPI协议 */
    nor->reg_proto = SNOR_PROTO_1_1_1;    // 寄存器操作使用标准SPI
    nor->read_proto = SNOR_PROTO_1_1_1;   // 读操作默认模式
    nor->write_proto = SNOR_PROTO_1_1_1;  // 写操作默认模式

    /* 3. 分配缓冲区(bounce buffer) */
    nor->bouncebuf_size = PAGE_SIZE;  // 初始按页大小分配(通常4KB)
    nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size, GFP_KERNEL);
    if (!nor->bouncebuf)
        return -ENOMEM;  // 用于DMA操作的临时缓冲区

    /* 4. 获取Flash芯片信息 */
    info = spi_nor_get_flash_info(nor, name);  // 通过JEDEC ID或名称匹配
    if (IS_ERR(info))
        return PTR_ERR(info);
    nor->info = info;  // 关联芯片信息表

    spi_nor_debugfs_init(nor, info);  // 初始化debugfs接口

    /* 5. 初始化互斥锁和特殊标志 */
    mutex_init(&nor->lock);  // 初始化设备操作锁

    // Xilinx S3AN特殊标志(与Atmel共享制造商ID)
    if (info->flags & SPI_NOR_XSR_RDY)
        nor->flags |= SNOR_F_READY_XSR_RDY;

    // 支持写保护锁的区域
    if (info->flags & SPI_NOR_HAS_LOCK)
        nor->flags |= SNOR_F_HAS_LOCK;

    /* 6. 初始化MTD操作接口 */
    mtd->_write = spi_nor_write;  // 绑定写操作函数

    /* 7. 初始化Flash参数(核心步骤) */
    ret = spi_nor_init_params(nor);  // 基于info和SFDP表初始化参数
    if (ret)
        return ret;

    /* 8. 设置MTD设备属性 */
    if (!mtd->name)
        mtd->name = dev_name(dev);  // 默认使用设备名
    mtd->priv = nor;        // 关联私有数据
    mtd->type = MTD_NORFLASH;  // 设备类型
    mtd->writesize = nor->params->writesize;  // 写入页大小
    mtd->flags = MTD_CAP_NORFLASH;  // 设备能力标志
    mtd->size = nor->params->size;  // 总容量
    mtd->_erase = spi_nor_erase;    // 擦除操作
    mtd->_read = spi_nor_read;      // 读操作
    mtd->_suspend = spi_nor_suspend; // 电源管理
    mtd->_resume = spi_nor_resume;
    mtd->_get_device = spi_nor_get_device;  // 设备引用计数
    mtd->_put_device = spi_nor_put_device;

    /* 9. 处理特殊功能标志 */
    if (info->flags & USE_FSR)
        nor->flags |= SNOR_F_USE_FSR;  // 使用Flag Status Register
    if (info->flags & SPI_NOR_HAS_TB) {
        nor->flags |= SNOR_F_HAS_SR_TB;  // 支持Top/Bottom保护
        if (info->flags & SPI_NOR_TB_SR_BIT6)
            nor->flags |= SNOR_F_HAS_SR_TB_BIT6;  // TB位在状态寄存器第6位
    }

    /* 10. 擦除特性配置 */
    if (info->flags & NO_CHIP_ERASE)
        nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;  // 不支持整片擦除
    if (info->flags & USE_CLSR)
        nor->flags |= SNOR_F_USE_CLSR;  // 需要清除状态寄存器

    /* 11. 写保护配置 */
    if (info->flags & SPI_NOR_SWP_IS_VOLATILE)
        nor->flags |= SNOR_F_SWP_IS_VOLATILE;  // 易失性写保护
    if (info->flags & SPI_NOR_4BIT_BP) {
        nor->flags |= SNOR_F_HAS_4BIT_BP;  // 4位块保护
        if (info->flags & SPI_NOR_BP3_SR_BIT6)
            nor->flags |= SNOR_F_HAS_SR_BP3_BIT6;  // BP3位在状态寄存器第6位
    }

    /* 12. 特殊模式配置 */
    if (info->flags & SPI_NOR_NO_ERASE)
        mtd->flags |= MTD_NO_ERASE;  // 设备不支持擦除操作

    /* 13. 设备树特殊处理 */
    if (of_property_read_bool(np, "broken-flash-reset"))
        nor->flags |= SNOR_F_BROKEN_RESET;  // 标记复位有缺陷的设备

    /* 14. 配置SPI协议和操作码 */
    ret = spi_nor_setup(nor, hwcaps);  // 设置读/写/擦除操作码和协议
    if (ret)
        return ret;

    /* 15. 4字节地址模式处理 */
    if (info->flags & SPI_NOR_4B_OPCODES)
        nor->flags |= SNOR_F_4B_OPCODES;  // 支持4字节地址指令

    /* 16. 初始化地址宽度 */
    ret = spi_nor_set_addr_width(nor);  // 根据容量设置3B/4B地址模式
    if (ret)
        return ret;

    /* 17. 注册锁操作 */
    spi_nor_register_locking_ops(nor);  // 初始化写保护操作

    /* 18. 发送初始化序列 */
    ret = spi_nor_init(nor);  // 执行厂商特定的初始化
    if (ret)
        return ret;

    /* 19. OTP区域初始化 */
    spi_nor_otp_init(nor);  // 一次性可编程区域设置
    ... ...

代码是一个函数用于扫描和初始化一个 SPI NOR Flash 存储设备。它主要完成以下任务:

  • 检查设备的基本配置。
  • 配置 SPI 通信协议。
  • 分配和初始化缓冲区。
  • 读取并解析设备的 Flash 信息。
  • 初始化 MTD(Memory Technology Device)子系统。
  • 配置 SPI NOR 的具体参数和操作。
  • 初始化设备的其他功能(如 OTP、锁定操作等)。
  • 打印设备信息。

关键点解析:

(1)协议配置
复制代码
nor->reg_proto = SNOR_PROTO_1_1_1;    // 寄存器操作使用标准SPI
nor->read_proto = SNOR_PROTO_1_1_1;   // 读操作默认模式
nor->write_proto = SNOR_PROTO_1_1_1;  // 写操作默认模式

SNOR_PROTO_1_1_1 = SNOR_PROTO_STR(1, 1, 1),

#define SNOR_PROTO_STR(_inst_nbits, _addr_nbits, _data_nbits)	\
	(SNOR_PROTO_INST(_inst_nbits) |				\
	 SNOR_PROTO_ADDR(_addr_nbits) |				\
	 SNOR_PROTO_DATA(_data_nbits))

使用了 SNOR_PROTO_1_1_1 初始化一个 SPI NOR Flash 存储器的配置,表示指令、地址和数据的位宽均为 1 位,即指定其通信协议为标准 SPI 模式

(2)初始化Flash参数(核心步骤)
复制代码
ret = spi_nor_init_params(nor);  // 基于info和SFDP表初始化参数

整体调用流程:

复制代码
spi_nor_init_params()
├── 分配 params 内存
├── spi_nor_info_init_params() [阶段1: 基础默认参数]
│   ├── 设置默认参数(quad_enable, params->setup, set_4byte_addr_mode等)
│   ├── 配置读取能力(READ, READ_FAST, DUAL/QUAD/OCTAL等)
│   ├── 配置页编程能力(PP)
│   └── 配置擦除类型(4K, sector等)
├── spi_nor_manufacturer_init_params() [阶段2: 制造商参数]
│   └── 调用制造商特定的初始化(通过->default_init钩子)
├── spi_nor_sfdp_init_params() [阶段3: SFDP标准参数] (条件执行)
│   ├── 备份当前参数
│   ├── spi_nor_parse_sfdp() [解析SFDP表]
│   └── 失败时恢复备份参数
├── spi_nor_post_sfdp_fixups() [阶段4: SFDP后修正]
│   ├── 调用制造商特定的修正(->post_sfdp)
│   └── 调用闪存信息特定的修正(->post_sfdp)
└── spi_nor_late_init_params() [阶段5: 后期初始化]
    └── 初始化默认锁定操作(如果需要)
(3)MTD子系统集成
复制代码
mtd->_write = spi_nor_write;
... ...
if (!mtd->name)
    mtd->name = dev_name(dev);
mtd->priv = nor;
mtd->type = MTD_NORFLASH;
mtd->writesize = nor->params->writesize;
mtd->flags = MTD_CAP_NORFLASH;
mtd->size = nor->params->size;
mtd->_erase = spi_nor_erase;
mtd->_read = spi_nor_read;
mtd->_suspend = spi_nor_suspend;
mtd->_resume = spi_nor_resume;
mtd->_get_device = spi_nor_get_device;
mtd->_put_device = spi_nor_put_device;
  • 初始化 MTD 子系统,设置读写、擦除、挂起、恢复等操作的回调函数。
  • 设置 MTD 的类型为 MTD_NORFLASH,表示这是一个 NOR Flash 设备。
  • 配置 MTD 的其他参数,如名称、大小、写块大小等。
(3)配置 SPI 通信参数
复制代码
ret = spi_nor_setup(nor, hwcaps);
... ...
ret = spi_nor_set_addr_width(nor);
  • 调用 spi_nor_setup 配置 SPI 通信参数,如操作码、虚拟周期数、SPI 协议等。
  • 调用 spi_nor_set_addr_width 设置地址宽度。

其中:

spi_nor_setup
复制代码
static int spi_nor_setup(struct spi_nor *nor,
			 const struct spi_nor_hwcaps *hwcaps)
{
	if (!nor->params->setup)
		return 0;

	return nor->params->setup(nor, hwcaps);
}
  • 该函数用于设置或初始化一个 spi_nor 对象,如果 spi_norparams->setup 函数指针为空,则无需设置,如果 setup 函数指针存在,则调用它并返回其结果,nor->params->setup有如下初始化:

    --> spi_nor_scan
    --> spi_nor_init_params
    --> spi_nor_info_init_params
    --> params->setup = spi_nor_default_setup

spi_nor_default_setup 主要是为一个 SPI NOR 闪存设备设置默认的硬件能力和操作命令,以确保设备能够与 SPI 控制器正确协作:

关键部分:

(1)通过按位与操作,计算出 SPI 控制器和 SPI 闪存都支持的硬件能力掩码 shared_mask

复制代码
shared_mask = hwcaps->mask & params->hwcaps.mask;

(2)如果使用 spimem 接口,调用 spi_nor_spimem_adjust_hwcaps 函数进一步调整 shared_mask。如果不使用 spimem 接口,移除不支持的 SPI n-n-n 协议能力

复制代码
 if (nor->spimem) {
        spi_nor_spimem_adjust_hwcaps(nor, &shared_mask);
    } else {
        ignored_mask = SNOR_HWCAPS_X_X_X | SNOR_HWCAPS_X_X_X_DTR; // 定义需要忽略的能力掩码
        if (shared_mask & ignored_mask) {
            dev_dbg(nor->dev,
                    "SPI n-n-n protocols are not supported.\n"); // 输出调试信息
            shared_mask &= ~ignored_mask; // 移除不支持的能力
        }
    }

(3)调用 spi_nor_select_readspi_nor_select_ppspi_nor_select_erase 函数,分别选择读、写和擦除操作命令。

复制代码
    err = spi_nor_select_read(nor, shared_mask);
    ...
    /* 选择 Page Program(页编程)写操作命令。 */
    err = spi_nor_select_pp(nor, shared_mask);
    ...
    /* 选择 Sector Erase(扇区擦除)操作命令。 */
    err = spi_nor_select_erase(nor);

spi_nor_select_read 函数通过硬件能力协商和操作命令选择,最终从spi_nor_read_command_index中选择一个和最适合该SPI设备硬件的能力:

复制代码
read = &nor->params->reads[cmd];
nor->read_opcode = read->opcode;
nor->read_proto = read->proto;

enum spi_nor_read_command_index {
    ...
	SNOR_CMD_READ_1_1_1_DTR,

	/* Dual SPI */
	SNOR_CMD_READ_1_1_2,
    ...
	SNOR_CMD_READ_1_2_2_DTR,

	/* Quad SPI */
	SNOR_CMD_READ_1_1_4,
    ...
	SNOR_CMD_READ_1_4_4_DTR,

	/* Octal SPI */
	SNOR_CMD_READ_1_1_8,
    ...
	SNOR_CMD_READ_MAX
};

其他两个函数同理。

spi_nor_set_addr_width

主要做了以下几件事:

  • 地址宽度的选择
    • 优先使用 SFDP 配置的地址宽度。
    • 对于特定协议(如 8D-8D-8D),强制使用 4 字节地址宽度。
    • 根据设备大小动态调整地址宽度(超过 16MiB 时使用 4 字节)。
  • 4 字节操作码配置
    • 如果设备支持 4 字节地址操作码,并且满足特定条件,则配置 4 字节操作码。
(4)初始化设备
复制代码
ret = spi_nor_init(nor);


static int spi_nor_init(struct spi_nor *nor)
{
	int err;

	// 尝试启用八进制 DTR 模式(Double Transfer Rate)
	err = spi_nor_octal_dtr_enable(nor, true);
    ...

	// 尝试启用四线模式(Quad I/O)
    ...
	/*
	 * 一些 SPI NOR 闪存在上电复位后会默认进入写保护状态,
	 * 以防止在启动过程中发生意外写入。
	 * 为了向后兼容,默认情况下需要在上电时解锁整个闪存内存数组。
	 * 根据内核配置:
	 * (1) 不做任何操作,
	 * (2) 总是解锁整个闪存数组,
	 * (3) 仅在软件写保护位是易失性的情况下解锁整个闪存数组。
	 * 后者由 SNOR_F_SWP_IS_VOLATILE 标志指示。
	 */
	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) ||
	    (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) &&
	    nor->flags & SNOR_F_SWP_IS_VOLATILE))
		// 如果配置为禁用软件写保护,或者在写保护易失性时解锁闪存
		spi_nor_try_unlock_all(nor);

	// 如果地址宽度为 4 字节,且未使用 8D-8D-8D 协议,且不支持 4 字节操作码
	if (nor->addr_width == 4 &&
	    nor->read_proto != SNOR_PROTO_8_8_8_DTR &&
	    !(nor->flags & SNOR_F_4B_OPCODES)) {
		/*
		 * 如果 RESET# 引脚未正确连接,或系统未在启动序列中执行复位命令,
		 * 则无法 100% 防止意外重启(如崩溃)。警告用户(或系统设计者)可能存在风险。
		 */
		WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET,
			  "enabling reset hack; may not recover from unexpected reboots\n");
		// 强制启用 4 字节地址模式(如果复位引脚不可靠,则启用"复位 hack"
		nor->params->set_4byte_addr_mode(nor, true);
	}
    ...
}

主要做了下面几个关键事情:

  • 调用 spi_nor_octal_dtr_enable 尝试启用八进制 DTR 模式。
  • 调用 spi_nor_quad_enable 启用四线模式。
  • 如果地址宽度为 4 字节,且未使用 8D-8D-8D 协议,且不支持 4 字节操作码:
    • 检查 SNOR_F_BROKEN_RESET 标志。
    • 如果复位引脚连接不当或系统未在启动序列中执行复位命令,则打印警告信息。
    • 调用 set_4byte_addr_mode 启用 4 字节地址模式。
3、spi_nor_create_read_dirmap
复制代码
static int spi_nor_create_read_dirmap(struct spi_nor *nor)
{
    /* 初始化SPI内存直接映射信息结构体 */
    struct spi_mem_dirmap_info info = {
        /* 设置操作模板: 包含命令、地址、dummy周期和数据输入 */
        .op_tmpl = SPI_MEM_OP(
            SPI_MEM_OP_CMD(nor->read_opcode, 0),       // 读取操作码
            SPI_MEM_OP_ADDR(nor->addr_width, 0, 0),    // 地址宽度
            SPI_MEM_OP_DUMMY(nor->read_dummy, 0),      // dummy周期数
            SPI_MEM_OP_DATA_IN(0, NULL, 0)             // 数据输入(长度初始为0)
        ),
        .offset = 0,                    // 映射起始偏移量(从0开始)
        .length = nor->mtd.size,        // 映射长度(整个闪存大小)
    };
    struct spi_mem_op *op = &info.op_tmpl;  // 获取操作模板指针

    /* 根据读取协议设置SPI操作参数 */
    spi_nor_spimem_setup_op(nor, op, nor->read_proto);

    /* 
     * 将dummy周期数转换为字节数:
     * 字节数 = (dummy周期数 * 总线宽度) / 8
     * 对于DTR(双传输率)模式需要乘以2
     */
    op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8;
    if (spi_nor_protocol_is_dtr(nor->read_proto))
        op->dummy.nbytes *= 2;

    /*
     * 显式设置数据总线宽度:
     * spi_nor_spimem_setup_op()只在数据字节数非零时设置总线宽度,
     * 所以这里需要单独设置数据总线宽度
     */
    op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);

    /* 创建直接读取映射描述符 */
    nor->dirmap.rdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem, &info);
    
    /* 返回描述符指针的错误状态(成功返回0,失败返回错误码) */
    return PTR_ERR_OR_ZERO(nor->dirmap.rdesc);
}

函数创建一个直接内存映射区域,简化从 SPI NOR 闪存读取数据的操作,提高读取效率

实现方式

  • 通过初始化 spi_mem_dirmap_info 结构体,定义读取操作模板(包括命令、地址、dummy 周期和数据输入)。
  • spi_nor_spimem_setup_op 根据读取协议配置 SPI 内存操作模板,包括命令、地址、dummy 和数据的总线宽度及 DTR 模式。
  • 调用 devm_spi_mem_dirmap_create 创建一个 SPI 内存的直接映射描述符(spi_mem_dirmap_desc),并将其附加到给定的设备上

spi_nor_create_write_dirmap创建直接写入映射同理

4、mtd_device_register

MTD 设备注册和分区解析函数,主要进行分区解析、回退分区信息、重启通知器分配等,**完成对该芯片逻辑分区注册至mtd子系统中。**涉及MTD体系结构内容较多,学习SPI FLASH设备初始化阶段不进行深入学习


三、spi_nor_probe函数整体总结

  • 内存分配
    • devm_kzalloc:分配 spi_nor 结构体的内存,并确保在设备释放时自动释放内存。
  • 初始化 spi_nor 结构体
    • 设置 nor->spimemnor->dev
    • 调用 spi_nor_set_flash_node 设置设备节点(如果存在设备树节点)。
  • 设置设备名称
    • 优先使用平台数据中的 name 字段。
    • 如果 name 不存在,则从 spi_mem 获取默认名称。
    • 如果平台数据中存在 type 字段,则优先使用 type 作为实际芯片名称。
  • 扫描 SPI NOR Flash
    • spi_nor_scan
      • 识别制造商 ID 和设备 ID。
      • 确定设备的页大小、块大小等特性。
      • 调用内部函数执行这些操作,通常包括发送特定的 SPI 命令来读取设备信息。
  • 处理大页支持
    • 如果设备的页大小超过系统页大小(PAGE_SIZE),则分配一个更大的缓冲区(bouncebuf)以避免缓冲区溢出。
    • 使用 devm_kfree 释放旧的缓冲区(如果存在),并使用 devm_kmalloc 分配新的缓冲区。
  • 创建直接映射
    • spi_nor_create_read_dirmap
      • 初始化读映射表,用于优化读操作。
    • spi_nor_create_write_dirmap
      • 初始化写映射表,用于优化写操作。
  • 注册 MTD 设备
    • mtd_device_register
      • 注册 mtd_info 结构体,使其可以被上层文件系统或其他子系统使用。
      • 如果存在分区信息,则设置分区信息。
      • 调用 mtd_device_parse_register 执行实际的注册操作。
相关推荐
深海蜗牛2 分钟前
Jenkins linux安装
linux·jenkins
愚戏师14 分钟前
Linux复习笔记(三) 网络服务配置(web)
linux·运维·笔记
JANYI201836 分钟前
嵌入式MCU和Linux开发哪个好?
linux·单片机·嵌入式硬件
熊大如如1 小时前
Java NIO 文件处理接口
java·linux·nio
晚秋大魔王1 小时前
OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——nettle库
linux·开源·harmonyos
农民小飞侠1 小时前
ubuntu 24.04 error: cannot uninstall blinker 1.7.0, record file not found. hint
linux·运维·ubuntu
某不知名網友1 小时前
Linux 软硬连接详解
linux·运维·服务器
hnlucky1 小时前
通俗易懂版知识点:Keepalived + LVS + Web + NFS 高可用集群到底是干什么的?
linux·前端·学习·github·web·可用性测试·lvs
scdifsn1 小时前
动手学深度学习12.4.硬件-笔记&练习(PyTorch)
pytorch·笔记·深度学习·缓存·内存·硬盘·深度学习硬件
Jogging-Snail1 小时前
Linux工作台文件操作命令全流程解析(高级篇之vim和nano精讲)
linux·运维·vim·文件操作·文本编辑·nano