[TOC]
Linux I2C 控制器驱动目录解析
[drivers/i2c/busses] [I2C 总线控制器(Adapter)驱动集合] [为不同 SoC/控制器实现 I2C 主控/从控能力,并向 i2c-core 注册 i2c_adapter]
介绍
drivers/i2c/busses/ 是 I2C 控制器(bus controller / host controller)驱动的集中目录:
- 每个文件(或一组文件)通常对应一种硬件 I2C 控制器 IP(例如某 SoC 内置 I2C、某 PCI/ACPI I2C 控制器、某外设桥接控制器)。
- 这些驱动的共同目标是:把硬件能力抽象成一个
i2c_adapter,并通过 i2c-core 让上层设备驱动(i2c_client驱动)能用统一接口读写从设备。
历史与背景
为了解决什么问题而诞生
Linux 需要把“不同厂家的 I2C 控制器寄存器/时序/中断/DMA差异”屏蔽掉,让上层设备驱动只关心“在某条 I2C 总线上读写某地址的寄存器”。因此形成了:
- i2c-core:统一框架与设备模型;
- busses:控制器驱动实现底层传输与错误恢复。
重要演进脉络(按技术方向)
- 平台描述方式演进:从板级硬编码(boardinfo)到 Device Tree/ACPI 为主;
- 从“纯轮询”到“中断/DMA”:提升吞吐与降低 CPU 占用;
- 复用与通用化:常见通用模块(如 bit-banging 算法、mux、regmap、PM/runtime、clk/reset)被广泛使用,减少重复代码。
社区与主流应用
这是内核里非常“常用且长期维护”的目录:几乎每个嵌入式/PC 平台都依赖其中至少一个控制器驱动才能让 I2C 外设工作。
核心原理与设计
核心工作原理(控制器驱动的标准骨架)
一个典型 busses 下的 I2C 控制器驱动,结构大体一致:
- probe(初始化)
解析平台资源:MMIO、IRQ、时钟/复位、DMA(可选)
初始化控制器寄存器:时钟分频、FIFO/中断、超时、过滤等
组装
struct i2c_adapter:algo或master_xfer/master_xfer_atomicfunctionalitydev.parent、nr、name等
i2c_add_adapter()或i2c_add_numbered_adapter()注册到 i2c-core
- 传输路径(最关键)
上层调用最终会落到控制器驱动的传输实现,常见两种风格:
- 基于
master_xfer()的自实现:驱动直接实现i2c_adapter的传输回调,支持 repeated-start、多 msg; - 基于通用算法层
i2c_algorithm:驱动提供底层 bit 操作或简单发送接口,交由算法层组织。
典型需要处理:
- START/STOP、地址相位、ACK/NACK
- repeated-start(组合事务:写寄存器地址后紧接读)
- FIFO 装填/取出,中断触发点
- 超时、仲裁丢失、总线错误(SCL/SDA 异常)
- 并发与上下文
- I2C 传输通常会睡眠(等待中断/完成),所以多数控制器驱动在进程上下文工作;
- 有的控制器支持 atomic 传输或用于紧急日志路径,会实现
*_atomic变体(但要求硬件/实现可在原子上下文完成)。
- 错误恢复与总线“卡死”处理
常见机制:
- 控制器软复位/清 FIFO
- 发送 STOP 强制释放
- SCL “钟控解锁”(部分硬件/平台需要)
- 结合
i2c_bus_recovery_info(如果驱动实现了恢复钩子)
主要优势(目录层面体现)
- 硬件差异封装:上层设备驱动与控制器解耦
- 性能可扩展:从简单轮询到 IRQ/DMA/FIFO 都能覆盖
- 生态完整:DT/ACPI、PM、runtime PM、clk/reset、DMA 等都能集成
局限性与不适用点(常见“坑位”)
- 时序/电气问题不在软件能完全解决:上拉、电平转换、总线长度、噪声会表现为 NACK/超时/卡死
- 控制器差异导致语义不一致:例如 repeated-start 支持不完整、10-bit 地址、SMBus PEC、clock-stretch 处理差异
- 调试成本:传输失败时需要同时考虑硬件、设备树参数、驱动状态机
使用场景
什么时候它是首选
- 你要让某个平台的 I2C 外设工作(RTC、EEPROM、sensor、PMIC、codec 等),必须先有对应控制器驱动提供 adapter。
- 你需要更高性能/更低 CPU:选支持 IRQ/FIFO/DMA 的控制器驱动路径更合适。
不推荐的场景(以及原因)
- 如果硬件只有 GPIO 且没有 I2C 控制器:可以用 bit-banging(通常不在 busses 的“控制器 IP 驱动”路径里解决所有问题),但性能与稳定性更受限。
- 对强实时、超高频 I/O:I2C 总线本身速率有限且共享仲裁,可能不合适,应考虑 SPI、并口、MIPI/I3C 等(取决于器件支持)。
对比分析(实现方式/开销/资源/隔离/启动)
这里对比三类常见实现形态(都可能出现在相关目录/组合中):
1) 硬件控制器驱动(本目录主角) vs GPIO bit-banging
- 实现方式:硬件控制器用寄存器/FIFO/IRQ;bit-banging 由软件翻转 GPIO 时序
- 性能开销:硬件控制器通常更低 CPU;bit-banging CPU 占用更高
- 资源占用:硬件控制器需要对应 IP;bit-banging 占 GPIO
- 隔离级别:硬件控制器对时序更稳定;bit-banging 对抢占/延迟更敏感
- 启动速度:都快;bit-banging 更依赖 GPIO 配置正确
2) 轮询模式 vs 中断模式 vs DMA 模式(同一控制器内的差异)
- 实现方式:轮询读写状态寄存器;中断驱动状态机;DMA 通过 DMA engine 搬运
- 性能开销:轮询 CPU 占用高;中断较均衡;DMA 在大块/高频更省 CPU
- 资源占用:DMA 需要通道与描述符;中断需要 IRQ;轮询最少资源
- 隔离级别:DMA/中断更能抵御长事务;轮询容易受调度抖动影响
- 启动速度:轮询最简单;DMA 初始化稍复杂
3) 纯 I2C vs SMBus 特性依赖
- 实现方式:I2C 是通用消息;SMBus 有额外语义(PEC、Alert、Host Notify 等)
- 性能/资源:SMBus 特性可能需要额外中断线/从模式支持
- 隔离:SMBus Alert 这类共享机制隔离更弱,需要更严格的错误处理
- 启动:SMBus 扩展可能引入额外注册/探测路径
总结
关键特性
drivers/i2c/busses/提供“把硬件 I2C 控制器变成i2c_adapter”的驱动实现集合- 核心关注点:传输状态机(START/STOP/repeated-start)、并发/睡眠、中断/DMA、错误恢复、DT/ACPI 资源管理
- 不同文件的差异主要来自:控制器能力(FIFO/DMA/从模式/恢复机制)与平台集成方式(DT/ACPI/PCI)
drivers/i2c/busses/i2c-stm32f4.c
stm32f4_i2c_hw_config / stm32f4_i2c_set_periph_clk_freq / stm32f4_i2c_set_rise_time / stm32f4_i2c_set_speed_mode:I2C 外设时钟与时序寄存器配置(初始化路径)
stm32f4_i2c_set_bits / stm32f4_i2c_clr_bits / stm32f4_i2c_disable_irq:寄存器位操作与中断屏蔽辅助
1 |
|
stm32f4_i2c_set_periph_clk_freq:配置 CR2.FREQ(单位 MHz)并校验硬件允许区间
1 | /** |
stm32f4_i2c_set_rise_time:配置 TRISE(按 I2C 规范最大上升沿时间离散化)
1 | /** |
stm32f4_i2c_set_speed_mode:配置 CCR(SCL 周期分频)与 FS(Standard/Fast 选择)
1 | /** |
stm32f4_i2c_hw_config:按 CR2→TRISE→CCR→CR1 使能的顺序完成硬件初始化
1 | /** |
stm32f4_i2c_wait_free_bus:轮询 SR2.BUSY 等待总线空闲(提交 START 前的门禁)
1 |
|
stm32f4_i2c_isr_event / stm32f4_i2c_isr_error / stm32f4_i2c_xfer_msg:中断驱动的 I2C 传输状态机(收发细节与 ACK/POS/BTF 处理)
stm32f4_i2c_write_byte / stm32f4_i2c_write_msg / stm32f4_i2c_read_msg:对 DR 的单字节收发封装
1 | /** |
stm32f4_i2c_terminate_xfer:结束一次消息(STOP 或重复 START)并唤醒等待者
1 | /** |
stm32f4_i2c_handle_write:写方向在 TXE/BTF 驱动下逐字节灌入 DR,并在结束时 terminate
1 | /** |
stm32f4_i2c_handle_read / stm32f4_i2c_handle_rx_done / stm32f4_i2c_handle_rx_addr:读方向的边界条件(1/2/3 字节)与 ACK/POS 控制
STM32F4 I2C 接收时涉及 移位寄存器与 DR 的装载时序:
RXNE:DR 非空(可读)BTF:字节传输完成;接收时常意味着“移位寄存器已有新字节,但 DR 里的旧字节未读走”等组合情况
对 2 字节接收:必须在读最后两字节之前先置 STOP/重复 START,否则会错过总线条件生成时机。
对 3 字节接收:必须在读 N-2 之前配置 NACK(通过清 ACK),让最后字节能以 NACK 结束。
1 | /** |
1 | /** |
1 | /** |
stm32f4_i2c_isr_event:事件中断总入口(SB/ADDR/TXE/RXNE/BTF 分发)
1 | /** |
stm32f4_i2c_isr_error:错误中断入口(ARLO/AF/BERR)并以 completion 终止传输
1 | /** |
stm32f4_i2c_xfer_msg:线程侧发起单条消息,并等待 ISR 完成(或超时)
1 | /** |
stm32f4_i2c_xfer / stm32f4_i2c_algo / stm32f4_i2c_probe / stm32f4_i2c_remove:适配器算法挂接与平台驱动生命周期(从 i2c_transfer 到硬件)
stm32f4_i2c_xfer:I2C core 的传输入口(为每个 msg 调用 xfer_msg,并在结束时返回 num)
1 | /** |
stm32f4_i2c_func / stm32f4_i2c_algo:向 I2C core 宣告能力并提供算法入口
1 | /** |
stm32f4_i2c_probe:资源获取、复位、IRQ 注册、硬件配置,并注册 i2c_adapter
1 | /** |
stm32f4_i2c_remove:从 I2C core 注销适配器
1 | /** |
of_match / platform_driver / module_platform_driver:设备树匹配与模块注册(结构性说明)
1 | static const struct of_device_id stm32f4_i2c_match[] = { |
drivers/i2c/busses/i2c-stm32f7.c
STM32F7 I2C 控制器驱动(i2c-stm32f7)整理
这份驱动相对 STM32F4 版本最大的差异:
- 寄存器模型变了(CR2/NBYTES/RELOAD/START/STOP 等)。
- 支持更多能力:DMA、SMBus(含 PEC/Alert/Host Notify)、Slave 模式、原子传输(polling)、Runtime PM/系统休眠。
- 时序配置从 “CCR/TRISE” 变成 “TIMINGR(PRESC/SCLDEL/SDADEL/SCLH/SCLL)”。
stm32f7_i2c_setup_timing / stm32f7_i2c_compute_timing / stm32f7_get_specs:I2C 总线时序输入解析与 TIMINGR 参数寻优计算
- 这部分代码的核心意义在于把“目标 I2C 速率 + 时序约束(上升/下降沿、滤波延迟、规范最小高低电平等)”转换为 TIMINGR 可编程字段(PRESC/SCLDEL/SDADEL/SCLH/SCLL)。该过程是确定性的数值搜索,与是否多核无关;单核下的主要差异仅在于计算发生在 probe/配置阶段,不涉及并发同步策略。
- 无 MMU 不改变该算法的数学与寄存器意义;但在“不可睡眠/原子上下文”场景下不应调用此类计算路径(本段位于配置阶段,通常在进程上下文执行)。
stm32f7_get_specs:按目标速率选择对应 I2C 规范约束集合
1 | static struct stm32f7_i2c_spec *stm32f7_get_specs(u32 rate) |
stm32f7_i2c_compute_timing:在规范约束与滤波/边沿条件下搜索 TIMINGR 最优解
作用与原理
将
clock_src(Hz)与speed_freq(Hz)分别换算为纳秒周期:i2cclk = round(NSEC_PER_SEC / clock_src):I2C 内核时钟周期(ns)i2cbus = round(NSEC_PER_SEC / speed_freq):目标总线周期(ns)
将设备树给出的数字滤波宽度
dnf_dt(ns)换算为滤波抽头数dnf(0..15):dnf = round(dnf_dt / i2cclk),并进一步得到dnf_delay = dnf * i2cclk。通过 I2C 规范给出的
hddat_min/vddat_max/sudat_min/l_min/h_min与板级rise_time/fall_time,联立得到:SDADEL可行区间[sdadel_min, sdadel_max]SCLDEL下界scldel_min
先枚举
(PRESC,SCLDEL,SDADEL)的可行组合,再对每组枚举(SCLL,SCLH),计算得到实际tscl并使其落入[clk_min, clk_max],同时满足tscl_l >= l_min、tscl_h >= h_min等约束;最终以|tscl - i2cbus|最小作为“最优解”。
1 |
|
stm32f7_get_lower_rate:计算“降档”的下一个更低速率档位
1 | static u32 stm32f7_get_lower_rate(u32 rate) |
stm32f7_i2c_setup_timing:解析固件时序输入并调用 compute_timing 生成最终寄存器字段
1 | static int stm32f7_i2c_setup_timing(struct stm32f7_i2c_dev *i2c_dev, |
stm32f7_i2c_hw_config / stm32f7_i2c_xfer_core / stm32f7_i2c_xfer_msg:将计算得到的时序写入外设并发起一次主机传输
- 这三段代码的关键点不在“代码如何顺序执行”,而在于:TIMINGR 字段组合的寄存器语义、CR1/CR2 的事务编排语义、以及 atomic 传输时“禁止中断并改用轮询驱动状态机”的策略。这些约束在单核下同样成立。
stm32f7_i2c_hw_config:把 timing/滤波配置固化到寄存器并使能外设
作用与原理
- 将
compute_timing()计算出的presc/scldel/sdadel/sclh/scll映射为TIMINGR的位域组合值并写入。 - 按
analog_filter决定是否关闭模拟滤波(通过ANFOFF位的“反向语义”实现)。 - 先清空
DNF位域再设置DNF(n),避免残留位影响。 - 最后置位
PE使能外设;这一步对寄存器写入的生效顺序具有控制意义。
1 | /** |
stm32f7_i2c_xfer_core:I2C 主机传输的公共核心(支持普通与 atomic 两种驱动方式)
作用与原理
- 负责一次
master_xfer的高层编排:运行时电源管理、等待总线空闲、发起首个消息、等待完成、错误清理与超时处理。 atomic为真时不依赖中断完成量complete,而是通过轮询函数驱动状态机(避免不可睡眠上下文等待/调度)。
1 | /** |
stm32f7_i2c_xfer_msg:为单条 i2c_msg 配置 CR1/CR2,并选择 DMA 或 IRQ 驱动数据通道
作用与原理
把
i2c_msg映射为 I2C 外设事务参数:RD_WRN:读/写方向SADD7/SADD10 + ADD10:7 位或 10 位地址格式NBYTES + RELOAD:分段传输机制(单段最多 0xFF 字节)START:启动/重复起始
依据
atomic决定是否允许中断:atomic 模式下 主动清掉所有中断使能位,后续由轮询驱动事件推进。依据 DMA 条件决定数据通道:
- 仅在
i2c_dev->dma存在且!atomic时尝试 DMA - 通过
i2c_get_dma_safe_msg_buf()获取满足对齐/缓存一致性要求的安全缓冲区;失败则回退中断方式 - 成功则置
RXDMAEN/TXDMAEN,否则置RXIE/TXIE
- 仅在
1 | /** |
stm32f7_i2c_isr_event / stm32f7_i2c_isr_event_thread / stm32f7_i2c_handle_isr_errs:中断顶半部快速分流与线程化状态机推进
平台关注:单核、无 MMU、ARMv7-M(STM32H750)
- 这组函数体现的关键机制是 线程化中断:顶半部只做“快速判定与少量不可阻塞工作”,把需要较长处理、可能睡眠/等待的逻辑放到线程化 handler 中完成。
atomic传输模式下会关闭本外设的中断使能位,状态推进改由轮询路径调用stm32f7_i2c_isr_event()完成,因此 ISR 顶半部的逻辑需要保持“可被轮询复用”的性质。
stm32f7_i2c_write_tx_data:向 TXDR 写入 1 字节并递减剩余计数
1 | /** |
stm32f7_i2c_read_rx_data:从 RXDR 读取 1 字节或执行接收 FIFO 刷新
1 | /** |
stm32f7_i2c_reload:处理 RELOAD 分段传输时重装 NBYTES
1 | /** |
stm32f7_i2c_smbus_reload:SMBus 块读类协议按“首字节长度”重装 NBYTES
1 | /** |
stm32f7_i2c_handle_isr_errs:错误/异常标志处理与收尾
1 | /** |
stm32f7_i2c_isr_event:顶半部快速处理与线程唤醒判定
1 |
|
stm32f7_i2c_isr_event_thread:线程化事件处理,推进 RELOAD/消息链/STOP/NACK
1 | /** |
stm32f7_i2c_isr_error_thread:独立 error IRQ 线程化处理入口
1 | /** |
stm32f7_i2c_wait_polling / stm32f7_i2c_dma_callback / stm32f7_i2c_wait_free_bus:atomic 轮询推进与 DMA 完成收尾、总线恢复
stm32f7_i2c_disable_dma_req:关闭外设侧 DMA 请求位
1 | /** |
stm32f7_i2c_dma_callback:DMA 结束/中止时的统一收尾(成功与错误路径复用)
1 | /** |
stm32f7_i2c_wait_polling:atomic 模式下用轮询驱动 ISR 顶半部推进状态机
1 | /** |
stm32f7_i2c_release_bus:通过禁用再重新配置外设以恢复可能卡死的总线状态
1 | /** |
stm32f7_i2c_wait_free_bus:等待 BUSY 清零,超时则尝试 release_bus 后返回忙
1 | /** |
stm32f7_i2c_smbus_xfer_msg / stm32f7_i2c_smbus_rep_start / stm32f7_i2c_smbus_check_pec — SMBus 事务编排(首阶段/重复起始/PEC 校验)
stm32f7_i2c_smbus_xfer_msg:SMBus 首阶段(command + 可选数据)配置并启动 START
1 | /** |
stm32f7_i2c_smbus_rep_start:SMBus 第二阶段(Repeated-START 切换到读)重新配置
1 | /** |
stm32f7_i2c_smbus_check_pec:读取硬件计算 PEC 与接收 PEC 字节比较
1 | /** |
stm32f7_i2c_smbus_xfer_msg / stm32f7_i2c_smbus_rep_start / stm32f7_i2c_smbus_check_pec:SMBus 事务编排(首阶段 / 重复起始 / PEC 校验)
stm32f7_i2c_smbus_xfer_msg:SMBus 首阶段(command + 可选数据)配置并启动 START(续)
1 | if (!i2c_dev->use_dma) { |
stm32f7_i2c_smbus_rep_start:SMBus 第二阶段(Repeated-START 切换到读)重新配置并触发
1 | /** |
stm32f7_i2c_smbus_check_pec:PEC 校验(硬件 PECR vs 接收缓冲区 PEC 字节)
1 | /** |










