4. SPI Flash 驱动框架:Linux SPI 子系统架构、控制器驱动与设备驱动注册流程
在嵌入式 Linux 系统中,SPI Flash 的读写操作依赖于一套层次分明的驱动框架。理解 Linux SPI 子系统的架构、控制器驱动的职责以及设备驱动的注册流程,是进行 SPI Flash 驱动开发与调试的基础。本章将深入剖析这三部分内容,帮助您建立完整的驱动知识体系。
4.1 Linux SPI 子系统整体架构
Linux SPI 子系统采用标准的分层架构,从上到下依次为:设备驱动层、核心层 和 控制器驱动层。这种分层设计实现了硬件操作与上层逻辑的解耦,使得 SPI Flash 驱动可以方便地适配不同的 SPI 控制器。
架构核心思想: 设备驱动层通过核心层提供的通用 API 与控制器驱动层交互,无需关心底层寄存器细节;控制器驱动层则负责与具体的硬件外设通信。
4.1.1 各层职责与关键数据结构
| 层次 | 核心数据结构 | 主要职责 |
|---|---|---|
| 设备驱动层 | struct spi_driverstruct spi_device |
注册 SPI 设备驱动,实现 probe()、remove() 回调;通过 spi_transfer() 发起读写请求。 |
| 核心层 | struct spi_controllerstruct spi_message |
管理 SPI 总线、设备枚举、消息队列调度;提供统一的 spi_sync() / spi_async() 接口。 |
| 控制器驱动层 | struct spi_masterstruct 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 的板级初始化或设备树匹配过程中。典型步骤如下:
- 分配 spi_controller 结构体: 使用
spi_alloc_master()或spi_alloc_slave()分配并初始化控制器对象。 - 设置硬件参数: 配置
max_speed_hz、mode_bits、bits_per_word_mask等能力位。 - 实现传输回调: 编写
transfer_one_message()函数,解析消息中的spi_transfer列表,操作硬件 FIFO 或 DMA 完成数据交换。 - 注册控制器: 调用
spi_register_controller()将控制器添加到 SPI 核心层。
常见错误: 控制器驱动中
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 控制器驱动是否正确注册。
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)、设备树节点状态、控制器驱动是否成功注册。