【Linux驱动开发】第17天:I2C子系统整体架构

目录

  1. 什么是I2C?
  2. I2C硬件基础:两根线搞定所有通信
  3. [Linux I2C子系统三层架构详解](#Linux I2C子系统三层架构详解)
  4. 实操:RK3399原生I2C控制器设备树解析
  5. 常用I2C核心API速览
  6. 核心总结+思维导图

1. 什么是I2C?

I2C(Inter-Integrated Circuit)是嵌入式系统中最常用的低速串行总线,用于连接各种低速外设,如触摸屏、传感器、EEPROM、RTC时钟等。

1.1 I2C总线核心概念

概念 说明
I2C适配器(主设备) 发起所有通信,控制整个总线的时钟与数据传输
I2C从设备 被动响应主设备的请求,不能主动发起通信
I2C总线 由SCL和SDA两根信号线构成的物理通信通道
SCL时钟线 由主设备驱动,为总线提供同步时钟信号
SDA数据线 双向数据线,主从设备通过它传输数据
从设备地址 每个从设备唯一的7位标识,用于总线寻址

1.2 I2C通信流程

I2C总线上的数据传输遵循严格的主从通信协议,具体流程如下:

  1. 起始条件(START Condition):主设备将SDA线从高电平拉低,同时SCL线保持高电平,标志一次通信的开始
  2. 发送从设备地址与读写位:主设备发送7位从设备地址,随后发送1位读写控制位(0表示写操作,1表示读操作)
  3. 从设备应答(ACK):地址匹配的从设备在第9个SCL周期将SDA线拉低,发送应答信号确认连接
  4. 数据传输:主从设备之间按字节传输数据,每传输一个字节后接收方发送ACK应答,直至数据发送完毕
  5. 停止条件(STOP Condition):主设备将SDA线从低电平拉高,同时SCL线保持高电平,标志通信结束,释放总线

1.3 I2C总线时序图

下面展示一次完整的I2C写操作时序,包含起始条件、从设备地址、读写位、应答、数据和停止条件:
从设备 I2C总线(SCL/SDA) 主设备(适配器) 从设备 I2C总线(SCL/SDA) 主设备(适配器) #mermaid-svg-70FUa5ha93g3YaaG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-70FUa5ha93g3YaaG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-70FUa5ha93g3YaaG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-70FUa5ha93g3YaaG .error-icon{fill:#552222;}#mermaid-svg-70FUa5ha93g3YaaG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-70FUa5ha93g3YaaG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-70FUa5ha93g3YaaG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-70FUa5ha93g3YaaG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-70FUa5ha93g3YaaG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-70FUa5ha93g3YaaG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-70FUa5ha93g3YaaG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-70FUa5ha93g3YaaG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-70FUa5ha93g3YaaG .marker.cross{stroke:#333333;}#mermaid-svg-70FUa5ha93g3YaaG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-70FUa5ha93g3YaaG p{margin:0;}#mermaid-svg-70FUa5ha93g3YaaG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-70FUa5ha93g3YaaG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-70FUa5ha93g3YaaG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-70FUa5ha93g3YaaG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-70FUa5ha93g3YaaG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-70FUa5ha93g3YaaG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-70FUa5ha93g3YaaG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-70FUa5ha93g3YaaG .sequenceNumber{fill:white;}#mermaid-svg-70FUa5ha93g3YaaG #sequencenumber{fill:#333;}#mermaid-svg-70FUa5ha93g3YaaG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-70FUa5ha93g3YaaG .messageText{fill:#333;stroke:none;}#mermaid-svg-70FUa5ha93g3YaaG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-70FUa5ha93g3YaaG .labelText,#mermaid-svg-70FUa5ha93g3YaaG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-70FUa5ha93g3YaaG .loopText,#mermaid-svg-70FUa5ha93g3YaaG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-70FUa5ha93g3YaaG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-70FUa5ha93g3YaaG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-70FUa5ha93g3YaaG .noteText,#mermaid-svg-70FUa5ha93g3YaaG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-70FUa5ha93g3YaaG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-70FUa5ha93g3YaaG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-70FUa5ha93g3YaaG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-70FUa5ha93g3YaaG .actorPopupMenu{position:absolute;}#mermaid-svg-70FUa5ha93g3YaaG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-70FUa5ha93g3YaaG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-70FUa5ha93g3YaaG .actor-man circle,#mermaid-svg-70FUa5ha93g3YaaG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-70FUa5ha93g3YaaG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ① 起始条件:SDA先拉低,SCL保持高 ② 发送7位从设备地址 + 1位读写位 ③ 从设备应答(ACK) ④ 数据传输(每字节后跟ACK) ⑤ 停止条件:SDA先拉高,SCL保持高 SDA ↓(SCL高电平) 发送地址位 A6~A0 + R/W(0=写) 第9个SCL周期拉低SDA 发送数据字节 D7~D0 ACK应答 SDA ↑(SCL高电平)

时序说明

  • 起始条件:SCL为高电平时,SDA从高电平切换到低电平
  • 地址发送:主设备在SCL每个时钟周期发送1位数据,先发送最高位(MSB)
  • 应答位:每个字节传输完成后,接收方在第9个SCL周期将SDA拉低表示应答
  • 停止条件:SCL为高电平时,SDA从低电平切换到高电平
  • 整个通信过程中,SDA数据只能在SCL低电平时变化,SCL高电平时SDA必须保持稳定

2. I2C硬件基础:两根线搞定所有通信

I2C总线只需要两根线就能连接上百个设备,这是它最大的优势:

  • SCL(Serial Clock):串行时钟线,由主设备控制,同步数据传输
  • SDA(Serial Data):串行数据线,主从设备双向传输数据

核心特性

  1. 主从架构:同一时间只能有一个主设备,多个从设备共享总线
  2. 唯一地址:每个从设备有一个7位的唯一地址(部分设备支持10位地址)
  3. 半双工通信:同一时间只能单向传输数据
  4. 低速总线:标准模式100kbps,快速模式400kbps,高速模式3.4Mbps
  5. 总线仲裁:多个主设备同时发起通信时,通过仲裁机制决定谁先使用总线

3. Linux I2C子系统三层架构详解

Linux内核将I2C子系统设计为三层架构,实现了硬件与软件的解耦,极大提高了驱动的复用性和可移植性。

三层架构总览

层级 名称 定位 核心职责
第一层 适配器驱动层(Adapter Driver) 最底层,直接操作硬件 初始化控制器、收发数据、处理中断
第二层 核心层(I2C Core) 中间层,承上启下 提供通用API、管理总线与设备模型
第三层 从设备驱动层(Client Driver) 最上层,面向外设 实现具体外设功能(触摸屏、传感器等)

3.1 第一层:I2C适配器驱动层(Adapter Driver)

  • 定位:最底层,直接和硬件打交道
  • 职责:实现I2C控制器的硬件操作,包括初始化控制器、发送数据、接收数据、处理中断等
  • 本质:一个 platform 驱动,因为I2C控制器是SoC平台自带的外设
  • RK3399对应drivers/i2c/busses/i2c-rk3x.c,这是瑞芯微所有平台通用的I2C控制器驱动

核心工作:将内核通用的I2C消息转换为具体控制器能识别的硬件操作,对上层屏蔽硬件差异。


3.2 第二层:I2C核心层(I2C Core)

  • 定位:中间层,连接适配器驱动和从设备驱动的桥梁
  • 职责
    1. 提供通用的I2C核心API,对上层屏蔽底层硬件差异
    2. 管理I2C总线和设备,负责总线的注册、注销和设备的枚举
    3. 实现I2C设备模型,将I2C设备和驱动关联起来
  • 核心文件drivers/i2c/i2c-core.c
  • 对驱动开发者的意义:我们写I2C从设备驱动时,只需要调用核心层提供的API,不需要关心底层控制器的具体实现

3.3 第三层:I2C从设备驱动层(Client Driver)

  • 定位:最上层,和具体的外设打交道
  • 职责:实现具体外设的功能,如触摸屏的坐标读取、传感器的数据采集等
  • 本质 :一个 i2c_driver,通过I2C总线和从设备通信
  • 常见例子
    • FT5x06 触摸屏驱动
    • MMA7660 加速度传感器驱动
    • RX8010 RTC驱动

核心工作:调用核心层提供的API,向I2C从设备发送命令和读取数据,实现外设的具体功能。

小结:适配器与从设备的关系及分层优势

核心关系:一个I2C适配器对应一条I2C总线,一条总线上可挂多个从设备(最多127个),每个从设备有唯一7位地址。适配器是主设备,主动发起通信;从设备被动响应。

分层优势

  • 硬件与软件解耦:从设备驱动不关心底层SoC型号,更换控制器只需改适配器驱动
  • 代码复用性高:适配器驱动被所有从设备共享,核心层API被所有I2C驱动复用
  • 可移植性强:从设备驱动可轻松移植到任何有对应适配器驱动的平台
  • 易于维护扩展:新增控制器只需写适配器驱动,新增外设只需写从设备驱动

4. 实操:RK3399原生I2C控制器设备树解析

现在结合飞凌OK3399-C设备树,解析RK3399原生I2C控制器的设备树节点。

4.1 定位RK3399 I2C节点

RK3399的I2C控制器定义在rk3399.dtsi中,板级DTS(rk3399-ok3399-c.dts)中根据实际硬件配置启用或禁用对应的I2C总线。

在设备树中搜索i2c,可以找到以下启用的I2C节点:

dts 复制代码
&i2c0 {
    status = "okay";
    i2c-scl-rising-time-ns = <168>;
    i2c-scl-falling-time-ns = <4>;
    clock-frequency = <400000>;

    rk808: pmic@1b {
        compatible = "rockchip,rk808";
        reg = <0x1b>;
        // ... PMIC其他配置
    };
};

&i2c1 {
    status = "okay";
    i2c-scl-rising-time-ns = <300>;
    i2c-scl-falling-time-ns = <15>;

    polytouch: edt-ft5x06@38{
        compatible = "edt,edt-ft5406", "edt,edt-ft5x06";
        reg = <0x38>;
        // ... 触摸屏其他配置
    };

    gs_mma7660: gs-mma7660@4c{
        compatible = "gs_mma7660";
        reg = <0x4c>;
        // ... 加速度传感器其他配置
    };

    rtc@32 {
        compatible = "rx8010";
        reg = <0x32>;
        // ... RTC其他配置
    };
};

&i2c2 {
    status = "okay";
    i2c-scl-rising-time-ns = <300>;
    i2c-scl-falling-time-ns = <15>;

    wm8960: wm8960@1a {
        compatible = "wlf,wm8960";
        reg = <0x1a>;
        // ... 音频Codec其他配置
    };
};

&i2c7 {
    status = "okay";
    i2c-scl-rising-time-ns = <300>;
    i2c-scl-falling-time-ns = <15>;

    fusb0: fusb30x@22 {
        compatible = "fairchild,fusb302";
        reg = <0x22>;
        // ... USB PD芯片其他配置
    };
};

4.2 逐行解析I2C控制器节点

i2c1为例,逐行解析每个属性的含义:

dts 复制代码
&i2c1 {
    status = "okay";  // 启用该I2C总线
    i2c-scl-rising-time-ns = <300>;  // SCL时钟上升沿时间,单位纳秒
    i2c-scl-falling-time-ns = <15>;  // SCL时钟下降沿时间,单位纳秒
    clock-frequency = <400000>;  // 总线时钟频率,400kHz(快速模式)

    // 挂在i2c1总线上的从设备节点
    polytouch: edt-ft5x06@38{
        compatible = "edt,edt-ft5406", "edt,edt-ft5x06";  // 驱动匹配字符串
        reg = <0x38>;  // 从设备地址:0x38(7位地址)
        interrupt-parent = <&gpio1>;  // 中断父控制器
        interrupts = <RK_PC6 IRQ_TYPE_EDGE_FALLING>;  // 中断配置
        // ... 其他从设备特定属性
    };
};

4.3 从设备节点核心属性

所有I2C从设备节点都必须包含以下两个核心属性:

  1. compatible:与从设备驱动匹配的字符串
  2. reg:从设备的7位I2C地址(注意:不是8位地址,不需要左移)

4.4 如何添加自己的I2C从设备节点

如果需要在OK3399-C上添加一个新的I2C设备,只需要在对应的I2C总线节点下添加从设备节点即可。

示例:在i2c1总线上添加一个地址为0x50的EEPROM设备:

dts 复制代码
&i2c1 {
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;  // EEPROM的I2C地址
        status = "okay";
    };
};

