驱动开发硬核特训 · Day 32:深入理解 I2C 内核子系统

📘 推荐阅读:
点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
支持原创,支持国产嵌入式生态!


一、引言

I2C 是嵌入式系统中最常用的总线之一,广泛应用于音频芯片、RTC、电源管理芯片、温湿度传感器等外围设备的通信。在 Linux 内核中,I2C 被封装成一个完整的子系统,提供了标准的驱动模型、设备注册机制、传输抽象层,使得设备驱动开发高度模块化。

本文将围绕 I2C 内核子系统的架构展开,从基础结构、通信机制、驱动模型、设备树匹配到实际代码分析,带你系统理解并掌握这一关键子系统。


二、I2C 子系统架构概览

1. 基本结构

在 Linux 内核中,I2C 子系统分为三大核心组成:

模块说明
I2C Adapter适配器,代表 I2C 控制器硬件资源
I2C Bus逻辑总线,挂载多个设备节点
I2C Client客户端,代表挂在 I2C 总线上的设备

内核通过抽象出 i2c_adapteri2c_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_adapteri2c_add_adapter()
设备驱动(从机)i2c_driveri2c_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_adapteri2c_client 的关系?

答:
i2c_client 通过 adapter 字段指向所依赖的 i2c_adapter,即总线上每个设备都挂在一个具体的控制器上,adapter 是 master,client 是 slave。


Q2:I2C 驱动为何不直接使用 platform_driver

答:
虽然很多 I2C 控制器底层是通过 platform 驱动实现,但 I2C 子系统为了统一设备驱动模型,使用了更抽象的 i2c_driver,避免与具体平台耦合,便于复用和设备管理。


Q3:i2c_transferi2c_master_send 有何区别?

答:
i2c_transfer 更底层,支持多段传输(read+write等组合),而 i2c_master_send 是封装后的简单写操作,适用于多数 I2C 设备。


Q4:如何在设备树中区分多个同类设备?

答:
通过设备地址(reg 属性)区分,从而可挂载多个如 EEPROM 的从设备。


Q5:为什么设备驱动可以不关心 I2C 控制器?

答:
I2C 子系统封装了适配器与设备间的通信协议,驱动开发只需要实现与 i2c_client 交互的接口,无需关注控制器的实现细节。


🔚 结语

I2C 是嵌入式开发绕不开的基础子系统之一,掌握其内核架构与驱动模型,不仅有助于设备驱动开发,也有助于深入理解 Linux 子系统的模块化设计思想。

📘 推荐学习资料:
点击京东购买《Yocto项目实战教程:高效定制嵌入式Linux系统》
内容涵盖 I2C 驱动开发 + Yocto 菜谱集成 + 设备树配置等全流程实践。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值