1.Platform虚拟总线驱动
核心价值
作用 | 实现机制 | 优势 |
---|---|---|
统一管理片上资源 | 虚拟总线抽象,无需物理连接 | 简化SoC设备管理 |
设备与驱动解耦 | platform_device 描述资源,platform_driver 实现逻辑 |
提高代码复用性和可移植性 |
自动匹配 | 总线通过名称或ID表匹配设备与驱动 | 减少手动绑定代码 |
安全资源访问 | 内核统一分配资源(内存、中断) | 避免硬件冲突 |
扩展思考:在分布式系统(如OpenHarmony)中,虚拟总线概念进一步演化为"软总线",实现跨设备的服务发现与通信
相关结构体
struct platform_driver{} -结构体
cpp#include <linux/platform_device.h> struct platform_driver { int (*probe)(struct platform_device *); int (*remove)(struct platform_device *); void (*shutdown)(struct platform_device *); int (*suspend)(struct platform_device *, pm_message_t state); int (*resume)(struct platform_device *); struct device_driver driver; const struct platform_device_id *id_table; bool prevent_deferred_probe; };
platform_driver是 Linux 内核中用于管理片上资源(如 SoC 外设控制器)的核心结构体,属于 Platform 总线驱动模型的一部分。Platform 总线是一种虚拟总线,用于连接 CPU 直接寻址的设备(如 GPIO、定时器等),实现驱动与设备的分离与动态匹配
关键成员功能说明:
成员 功能说明 是否必需 probe
驱动与设备匹配成功后调用,负责初始化硬件、注册字符设备/中断/DMA 等资源。 必需 remove
驱动卸载或设备移除时调用,用于释放资源(如内存、中断)。 必需 driver
内嵌的 device_driver
结构体,包含驱动名称(.name
)、模块所有者(.owner
)等基础信息。必需 id_table
支持多设备匹配的标识表,用于非设备树场景。当设备名与表中某项匹配时,触发 probe
。可选 of_match_table
设备树匹配表(位于 driver.of_match_table
),通过设备节点的compatible
属性匹配驱动(如{ .compatible = "xxx-gpio" }
)。设备树场景必需 shutdown
/suspend
/resume
设备关机、休眠和唤醒的回调函数,用于电源管理。 可选 注:
driver.owner
通常设为THIS_MODULE
,但现代内核(5.x+)已自动填充,无需显式赋值。driver.name
是传统匹配方式的关键,需与platform_device.name
一致。platform_driver 的注册与注销
驱动需通过以下接口注册到 Platform 总线:
cpp// 注册驱动 int platform_driver_register(struct platform_driver *drv); // 注销驱动 void platform_driver_unregister(struct platform_driver *drv);
典型驱动框架示例:
cpp#include <linux/platform_device.h> #include <linux/module.h> static int my_probe(struct platform_device *pdev) { printk("Device matched! Resource: MEM 0x%x, IRQ %d\n", pdev->resource[0].start, pdev->resource[1].start); // 初始化硬件、注册字符设备等 return 0; } static int my_remove(struct platform_device *pdev) { // 释放资源 return 0; } static const struct of_device_id my_of_match[] = { { .compatible = "my-device" }, // 设备树匹配标识 { } }; MODULE_DEVICE_TABLE(of, my_of_match); static struct platform_driver my_driver = { .driver = { .name = "my_driver", .of_match_table = my_of_match, }, .probe = my_probe, .remove = my_remove, }; module_platform_driver(my_driver); // 自动生成 init/exit 函数 MODULE_LICENSE("GPL");
设备匹配机制
Platform 总线通过以下方式匹配设备与驱动:
- 设备树(DT)优先 设备节点的
compatible
属性与driver.of_match_table
匹配(如"my-device"
)。- 传统名称匹配 若未使用设备树,则比较
platform_device.name
与driver.name
(如"my_driver"
)。- ID 表扩展匹配 通过
id_table
支持多设备名匹配(如{"deviceA"}, {"deviceB"}
)。资源获取与操作
- 硬件资源访问 : 在
probe
函数中,通过platform_device
获取内存、中断等资源:
cppstruct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); int irq = platform_get_irq(pdev, 0);
- 设备树参数解析: 使用of_property_read_u32() 等接口读取设备树中的自定义属性(如寄存器地址)。
应用场景与优势
- 统一管理片上外设 用于 GPIO、ADC、PWM 等非标准总线设备,避免为每个外设实现独立总线驱动。
- 驱动与硬件解耦 设备资源(地址、中断)通过设备树或
platform_device
描述,驱动无需硬编码,提升可移植性。- 支持热插拔与电源管理 通过
suspend
/resume
实现低功耗模式,shutdown
确保安全断电。总结
platform_driver
是 Linux 驱动开发的核心抽象层,通过分离设备描述与驱动逻辑,显著提升代码复用性和可维护性。开发者需重点关注:
- 匹配机制:设备树兼容性 > 名称匹配 > ID 表。
- 生命周期管理 :资源初始化在
probe
中完成,清理在remove
中实现。- 资源获取 :利用
platform_get_resource()
等接口安全访问硬件资源。
结构体关系
cppstruct platform_driver{} //device的platform包装 struct platform_device_id{} //非设备树设备,id信息匹配结构体 struct device_drive{} //设备驱动核心结构体 struct of_device_id{} //设备树设备,compatible匹配结构体 struct bus_type{} //设备驱动归属总线类型结构体 struct platform_device{} //device_drive的platform包装 struct device{} //设备核心结构体 struct device_node{} //设备树节点信息结构体 struct device_type{} //... struct bus_type{} struct device_drive{} struct of_device_id{} struct bus_type{}
在 Linux 设备驱动模型中,这些结构体共同构成了设备管理的核心框架,其关系可概括为 "总线-设备-驱动"三层模型,同时融合了设备树(Device Tree)的支持。以下是各结构体的作用及相互关系分析:
一、核心设备模型结构体
struct device
- 作用:表示系统中任何硬件或虚拟设备的通用抽象,是设备模型的基类。
- 关键字段 :
parent
:指向父设备(如总线控制器)。bus
:设备所属的总线类型(如platform_bus_type
)。driver
:绑定到该设备的驱动(device_driver
)。of_node
:关联的设备树节点(device_node
)。- 关系 :
- 被
platform_device
、pci_device
等继承(通过内嵌struct device
成员)。struct device_driver
- 作用:描述设备驱动的通用行为,与总线无关。
- 关键字段 :
name
:驱动名称(用于匹配设备)。bus
:所属总线类型。probe
/remove
:设备初始化/卸载的回调函数。of_match_table
:设备树匹配表(of_device_id
数组)。- 关系 :
- 被
platform_driver
继承(通过内嵌struct device_driver driver
成员)。struct bus_type
- 作用 :定义总线类型(如
platform_bus_type
),管理设备与驱动的匹配规则。- 关键字段 :
name
:总线名称(如"platform"
)。match()
:设备与驱动的匹配函数(如按名称或设备树匹配)。dev_attrs
/drv_attrs
:总线上设备/驱动的默认属性。- 关系 :
- 平台总线
platform_bus_type
是其实例,用于管理platform_device
和platform_driver
。
二、平台设备相关结构体
struct platform_device
- 作用 :描述 SoC 集成的静态设备(如 GPIO 控制器),继承自
device
。- 关键字段 :
name
:设备名称(与驱动匹配)。resource
:设备资源数组(内存地址、IRQ 等)。dev.of_node
:关联的设备树节点。- 资源获取 :
- 驱动通过
platform_get_resource()
获取资源。struct platform_driver
- 作用 :管理
platform_device
的驱动,继承自device_driver
。- 关键字段 :
probe()
/remove()
:设备初始化和卸载函数。driver.name
:驱动名称(与设备名称匹配)。id_table
:支持设备的 ID 表(platform_device_id
数组)。- 匹配流程 :
- 通过总线
match()
函数比较设备名称或设备树属性。struct platform_device_id
- 作用:定义驱动支持的设备 ID 表,用于非设备树匹配。
- 示例:
cppstatic const struct platform_device_id my_id_table[] = { { "led-ip", 0 }, // 匹配名为 "led-ip" 的设备 { } };
三、设备树(Device Tree)相关结构体
struct device_node
- 作用:设备树在内核中的表示,描述设备的硬件属性(地址、中断等)。
- 关键字段 :
name
:节点名称。compatible
:驱动匹配的关键属性。- 关系 :
- 与
device
通过dev.of_node
关联。struct of_device_id
- 作用 :驱动定义的设备树匹配表,通过
compatible
属性匹配设备节点。- 示例:
cppstatic const struct of_device_id my_driver_match[] = { { .compatible = "vendor,my-device" }, // 匹配设备树中的 compatible 属性 { } };
- 使用场景 :
- 在
platform_driver.driver.of_match_table
中注册。
四、其他辅助结构体
struct device_type
- 作用 :定义设备的类型(如
"uart"
、"i2c"
),用于分类管理设备。- 关系 :
- 被
device
引用(dev.type
),影响设备在/sys
中的属性。结构体关系总结
结构体 依赖关系 核心功能 bus_type
管理 device
和device_driver
定义总线匹配规则(如 platform_bus_type
)device
被 platform_device
继承设备的通用抽象(父设备、资源等) device_driver
被 platform_driver
继承驱动的通用行为( probe
、remove
)platform_device
内嵌 device
;关联device_node
SoC 设备的资源描述(内存、IRQ) platform_driver
内嵌 device_driver
;包含of_device_id
和platform_device_id
平台设备的驱动逻辑 of_device_id
被 platform_driver.driver.of_match_table
引用设备树驱动的匹配表 platform_device_id
被 platform_driver.id_table
引用非设备树驱动的匹配表 device_node
被 device.of_node
引用设备树节点的内核表示 关键交互流程
- 设备注册
platform_device
通过platform_device_register()
注册到平台总线。- 设备树节点在启动时自动转换为
platform_device
(通过of_platform_populate()
)。- 驱动注册
platform_driver
通过platform_driver_register()
注册,其内嵌的device_driver
关联到platform_bus_type
。- 总线调用
match()
比较设备与驱动的名称或设备树compatible
属性。- 驱动绑定
- 匹配成功后,总线调用驱动的
probe()
函数初始化设备(如获取资源、注册字符设备)。- 资源通过
platform_get_resource()
从platform_device.resource
中提取。总结:Linux设备模型的核心设计思想
这些结构体通过分层抽象实现硬件与驱动的解耦:
- 统一接口
device
和device_driver
作为基类,为不同总线(如 PCI、Platform)提供一致的操作接口。- 动态匹配 总线(
bus_type
)通过match()
实现设备与驱动的运行时绑定,支持设备树动态配置。- 资源标准化
platform_device
集中管理硬件资源(内存、IRQ),驱动通过标准 API(如platform_get_resource()
)访问。- 设备树整合
device_node
和of_device_id
将硬件描述从驱动代码剥离,提升跨平台兼容性。下图展示了主要结构体的关系:
cppgraph TD bus_type[bus_type] -->|管理| device(device) bus_type -->|管理| device_driver(device_driver) device_driver -->|内嵌于| platform_driver device -->|内嵌于| platform_device platform_device -->|关联| device_node platform_driver -->|包含| of_device_id platform_driver -->|包含| platform_device_id device_node -->|描述硬件| of_device_id
1.of_match_table的赋值规范
在 Linux 内核的platform_driver驱动框架中,of_device_id匹配表(即bus_devID[])的尾部需要一个空结构体{},这是内核设计中的强制性规范,主要作用是为匹配表的遍历提供安全终止标志。以下是具体原因和设计逻辑:
1. 空结构体的核心作用:标记数组结束
- 终止遍历 :Linux 内核在遍历
of_device_id
数组时,通过检查每个元素的.compatible
字段是否为NULL
来判断是否到达数组末尾。空结构体{}
会被初始化为全零(包括.compatible = NULL
),因此内核遇到它时会停止遍历,避免越界访问。- 防内存越界:若未添加空结构体,内核可能继续读取数组外的内存,导致未定义行为(如崩溃或数据错误)。
2. 内核匹配流程依赖此设计
在
platform
总线匹配设备与驱动的过程中,of_match_table
的匹配逻辑如下:
cpp// 简化版内核匹配逻辑(drivers/base/platform.c) static int platform_match(struct device *dev, struct device_driver *drv) { // ... const struct of_device_id *matches = drv->of_match_table; for (; matches->compatible[0]; matches++) { // 遍历直到遇到 NULL if (of_driver_match_device(dev, drv)) return 1; } return 0; }
- 循环终止条件 :
matches->compatible[0]
为'\\0'
(即NULL
)时退出循环。- 空结构体的必要性 :若缺少
{}
,循环无法终止,导致内核崩溃。3. 实际开发中的注意事项
语法规范
空结构体必须作为数组的最后一个元素,例如:
cppc static const struct of_device_id bus_devID[] = { { .compatible = "mytest" }, // 有效匹配项 { } // 必须的终止标记 };
❌ 错误示例:若省略{},部分内核版本会编译报错,或运行时触发警告。
- 设备树兼容性匹配
- 设备树节点的
compatible
属性(如"mytest"
)需与驱动中的定义完全一致(包括大小写)。- 多设备支持时,可扩展数组:
cpp{ .compatible = "vendor,device-a" }, { .compatible = "vendor,device-b" }, { } // 仍需要终止标记
模块化支持。
通过
MODULE_DEVICE_TABLE(of, bus_devID)
导出匹配表,使驱动可动态加载时被内核识别
相关函数
2.module_platform_driver()-一键注册宏
cpp#include <linux/platform_device.h> #define module_platform_driver(__platform_driver) \ module_driver(__platform_driver, platform_driver_register, \ platform_driver_unregister) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
宏展开过程
cppstatic int __init my_driver_init(void) { return platform_driver_register(&my_driver); // 注册驱动 } module_init(my_driver_init); static void __exit my_driver_exit(void) { platform_driver_unregister(&my_driver); // 注销驱动 } module_exit(my_driver_exit);
##
运算符 :连接符号(如my_driver
+_init
→my_driver_init
)。__VA_ARGS__
:支持可变参数传递(通常为空)底层注册逻辑
platform_driver_register()
将驱动绑定到平台总线(platform_bus_type
),触发设备匹配流程:
- 匹配成功时调用驱动的
probe()
函数;- 卸载时调用
remove()
函数典型驱动代码结构
cpp#include <linux/platform_device.h> static int my_probe(struct platform_device *pdev) { /* 初始化硬件(如配置寄存器、申请中断) */ return 0; } static int my_remove(struct platform_device *pdev) { /* 释放资源(如注销设备、释放内存) */ return 0; } static const struct of_device_id my_of_match[] = { { .compatible = "vendor,my-device" }, // 设备树匹配标识 {} }; static struct platform_driver my_driver = { .probe = my_probe, .remove = my_remove, .driver = { .name = "my_device", // 驱动名称(传统匹配) .of_match_table = my_of_match, // 设备树匹配表 }, }; module_platform_driver(my_driver); // 一行代码完成注册/注销
工作流程
cppgraph TD A[insmod driver.ko] --> B[自动调用 my_driver_init] B --> C[注册驱动到平台总线] C --> D{设备与驱动匹配} D -->|成功| E[执行 my_probe 初始化设备] F[rmmod driver.ko] --> G[调用 my_driver_exit] G --> H[注销驱动并执行 my_remove]
3.platform_driver_register()
cpp#include <linux/platform_device.h> int platform_driver_register(struct platform_driver *drv);
platform_driver_register()
是 Linux 内核中用于注册平台驱动(platform driver)的核心函数,属于platform
总线机制的关键部分。该机制通过虚拟总线实现驱动与设备的分离,提升代码可移植性和可维护性。功能:
将
platform_driver
结构体注册到内核的platform
虚拟总线,触发驱动与设备的匹配流程。匹配成功后,调用驱动的probe()
函数初始化设备
- 参数 :
drv
指向自定义的platform_driver
结构体。- 返回值 :成功返回
0
,失败返回负数错误码
platform_driver
结构体解析
platform_driver
是驱动注册的核心数据结构,需开发者自定义并初始化其成员:
cppstruct platform_driver { int (*probe)(struct platform_device *); // 设备匹配成功后调用的初始化函数 int (*remove)(struct platform_device *); // 设备移除或驱动卸载时的清理函数 void (*shutdown)(struct platform_device *); // 设备断电时调用 int (*suspend)(struct platform_device *, pm_message_t); // 设备休眠时调用 int (*resume)(struct platform_device *); // 设备唤醒时调用 struct device_driver driver; // 内嵌的标准驱动结构 const struct platform_device_id *id_table; // 支持多设备的ID匹配表 };
关键成员说明:
probe
与remove
probe
:负责设备初始化(如申请资源、注册字符设备等),是驱动的核心逻辑入口。remove
:释放资源(如注销设备、释放内存等),与probe
逻辑对应。struct device_driver driver
name
:驱动名称,用于与传统设备(非设备树)匹配。of_match_table
:设备树匹配表(类型为of_device_id
),通过compatible
属性匹配设备树节点。 示例:
cppstatic const struct of_device_id my_driver_match[] = { { .compatible = "vendor,my-device" }, // 设备树节点的 compatible 值 {} };
id_table
。支持驱动匹配多个设备名称(如
"device_v1"
,"device_v2"
),用于非设备树场景函数内部机制
注册流程
- 设置
driver.bus = &platform_bus_type
,绑定到平台总线。- 将
probe
/remove
等函数指针挂载到总线的默认实现(如platform_drv_probe
)。- 调用
driver_register()
将驱动加入内核总线系统。匹配机制
当驱动与设备注册后,总线通过
platform_match()
函数进行匹配,优先级如下:
- 设备树 :检查设备树节点的
compatible
是否与of_match_table
匹配。- ID 表 :检查设备名是否在
id_table
中。- 名称匹配 :比较设备与驱动的
name
字段是否一致。 ✅ 匹配成功后,调用驱动的probe()
函数。使用示例
1. 驱动代码框架
cpp#include <linux/platform_device.h> static int my_probe(struct platform_device *pdev) { // 1. 获取设备资源(内存、中断等) // 2. 初始化硬件(如配置GPIO、寄存器) // 3. 注册字符设备/网络设备等 return 0; } static int my_remove(struct platform_device *pdev) { // 释放资源,注销设备 return 0; } static const struct of_device_id my_of_match[] = { { .compatible = "vendor,my-device" }, {} }; static struct platform_driver my_driver = { .driver = { .name = "my_driver", .of_match_table = my_of_match, }, .probe = my_probe, .remove = my_remove, }; module_platform_driver(my_driver); // 简化注册/注销
2. 设备树节点示例
cppmy_device { compatible = "vendor,my-device"; reg = <0x10000000 0x1000>; // 内存资源 interrupts = <0 45 IRQ_TYPE_LEVEL_HIGH>; // 中断资源 };
4.platform_driver_unregister()
cpp#include <linux/platform_device.h> void platform_driver_unregister(struct platform_driver *drv);
platform_driver_unregister
是 Linux 内核中用于注销平台设备驱动的核心函数,其功能是安全移除已注册的驱动并释放资源。功能概述
核心作用。调用后,驱动不再响应匹配的设备请求,并触发资源清理。
用于从平台总线(
platform_bus_type
)中注销指定的platform_driver
驱动结构体调用时机
通常在驱动模块卸载函数(
module_exit
)中调用,与platform_driver_register
配对使用
IIC总线驱动
相关结构体
1.struct i2c_driver{}-结构体
cpp#include <linux/i2c.h> struct i2c_driver { int (*probe)(struct i2c_client *client, const struct i2c_device_id *id); // 当IIC总线驱动的驱动信息和设备信息匹配成功后,需要执行的函数 int (*remove)(struct i2c_client *client); // 当外接的使用IIC总线驱动的设备移出当前环境后,需要执行的函数 struct device_driver driver; // 保存的是当前使用IIC总线驱动的驱动信息结构体 const struct i2c_device_id *id_table; // 用于IIC总线驱动信息和设备信息匹配的结构体(通过IIC总线驱动的match函数进行匹配) };
关键成员详解
以下是
i2c_driver
的核心成员及其作用:
成员 类型/说明 是否必需 probe
int (*)(struct i2c_client *, const struct i2c_device_id *)
✅ 是 设备匹配成功后调用,负责初始化硬件、注册字符设备等。 remove
int (*)(struct i2c_client *)
✅ 是 驱动卸载或设备移除时调用,释放资源(内存、中断等)。 driver
struct device_driver
✅ 是 标准驱动模型结构体,需初始化 .name
(驱动名称)和.of_match_table
(设备树匹配表)。id_table
const struct i2c_device_id *
✅ 是 支持的设备 ID 表,用于非设备树场景的匹配(如 {"at24c08", 0}
)。shutdown
void (*)(struct i2c_client *)
❌ 可选 设备断电时调用(如系统关机)。 suspend
/resume
电源管理函数(休眠/唤醒)。 ❌ 可选 detect
int (*)(struct i2c_client *, struct i2c_board_info *)
❌ 可选 自动检测设备时调用(较少使用)。
probe
和remove
是驱动必须实现的函数。id_table
与driver.of_match_table
至少需初始化一个(设备树优先)两种驱动模式对比
Linux I2C 驱动有两种编写模式,需避免混用成员初始化:
模式 特点 初始化成员 Legacy Model 需手动创建 i2c_client
,硬编码设备地址(如i2c_new_device()
)。attach_adapter
、detach_client
Standard Model 由内核自动创建 i2c_client
,通过设备树或id_table
匹配设备。probe
、remove
、id_table
注意:若同时初始化两种模式的成员(如 attach_adapter 和 probe),内核会报错
使用示例
驱动定义与注册
cpp#include <linux/i2c.h> static int my_probe(struct i2c_client *client, const struct i2c_device_id *id) { // 初始化设备:申请资源、注册字符设备等 return 0; } static int my_remove(struct i2c_client *client) { // 释放资源 return 0; } static const struct i2c_device_id my_id_table[] = { { "at24c08", 0 }, // 设备名与地址 {} }; static const struct of_device_id my_of_match[] = { { .compatible = "vendor,eeprom" }, // 设备树匹配标识 {} }; struct i2c_driver my_driver = { .driver = { .name = "my_eeprom", .of_match_table = my_of_match, }, .probe = my_probe, .remove = my_remove, .id_table = my_id_table, }; module_i2c_driver(my_driver); // 注册驱动
数据传输
在 probe中可通过 i2c_transfer读写设备:
cppstatic int read_data(struct i2c_client *client, u8 reg, u8 *buf, int len) { struct i2c_msg msg[2] = { { // 写寄存器地址 .addr = client->addr, .flags = 0, .len = 1, .buf = ®, }, { // 读数据 .addr = client->addr, .flags = I2C_M_RD, .len = len, .buf = buf, } }; return i2c_transfer(client->adapter, msg, 2); }
在 Linux I2C 架构中的位置
i2c_driver
属于 设备驱动层,与以下组件交互:
i2c_client
:表示物理 I2C 设备(地址、名称等)。i2c_adapter
:描述 I2C 控制器(适配器),提供master_xfer()
硬件操作函数。- I2C 核心层 :通过
i2c_add_driver()
注册驱动,触发总线匹配(调用probe
)注意事项
- 匹配优先级 :设备树 >
id_table
> 驱动名称。- 资源释放 :
remove
必须逆向回滚probe
中的操作。- 并发处理 :I2C 消息(
i2c_msg
)需正确处理多设备并发访问
2.struct i2c_client{}-结构体
cpp#include <linux/i2c.h> struct i2c_client { unsigned short flags; // 标志位(如地址位数、协议特性) unsigned short addr; // 设备地址(7位或10位) char name[I2C_NAME_SIZE]; // 设备名称(用于匹配驱动) struct i2c_adapter *adapter; // 所属总线适配器 struct i2c_driver *driver; // 绑定的设备驱动 struct device dev; // 内嵌的设备模型结构体 int irq; // 设备中断号 struct list_head detected; // 已探测设备链表 };
i2c_client
代表一个物理 I²C 设备(如传感器、EEPROM),由内核在设备初始化时动态创建。它作为设备驱动的操作入口,封装了设备的硬件信息(地址、中断等)和所属总线适配器(i2c_adapter
)关键成员详解
addr
- 设备的 I²C 从机地址(7位或10位),主机通过此地址访问设备。
- 地址组成 :
- 读写位 :第7位(0=写,1=读),例如地址
0x50
写操作时为0xA0
(0x50 << 1 | 0
)。- 地址类型 :
I2C_M_TEN
标志表示10位地址(默认为7位)。
adapter
- 指向所属的
i2c_adapter
(总线控制器),通过adapter->algo->master_xfer()
实现底层数据传输。- 例如:适配器
i2c-0
控制总线时序,设备通过其收发数据。
name
- 设备名称(如
"mpu6050"
),与i2c_driver
的id_table
或设备树compatible
属性匹配。
irq
- 设备的中断号,用于触发事件(如数据就绪)。需在驱动中注册中断处理函数。
driver
- 指向管理该设备的
i2c_driver
,在匹配成功后由内核赋值。驱动通过probe()
初始化设备在 I²C 子系统中的角色
- 设备抽象
- 将物理设备(如 MPU6050 陀螺仪)抽象为内核对象,提供统一的访问接口。
- 驱动匹配
- 与
i2c_driver
通过名称或设备树匹配,触发probe()
初始化设备。- 总线管理
- 通过
adapter
关联总线控制器,确保数据传输由硬件适配器处理i2c_client 的创建方式
方法 描述 适用场景 示例 设备树(首选) 在设备树节点定义 compatible
和reg
(地址),内核解析生成i2c_client
。新内核(≥3.0) reg = <0x69>; compatible="invensense,mpu6050"
i2c_board_info 静态定义设备信息,通过 i2c_register_board_info()
注册。旧内核或无设备树平台 I2C_BOARD_INFO("24c08", 0x50)
i2c_new_device() 动态创建设备,需手动指定地址和适配器。 运行时动态添加设备 i2c_new_device(adapter, &info)
与 i2c_driver 的交互流程
- 匹配阶段
- 内核根据设备树
compatible
或i2c_driver->id_table
匹配设备与驱动。- 初始化阶段
- 匹配成功后调用
i2c_driver->probe()
,传入i2c_client
指针,驱动可初始化硬件(如配置寄存器)。- 数据传输
- 驱动通过
i2c_transfer()
或i2c_smbus_*
接口发送数据,最终由adapter->algo->master_xfer()
实现。- 资源释放
- 设备移除时调用
i2c_driver->remove()
释放资源(如注销中断)
cppgraph TD i2c_client[i2c_client] --> |依附于适配器|i2c_adapter(i2c_adapter) i2c_client --> |绑定驱动|i2c_driver(i2c_driver) i2c_driver --> |初始化设备|probe(probe) i2c_adapter --> |实现传输逻辑|i2c_algorithm(i2c_algorithm) i2c_algorithm --> |硬件时序控制|master_xfer(master_xfer)
3.struct i2c_adapter{}-结构体
cpp#include <linux/i2c.h> struct i2c_adapter { struct module *owner; // 所属模块(用于引用计数) unsigned int class; // 允许探测的设备类别 const struct i2c_algorithm *algo; // 总线访问算法(关键操作函数集) void *algo_data; // 算法私有数据 struct rt_mutex bus_lock; // 总线访问互斥锁(防止并发冲突) int timeout; // 传输超时时间(单位:jiffies) int retries; // 传输失败重试次数 struct device dev; // 关联的底层设备结构体 int nr; // 适配器编号(如 i2c-0 中的 0) char name[48]; // 适配器名称(如 "i2c-imx") struct list_head userspace_clients; // 用户空间创建的客户端链表 const struct i2c_adapter_quirks *quirks; // 适配器特殊行为/限制描述 struct irq_domain *host_notify_domain; // 主机通知中断域(I2C v3.0 特性) struct regulator *bus_regulator; // 总线电源调节器(可选) };
4.struct i2c_msg{}-结构体
cppstruct i2c_msg { __u16 addr; /* slave address */ __u16 flags; //读写标志位 __u16 len; /* msg length */ __u8 *buf; /* pointer to msg data */ }; #define I2C_M_RD 0x0001 /* read data, from slave to master */ /* I2C_M_RD is guaranteed to be 0x0001! */ #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ #define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */ /* makes only sense in kernelspace */ /* userspace buffers are copied anyway */ #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
结构体定义与作用
核心定位
- 物理适配器抽象 :每个
i2c_adapter
对应一个硬件 I2C 控制器(如 SoC 中的 I2C0、I2C1),控制总线的信号时序(如 START、STOP、ACK)。- 通信桥梁 :为挂载在该总线上的 I2C 设备(
i2c_client
)提供数据传输通道。在 I2C 架构中的位置
- 总线驱动层:属于 I2C 子系统三层架构中的第二层(适配器层),向上对接 I2C 核心层,向下控制硬件控制器
关键成员详解
类型/说明 成员 作用 const struct i2c_algorithm *
algo
核心通信方法 :指向 i2c_algorithm
,提供底层传输函数(如master_xfer
)。struct device
dev
设备模型接口:关联的物理设备信息(如设备树节点、父设备)。 char[48]
name
适配器名称:标识总线(如 "i2c-0"),用于调试和日志。 int
nr
总线编号:动态或静态分配的适配器 ID(如 0、1)。 int
timeout
传输超时 :以 jiffies
为单位的等待时间。int
retries
重试次数:传输失败后的重试机制。 struct rt_mutex
bus_lock
总线锁:保护多设备并发访问的互斥锁。 struct i2c_bus_recovery_info *
bus_recovery_info
总线恢复机制:处理总线锁死(如 SCL/SDA 线拉低)的硬件操作函数。 说明:
algo
是核心成员 :通过master_xfer
实现实际数据传输(如i2c_transfer
最终调用此函数)。bus_recovery_info
为可选:部分厂商(如 NXP、ST)实现,瑞芯微可能未支持。与
i2c_algorithm
的关系
i2c_adapter
依赖i2c_algorithm
实现具体通信逻辑,二者关系如下:
- 绑定方式:
cppstruct i2c_adapter my_adapter = { .algo = &my_algorithm, // 指向自定义算法 };
- 算法实现 :
i2c_algorithm
必须实现以下函数:
master_xfer
:执行 I2C 消息传输(如读写从设备寄存器)。functionality
:返回适配器支持的功能(如 I2C_FUNC_I2C、I2C_FUNC_SMBUS_EMUL)。在 Linux I2C 子系统中的角色
组件 关联点 i2c_client
通过 i2c_client->adapter
绑定到适配器,实现设备与总线的关联。i2c_driver
驱动通过 i2c_add_driver
注册后,由 I2C 核心层匹配适配器上的设备。I2C 核心层 调用 i2c_add_adapter()
注册适配器,将其加入全局链表。
5.注册与注销方法
注册适配器
cpp// 动态分配总线号 int i2c_add_adapter(struct i2c_adapter *adap); // 静态指定总线号(如适配器编号需固定) int i2c_add_numbered_adapter(struct i2c_adapter *adap);
返回值 :成功返回
0
,失败返回错误码(如-ENOMEM
)注销适配器
cppvoid i2c_del_adapter(struct i2c_adapter *adap);
使用示例(适配器初始化)
cpp#include <linux/i2c.h> static const struct i2c_algorithm my_algo = { .master_xfer = my_xfer_function, // 实现硬件传输函数 .functionality = my_func_check, // 返回支持的功能 }; struct i2c_adapter my_adapter = { .owner = THIS_MODULE, .name = "my_i2c_adapter", .algo = &my_algo, // 绑定算法 .timeout = HZ, // 超时时间(1秒) .retries = 3, // 重试3次 }; static int __init my_adapter_init(void) { return i2c_add_adapter(&my_adapter); // 动态注册 } module_init(my_adapter_init); static void __exit my_adapter_exit(void) { i2c_del_adapter(&my_adapter); // 注销 } module_exit(my_adapter_exit);
注意事项
- 硬件依赖 :
master_xfer
需根据控制器手册实现(如操作寄存器生成 START/STOP 信号)。- 并发控制 : 多设备访问时需通过
bus_lock
避免冲突。- 设备树支持 : 适配器通常在设备树中描述(如
compatible = "fsl,imx6ul-i2c"
),驱动需匹配并初始化。
总结
i2c_adapter
是 Linux I2C 总线驱动的核心载体,其核心职责包括:
- 抽象硬件控制器:管理物理总线资源(如时钟、中断)。
- 绑定通信算法 :通过
i2c_algorithm
实现数据传输。- 连接设备与驱动 :作为枢纽关联
i2c_client
和i2c_driver
。 开发者需重点关注algo
的实现与注册流程,并结合设备树完成适配器驱动开发。
相关函数
6.module_i2c_driver()-一键注册宏
cpp#define module_i2c_driver(__i2c_driver) \ module_driver(__i2c_driver, i2c_add_driver, \ i2c_del_driver) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
用于iic设备驱动的一键注册
7.i2c_transfer()
cpp#include <linux/i2c.h> int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
i2c_transfer()
是 Linux 内核 I2C 子系统的核心函数,用于实现 I2C 总线上的数据传输。它支持多消息连续传输,适用于读写寄存器、传感器数据交互等场景。
核心作用。
通过指定的 I2C 适配器(
i2c_adapter
)执行一组 I2C 消息(i2c_msg
),实现主设备与从设备间的同步数据传输
- 同步执行:函数阻塞直至所有消息传输完成或出错。
- 多消息支持:支持连续发送、接收或混合操作(如"写-读"序列)。
在 I2C 架构中的位置
位于 I2C 核心层 ,调用适配器的
master_xfer
方法(如i2c_imx_xfer
),最终由硬件控制器完成物理传输参数说明
类型 参数 说明 struct i2c_adapter *
adap
I2C 适配器对象,代表物理总线控制器(如 i2c0
)。struct i2c_msg *
msgs
消息结构体数组指针,每个元素描述一个独立传输操作(写/读)。 int
num
消息数量( msgs
数组长度)。i2c_msg结构体关键字段
字段 说明 addr
从设备地址(7位或10位)。 flags
传输标志: • 0
:写操作 •I2C_M_RD
:读操作。len
数据长度(字节数)。 buf
数据缓冲区指针(写操作时为发送数据,读操作时为接收数据存储位置)。 返回值
- 成功 :返回实际传输的消息数量(应与
num
相等)。- 失败 :返回负值错误码(如
EIO
表示总线错误,EINVAL
表示无效参数)
IIC在设备树节点解析
cpp// 在stm32mp151.dtsi文件中最初编写了IIC1外设控制器的设备树节点信息 i2c1: i2c@40012000 { compatible = "st,stm32mp15-i2c"; // iic1外设控制器节点的compatible值 reg = <0x40012000 0x400>; // 当前iic1外设控制器的寄存器属性:起始地址 可操作的地址大小 interrupt-names = "event", "error"; // 当前iic1外设控制器支持的中断模式:外部时间中断触发iic外设控制器、iic外设控制器使用错误 interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>, // iic1支持的外部中断的中断信息 <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>; clocks = <&rcc I2C1_K>; // iic1外设控制器时钟属性 resets = <&rcc I2C1_R>; // iic1外设控制器复位属性 #address-cells = <1>; // 当前iic1节点的子节点用于描述地址信息需要1个32位数 #size-cells = <0>; // 当前iic1节点的子节点用于描述地址大小需要0个32位数 dmas = <&dmamux1 33 0x400 0x80000001>, // iic1外设控制器支持的DMA属性 <&dmamux1 34 0x400 0x80000001>; dma-names = "rx", "tx"; // iic1外设控制器支持的DMA传输模式:接收 发送 power-domains = <&pd_core>; st,syscfg-fmp = <&syscfg 0x4 0x1>; wakeup-source; i2c-analog-filter; status = "disabled"; // iic1外设控制器当前的状态属性:未使能状态 }; 在stm32mp15xx-fsmp1x.dtsi文件中引用并修改了iic5外设控制器节点信息 &i2c5 { pinctrl-names = "default", "sleep"; // iic5外设控制器两种引脚工作模式:默认工作模式(正常使用模式)、低功耗模式 pinctrl-0 = <&i2c5_pins_a>; // iic5外设控制器在正常使用模式下使用的引脚复用组信息(a组) pinctrl-1 = <&i2c5_sleep_pins_a>; // iic5外设控制器在低功耗模式下使用的引脚复用组信息(a组) i2c-scl-rising-time-ns = <100>; // iic5外设控制器支持的时钟线上升沿时间:100ns i2c-scl-falling-time-ns = <7>; // iic5外设控制器支持的时钟线下降沿时间:7ns clock-frequency = <100000>; // iic5外设控制器支持的时钟频率:100000Hz /* spare dmas for other usage */ /delete-property/dmas; // 删除iic5支持的DMA属性 /delete-property/dma-names; // 删除iic5支持的DMA属性模式 status = "okay"; // 修改后的iic5外设控制器状态属性:处于使能状态 hdmi-transmitter@39 { // 使用iic5外设控制器的实际外设节点信息 compatible = "sil,sii9022"; // hdmi-transmitter外设节点的compatible值 reg = <0x39>; // 描述hdmi-transmitter外设的地址信息(7位从机地址) iovcc-supply = <&v3v3_hdmi>; cvcc12-supply = <&v1v2_hdmi>; reset-gpios = <&gpioa 13 GPIO_ACTIVE_LOW>; interrupts = <14 IRQ_TYPE_EDGE_FALLING>; interrupt-parent = <&gpioa>; #sound-dai-cells = <0>; status = "okay"; // hdmi-transmitter外设的状态属性:处于使能状态 在stm32mp15-pinctrl.dtsi文件中描述引脚复用组信息 // iic5正常工作模式下使用的引脚复用a组 i2c5_pins_a: i2c5-0 { pins { pinmux = <STM32_PINMUX('A', 11, AF4)>, /* I2C5_SCL */ // 将PA11引脚复用为I2C5_SCL的功能 <STM32_PINMUX('A', 12, AF4)>; /* I2C5_SDA */ // 将PA12引脚复用为I2C5_SDA的功能 bias-disable; drive-open-drain; slew-rate = <0>; }; }; // iic5低功耗模式下使用的引脚复用a组 i2c5_sleep_pins_a: i2c5-sleep-0 { pins { pinmux = <STM32_PINMUX('A', 11, ANALOG)>, /* I2C5_SCL */ <STM32_PINMUX('A', 12, ANALOG)>; /* I2C5_SDA */ 对于FSMP1A开发板套件而言,我们需要引用并修改IIC1外设控制器节点,并添加使用IIC1外设控制器的实际外设节点信息 在stm32mp157a-fsmp1a.dts文件中编写以下内容:(用于引用并修改了别的文件中写好的节点信息,需要编写在根节点外) &i2c1 { pinctrl-names = "default", "sleep"; pinctrl-0 = <&i2c1_pins_b>; pinctrl-1 = <&i2c1_sleep_pins_b>; i2c-scl-rising-time-ns = <100>; i2c-scl-falling-time-ns = <7>; clock-frequency = <100000>; /* spare dmas for other usage */ /delete-property/dmas; /delete-property/dma-names; status = "okay"; mysi7006@40 { compatible = "hqyj,si7006"; reg = <0x40>; status = "okay"; }; }; 编写玩设备树文件后,需要在内核源码顶层目录下make dtbs编译设备树文件,将生成的设备树镜像文件拷贝到tftpboot文件夹中
SPI总线驱动
相关结构体
8.struct spi_driver{}-结构体
cpp#include <linux/spi/spi.h> struct spi_driver { const struct spi_device_id *id_table; // 支持的设备 ID 列表(用于匹配) int (*probe)(struct spi_device *spi); // 设备探测函数(匹配时调用) int (*remove)(struct spi_device *spi); // 设备移除函数(卸载时调用) void (*shutdown)(struct spi_device *spi); // 设备关闭函数(关机时调用) struct device_driver driver; // 内嵌的设备驱动基类 };
spi_driver
是 Linux 内核中描述 SPI 设备驱动的核心结构体,用于管理挂载在 SPI 总线上的从设备(如传感器、Flash 存储器等)。其作用类似于 I²C 子系统中的i2c_driver
,负责设备的初始化、数据传输和资源管理。关键成员作用:
id_table
- 指向
spi_device_id
数组,定义驱动支持的设备名称(如{"mpu6050", 0}
),用于与传统匹配方式绑定设备。- 若使用设备树(Device Tree),则通过
driver.of_match_table
指定compatible
属性匹配(如{"invensense,mpu6050"}
)。probe()
- 当驱动与设备匹配成功后调用,负责初始化硬件(如配置寄存器、申请中断、注册字符设备等)。
- 与 I²C 驱动不同,SPI 驱动可在运行时动态调整总线参数(如时钟频率、SPI 模式)。
remove()
- 设备卸载时调用,释放资源(如注销中断、销毁设备节点、释放内存)。
driver
- 内嵌的
device_driver
结构体,需设置以下字段:
.name
:驱动名称(如"icm20607"
)。.owner
:模块所有者(通常为THIS_MODULE
)。.of_match_table
:设备树匹配表(类型为of_device_id
)
9.struct spi_device{}-结构体
spi_device是 Linux 内核中描述 SPI(Serial Peripheral Interface)从设备的核心结构体,用于抽象物理 SPI 设备(如传感器、Flash 存储器等)的硬件属性和配置信息。其作用类似于 I²C 子系统中的 i2c_client,但针对 SPI 总线特性设计
核心定义与作用
spi_device
定义在include/linux/spi/spi.h
中,主要作用包括:
- 硬件抽象:封装 SPI 设备的物理参数(如时钟频率、片选引脚、传输模式等)。
- 连接控制器 :通过
spi_controller
(原spi_master
)关联到具体的 SPI 总线控制器,实现底层硬件操作。- 驱动匹配 :与
spi_driver
绑定,触发设备初始化(probe
)和资源释放(remove
)关键成员详解
以下是
spi_device
的核心字段及其功能:
类型 成员 作用 struct device
dev
内嵌的通用设备模型基类,用于设备注册、sysfs 接口管理等。 struct spi_controller*
controller
指向关联的 SPI 控制器(如 SoC 的 SPI 硬件模块),管理底层时序和传输。 u32
max_speed_hz
SPI 通信的最大时钟频率(单位:Hz),驱动可动态调整。 u8
chip_select
片选引脚编号(CS),用于选择总线上的特定设备。 u16
mode
SPI 传输模式标志位(见下表详解)。 u8
bits_per_word
每个数据字的位数(默认为 8)。 int
irq
设备的中断号(若支持中断)。 int
cs_gpio
片选引脚对应的 GPIO 编号(若使用 GPIO 模拟片选)。 char[SPI_NAME_SIZE]
modalias
设备别名(如 "mpu6050"
),用于匹配驱动。SPI 模式标志位(
mode
字段)SPI 模式由
CPOL
(时钟极性)和CPHA
(时钟相位)组合定义,常见模式如下
标志位 值 含义 SPI_MODE_0
0x00
CPOL=0, CPHA=0(标准模式) SPI_MODE_1
0x01
CPOL=0, CPHA=1 SPI_MODE_2
0x02
CPOL=1, CPHA=0 SPI_MODE_3
0x03
CPOL=1, CPHA=1 SPI_CS_HIGH
0x04
片选高电平有效(默认低电平) SPI_LSB_FIRST
0x08
数据低位先传输(默认高位先传输) SPI_3WIRE
0x10
三线模式(共享数据线) SPI_LOOP
0x20
回环测试模式
10.struct spi_controller{}-结构体
相关函数
11.module_spi_driver()-一键注册宏
cpp#define module_spi_driver(__spi_driver) \ module_driver(__spi_driver, spi_register_driver, \ spi_unregister_driver) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit); // 一键注册宏包含了 // 1、入口函数、入口函数的声明、入口函数中实现将驱动信息加载到内核空间中 // 2、出口函数、出口函数的声明、出口函数中实现将驱动信息从内核空间中移除
12.spi_write()
cpp#include <linux/spi/spi.h> int spi_write(struct spi_device *spi, const void *buf, size_t len);
spi_write
是 Linux SPI 子系统中的核心函数,用于实现同步数据写入操作。其作用是将数据通过 SPI 总线发送到指定的从设备(Slave Device)。
spi
: 指向目标 SPI 设备的spi_device
结构体指针,包含设备地址、总线参数等。buf
: 待发送数据的缓冲区指针。len
: 发送数据的字节长度。- 返回值 :成功返回 0,失败返回负的错误码(如
EIO
表示硬件错误)核心功能
- 将
buf
中的数据通过 SPI 总线同步写入到指定设备。- 调用时会阻塞当前线程直至传输完成(可睡眠上下文)
Linux 内核中的实现机制
spi_write
在 Linux 内核中通过组合spi_transfer
和spi_message
实现:
cppstatic inline int spi_write(struct spi_device *spi, const void *buf, size_t len) { struct spi_transfer t = { .tx_buf = buf, // 发送缓冲区 .len = len, // 数据长度 }; struct spi_message m; spi_message_init(&m); // 初始化消息 spi_message_add_tail(&t, &m); // 添加传输段 return spi_sync(spi, &m); // 同步执行传输 }
spi_transfer
: 描述单次数据传输(方向、缓冲区、长度)。spi_message
: 管理多个spi_transfer
,支持复杂传输序列(如先写后读)。spi_sync
: 同步执行消息队列,阻塞等待传输完成。参数配置与传输控制
SPI 模式与时钟
- 通过
spi_setup(spi)
配置设备参数(模式、时钟频率)。- 示例:设置模式为
SPI_MODE_3
(CPOL=1, CPHA=1):
cppspi->mode = SPI_MODE_3; spi->max_speed_hz = 10000000; // 10 MHz spi_setup(spi);
片选信号(CS)管理
- 驱动需手动控制 GPIO 模拟片选:
cppgpio_direction_output(cs_pin, 0); // 拉低 CS 开始传输 spi_write(spi_dev, data, len); gpio_set_value(cs_pin, 1); // 拉高 CS 结束传输
错误示例:未保持 CS 持续低电平导致传输中断
SPI的设备树节点解析
cpp在stm32mp151.dtsi中最初编写的SPI4外设控制器节点信息 spi4: spi@44005000 { #address-cells = <1>; // 当前设备树节点的子节点描述地址需要使用1个32位数 #size-cells = <0>; // 当前设备树节点的子节点描述地址大小需要使用0个32位数 compatible = "st,stm32h7-spi"; // spi4设备树节点的compatible值 reg = <0x44005000 0x400>; // spi4设备树节点的起始地址和可操作的地址大小 interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>; // spi4设备树节点支持的中断属性 clocks = <&rcc SPI4_K>; // spi4设备树节点的时钟属性 resets = <&rcc SPI4_R>; // spi4设备树节点的复位属性 dmas = <&dmamux1 83 0x400 0x01>, // spi4设备树节点支持的DMA属性 <&dmamux1 84 0x400 0x01>; dma-names = "rx", "tx"; // spi4设备树节点支持的DMA工作模式(发送和接收) power-domains = <&pd_core>; status = "disabled"; // 当前spi4设备树节点的状态:处于未使能状态 }; 在参考板设备树文件stm32mp15xx-dkx.dtsi文件中引用并更改了spi4节点信息 &spi4 { pinctrl-names = "default", "sleep"; // 当前spi4外设控制器的工作模式(default:正常工作模式、sleep:低功耗模式) pinctrl-0 = <&spi4_pins_b>; // 在正常工作模式下,spi4使用的引脚复用组(b组) pinctrl-1 = <&spi4_sleep_pins_b>; // 在低功耗模式下,spi4使用的引脚复用组(b组) status = "disabled"; // 当前spi4的状态:处于未使能状态 }; 在stm32mp15-pinctrl.dtsi文件中定义了引脚服用组信息 spi4_pins_b: spi4-1 { pins1 { pinmux = <STM32_PINMUX('E', 12, AF5)>, /* SPI4_SCK */ <STM32_PINMUX('E', 14, AF5)>; /* SPI4_MOSI */ bias-disable; drive-push-pull; slew-rate = <1>; }; pins2 { pinmux = <STM32_PINMUX('E', 13, AF5)>; /* SPI4_MISO */ bias-disable; }; }; spi4_sleep_pins_b: spi4-sleep-1 { pins { pinmux = <STM32_PINMUX('E', 12, ANALOG)>, /* SPI4_SCK */ <STM32_PINMUX('E', 13, ANALOG)>, /* SPI4_MISO */ <STM32_PINMUX('E', 14, ANALOG)>; /* SPI4_MOSI */ }; }; // 问题:没有查看到片选线的引脚信息 // 解决:进入~/FSMP1A/linux-5.10.10/Documentation/devicetree/bindings/spi,存在关于spi设备树编写的相关规则 查看spi-bus.txt文件即可 // 在stm32mp157a-fsmp1a.dts文件下引用并更改spi4节点信息,并加上spi4节点下的外设节点信息(在根节点外引用) &spi4 { pinctrl-names = "default", "sleep"; // 当前spi4外设控制器的工作模式(default:正常工作模式、sleep:低功耗模式) pinctrl-0 = <&spi4_pins_b>; // 在正常工作模式下,spi4使用的引脚复用组(b组) pinctrl-1 = <&spi4_sleep_pins_b>; // 在低功耗模式下,spi4使用的引脚复用组(b组) cs-gpios = <&gpioe 11 0>; // 片选线配置的相关属性 status = "okay"; // 当前spi4的状态:处于未使能状态 mym74hc595@0 { compatible = "hqyj,m74hc595"; spi-max-frequency = <1000000>; reg = <0x0>; }; }; // 编写完设备树文件后,需要到内核源码顶层目录下make dtbs编译并生成新的设备树镜像文件,并将新的设备树镜像文件拷贝到tftpboot下