核心思想:房东与租客
把这个关系想象成房东和租客:
- SPI 控制器 (SPI Controller):这是 SoC 内部的硬件,好比一栋公寓楼。
platform_driver(控制器驱动):这是房东。他的职责是管理这栋公寓楼(初始化硬件、设置时钟、注册总线),并把房间挂出去招租。- SPI 设备 (SPI Device):这是连接在 SPI 接口上的外部芯片(如 Flash、传感器),好比一个租客。
spi_driver(设备驱动):这是租客自己。他知道如何使用房间里的设施(通过 SPI 协议读写数据)。
房东 (platform_driver) 并不关心租客 (spi_driver) 在房间里做什么,他只负责提供可用的房间 (spi_device)。租客也无需关心公寓楼是怎么建的,他只需要拿到钥匙 (struct spi_device) 就能入住。
1. 设备树 (DTS) 中的描述
设备树清晰地描述了这种“公寓楼”和“租客”的硬件关系。
// 根节点
/ {
soc {
// ... 其他SoC内部设备
// 1. 定义 "公寓楼" (SPI控制器)
// 这是 SoC 内部的一个设备,所以它是一个 platform_device
spi1: ecspi@02008000 {
compatible = "fsl,imx6ul-ecspi"; // 用于匹配 "房东" (platform_driver)
reg = <0x02008000 0x4000>; // 控制器寄存器地址
interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ECSPI1>, <&clks IMX6UL_CLK_ECSPI1>;
clock-names = "ipg", "per";
status = "disabled";
// 2. 在 "公寓楼" 里定义 "租客" (SPI设备)
// 这是一个子节点,表示它连接在这个SPI控制器上
flash: m25p80@0 {
compatible = "jedec,spi-nor"; // 用于匹配 "租客" (spi_driver)
reg = <0>; // 片选(Chip Select)编号,这里是 CS0
spi-max-frequency = <20000000>; // 最大SPI时钟频率
};
};
// ...
};
};
spi1: ecspi@02008000节点:- 它描述了 SoC 内部的 SPI 控制器硬件。
- 内核会为它创建一个
struct platform_device。 - 它的
compatible = "fsl,imx6ul-ecspi"将用于匹配一个platform_driver。
flash: m25p80@0节点:- 它是
spi1的子节点,表示这个 Flash 芯片物理上连接在spi1控制器上。 - 内核不会立即为它创建设备。它会等待它的父节点(
spi1)准备就绪。 - 它的
compatible = "jedec,spi-nor"将用于匹配一个spi_driver。
- 它是
2. 关系图解
这张图展示了从硬件到驱动的完整流程:
+--------------------------------------------------------------------------------------------------+
| 硬件层 (Hardware) |
| +---------------------------------+ +------------------------------------+ |
| | SoC (e.g., i.MX6ULL) | | 外部电路板 (Board) | |
| | | | | |
| | +---------------------------+ | --(SPI Bus Lines)--> | +-------------------------------+ | |
| | | SPI Controller #1 | | (MISO, MOSI, CLK) | | SPI Flash Chip (e.g., W25Q64) | | |
| | +---------------------------+ | | +-------------------------------+ | |
| +---------------------------------+ +------------------------------------+ |
+--------------------------------------------------------------------------------------------------+
| (由设备树描述)
+--------------------------------------------------------------------------------------------------+
| 设备树 (Device Tree) |
| +---------------------------------------------------------------------------------------------+ |
| | spi1: ecspi@02008000 { | |
| | compatible = "fsl,imx6ul-ecspi"; <-----------------+ | |
| | ... | | |
| | flash: m25p80@0 { | | |
| | compatible = "jedec,spi-nor"; <------------+ | | |
| | ... | | | |
| | }; | | | |
| | }; | | | |
| +---------------------------------------------------------------------------------------------+ |
+--------------------------------------------------------------------------------------------------+
| (内核解析并创建对象)
+--------------------------------------------------------------------------------------------------+
| 内核对象与驱动层 |
| |
| [platform_bus] [spi_bus] |
| | | |
| +--------------------+ (匹配 compatible) +--------------------------+ |
| | platform_device | <------------------------- | platform_driver | |
| | (for spi1) | | (e.g., spi-imx.c) | |
| +--------------------+ +--------------------------+ |
| | | |
| | | probe() 函数被调用 |
| | | |
| +----------------------------------------------------------+ |
| | |
| | 1. 初始化控制器硬件 |
| | 2. 调用 spi_register_master() 注册一个 spi_master |
| | |
| V |
| +--------------------+ |
| | spi_master | (代表一个可用的SPI总线) |
| +--------------------+ |
| | |
| | SPI核心层发现 spi_master 及其DTS子节点(flash) |
| | 为子节点创建 spi_device |
| V |
| +--------------------+ (匹配 compatible) +--------------------------+ |
| | spi_device | <------------------------- | spi_driver | |
| | (for flash) | | (e.g., spi-nor.c) | |
| +--------------------+ +--------------------------+ |
| | | |
| +----------------------------------------------------------+ |
| | |
| | probe() 函数被调用, 驱动获得 spi_device 句柄, |
| | 可以通过 spi_sync(), spi_write() 等函数与硬件通信 |
| V |
| 与物理 Flash 芯片通信 |
+--------------------------------------------------------------------------------------------------+
3. 代码与工作流程详解
-
内核启动:内核解析设备树,发现
spi1节点。因为它是一个顶层 SoC 设备,内核为其创建一个struct platform_device并将其挂在platform_bus上。 -
Platform 驱动匹配:
platform_bus遍历所有已注册的platform_driver。spi-imx.c驱动的of_match_table包含了"fsl,imx6ul-ecspi"。匹配成功! -
Platform Driver Probe 执行:内核调用
spi-imx.c中的probe函数,即imx_spi_probe()。// filepath: drivers/spi/spi-imx.c (简化) static const struct of_device_id imx_spi_dt_ids[] = { { .compatible = "fsl,imx6ul-ecspi", }, // 匹配表 { /* sentinel */ } }; static int imx_spi_probe(struct platform_device *pdev) { struct spi_master *master; // ... 分配内存, 获取时钟, 映射寄存器 ... // 关键步骤: 注册 "公寓楼" // 告诉内核,这里有一个可用的 SPI 总线了 master = spi_alloc_master(&pdev->dev, ...); // ... 配置 master ... devm_spi_register_master(&pdev->dev, master); // 注册 master return 0; } static struct platform_driver imx_spi_driver = { .driver = { .name = "imx-spi", .of_match_table = imx_spi_dt_ids, // 声明匹配表 }, .probe = imx_spi_probe, // ... }; module_platform_driver(imx_spi_driver); -
SPI Device 创建:当
spi_register_master()被调用后,SPI 核心层知道一个新的 SPI 总线(公寓楼)已经就绪。它会回头查看这个总线对应设备树节点 (spi1) 的所有子节点。它发现了flash节点,于是为它创建一个struct spi_device并将其挂在spi_bus上。 -
SPI 驱动匹配:
spi_bus遍历所有已注册的spi_driver。spi-nor.c驱动的of_match_table包含了"jedec,spi-nor"。匹配成功! -
SPI Driver Probe 执行:内核调用
spi-nor.c中的probe函数。// filepath: drivers/mtd/spi-nor/spi-nor.c (简化) static const struct of_device_id spi_nor_of_ids[] = { { .compatible = "jedec,spi-nor" }, // 匹配表 // ... 其他兼容的 flash 类型 { /* sentinel */ } }; static int spi_nor_probe(struct spi_device *spi) { struct spi_nor *nor; // ... // 参数 `spi` 就是内核为 flash 创建的 spi_device // 驱动现在可以通过这个 `spi` 句柄与硬件通信了 ret = spi_nor_scan(nor, spi->modalias, SPI_NOR_DUAL); // ... return mtd_device_register(&nor->mtd, ...); } static struct spi_driver spi_nor_driver = { .driver = { .name = "spi-nor", .of_match_table = spi_nor_of_ids, // 声明匹配表 }, .probe = spi_nor_probe, // ... }; module_spi_driver(spi_nor_driver);
总结
platform_driver和spi_driver是并行的、服务于不同总线类型的驱动。- 它们通过设备树的父子关系和内核的分步初始化流程联系在一起。
platform_driver作为基础设施提供者(房东),负责准备好 SPI 总线。spi_driver作为设备使用者(租客),在总线就绪后,负责与具体的 SPI 从设备通信。
1165

被折叠的 条评论
为什么被折叠?



