2. Linux I2C 子系统基础:I2C 总线协议简介、Linux I2C 核心框架、I2C 设备驱动模型
在 Camera Sensor 驱动开发中,I2C 总线是连接主控 SoC 与 Sensor 模组最常用的控制通道。Sensor 的寄存器读写、初始化序列下发、工作模式切换等操作,全部依赖 I2C 通信完成。本章从物理协议、内核框架、驱动模型三个层次,为你建立完整的 I2C 子系统知识体系。
2.1 I2C 总线协议简介
I2C(Inter-Integrated Circuit)是一种同步、半双工、多主机的串行通信总线,由 Philips 公司(现 NXP)在 1980 年代发明。它仅需两根信号线即可完成设备间的数据交换:串行数据线 SDA 和串行时钟线 SCL。
核心特征: 两根线、多设备、主从通信、7 位/10 位地址、标准模式 100 kHz / 快速模式 400 kHz / 高速模式 3.4 MHz。
2.1.1 物理层与电气特性
- SDA(Serial Data Line): 双向数据线,用于传输地址和数据。
- SCL(Serial Clock Line): 时钟线,由主设备驱动,同步数据传输。
- 开漏输出 + 上拉电阻: 总线空闲时 SDA 和 SCL 均为高电平;设备通过拉低总线实现“线与”逻辑。
- 多设备挂载: 每个从设备有唯一地址(7 位或 10 位),同一总线上最多可挂载 127 个 7 位地址设备。
Camera Sensor 场景: 绝大多数 Sensor 使用 7 位 I2C 地址,典型地址范围 0x20 ~ 0x7F。例如 OV5640 的 7 位地址为 0x3C(写)/ 0x3D(读)。
2.1.2 通信时序与数据帧格式
一次完整的 I2C 传输包含以下阶段:
- 起始条件(START): SCL 高电平时,SDA 由高变低。
- 从设备地址 + 读写位: 主设备发送 7 位地址 + 1 位 R/W(0=写,1=读)。
- 应答位(ACK): 从设备在第 9 个 SCL 周期拉低 SDA 表示应答。
- 数据字节传输: 每字节 8 位,MSB 先传,后跟 ACK。
- 停止条件(STOP): SCL 高电平时,SDA 由低变高。
常见踩坑点: Sensor 的 I2C 地址通常以 8 位形式给出(左移 1 位),例如数据手册写 0x78,实际 7 位地址为 0x3C。务必确认驱动中使用的地址格式。
2.2 Linux I2C 核心框架
Linux 内核将 I2C 子系统抽象为三层架构:I2C 核心层、I2C 总线驱动层(适配器驱动)、I2C 设备驱动层。Camera Sensor 驱动属于设备驱动层,直接调用核心层提供的 API 与硬件通信。
2.2.1 三层架构总览
| 层次 | 职责 | 关键数据结构 | 开发者关注点 |
|---|---|---|---|
| I2C 核心层 | 提供统一的注册/注销接口、数据传输 API、设备模型管理 | i2c_core、i2c_adapter、i2c_client |
调用 i2c_transfer()、i2c_smbus_*() 等 API |
| I2C 总线驱动层 | 实现具体 SoC 的 I2C 控制器硬件操作(如 STM32、i.MX、RK 的 I2C IP) | i2c_algorithm、platform_driver |
SoC 厂商提供,Sensor 驱动开发者无需修改 |
| I2C 设备驱动层 | 驱动具体 I2C 从设备(如 Camera Sensor、EEPROM、触摸屏) | i2c_driver、i2c_device_id |
编写 probe()、remove()、寄存器读写函数 |
对 Sensor 驱动开发者: 你只需要关注设备驱动层。核心层和总线驱动层由内核和 SoC 厂商维护,你通过
i2c_client 结构体获取适配器引用,然后调用传输函数即可。
2.2.2 核心数据结构与 API
在编写 Sensor 驱动时,你会频繁使用以下核心数据结构和 API:
| 数据结构 / API | 所在头文件 | 作用 |
|---|---|---|
struct i2c_client |
linux/i2c.h |
代表一个 I2C 从设备,包含适配器指针、地址、驱动数据 |
struct i2c_driver |
linux/i2c.h |
I2C 设备驱动的注册结构体,包含 probe/remove/id_table |
struct i2c_msg |
linux/i2c.h |
描述一次 I2C 传输消息(地址、方向、缓冲区、长度) |
i2c_transfer() |
linux/i2c.h |
发送一组 i2c_msg 到适配器,返回成功传输的消息数 |
i2c_smbus_write_byte_data() |
linux/i2c.h |
SMBus 兼容的写操作:向寄存器地址写入 1 字节数据 |
i2c_smbus_read_byte_data() |
linux/i2c.h |
SMBus 兼容的读操作:从寄存器地址读取 1 字节数据 |
推荐做法: Camera Sensor 驱动中优先使用
i2c_smbus_write_byte_data() 和 i2c_smbus_read_byte_data() 进行单寄存器读写。对于批量初始化序列,使用 i2c_transfer() 组合多个 i2c_msg 以提高效率。
2.3 I2C 设备驱动模型
Linux 设备驱动模型为 I2C 设备提供了“总线-设备-驱动”的匹配机制。当设备树中描述的 I2C 设备与注册的 i2c_driver 匹配时,内核自动调用驱动的 probe() 函数。
2.3.1 驱动注册与匹配流程
- 设备注册: 设备树解析或板级代码注册
i2c_board_info,生成i2c_client挂到 I2C 总线上。 - 驱动注册: 调用
i2c_add_driver()注册i2c_driver到 I2C 总线。 - 匹配: 总线遍历所有
i2c_client,与驱动的id_table或设备树compatible属性进行匹配。 - probe() 调用: 匹配成功后,内核调用驱动的
probe()函数,传入匹配的i2c_client指针。 - 设备初始化: 在
probe()中完成 Sensor 上电、复位、ID 读取、注册 V4L2 子设备等操作。
匹配失败排查: 如果驱动
probe() 未被调用,首先检查设备树中 compatible 属性是否与 i2c_driver 的 id_table 或 of_match_table 完全一致(包括大小写和逗号)。
2.3.2 i2c_driver 结构体详解
每个 I2C 设备驱动必须定义一个 i2c_driver 结构体,并填充关键回调:
static const struct i2c_device_id sensor_id[] = {
{ "ov5640", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "ovti,ov5640" },
{ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
static struct i2c_driver sensor_i2c_driver = {
.driver = {
.name = "ov5640",
.of_match_table = sensor_of_match,
},
.probe = sensor_probe,
.remove = sensor_remove,
.id_table = sensor_id,
};
module_i2c_driver(sensor_i2c_driver);
关键点:
of_match_table用于设备树匹配,id_table用于传统板级匹配。module_i2c_driver()宏展开为module_init()和module_exit(),自动处理注册和注销。probe()函数中通过client->addr获取设备 I2C 地址,通过client->adapter获取适配器。
2.3.3 设备树中 I2C 设备的描述
在设备树中,Camera Sensor 作为 I2C 总线的子节点出现,必须包含 compatible、reg(I2C 地址)等属性:
&i2c2 {
status = "okay";
clock-frequency = <400000>; /* 400 kHz 快速模式 */
ov5640: camera@3c {
compatible = "ovti,ov5640";
reg = <0x3c>; /* 7 位 I2C 地址 */
clocks = <&clks IMX8MM_CLK_CLKO2>;
reset-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
AVDD-supply = <®_avdd>;
DVDD-supply = <®_dvdd>;
DOVDD-supply = <®_dovdd>;
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi_ep>;
bus-type = <CSI2_DPHY>;
data-lanes = <1 2>;
};
};
};
};
设备树避坑:
reg属性必须与 Sensor 数据手册中的 7 位 I2C 地址一致,不要左移。clock-frequency在父节点中设置,影响总线上所有设备,不要超过 Sensor 支持的最大频率。- 电源和复位 GPIO 的描述方式因 SoC 而异,务必参考对应 SoC 的设备树绑定文档。
2.4 本章小结
| 知识点 | 核心内容 | 对 Sensor 驱动的意义 |
|---|---|---|
| I2C 总线协议 | 两根线、主从通信、START/STOP、地址+数据+ACK | 理解 Sensor 寄存器读写的底层时序 |
| Linux I2C 核心框架 | 三层架构:核心层 + 总线驱动层 + 设备驱动层 | 知道调用哪些 API 与 Sensor 通信 |
| I2C 设备驱动模型 | i2c_driver / i2c_client / probe() 匹配机制 | 掌握驱动注册和设备树描述的写法 |