📘 推荐阅读:
点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
支持原创,支持国产嵌入式生态!
一、引言
I2C 是嵌入式系统中最常用的总线之一,广泛应用于音频芯片、RTC、电源管理芯片、温湿度传感器等外围设备的通信。在 Linux 内核中,I2C 被封装成一个完整的子系统,提供了标准的驱动模型、设备注册机制、传输抽象层,使得设备驱动开发高度模块化。
本文将围绕 I2C 内核子系统的架构展开,从基础结构、通信机制、驱动模型、设备树匹配到实际代码分析,带你系统理解并掌握这一关键子系统。
二、I2C 子系统架构概览
1. 基本结构
在 Linux 内核中,I2C 子系统分为三大核心组成:
模块 | 说明 |
---|---|
I2C Adapter | 适配器,代表 I2C 控制器硬件资源 |
I2C Bus | 逻辑总线,挂载多个设备节点 |
I2C Client | 客户端,代表挂在 I2C 总线上的设备 |
内核通过抽象出 i2c_adapter
与 i2c_client
这两个核心结构体,构建起一个平台无关的通信机制。
2. 关键结构体
struct i2c_adapter {
const struct i2c_algorithm *algo;
struct device dev;
int nr;
...
};
struct i2c_client {
unsigned short addr;
struct i2c_adapter *adapter;
struct device dev;
...
};
三、驱动模型与注册流程
1. 驱动模型概述
I2C 驱动遵循内核标准的 总线-设备-驱动模型。驱动开发通常分为以下两类:
驱动类型 | 注册结构体 | 注册接口 |
---|---|---|
控制器驱动(主机) | i2c_adapter | i2c_add_adapter() |
设备驱动(从机) | i2c_driver | i2c_register_driver() |
2. 典型注册流程图
[Device Tree 节点]
↓
of_i2c_register_devices()
↓
i2c_new_device() / i2c_new_probed_device()
↓
匹配 i2c_driver
↓
调用 probe() → 初始化设备
四、设备树中的配置方式
设备树中,I2C 控制器与从设备通过如下方式描述:
1. 控制器节点(示例:i.MX8MP)
i2c1: i2c@30a20000 {
compatible = "fsl,imx8mp-i2c", "fsl,imx21-i2c";
reg = <0x30a20000 0x10000>;
clocks = <&clk IMX8MP_CLK_I2C1_ROOT>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
};
2. 挂载设备节点(如 EEPROM)
at24@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
✅ 注意事项:
#address-cells = <1>
和reg = <0x50>
共同决定设备地址;compatible
属性用于驱动匹配。
五、典型驱动分析:at24 EEPROM 驱动
1. 注册结构体
static struct i2c_driver at24_driver = {
.driver = {
.name = "at24",
.of_match_table = at24_of_match,
},
.probe = at24_probe,
.remove = at24_remove,
.id_table = at24_ids,
};
2. 匹配表与设备树关联
static const struct of_device_id at24_of_match[] = {
{ .compatible = "atmel,24c02" },
{ /* sentinel */ }
};
3. 驱动注册入口
module_i2c_driver(at24_driver);
这是宏定义,会在模块加载时自动调用 i2c_register_driver()
。
六、传输接口与调试方法
1. 传输 API
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
2. 通信抓包调试
-
启用调试信息:
echo 1 > /sys/module/i2c_core/parameters/debug
-
使用
i2cdetect/i2cget/i2cset
工具验证通信:i2cdetect -y 0 i2cget -y 0 0x50
七、从实战出发,理清驱动逻辑
从 platform 驱动角度理解:
- 控制器驱动往往挂在
platform_driver
下注册; - 设备驱动使用
i2c_driver
注册,与设备树通过compatible
匹配; - 匹配后会调用
probe()
,获取寄存器资源、注册字符设备等操作。
八、总结要点
关键点 | 说明 |
---|---|
总线模型 | 与 platform 总线分离,I2C 是专用子系统 |
通信机制 | 统一使用 i2c_transfer 封装硬件实现 |
驱动结构 | 控制器使用 i2c_adapter ,设备使用 i2c_driver |
匹配机制 | 基于设备树的 compatible + of_match_table |
九、经典问题与答案(用于复习巩固)
Q1:i2c_adapter
和 i2c_client
的关系?
答:
i2c_client
通过 adapter
字段指向所依赖的 i2c_adapter
,即总线上每个设备都挂在一个具体的控制器上,adapter 是 master,client 是 slave。
Q2:I2C 驱动为何不直接使用 platform_driver
?
答:
虽然很多 I2C 控制器底层是通过 platform 驱动实现,但 I2C 子系统为了统一设备驱动模型,使用了更抽象的 i2c_driver
,避免与具体平台耦合,便于复用和设备管理。
Q3:i2c_transfer
与 i2c_master_send
有何区别?
答:
i2c_transfer
更底层,支持多段传输(read+write等组合),而 i2c_master_send
是封装后的简单写操作,适用于多数 I2C 设备。
Q4:如何在设备树中区分多个同类设备?
答:
通过设备地址(reg
属性)区分,从而可挂载多个如 EEPROM 的从设备。
Q5:为什么设备驱动可以不关心 I2C 控制器?
答:
I2C 子系统封装了适配器与设备间的通信协议,驱动开发只需要实现与 i2c_client
交互的接口,无需关注控制器的实现细节。
🔚 结语
I2C 是嵌入式开发绕不开的基础子系统之一,掌握其内核架构与驱动模型,不仅有助于设备驱动开发,也有助于深入理解 Linux 子系统的模块化设计思想。
📘 推荐学习资料:
点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
内容涵盖 I2C 驱动开发 + Yocto 菜谱集成 + 设备树配置等全流程实践。