5. 常用I2C核心API速览

编写I2C从设备驱动时,最常用的核心API有以下几个:

5.1 从设备驱动注册/注销

c 复制代码
// 注册I2C从设备驱动
int i2c_add_driver(struct i2c_driver *driver);

// 注销I2C从设备驱动
void i2c_del_driver(struct i2c_driver *driver);

// 简化宏,自动生成module_init和module_exit
module_i2c_driver(driver);

5.2 数据读写API

c 复制代码
// 从从设备的指定寄存器读取一个字节
u8 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);

// 向从设备的指定寄存器写入一个字节
int i2c_smbus_write_byte_data(const struct i2c_client *client, u8 command, u8 value);

// 从从设备的指定寄存器读取多个字节
int i2c_smbus_read_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, u8 *values);

// 向从设备的指定寄存器写入多个字节
int i2c_smbus_write_i2c_block_data(const struct i2c_client *client, u8 command, u8 length, const u8 *values);

5.3 通用传输API

c 复制代码
// 通用I2C消息传输函数,可以发送任意数量的消息
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

6. 核心总结+思维导图

6.1 核心总结

  1. I2C本质:主从架构的低速串行总线,只用两根线就能连接多个外设
  2. 三层架构
    • 适配器驱动层:直接操作I2C控制器硬件
    • 核心层:提供通用API,管理总线和设备
    • 从设备驱动层:实现具体外设的功能
  3. 核心关系:一个适配器对应一条总线,一条总线上可以挂多个从设备,每个从设备有唯一地址
  4. 设备树规则
    • I2C控制器节点在rk3399.dtsi中定义
    • 板级DTS中启用需要的总线并添加从设备节点
    • 从设备节点必须包含compatiblereg属性
  5. 驱动开发:编写I2C从设备驱动时,只需要调用核心层提供的API,不需要关心底层控制器的实现

6.2 思维导图总结

相关推荐
Cat_Rocky12 小时前
Linux-基于Jenkins自动打包并部署Tomcat环境
linux·tomcat·jenkins
龙亘川12 小时前
新型智慧城市 + 城市大数据应用完整解决方案(架构 + 平台建设 + 落地实践)
大数据·架构·智慧城市·信息化建设
hj28625113 小时前
Linux 进程、作业控制、定时任务 完整版整理笔记
linux·运维·笔记
时间静止不是简史13 小时前
CentOS 7 虚拟机 NAT 网络排障:DHCP 服务为何启动即停
linux·网络·centos
剑神一笑13 小时前
Linux wget 命令详解:从基础到高级下载技巧
linux·运维·服务器
AOwhisky13 小时前
Ceph系列第二期:Ceph集群部署实战(cephadm)
linux·运维·笔记·分布式·ceph·云计算·存储
苏渡苇13 小时前
微服务间的远程接口调用:OpenFeign 的使用
spring cloud·微服务·架构·springboot·openfeign·sca