4. SPI Flash 驱动框架:Linux SPI 子系统架构、控制器驱动与设备驱动注册流程

在嵌入式 Linux 系统中,SPI Flash 的读写操作依赖于一套层次分明的驱动框架。理解 Linux SPI 子系统的架构、控制器驱动的职责以及设备驱动的注册流程,是进行 SPI Flash 驱动开发与调试的基础。本章将深入剖析这三部分内容,帮助您建立完整的驱动知识体系。

4.1 Linux SPI 子系统整体架构

Linux SPI 子系统采用标准的分层架构,从上到下依次为:设备驱动层、核心层 和 控制器驱动层。这种分层设计实现了硬件操作与上层逻辑的解耦,使得 SPI Flash 驱动可以方便地适配不同的 SPI 控制器。

架构核心思想: 设备驱动层通过核心层提供的通用 API 与控制器驱动层交互,无需关心底层寄存器细节;控制器驱动层则负责与具体的硬件外设通信。
Linux SPI 子系统分层架构 Linux SPI 子系统分层架构 设备驱动层 (SPI Device Driver) spi_driver / spi_device → 实现 probe/remove/transfer 核心层 (SPI Core) spi.c / spi.h → 总线管理、消息队列、数据传输抽象 控制器驱动层 (SPI Controller Driver) spi_master / spi_controller → 操作硬件寄存器、收发数据 硬件层 (SPI Controller Hardware) 数据流方向:设备驱动 → 核心层 → 控制器驱动 → 硬件

4.1.1 各层职责与关键数据结构

层次 核心数据结构 主要职责
设备驱动层 struct spi_driver
struct spi_device
注册 SPI 设备驱动,实现 probe()、remove() 回调;通过 spi_transfer() 发起读写请求。
核心层 struct spi_controller
struct spi_message
管理 SPI 总线、设备枚举、消息队列调度;提供统一的 spi_sync() / spi_async() 接口。
控制器驱动层 struct spi_master
struct spi_transfer
初始化 SPI 控制器硬件(时钟、模式、片选);实现底层 FIFO/DMA 数据收发。
调试要点: 在调试 SPI Flash 驱动时,首先确认 spi_device 的 mode、max_speed_hz 和 bits_per_word 是否与 Flash 芯片规格匹配。这些参数在设备驱动注册时由板级信息或设备树指定。

4.2 SPI 控制器驱动

SPI 控制器驱动是连接核心层与硬件层的桥梁。它负责初始化 SPI 外设寄存器、配置时钟极性/相位、管理片选信号以及执行实际的数据传输。不同的 SoC 平台(如 i.MX6、STM32MP1、Allwinner)拥有不同的 SPI 控制器,但驱动框架遵循相同的接口规范。

4.2.1 控制器驱动的核心接口

每个 SPI 控制器驱动都需要实现 struct spi_controller 中的关键回调函数:

/* 控制器驱动必须实现的核心回调 */
struct spi_controller {
    /* 传输一个 spi_message(同步/异步) */
    int (*transfer_one_message)(struct spi_controller *ctlr,
                                struct spi_message *msg);
    /* 设置片选信号 */
    void (*set_cs)(struct spi_device *spi, bool enable);
    /* 准备传输(可选,用于 DMA 映射等) */
    int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
    /* 完成传输后的清理 */
    int (*unprepare_transfer_hardware)(struct spi_controller *ctlr);
    /* ... 其他成员 */
};

4.2.2 控制器驱动注册流程

控制器驱动的注册通常发生在 SoC 的板级初始化或设备树匹配过程中。典型步骤如下:

  1. 分配 spi_controller 结构体: 使用 spi_alloc_master() 或 spi_alloc_slave() 分配并初始化控制器对象。
  2. 设置硬件参数: 配置 max_speed_hz、mode_bits、bits_per_word_mask 等能力位。
  3. 实现传输回调: 编写 transfer_one_message() 函数,解析消息中的 spi_transfer 列表,操作硬件 FIFO 或 DMA 完成数据交换。
  4. 注册控制器: 调用 spi_register_controller() 将控制器添加到 SPI 核心层。
SPI 控制器驱动注册流程 SPI 控制器驱动注册流程 1. spi_alloc_master() 2. 设置硬件参数与回调 3. 实现 transfer_one_message 4. spi_register_controller() 注册成功后,核心层会扫描总线上的设备,并触发匹配的设备驱动 probe()
常见错误: 控制器驱动中 transfer_one_message 未正确处理 spi_transfer 的 cs_change 标志,可能导致多笔传输之间片选信号异常,Flash 读写失败。务必在调试时用逻辑分析仪检查片选时序。

