4. Linux内核I2C子系统架构

Linux 内核的 I2C 子系统采用分层、模块化的设计思想,将复杂的 I2C 通信协议抽象为三个清晰的层次:I2C 核心层、I2C 总线驱动层(适配器层)和 I2C 设备驱动层。这种架构极大地提高了代码的可移植性和复用性,使得驱动开发者可以专注于特定设备的逻辑,而无需关心底层硬件时序。

核心设计理念: 将“控制器”与“设备”分离。I2C 核心层作为粘合剂,向上提供统一的设备驱动接口,向下屏蔽不同 I2C 适配器(控制器)的硬件差异。

4.1 分层架构总览

下图清晰地展示了 I2C 子系统的三层架构及其数据流关系。用户空间通过字符设备或内核空间的其他驱动,经由 I2C 设备驱动层与核心层交互,最终由适配器驱动完成与硬件寄存器的通信。

Linux I2C 子系统分层架构图 Linux I2C 子系统分层架构 用户空间 (User Space) 内核空间 (Kernel Space) I2C 设备驱动层 (I2C Device Driver) i2c_driver / i2c_client / 具体设备逻辑 (如 EEPROM, Sensor) I2C 核心层 (I2C Core) i2c-core / 协议算法 / 总线管理 / sysfs 接口 I2C 总线驱动层 (I2C Adapter Driver) i2c_adapter / i2c_algorithm / 硬件寄存器操作 硬件 (I2C Controller / Physical Bus) 数据 流向 ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

4.2 I2C 核心层 (I2C Core)

I2C 核心层是子系统的中枢,它不直接操作硬件,而是提供一套框架和辅助函数。核心层的主要职责包括:

  • 总线管理: 维护系统中的 I2C 总线(适配器)和设备(客户端)的注册与注销。
  • 驱动模型: 实现 Linux 设备驱动模型中的 bus_type,负责匹配 i2c_driver 和 i2c_client。
  • 协议抽象: 提供标准的 I2C 消息传输函数,如 i2c_transfer()、i2c_smbus_read_byte_data() 等。
  • sysfs 接口: 在 /sys/bus/i2c/ 目录下创建总线、设备和驱动的属性文件,供用户空间查询。
关键文件: 核心层代码主要位于 drivers/i2c/i2c-core.c 和 drivers/i2c/i2c-core.h。它是所有 I2C 驱动的基础依赖。

4.3 I2C 总线驱动层 (I2C Adapter Driver)

这一层也称为 I2C 适配器驱动,直接与 SoC 上的 I2C 控制器硬件打交道。它的核心任务是:

  1. 初始化硬件: 配置 I2C 控制器的时钟频率、中断、GPIO 引脚等。
  2. 实现算法: 填充 struct i2c_algorithm 结构体,提供 master_xfer() 函数,该函数负责在 I2C 总线上发起并完成实际的起始信号、数据收发和停止信号。
  3. 注册适配器: 通过 i2c_add_adapter() 或 i2c_add_numbered_adapter() 将适配器注册到核心层。
I2C 适配器驱动核心数据结构
数据结构 作用 关键成员
struct i2c_adapter 代表一个物理 I2C 控制器 nr (总线编号), algo (算法指针), dev (设备模型)
struct i2c_algorithm 定义硬件操作函数集 master_xfer(), smbus_xfer(), functionality()

4.4 I2C 设备驱动层 (I2C Device Driver)

这是驱动开发者最常接触的层次。设备驱动层负责实现特定 I2C 从设备(如温度传感器、EEPROM、触摸屏控制器)的逻辑。其核心工作包括:

  • 定义驱动: 实现 struct i2c_driver 结构体,填充 probe()、remove()、id_table 等回调函数。
  • 与设备通信: 在 probe() 函数中,通过核心层提供的 API(如 i2c_smbus_read_word_data())与硬件交互。
  • 创建设备节点: 通常使用 miscdevice 或 input_dev 等框架,向用户空间暴露访问接口。
开发者视角: 编写 I2C 设备驱动时,你几乎不需要关心适配器层的细节。你只需要知道你的设备挂载在哪个 I2C 总线上,以及它的从设备地址是多少。核心层会自动将你的请求路由到正确的适配器。

4.5 I2C 适配器 (I2C Adapter) 详解

适配器是连接软件算法与硬件控制器的桥梁。每个注册的 I2C 适配器都对应一个总线编号(如 /dev/i2c-0、/dev/i2c-1)。适配器的主要属性如下:

I2C 适配器关键属性与功能
属性/功能 描述 示例值
总线编号 (nr) 系统中唯一的 I2C 总线标识 0, 1, 2
算法 (algo) 指向 i2c_algorithm 的指针,定义了如何操作硬件 bitbanging 或 硬件内置控制器
超时时间 (timeout) 一次 I2C 传输的最大等待时间 通常为 1 秒 (HZ)
重试次数 (retries) 传输失败时的最大重试次数 3
功能标志 (class) 指示适配器支持的 I2C 协议变体 (如 SMBus) I2C_CLASS_HWMON

适配器驱动的实现通常位于 drivers/i2c/busses/ 目录下,文件名以 i2c- 开头,例如 i2c-imx.c (i.MX 平台)、i2c-omap.c (OMAP 平台)。

注意事项: 在设备树 (Device Tree) 流行的今天,适配器驱动的初始化参数(如时钟频率、引脚复用)通常由设备树节点提供。驱动开发者需要确保 probe() 函数能正确解析设备树属性。

4.6 各层协作流程

当一个 I2C 设备驱动需要读取从设备的数据时,完整的调用链如下:

  1. 设备驱动 调用 i2c_smbus_read_byte_data(client, reg)。
  2. I2C 核心层 接收到请求,将其封装成一个 struct i2c_msg 消息数组。
  3. I2C 核心层 根据 client->adapter 找到对应的适配器,并调用 adapter->algo->master_xfer()。
  4. 适配器驱动 的 master_xfer() 函数操作硬件寄存器,在 SCL 和 SDA 线上产生符合 I2C 协议的时序。
  5. 硬件完成物理传输后,数据通过适配器驱动、核心层,最终返回给设备驱动。
I2C 读操作调用流程图 I2C 读操作调用流程图 (以 i2c_smbus_read_byte_data 为例) 1. 设备驱动调用 API 2. 核心层封装 i2c_msg 3. 适配器 master_xfer() 4. 硬件 SCL/SDA 时序 数据返回路径 硬件 → 适配器驱动 适配器驱动 → 核心层 核心层 → 设备驱动 最终返回给调用者

4.7 关键数据结构关系

理解以下四个核心数据结构之间的关系,是掌握 I2C 子系统的关键:

I2C 子系统核心数据结构关系
数据结构 所属层次 代表对象 关联关系
struct i2c_adapter 总线驱动层 物理 I2C 控制器 包含 struct i2c_algorithm *algo
struct i2c_algorithm 总线驱动层 硬件操作算法 被 i2c_adapter 引用
struct i2c_client 设备驱动层 挂载在总线上的从设备 包含 struct i2c_adapter *adapter
struct i2c_driver 设备驱动层 设备驱动逻辑 通过 id_table 匹配 i2c_client
总结: I2C 子系统的分层设计体现了 Linux 内核“机制与策略分离”的原则。核心层提供机制(总线管理、消息传递),适配器驱动提供策略(如何操作硬件),设备驱动提供策略(如何使用设备)。这种清晰的职责划分,使得驱动开发工作变得模块化和高效。