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 执行实际的注册操作。
相关推荐
哈哈幸运1 小时前
MySQL运维三部曲初级篇:从零开始打造稳定高效的数据库环境
linux·运维·数据库·mysql·性能优化
soulermax2 小时前
数字ic后端设计从入门到精通2(含fusion compiler, tcl教学)
java·linux·服务器
黑心老人2 小时前
Mac OS系统下kernel_task占用大量CPU资源导致系统卡顿
linux·运维·服务器·macos
口嗨农民工2 小时前
ubuntu18.04启动不了修复
linux·运维·ubuntu
nenchoumi31192 小时前
VLA论文精读(十四)PointVLA: Injecting the 3D World into Vision-Language-Action Models
论文阅读·笔记·学习·vla
星云之2 小时前
Unity入门笔记(缘更)
笔记·unity·游戏引擎
自己做的好吃2 小时前
基于尚硅谷FreeRTOS视频笔记——13—HAL库和RTOS时钟源问题
笔记
辣个蓝人QEX3 小时前
【ZYNQ MP开发】Linux下使用bootgen命令生成BOOT.bin报错架构不对问题探究
linux·arm开发·xilinx·zynq·mpsoc·bootgen·u-boot移植
Vesan,3 小时前
无人机飞控运行在stm32上的RTOS实时操作系统上,而不是linux这种非实时操作系统的必要性
linux·stm32·无人机