4.3 SPI 设备驱动注册流程

SPI 设备驱动(如 SPI Flash 驱动)的注册遵循 Linux 设备模型的标准流程。设备驱动通过 spi_register_driver() 注册到 SPI 总线,当 SPI 核心层发现匹配的设备节点时,调用驱动的 probe() 函数完成初始化。

4.3.1 设备驱动注册步骤

步骤 操作 关键函数/数据结构
1 定义 spi_driver 结构体 struct spi_driver 包含 probe、remove、id_table
2 填充 of_match_table 或 id_table 用于设备树或传统板级匹配
3 调用 spi_register_driver() 将驱动注册到 SPI 总线
4 核心层匹配设备,调用 probe() 在 probe() 中获取 spi_device 参数,初始化 Flash 芯片
5 注册 MTD 或字符设备 将 Flash 抽象为 MTD 设备,供文件系统层访问

4.3.2 设备驱动代码示例

以下是一个简化的 SPI Flash 设备驱动注册代码片段,展示了从定义驱动到 probe 的完整流程:

/* 1. 定义 spi_driver 结构体 */
static const struct of_device_id myflash_of_match[] = {
    { .compatible = "jedec,spi-nor" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, myflash_of_match);

static int myflash_probe(struct spi_device *spi)
{
    struct flash_priv *priv;
    int ret;

    /* 获取 SPI 设备参数 */
    dev_info(&spi->dev, "SPI Flash detected, mode=%d, max_speed=%d\n",
             spi->mode, spi->max_speed_hz);

    /* 分配私有数据结构 */
    priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->spi = spi;
    spi_set_drvdata(spi, priv);

    /* 初始化 Flash 芯片(读取 JEDEC ID、配置状态寄存器等) */
    ret = flash_init_hardware(priv);
    if (ret)
        return ret;

    /* 注册 MTD 设备 */
    ret = mtd_device_register(&priv->mtd, NULL, 0);
    if (ret)
        return ret;

    return 0;
}

static void myflash_remove(struct spi_device *spi)
{
    struct flash_priv *priv = spi_get_drvdata(spi);
    mtd_device_unregister(&priv->mtd);
}

static struct spi_driver myflash_driver = {
    .driver = {
        .name   = "my_spi_flash",
        .of_match_table = myflash_of_match,
    },
    .probe  = myflash_probe,
    .remove = myflash_remove,
};

/* 2. 注册驱动 */
module_spi_driver(myflash_driver);

4.3.3 设备树匹配与参数传递

在现代嵌入式 Linux 中,SPI 设备信息通常通过设备树(Device Tree)传递。以下是一个典型的 SPI Flash 设备树节点:

&spi1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_spi1;

    flash@0 {
        compatible = "jedec,spi-nor";
        reg = <0>;               /* 片选编号 */
        spi-max-frequency = <50000000>;  /* 50MHz */
        spi-cpol;                /* 时钟极性 */
        spi-cpha;                /* 时钟相位 */
        #address-cells = <1>;
        #size-cells = <1>;
    };
};
调试技巧: 使用 ls /sys/bus/spi/devices/ 查看系统识别的 SPI 设备列表。如果设备节点未出现,检查设备树中 status = "okay" 是否设置,以及 SPI 控制器驱动是否正确注册。
SPI 设备驱动注册与匹配流程 SPI 设备驱动注册与匹配流程 spi_register_driver() SPI 核心层扫描总线 匹配设备树 / id_table 调用 probe() 初始化 Flash 设备树节点 (spi1: flash@0) 创建 spi_device 对象 设置 mode / max_speed / chip_select 匹配 匹配成功后,spi_device 与 spi_driver 绑定,probe() 中可获取设备树中配置的 spi-max-frequency 等参数

4.4 本章小结

  • SPI 子系统架构 分为设备驱动层、核心层和控制器驱动层,核心层提供统一的 spi_sync() / spi_async() 接口。
  • 控制器驱动 负责硬件初始化与数据传输,必须实现 transfer_one_message 回调,并通过 spi_register_controller() 注册。
  • 设备驱动注册 通过 spi_register_driver() 完成,设备树匹配后触发 probe(),在 probe 中完成 Flash 芯片初始化和 MTD 设备注册。
  • 调试关键点: 检查 spi_device 参数(mode、max_speed_hz)、设备树节点状态、控制器驱动是否成功注册。