3. SPI控制器驱动注册:platform_driver结构体、spi_alloc_master/spi_register_master接口使用与初始化流程
SPI控制器驱动是Linux SPI子系统与硬件平台之间的桥梁。本章深入剖析SPI控制器驱动的注册机制,重点讲解platform_driver结构体的填充、spi_alloc_master与spi_register_master接口的正确使用,以及完整的初始化流程。掌握这些核心步骤,是编写稳定、可移植的SPI控制器驱动的关键。
3.1 SPI控制器驱动架构概览
在Linux内核中,SPI控制器驱动遵循主机控制器驱动(Host Controller Driver)模型。其核心思想是:通过platform_driver匹配设备树或平台设备,在探测(probe)阶段分配并注册一个SPI主控制器(spi_master),该控制器随后负责管理其总线上的所有SPI从设备。
3.2 platform_driver结构体填充
SPI控制器驱动首先需要定义一个platform_driver结构体实例,并填充必要的回调函数。该结构体是驱动与platform总线交互的接口。
| 成员 | 类型 | 说明 | 必须实现 |
|---|---|---|---|
| probe | int (*probe)(struct platform_device *) | 设备匹配成功时调用,完成master分配、硬件初始化、master注册 | 是 |
| remove | int (*remove)(struct platform_device *) | 设备移除时调用,释放资源、注销master | 是 |
| shutdown | void (*shutdown)(struct platform_device *) | 系统关机或重启时调用,执行优雅关闭 | 否 |
| driver | struct device_driver | 包含name、of_match_table、pm等子成员 | 是 |
| id_table | const struct platform_device_id * | 传统平台设备匹配表(非设备树场景) | 按需 |
.of_match_table进行匹配,并配合MODULE_DEVICE_TABLE(of, ...)导出兼容性列表。这有助于驱动在设备树中自动绑定。
以下是一个典型的SPI控制器platform_driver定义示例:
static const struct of_device_id spi_imx_of_match[] = {
{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data },
{ .compatible = "fsl,imx8mm-ecspi", .data = &imx8mm_ecspi_devtype_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_of_match);
static struct platform_driver spi_imx_driver = {
.driver = {
.name = "spi_imx",
.of_match_table = spi_imx_of_match,
.pm = &spi_imx_pm_ops,
},
.probe = spi_imx_probe,
.remove = spi_imx_remove,
.shutdown = spi_imx_shutdown,
};
module_platform_driver(spi_imx_driver);
3.3 spi_alloc_master:分配主控制器对象
spi_alloc_master是SPI核心层提供的分配函数,用于在内存中创建一个spi_master结构体实例,并为其关联的设备私有数据分配空间。
struct spi_master *spi_alloc_master(struct device *dev, unsigned int size)
参数说明:
dev:父设备指针,通常传入platform_device中的&pdev->dev。size:附加的私有数据区大小(字节),用于存放控制器驱动的自定义结构体。
使用spi_alloc_master时,驱动开发者需要理解其内存布局:
kzalloc(sizeof(struct spi_master), GFP_KERNEL)手动分配spi_master。必须使用spi_alloc_master,因为它会正确初始化核心内部字段、引用计数和锁。手动分配会导致内核崩溃或资源泄漏。
3.4 spi_register_master:注册主控制器
在完成spi_master的字段填充和硬件初始化后,驱动调用spi_register_master将控制器注册到SPI核心子系统。注册成功后,核心层会扫描设备树或ACPI表,枚举该总线上的所有SPI从设备。
int spi_register_master(struct spi_master *master)
参数: 已填充好的spi_master指针。
返回值: 0表示成功,负值表示错误码。
注册过程中,核心层会执行以下关键步骤:
- 总线号分配:如果master->bus_num为0,核心层自动分配动态总线号;否则使用指定的静态总线号。
- 设备树扫描:遍历设备树中该控制器节点下的所有SPI从设备子节点,为每个从设备创建spi_device结构体。
- 从设备注册:将spi_device添加到SPI总线,触发SPI从设备驱动的probe匹配。
- sysfs接口创建:在/sys/bus/spi/devices/下创建控制器和从设备的属性文件。
3.5 完整初始化流程
SPI控制器驱动的完整初始化流程可以归纳为以下步骤,这些步骤通常在probe函数中顺序执行:
3.6 核心字段填充详解
在调用spi_register_master之前,驱动必须正确填充spi_master结构体的关键字段。下表列出了最常用的字段及其含义:
| 字段 | 类型 | 说明 | 典型值/设置 |
|---|---|---|---|
| bus_num | u16 | SPI总线编号,0表示由核心动态分配 | pdev->id 或 -1 |
| num_chipselect | u16 | 支持的片选信号数量 | 硬件支持的CS引脚数 |
| mode_bits | u16 | 支持的SPI模式位掩码 | SPI_CPOL | SPI_CPHA | SPI_CS_HIGH |
| bits_per_word_mask | u32 | 支持的每字位数掩码 | SPI_BPW_MASK(8) | SPI_BPW_MASK(16) |
| min_speed_hz | u32 | 支持的最小SPI时钟频率 | 硬件限制,如1000 |
| max_speed_hz | u32 | 支持的最大SPI时钟频率 | 硬件限制,如50000000 |
| transfer_one | 函数指针 | 单次传输处理函数(推荐) | 驱动实现的传输函数 |
| setup | 函数指针 | 从设备参数设置回调 | 驱动实现的配置函数 |
| prepare_message | 函数指针 | 消息预处理回调(可选) | DMA配置等 |
| unprepare_message | 函数指针 | 消息后处理回调(可选) | DMA清理等 |
| auto_runtime_pm | bool | 是否启用运行时PM自动管理 | true/false |
3.7 完整probe函数示例
以下是一个简化的SPI控制器驱动probe函数实现,展示了上述流程的代码落地:
static int spi_imx_probe(struct platform_device *pdev)
{
struct spi_master *master;
struct spi_imx_data *spi_imx;
struct resource *res;
int ret;
/* 1. 分配spi_master,附带私有数据结构体大小 */
master = spi_alloc_master(&pdev->dev, sizeof(struct spi_imx_data));
if (!master)
return -ENOMEM;
/* 获取私有数据指针 */
spi_imx = spi_master_get_devdata(master);
platform_set_drvdata(pdev, master);
/* 2. 获取硬件资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
spi_imx->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(spi_imx->base)) {
ret = PTR_ERR(spi_imx->base);
goto out_master_put;
}
spi_imx->irq = platform_get_irq(pdev, 0);
if (spi_imx->irq < 0) {
ret = spi_imx->irq;
goto out_master_put;
}
/* 3. 填充spi_master核心字段 */
master->bus_num = pdev->id;
master->num_chipselect = 2;
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16);
master->min_speed_hz = 1000;
master->max_speed_hz = 50000000;
master->transfer_one = spi_imx_transfer_one;
master->setup = spi_imx_setup;
master->prepare_message = spi_imx_prepare_message;
master->unprepare_message = spi_imx_unprepare_message;
master->auto_runtime_pm = true;
/* 4. 硬件初始化:时钟使能、GPIO配置、DMA设置等 */
ret = spi_imx_hw_init(spi_imx);
if (ret)
goto out_master_put;
/* 5. 注册spi_master到核心层 */
ret = spi_register_master(master);
if (ret) {
dev_err(&pdev->dev, "failed to register spi master: %d\n", ret);
goto out_hw_deinit;
}
dev_info(&pdev->dev, "SPI controller registered, bus %d\n", master->bus_num);
return 0;
out_hw_deinit:
spi_imx_hw_deinit(spi_imx);
out_master_put:
spi_master_put(master);
return ret;
}
spi_alloc_master分配的内存需要通过spi_master_put释放,而不是直接kfree。注册成功后,remove函数中应调用spi_unregister_master。
3.8 remove函数与资源释放
与probe对应的remove函数负责在驱动卸载或设备移除时执行清理:
static int spi_imx_remove(struct platform_device *pdev)
{
struct spi_master *master = platform_get_drvdata(pdev);
struct spi_imx_data *spi_imx = spi_master_get_devdata(master);
/* 1. 注销spi_master,核心层会清理所有从设备 */
spi_unregister_master(master);
/* 2. 硬件去初始化 */
spi_imx_hw_deinit(spi_imx);
/* 3. 释放spi_master引用 */
spi_master_put(master);
return 0;
}
spi_unregister_master会同步等待所有正在进行的传输完成,并移除所有从设备。因此,在调用该函数之前,驱动应确保没有新的传输请求被提交。对于支持运行时PM的驱动,还需在remove中禁用PM。
3.9 调试与验证要点
在SPI控制器驱动开发过程中,以下调试手段可以帮助快速定位问题:
- 检查dmesg日志:关注probe函数是否成功执行,以及spi_register_master返回的错误码。
- 查看sysfs节点:注册成功后,
/sys/bus/spi/devices/下应出现对应的spi设备目录。 - 使用devicetree验证:确保设备树中SPI控制器节点的
compatible属性与驱动匹配,且子节点格式正确。 - 调试传输函数:在
transfer_one中添加dev_dbg打印,观察传输参数(频率、模式、数据长度)是否正确。 - 检查时钟和GPIO:使用
clk_get_rate确认时钟频率,使用GPIO调试工具确认片选信号电平。
CONFIG_SPI_DEBUG=y — 启用SPI核心层调试输出
CONFIG_DYNAMIC_DEBUG=y — 支持动态开启驱动中的dev_dbg/pr_debug