0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

驱动之路#43:一文理清I2C子系统架构

BSP调试从0到1 来源:嵌入式分享 作者:嵌入式分享 2026-06-10 16:05 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

前面几篇文章,我们已经简单聊过 I2C 通信机制、I2C 为什么要上拉,以及 I2C 和 SMBus 的关系。

到这里,如果只是理解 I2C 协议本身,基本已经够入门了。

但真正进入 Linux 驱动开发时,又会遇到一个新问题:

Linux 内核里的 I2C 子系统到底是怎么组织的?

比如我们经常会看到这些东西:

/dev/i2c-2i2c_transfer()i2c_smbus_read_byte_data()structi2c_clientstructi2c_driverstructi2c_adapterdrivers/i2c/busses/i2c-rk3x.cdrivers/i2c/i2c-dev.c

刚开始看时,确实容易一头雾水。

这些东西到底谁管谁?
应用层怎么访问 I2C?
设备驱动怎么和设备树匹配?
最后又是谁真正去控制 SDA / SCL 产生时序?

这篇文章就以 RK3576 平台为例,从整体架构角度,先把 Linux I2C 子系统的大框架捋一遍。

不追求一上来逐行啃源码,先建立索引。后面真正看驱动时,知道每一层大概在干什么,就不会迷路。

1. 先说结论

Linux I2C 子系统可以简单理解成四层:

wKgZPGon6f6AOA3NAAAtuE5_krA271.png

如果从一次访问链路来看,大概就是:

APP/ i2ctoolsi2c-dev.c 或具体 I2C 设备驱动i2c-coreI2C 控制器驱动RK3576 I2C 控制器SDA / SCL外设

数据返回时,再沿着这条链路反向返回。

所以 Linux I2C 子系统的核心设计思想,其实就是一句话:

把“控制器硬件操作”和“外设功能逻辑”拆开,让不同平台、不同设备都能复用同一套框架。

这也是 Linux 驱动子系统非常典型的设计方式。

2. 第1层:I2C 控制器驱动层

先从最底层说起。

I2C 控制器驱动层,负责对接芯片内部真实存在的 I2C 控制器。

以 RK3576 为例,SoC 内部会有多路 I2C 控制器,比如 I2C0、I2C1、I2C2 等。

这些控制器最终负责控制 SDA / SCL,在总线上产生 Start、Stop、ACK、地址、数据等时序。

RK 平台相关驱动路径一般类似:

kernel-6.1/drivers/i2c/busses/i2c-rk3x.c
wKgZPGon6f6AaDO4AAG9PpqkuCU375.png

这一层可以理解成:

I2C子系统和 RK3576 I2C 硬件之间的“翻译官”。上层只会告诉它:我要给 0x38 这个设备发一段数据;我要从 0x5d 这个设备读几个字节;我要完成一组 i2c_msg 传输。

而控制器驱动要做的,就是把这些抽象请求,转换成硬件寄存器操作,最终在 SDA / SCL 上产生真实波形。

这一层主要负责:

[ ]初始化 I2C 控制器;[ ]配置时钟和通信速率;[ ]处理中断或 DMA[ ]实现 master_xfer 传输函数;[ ]处理 ACK 失败、超时、仲裁丢失等异常;[ ]操作硬件寄存器产生 I2C 总线时序。

比如在 RK3576 上,最终真正和硬件寄存器打交道的,就是这一层。

3. 第2层:I2C 核心层 i2c-core

控制器驱动之上,就是 I2C 子系统的核心层。

源码路径一般在:

kernel-6.1/drivers/i2c/i2c-core.ckernel-6.1/drivers/i2c/i2c-core-base.c

这一层可以理解为 I2C 子系统的“调度中心”。

它不直接关心你下面是 RK3576、全志 T527、STM32,还是其他平台。

它关心的是:

系统里有哪些 I2C 控制器?有哪些 I2C 设备?有哪些 I2C 驱动?设备和驱动怎么匹配?上层要传输数据时,应该调用哪个控制器?

所以 i2c-core 的作用非常关键。

它向上提供统一接口,向下调用具体控制器驱动。

常见接口包括:

i2c_transfer()i2c_smbus_xfer()i2c_master_send()i2c_master_recv()

对于设备驱动来说,不需要知道底层 I2C 控制器寄存器怎么配置,只需要调用这些通用接口即可。

