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 传输包含以下阶段:

  1. 起始条件(START): SCL 高电平时,SDA 由高变低。
  2. 从设备地址 + 读写位: 主设备发送 7 位地址 + 1 位 R/W(0=写,1=读)。
  3. 应答位(ACK): 从设备在第 9 个 SCL 周期拉低 SDA 表示应答。
  4. 数据字节传输: 每字节 8 位,MSB 先传,后跟 ACK。
  5. 停止条件(STOP): SCL 高电平时,SDA 由低变高。
常见踩坑点: Sensor 的 I2C 地址通常以 8 位形式给出(左移 1 位),例如数据手册写 0x78,实际 7 位地址为 0x3C。务必确认驱动中使用的地址格式。
I2C 总线单次写操作时序图 I2C 单次写操作时序图(7 位地址) S START A6:A0 从设备地址 R/W 0=写 A ACK D7:D0 寄存器地址/数据 A ACK P STOP SCL SDA 一次传输包含:START + 地址 + R/W + ACK + 数据 + ACK + STOP

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 结构体获取适配器引用,然后调用传输函数即可。
Linux I2C 子系统三层架构 Linux I2C 子系统三层架构 用户空间(应用程序 / V4L2 框架) I2C 设备驱动层(Sensor 驱动) i2c_driver / i2c_client / probe() / remove() I2C 核心层 i2c_transfer() / i2c_smbus_*() / 设备模型管理 I2C 总线驱动层(适配器驱动) i2c_algorithm / master_xfer() / 硬件寄存器操作 I2C 控制器硬件(SoC 内部 IP) Sensor 驱动 开发者关注层

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 驱动注册与匹配流程

  1. 设备注册: 设备树解析或板级代码注册 i2c_board_info,生成 i2c_client 挂到 I2C 总线上。
  2. 驱动注册: 调用 i2c_add_driver() 注册 i2c_driver 到 I2C 总线。
  3. 匹配: 总线遍历所有 i2c_client,与驱动的 id_table 或设备树 compatible 属性进行匹配。
  4. probe() 调用: 匹配成功后,内核调用驱动的 probe() 函数,传入匹配的 i2c_client 指针。
  5. 设备初始化: 在 probe() 中完成 Sensor 上电、复位、ID 读取、注册 V4L2 子设备等操作。
匹配失败排查: 如果驱动 probe() 未被调用,首先检查设备树中 compatible 属性是否与 i2c_driver 的 id_table 或 of_match_table 完全一致(包括大小写和逗号)。
I2C 设备驱动匹配与 probe 流程图 I2C 设备驱动匹配与 probe 流程 设备树解析 / i2c_board_info 注册 生成 i2c_client 挂到 I2C 总线 i2c_add_driver() 注册 i2c_driver 匹配成功? 否 probe() 不被调用 是 调用 probe(i2c_client) 上电 → 复位 → 读 ID → 注册 V4L2 子设备

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 = <&reg_avdd>;
        DVDD-supply = <&reg_dvdd>;
        DOVDD-supply = <&reg_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() 匹配机制 掌握驱动注册和设备树描述的写法