比如:

ret= i2c_transfer(client->adapter, msgs, num);

至于这个adapter最后对应 RK3576 的哪个 I2C 控制器,就由 i2c-core 和控制器驱动去处理。


4. I2C 子系统里的三个关键结构体

看 I2C 子系统,绕不开三个结构体。它们就是整个框架的骨架。

(1)struct i2c_adapter

i2c_adapter表示一个 I2C 控制器。

比如 RK3576 的 I2C2 控制器,在内核里就会注册成一个i2c_adapter

可以简单理解为:

一个 i2c_adapter = 一条 I2C 总线 / 一个 I2C 控制器

它里面会包含:

控制器编号;控制器名称;支持的功能;传输函数;所属设备信息。

最关键的是传输函数,比如master_xfer

上层最终调用i2c_transfer()时,最后会走到这个 adapter 对应的master_xfer,再进入具体平台的控制器驱动。

所以,i2c_adapter代表的是“谁来发起 I2C 传输”。

(2)struct i2c_client

i2c_client表示一个 I2C 从设备。

比如 I2C2 总线上挂了一个地址为0x5d的 GT911 触摸芯片,那么这个 GT911 在内核里就会对应一个i2c_client

可以简单理解为:

一个 i2c_client = 一个挂在 I2C 总线上的外设

它里面通常包含:

设备地址;所属 i2c_adapter;设备名称;设备节点信息;驱动私有数据。

驱动里经常会看到:

staticintxxx_probe(structi2c_client *client)

这个client就代表当前匹配到的 I2C 设备。

驱动后续读写寄存器时,也通常是通过这个client找到设备地址和所属 adapter。

(3)struct i2c_driver

i2c_driver表示一个 I2C 设备驱动。

比如 GT911 触摸驱动、RTC 驱动、温度传感器驱动,都可以注册为一个i2c_driver

它里面通常包含:

probe 函数;remove函数;设备匹配表;驱动名称;电源管理回调。

当设备和驱动匹配成功后,内核就会调用驱动的probe()函数。

所以这三个结构体可以这样理解:

i2c_adapter:代表 I2C 控制器i2c_client :代表 I2C 外设i2c_driver :代表 I2C 外设驱动

这三个概念搞清楚,I2C 子系统的大框架基本就立起来了。

5. 第三层:I2C 设备驱动层

设备驱动层,就是针对具体 I2C 外设写的驱动。

比如:

触摸芯片驱动:gt911RTC 驱动:rtc-pcf8563.c湿度传感器驱动:aht20电源管理芯片驱动:pmic

这一层的职责不是直接控制 SDA / SCL,而是实现具体设备功能。

比如一个触摸驱动,它关心的是:

怎么读取触摸坐标;怎么初始化芯片;怎么处理中断;怎么上报input事件;怎么管理电源。

它不需要关心 RK3576 的 I2C 控制器寄存器怎么配置。

它只需要通过 i2c-core 提供的接口访问设备寄存器,比如:

i2c_smbus_read_byte_data()i2c_smbus_write_byte_data()i2c_transfer()

这样带来的好处很明显:

同一个 I2C 外设驱动,可以尽量复用在不同 SoC 平台上。

比如某个 I2C 触摸芯片驱动,在 RK 平台能用,在其他 ARM 平台上也可能能用。只要底层 I2C 控制器驱动正常,设备树配置正确,上层设备驱动通常不用关心底层硬件差异。

这就是 Linux 子系统分层带来的好处。

6. 第四层:用户空间接口层 i2c-dev

除了写内核设备驱动,Linux 还提供了一个用户空间访问 I2C 的方式。

对应文件是:

kernel-6.1/drivers/i2c/i2c-dev.c

这个文件实现了一个字符设备驱动,会为每个 I2C adapter 创建对应的设备节点

/dev/i2c-0/dev/i2c-1/dev/i2c-2...

比如 RK3576 的 I2C2,用户空间可能就能看到:

/dev/i2c-2

有了这个节点,用户空间程序或者 i2c-tools 就可以直接访问 I2C 总线。

常用工具包括:

i2cdetecti2cgeti2cseti2cdump

比如扫描 I2C2 总线:

i2cdetect-y2

读取某个设备寄存器:

i2cget-y20x380x00

这里的调用链路大概是:

i2cget/dev/i2c-2i2c-dev.ci2c-corei2c-rk3x.cRK3576 I2C2 控制器I2C 外设

所以i2c-dev.c的作用可以理解为:

给用户空间开了一个访问 I2C 总线的调试入口。

它非常适合调试阶段验证硬件。

比如我们想确认设备地址对不对、外设有没有 ACK、某个寄存器能不能读到,就可以先用 i2c-tools 测一下。

但如果是正式产品中的复杂设备功能,通常还是建议写内核驱动,而不是长期依赖用户空间直接操作 I2C。


7. 设备和驱动是怎么匹配的?

理解 I2C 子系统,除了看数据传输,还要理解设备和驱动怎么绑定。

在设备树里,我们可能会这样写:

wKgZPGon6f6Afj_zAAFdHHu5ldo295.png

这里表示:

AHT20挂在 I2C2 总线上;设备地址是 0x38;compatible 是"aosong,aht20"

内核启动时,会解析设备树,在 I2C2 这个 adapter 下创建一个对应的i2c_client

如果内核中有一个 I2C 驱动的匹配表里包含:

{ .compatible="aosong,aht20"}

那么设备和驱动就能匹配成功,随后调用驱动的probe()函数。

简化流程如下:

设备树描述 I2C 外设内核创建 i2c_clientI2C 驱动注册 i2c_driveri2c-core 完成匹配调用驱动 probe()驱动初始化外设

这就是 Linux “总线-设备-驱动”模型在 I2C 子系统里的体现。

8. 一次完整读取链路

下面以 RK3576 通过 I2C2 读取 AHT20 温湿度传感器为例,简单看一次完整链路。

假设用户空间执行:

i2cget-y20x380x00

大致流程如下:

1.用户空间执行 i2cget;2.i2cget 打开 /dev/i2c-2;3.通过 ioctl 设置从设备地址 0x38;4.通过 read/write 或 I2C_RDWR 发起访问;5. i2c-dev.c 接收用户请求;6. i2c-dev.c 调用 i2c-core 的传输接口;7. i2c-core 封装 i2c_msg,调用 i2c_transfer;8. i2c_transfer 找到 I2C2 对应的 i2c_adapter;9. 调用 adapter 的 master_xfer;10.进入 RK 平台 i2c-rk3x.c;11.rk3x_i2c_xfer 操作硬件寄存器;12.RK3576 I2C2 控制器在 SDA/SCL 上产生时序;13.AHT20 返回数据;14.数据沿原路径返回给 i2cget。

这条链路看起来长,但理解之后就很清晰。

一句话总结就是:

用户层负责发起请求,i2c-dev 负责接入用户空间,i2c-core 负责调度,控制器驱动负责干硬件活,外设负责响应数据。

9. 总结

Linux I2C 子系统看起来复杂,但拆开后其实就是几层分工。

i2c_adapter:代表 I2C 控制器;i2c_client :代表 I2C 外设;i2c_driver :代表 I2C 设备驱动;i2c-core  :负责把它们组织起来;i2c-dev  :负责给用户空间提供调试入口。

刚开始学习时,不建议直接扎进源码里逐行看。

更建议先建立这条主线:

设备树描述外设内核创建 i2c_client驱动注册 i2c_driveri2c-core 完成匹配并调用 probe设备驱动调用 i2c_transfer / smbus API控制器驱动 master_xfer 产生硬件时序

理解了这条线,再去看i2c-rk3x.ci2c-core-base.ci2c-dev.c,就不会像刚开始那样满屏函数乱飞了。

当然,本文只是先建立 I2C 子系统的整体认知,具体源码细节后面再慢慢拆。

(完)

下期可以继续聊:硬件 I2C 和软件 I2C 谁更坑?


本人专注Linux 嵌入式全栈开发,有项目合作 / 技术支持 / 交个朋友,欢迎后台私信。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • I2C
    I2C
    +关注

    关注

    28

    文章

    1570

    浏览量

    132199
  • I2C协议
    +关注

    关注

    0

    文章

    31

    浏览量

    9375
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Linux内核中I2C系统的设计思路

    [ 导读] 本文通过阅读内核代码,来梳理I2C子系统的整体视图。在开发I2C设备驱动程序时,往往缺乏对于
    发表于 09-06 09:40 1409次阅读
    Linux内核中<b class='flag-5'>I2C</b><b class='flag-5'>系统</b>的设计思路

    linux I2C子系统的相关资料分享

    文章目录linux I2C子系统框架在设备树中添加从设备信息,mpu5060I2C driver 程序的编写mpu6050 I2C程序具体实现linux
    发表于 02-10 06:06

    需要了解Linux驱动子系统I2C

    I2C总线驱动I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,比如起始,停止,应答信号和master_xfer的实现函数。
    发表于 05-07 10:58 1560次阅读

    Linux的I2C驱动架构

    1.     Linux的I2C驱动架构Linux中I2C总线的驱动分为两个部分,总线
    发表于 04-02 14:38 1066次阅读

    Linux驱动中的I2C驱动架构详细分析

    其实I2C接口非常的简单,即使用51单片机的gpio来模拟I2C,编写个e2prom或者其他I2C接口的
    发表于 08-01 17:35 3次下载
    Linux<b class='flag-5'>驱动</b>中的<b class='flag-5'>I2C</b><b class='flag-5'>驱动</b><b class='flag-5'>架构</b>详细分析

    Linux内核中I2C子系统的整体视图

    本文通过阅读内核代码,来梳理I2C子系统的整体视图。在开发I2C设备驱动程序时,往往缺乏对于系统
    的头像 发表于 12-31 10:40 3229次阅读
    Linux内核中<b class='flag-5'>I2C</b><b class='flag-5'>子系统</b>的整体视图

    文理清EMI的传播过程资料下载

    电子发烧友网为你提供文理清EMI的传播过程资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 04-04 08:52 5次下载
    <b class='flag-5'>一</b><b class='flag-5'>文理清</b>EMI的传播过程资料下载

    linux I2C子系统(及相关程序设计MPU6050)

    文章目录linux I2C子系统框架在设备树中添加从设备信息,mpu5060I2C driver 程序的编写mpu6050 I2C程序具体实现linux
    发表于 12-06 13:36 9次下载
    linux <b class='flag-5'>I2C</b><b class='flag-5'>子系统</b>(及相关程序设计MPU6050)

    嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序,I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接

    文章目录I2c协议和时序I2c介绍I2c硬件连接I2c总线的信号I2c总线写时序I2c总线读时序
    发表于 12-06 14:06 17次下载
    嵌入式内核及<b class='flag-5'>驱动</b>开发-09IIC<b class='flag-5'>子系统</b>框架使用(<b class='flag-5'>I2C</b>协议和时序,<b class='flag-5'>I2C</b><b class='flag-5'>驱动</b>框架,<b class='flag-5'>I2C</b>从设备<b class='flag-5'>驱动</b>开发,MPU6050硬件连接

    硬件I2C与模拟I2C

    硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,因而效率要远高于软件模拟的
    发表于 12-28 19:14 83次下载
    硬件<b class='flag-5'>I2C</b>与模拟<b class='flag-5'>I2C</b>

    Linux I2C驱动入门知识科普

    I2C 总线驱动I2C总线驱动就是SOC的 I2C控制器驱动,也叫做
    的头像 发表于 12-29 13:59 2982次阅读

    I2C控制器驱动介绍

    (控制器)抽象成 i2c_adapter。 对于I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。
    的头像 发表于 07-22 15:38 3697次阅读
    <b class='flag-5'>I2C</b>控制器<b class='flag-5'>驱动</b>介绍

    I2C子系统SW Architecture

    I2C SW Architecture 【driver 驱动层】由普通驱动工程师负责,【i2c 核心层】由 Linux 提供,【i2c 核心
    的头像 发表于 07-22 16:01 1969次阅读
    <b class='flag-5'>I2C</b><b class='flag-5'>子系统</b>SW Architecture

    I2C子系统几个主要的结构体

    I2C Data Structure 我们要搞懂个 Linux 子系统,必须研究它的数据结构,搞懂每个结构体存储了什么东西,才能梳理清楚该子系统
    的头像 发表于 07-22 16:04 1984次阅读
    <b class='flag-5'>I2C</b><b class='flag-5'>子系统</b>几个主要的结构体

    I2C转UART子系统设计

    电子发烧友网站提供《I2C转UART子系统设计.pdf》资料免费下载
    发表于 08-28 10:27 3次下载
    <b class='flag-5'>I2C</b>转UART<b class='flag-5'>子系统</b>设计