title: busses categories:
- linux
- drivers
- I2C
tags: - linux
- drivers
- I2C
abbrlink: 3b770652 date: 2026-01-12 14:42:46
https://github.com/wdfk-prog/linux-study
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_atomic
- functionality
- dev.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:寄存器位操作与中断屏蔽辅助 - #include <linux/clk.h>
- #include <linux/delay.h>
- #include <linux/err.h>
- #include <linux/i2c.h>
- #include <linux/interrupt.h>
- #include <linux/io.h>
- #include <linux/iopoll.h>
- #include <linux/module.h>
- #include <linux/of_address.h>
- #include <linux/of_irq.h>
- #include <linux/of.h>
- #include <linux/platform_device.h>
- #include <linux/reset.h>
- #include "i2c-stm32.h"
- #define STM32F4_I2C_CR1 0x00
- #define STM32F4_I2C_CR2 0x04
- #define STM32F4_I2C_DR 0x10
- #define STM32F4_I2C_SR1 0x14
- #define STM32F4_I2C_SR2 0x18
- #define STM32F4_I2C_CCR 0x1C
- #define STM32F4_I2C_TRISE 0x20
- #define STM32F4_I2C_FLTR 0x24
- #define STM32F4_I2C_CR1_POS BIT(11)
- #define STM32F4_I2C_CR1_ACK BIT(10)
- #define STM32F4_I2C_CR1_STOP BIT(9)
- #define STM32F4_I2C_CR1_START BIT(8)
- #define STM32F4_I2C_CR1_PE BIT(0)
- #define STM32F4_I2C_CR2_FREQ_MASK GENMASK(5, 0)
- #define STM32F4_I2C_CR2_FREQ(n) ((n) & STM32F4_I2C_CR2_FREQ_MASK)
- #define STM32F4_I2C_CR2_ITBUFEN BIT(10)
- #define STM32F4_I2C_CR2_ITEVTEN BIT(9)
- #define STM32F4_I2C_CR2_ITERREN BIT(8)
- #define STM32F4_I2C_CR2_IRQ_MASK (STM32F4_I2C_CR2_ITBUFEN | \
- STM32F4_I2C_CR2_ITEVTEN | \
- STM32F4_I2C_CR2_ITERREN)
- #define STM32F4_I2C_CCR_CCR_MASK GENMASK(11, 0)
- #define STM32F4_I2C_CCR_CCR(n) ((n) & STM32F4_I2C_CCR_CCR_MASK)
- #define STM32F4_I2C_CCR_FS BIT(15)
- #define STM32F4_I2C_CCR_DUTY BIT(14)
- #define STM32F4_I2C_TRISE_VALUE_MASK GENMASK(5, 0)
- #define STM32F4_I2C_TRISE_VALUE(n) ((n) & STM32F4_I2C_TRISE_VALUE_MASK)
- #define STM32F4_I2C_MIN_STANDARD_FREQ 2U
- #define STM32F4_I2C_MIN_FAST_FREQ 6U
- #define STM32F4_I2C_MAX_FREQ 46U
- #define HZ_TO_MHZ 1000000
- struct stm32f4_i2c_msg {
- u8 addr;
- u32 count;
- u8 *buf;
- int result;
- bool stop;
- };
- struct stm32f4_i2c_dev {
- struct i2c_adapter adap;
- struct device *dev;
- void __iomem *base;
- struct completion complete;
- struct clk *clk;
- int speed;
- int parent_rate;
- struct stm32f4_i2c_msg msg;
- };
- /**
- * @brief 置位寄存器中的 mask 位;使用 relaxed 访问以减少不必要的屏障开销。
- * @note I2C 外设寄存器通常为强顺序外设区;relaxed 语义依赖体系结构对 MMIO 的基本有序性保证。
- */
- static inline void stm32f4_i2c_set_bits(void __iomem *reg, u32 mask)
- {
- writel_relaxed(readl_relaxed(reg) | mask, reg);
- }
- /**
- * @brief 清零寄存器中的 mask 位;使用 read-modify-write。
- */
- static inline void stm32f4_i2c_clr_bits(void __iomem *reg, u32 mask)
- {
- writel_relaxed(readl_relaxed(reg) & ~mask, reg);
- }
- /**
- * @brief 关闭该控制器在 CR2 中使能的三类 IRQ:BUF/EVT/ERR。
- * @note 这里仅做寄存器位清除,不负责处理中断控制器侧的屏蔽。
- */
- static void stm32f4_i2c_disable_irq(struct stm32f4_i2c_dev *i2c_dev)
- {
- void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
- stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_IRQ_MASK);
- }
复制代码
stm32f4_i2c_set_periph_clk_freq:配置 CR2.FREQ(单位 MHz)并校验硬件允许区间 - /**
- * @brief 配置 CR2.FREQ:将父时钟频率写入硬件要求的 MHz 字段。
- *
- * 关键点:
- * - clk_get_rate() 返回 Hz;
- * - CR2.FREQ 的语义是 MHz(整数),因此用 DIV_ROUND_UP 做向上取整;
- * - Standard/Fast 模式下硬件对可接受的 MHz 区间不同,超出则拒绝配置。
- */
- static int stm32f4_i2c_set_periph_clk_freq(struct stm32f4_i2c_dev *i2c_dev)
- {
- u32 freq;
- u32 cr2 = 0;
- i2c_dev->parent_rate = clk_get_rate(i2c_dev->clk);
- freq = DIV_ROUND_UP(i2c_dev->parent_rate, HZ_TO_MHZ);
- if (i2c_dev->speed == STM32_I2C_SPEED_STANDARD) {
- if (freq < STM32F4_I2C_MIN_STANDARD_FREQ ||
- freq > STM32F4_I2C_MAX_FREQ) {
- dev_err(i2c_dev->dev,
- "bad parent clk freq for standard mode\n");
- return -EINVAL;
- }
- } else {
- if (freq < STM32F4_I2C_MIN_FAST_FREQ ||
- freq > STM32F4_I2C_MAX_FREQ) {
- dev_err(i2c_dev->dev,
- "bad parent clk freq for fast mode\n");
- return -EINVAL;
- }
- }
- cr2 |= STM32F4_I2C_CR2_FREQ(freq);
- writel_relaxed(cr2, i2c_dev->base + STM32F4_I2C_CR2);
- return 0;
- }
复制代码
stm32f4_i2c_set_rise_time:配置 TRISE(按 I2C 规范最大上升沿时间离散化) - /**
- * @brief 配置 TRISE:将 I2C 规范的最大 SCL 上升沿时间换算为寄存器值。
- *
- * 不直观点:
- * - TRISE 字段表达“最大上升沿时间对应的时钟周期数 + 1”;
- * - freq 取 CR2.FREQ 的 MHz 值(由 parent_rate 向上取整得到);
- * - Standard:t_rise_max=1000ns => TRISE = freq + 1;
- * - Fast:t_rise_max=300ns => TRISE = freq * 300/1000 + 1,即 freq*3/10 + 1。
- */
- static void stm32f4_i2c_set_rise_time(struct stm32f4_i2c_dev *i2c_dev)
- {
- u32 freq = DIV_ROUND_UP(i2c_dev->parent_rate, HZ_TO_MHZ);
- u32 trise;
- if (i2c_dev->speed == STM32_I2C_SPEED_STANDARD)
- trise = freq + 1;
- else
- trise = freq * 3 / 10 + 1;
- writel_relaxed(STM32F4_I2C_TRISE_VALUE(trise),
- i2c_dev->base + STM32F4_I2C_TRISE);
- }
复制代码
stm32f4_i2c_set_speed_mode:配置 CCR(SCL 周期分频)与 FS(Standard/Fast 选择) - /**
- * @brief 配置 CCR 以逼近目标 SCL 频率,并在 Fast 模式下置位 FS。
- *
- * 不直观点:
- * - Standard 模式:高/低电平对称,近似 SCL = parent_rate / (2*CCR);
- * - Fast 模式:此实现选择 duty=0(低电平约为高电平两倍),近似 SCL = parent_rate / (3*CCR);
- * - Fast 模式使用 DIV_ROUND_UP,避免 CCR 取整导致 SCL 过高或边界时序不满足。
- */
- static void stm32f4_i2c_set_speed_mode(struct stm32f4_i2c_dev *i2c_dev)
- {
- u32 val;
- u32 ccr = 0;
- if (i2c_dev->speed == STM32_I2C_SPEED_STANDARD) {
- val = i2c_dev->parent_rate / (I2C_MAX_STANDARD_MODE_FREQ * 2);
- } else {
- val = DIV_ROUND_UP(i2c_dev->parent_rate,
- I2C_MAX_FAST_MODE_FREQ * 3);
- ccr |= STM32F4_I2C_CCR_FS;
- }
- ccr |= STM32F4_I2C_CCR_CCR(val);
- writel_relaxed(ccr, i2c_dev->base + STM32F4_I2C_CCR);
- }
复制代码
stm32f4_i2c_hw_config:按 CR2→TRISE→CCR→CR1 使能的顺序完成硬件初始化 - /**
- * @brief 配置 I2C 外设工作参数并使能外设。
- *
- * 顺序要点:
- * - 先写 CR2.FREQ(为 TRISE/CCR 计算与硬件内部时序提供基准);
- * - 再写 TRISE 与 CCR;
- * - 最后置位 CR1.PE 使能外设。
- */
- static int stm32f4_i2c_hw_config(struct stm32f4_i2c_dev *i2c_dev)
- {
- int ret;
- ret = stm32f4_i2c_set_periph_clk_freq(i2c_dev);
- if (ret)
- return ret;
- stm32f4_i2c_set_rise_time(i2c_dev);
- stm32f4_i2c_set_speed_mode(i2c_dev);
- writel_relaxed(STM32F4_I2C_CR1_PE, i2c_dev->base + STM32F4_I2C_CR1);
- return 0;
- }
复制代码
stm32f4_i2c_wait_free_bus:轮询 SR2.BUSY 等待总线空闲(提交 START 前的门禁) - #define STM32F4_I2C_SR2_BUSY BIT(1)
- /**
- * @brief 轮询等待 BUSY 清零,避免在总线仍忙时发起新的传输序列。
- * @note readl_relaxed_poll_timeout 的时间单位由调用者给定(此处为 us 级参数)。
- */
- static int stm32f4_i2c_wait_free_bus(struct stm32f4_i2c_dev *i2c_dev)
- {
- u32 status;
- int ret;
- ret = readl_relaxed_poll_timeout(i2c_dev->base + STM32F4_I2C_SR2,
- status,
- !(status & STM32F4_I2C_SR2_BUSY),
- 10, 1000);
- if (ret) {
- dev_dbg(i2c_dev->dev, "bus not free\n");
- ret = -EBUSY;
- }
- return ret;
- }
复制代码
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 的单字节收发封装 - /**
- * @brief 向数据寄存器 DR 写入 1 字节(用于发送地址或数据)。
- */
- static void stm32f4_i2c_write_byte(struct stm32f4_i2c_dev *i2c_dev, u8 byte)
- {
- writel_relaxed(byte, i2c_dev->base + STM32F4_I2C_DR);
- }
- /**
- * @brief 写方向:把当前 msg->buf 指向的 1 字节写入 DR,并推进指针与剩余计数。
- * @note 这里依赖硬件在 TXE/BTF 事件下按时取走 DR 数据。
- */
- static void stm32f4_i2c_write_msg(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- stm32f4_i2c_write_byte(i2c_dev, *msg->buf++); /**< 推进 buf 指针,表示该字节已被提交到硬件。 */
- msg->count--; /**< 逻辑剩余字节数减少。 */
- }
- /**
- * @brief 读方向:从 DR 读取 1 字节写入 msg->buf,并推进指针与剩余计数。
- * @note DR 通常为 32 位读出,这里仅使用低 8 位;驱动用 u32 承接以匹配 readl 接口。
- */
- static void stm32f4_i2c_read_msg(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- u32 rbuf;
- rbuf = readl_relaxed(i2c_dev->base + STM32F4_I2C_DR);
- *msg->buf++ = rbuf; /**< 仅取低 8 位写入缓冲区。 */
- msg->count--;
- }
复制代码
stm32f4_i2c_terminate_xfer:结束一次消息(STOP 或重复 START)并唤醒等待者 - /**
- * @brief 结束当前消息的硬件动作,并通过 completion 通知线程侧退出等待。
- *
- * 不直观点:
- * - 对“最后一条消息”生成 STOP;
- * - 对“非最后一条消息”生成重复 START(用于 combined message)。
- */
- static void stm32f4_i2c_terminate_xfer(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- void __iomem *reg;
- stm32f4_i2c_disable_irq(i2c_dev); /**< 终止时先关中断,避免状态机继续推进造成重复 complete。 */
- reg = i2c_dev->base + STM32F4_I2C_CR1;
- if (msg->stop)
- stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP); /**< 最后一条:请求生成 STOP。 */
- else
- stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START); /**< 非最后:请求重复 START,交给下一条消息续接。 */
- complete(&i2c_dev->complete); /**< 唤醒 stm32f4_i2c_xfer_msg() 中等待的线程。 */
- }
复制代码
stm32f4_i2c_handle_write:写方向在 TXE/BTF 驱动下逐字节灌入 DR,并在结束时 terminate - /**
- * @brief 写方向处理:在 TXE 或 BTF 事件下推进发送。
- *
- * 要点:
- * - 当 count 归零时,关闭 ITBUFEN(避免继续因 TXE 触发抢占);
- * - 若已无数据可发,进入 terminate_xfer(),由其决定 STOP/重复 START。
- */
- static void stm32f4_i2c_handle_write(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
- if (msg->count) {
- stm32f4_i2c_write_msg(i2c_dev);
- if (!msg->count) {
- stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_ITBUFEN); /**< 无剩余数据:关闭 BUF 中断。 */
- }
- } else {
- stm32f4_i2c_terminate_xfer(i2c_dev);
- }
- }
复制代码
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 结束。 - /**
- * @brief RXNE 事件处理:根据剩余 count 决定“立即读 DR”或“等待 BTF 再读”。
- *
- * 不直观点:
- * - count==2/3 时,不在 RXNE 里读 DR,而是等待 BTF,以满足 STOP/NACK 的时序要求。
- */
- static void stm32f4_i2c_handle_read(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR2;
- switch (msg->count) {
- case 1:
- stm32f4_i2c_disable_irq(i2c_dev); /**< 最后 1 字节:直接关中断,读出并完成。 */
- stm32f4_i2c_read_msg(i2c_dev);
- complete(&i2c_dev->complete);
- break;
- case 2:
- case 3:
- stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR2_ITBUFEN); /**< 关闭 BUF 中断,避免 RXNE 反复打断;后续由 BTF 收尾。 */
- break;
- default:
- stm32f4_i2c_read_msg(i2c_dev); /**< N>3:可持续在 RXNE 读取,直到剩余进入 3/2 的边界区。 */
- }
- }
复制代码- /**
- * @brief BTF 事件处理(读方向):在“必须等待 BTF”的阶段完成关键时序动作与最后字节读取。
- */
- static void stm32f4_i2c_handle_rx_done(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- void __iomem *reg;
- u32 mask;
- int i;
- switch (msg->count) {
- case 2:
- reg = i2c_dev->base + STM32F4_I2C_CR1;
- /**
- * @brief 2 字节接收的关键点:
- * 在读取最后两字节(N-1, N)之前先置 STOP/重复 START,
- * 否则硬件可能无法在正确窗口发出相应总线条件。
- */
- if (msg->stop)
- stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP);
- else
- stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START);
- for (i = 2; i > 0; i--)
- stm32f4_i2c_read_msg(i2c_dev); /**< 依次读出最后两字节。 */
- reg = i2c_dev->base + STM32F4_I2C_CR2;
- mask = STM32F4_I2C_CR2_ITEVTEN | STM32F4_I2C_CR2_ITERREN;
- stm32f4_i2c_clr_bits(reg, mask); /**< 收尾:关闭事件/错误中断,避免重复进入状态机。 */
- complete(&i2c_dev->complete);
- break;
- case 3:
- /**
- * @brief 3 字节接收的关键点:
- * 在读取 N-2 之前清 ACK,确保最后 1 字节以 NACK 结束,从而正确终止读序列。
- */
- reg = i2c_dev->base + STM32F4_I2C_CR1;
- stm32f4_i2c_clr_bits(reg, STM32F4_I2C_CR1_ACK);
- stm32f4_i2c_read_msg(i2c_dev); /**< 读取 N-2,之后流程会进入剩余 2 字节路径。 */
- break;
- default:
- stm32f4_i2c_read_msg(i2c_dev);
- }
- }
复制代码- /**
- * @brief ADDR 事件处理(读方向):在地址阶段根据剩余字节数配置 ACK/POS/STOP/START,并清除 ADDR。
- *
- * 不直观点(硬件要求):
- * - 清 ADDR 需完成 SR1->SR2 的读序列;SR1 已在 isr_event 读取,此处读 SR2 即可完成清除。
- * - POS 位用于控制 NACK/ACK 作用于“当前字节还是下一字节”,是 2 字节接收的关键。
- */
- static void stm32f4_i2c_handle_rx_addr(struct stm32f4_i2c_dev *i2c_dev)
- {
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- u32 cr1;
- switch (msg->count) {
- case 0:
- stm32f4_i2c_terminate_xfer(i2c_dev);
- readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2); /**< 清 ADDR。 */
- break;
- case 1:
- /**
- * @brief 1 字节接收:
- * 关闭 ACK,并清 POS,使 NACK/STOP(或重复 START)在接收该字节后立即生效。
- */
- cr1 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR1);
- cr1 &= ~(STM32F4_I2C_CR1_ACK | STM32F4_I2C_CR1_POS);
- writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
- readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2); /**< 清 ADDR。 */
- if (msg->stop)
- cr1 |= STM32F4_I2C_CR1_STOP;
- else
- cr1 |= STM32F4_I2C_CR1_START;
- writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
- break;
- case 2:
- /**
- * @brief 2 字节接收:
- * 清 ACK 并置 POS,使 NACK 对“下一字节”生效,这是两字节接收的硬件约束用法。
- */
- cr1 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR1);
- cr1 &= ~STM32F4_I2C_CR1_ACK;
- cr1 |= STM32F4_I2C_CR1_POS;
- writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
- readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2); /**< 清 ADDR。 */
- break;
- default:
- /**
- * @brief N>2 接收:
- * 使能 ACK,清 POS,使硬件按常规逐字节 ACK,直到进入 3/2 字节边界区再切换策略。
- */
- cr1 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR1);
- cr1 |= STM32F4_I2C_CR1_ACK;
- cr1 &= ~STM32F4_I2C_CR1_POS;
- writel_relaxed(cr1, i2c_dev->base + STM32F4_I2C_CR1);
- readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2); /**< 清 ADDR。 */
- break;
- }
- }
复制代码
stm32f4_i2c_isr_event:事件中断总入口(SB/ADDR/TXE/RXNE/BTF 分发) - /**
- * @brief I2C 事件中断:根据 SR1 与当前中断使能位推导有效事件集合,并驱动状态机前进。
- *
- * 不直观点:
- * - 只在 ITBUFEN 使能时才把 TXE/RXNE 纳入 possible_status,避免把“未使能的事件”误判为有效;
- * - ADDR 事件后会开启 ITBUFEN,使 TXE/RXNE 开始参与推进;
- * - 写方向与读方向使用同一套 event 分发,但行为不同(handle_write vs handle_read/rx_done/rx_addr)。
- */
- static irqreturn_t stm32f4_i2c_isr_event(int irq, void *data)
- {
- struct stm32f4_i2c_dev *i2c_dev = data;
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- u32 possible_status = STM32F4_I2C_SR1_ITEVTEN_MASK;
- u32 status, ien, event, cr2;
- cr2 = readl_relaxed(i2c_dev->base + STM32F4_I2C_CR2);
- ien = cr2 & STM32F4_I2C_CR2_IRQ_MASK;
- if (ien & STM32F4_I2C_CR2_ITBUFEN)
- possible_status |= STM32F4_I2C_SR1_ITBUFEN_MASK; /**< BUF 中断开启时才关心 TXE/RXNE。 */
- status = readl_relaxed(i2c_dev->base + STM32F4_I2C_SR1);
- event = status & possible_status;
- if (!event) {
- dev_dbg(i2c_dev->dev,
- "spurious evt irq (status=0x%08x, ien=0x%08x)\n",
- status, ien);
- return IRQ_NONE;
- }
- if (event & STM32F4_I2C_SR1_SB)
- stm32f4_i2c_write_byte(i2c_dev, msg->addr); /**< SB:起始条件完成后写入 8-bit 地址(含 R/W 位)。 */
- if (event & STM32F4_I2C_SR1_ADDR) {
- if (msg->addr & I2C_M_RD) /**< 这里利用 “R/W 位为 1” 的事实来判断读方向。 */
- stm32f4_i2c_handle_rx_addr(i2c_dev);
- else
- readl_relaxed(i2c_dev->base + STM32F4_I2C_SR2); /**< 写方向:读 SR2 完成清 ADDR 序列。 */
- cr2 |= STM32F4_I2C_CR2_ITBUFEN; /**< 地址阶段结束后开启 BUF 中断,使 TXE/RXNE 参与状态机。 */
- writel_relaxed(cr2, i2c_dev->base + STM32F4_I2C_CR2);
- }
- if ((event & STM32F4_I2C_SR1_TXE) && !(msg->addr & I2C_M_RD))
- stm32f4_i2c_handle_write(i2c_dev);
- if ((event & STM32F4_I2C_SR1_RXNE) && (msg->addr & I2C_M_RD))
- stm32f4_i2c_handle_read(i2c_dev);
- if (event & STM32F4_I2C_SR1_BTF) {
- if (msg->addr & I2C_M_RD)
- stm32f4_i2c_handle_rx_done(i2c_dev);
- else
- stm32f4_i2c_handle_write(i2c_dev);
- }
- return IRQ_HANDLED;
- }
复制代码
stm32f4_i2c_isr_error:错误中断入口(ARLO/AF/BERR)并以 completion 终止传输 - /**
- * @brief I2C 错误中断:清除错误标志、设置 msg->result,并终止当前等待。
- *
- * 不直观点:
- * - AF(NACK)在发送模式下要求软件生成 STOP;
- * - 这里统一 disable_irq + complete,使线程侧从 wait_for_completion_timeout 返回并读取 result。
- */
- static irqreturn_t stm32f4_i2c_isr_error(int irq, void *data)
- {
- struct stm32f4_i2c_dev *i2c_dev = data;
- struct stm32f4_i2c_msg *msg = &i2c_dev->msg;
- void __iomem *reg;
- u32 status;
- status = readl_relaxed(i2c_dev->base + STM32F4_I2C_SR1);
- if (status & STM32F4_I2C_SR1_ARLO) { /**< 仲裁丢失:通常建议上层重试。 */
- status &= ~STM32F4_I2C_SR1_ARLO;
- writel_relaxed(status, i2c_dev->base + STM32F4_I2C_SR1);
- msg->result = -EAGAIN;
- }
- if (status & STM32F4_I2C_SR1_AF) { /**< ACK failure:从机 NACK。 */
- if (!(msg->addr & I2C_M_RD)) {
- reg = i2c_dev->base + STM32F4_I2C_CR1;
- stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_STOP); /**< 发送模式下必须显式生成 STOP。 */
- }
- status &= ~STM32F4_I2C_SR1_AF;
- writel_relaxed(status, i2c_dev->base + STM32F4_I2C_SR1);
- msg->result = -EIO;
- }
- if (status & STM32F4_I2C_SR1_BERR) { /**< 总线错误:非法 START/STOP 等。 */
- status &= ~STM32F4_I2C_SR1_BERR;
- writel_relaxed(status, i2c_dev->base + STM32F4_I2C_SR1);
- msg->result = -EIO;
- }
- stm32f4_i2c_disable_irq(i2c_dev);
- complete(&i2c_dev->complete);
- return IRQ_HANDLED;
- }
复制代码
stm32f4_i2c_xfer_msg:线程侧发起单条消息,并等待 ISR 完成(或超时) - /**
- * @brief 传输单条 I2C 消息:配置会话状态、使能中断、必要时发起 START,并等待完成。
- *
- * 不直观点:
- * - f4_msg->addr 使用 i2c_8bit_addr_from_msg() 得到“8-bit 地址(含 R/W 位)”,便于在 SB 事件直接写入 DR;
- * - is_first 时检查 BUSY 并发起 START;其余消息由 terminate_xfer 触发重复 START 来续接;
- * - ret 以 msg->result 为主,超时则覆盖为 -ETIMEDOUT。
- */
- static int stm32f4_i2c_xfer_msg(struct stm32f4_i2c_dev *i2c_dev,
- struct i2c_msg *msg, bool is_first,
- bool is_last)
- {
- struct stm32f4_i2c_msg *f4_msg = &i2c_dev->msg;
- void __iomem *reg = i2c_dev->base + STM32F4_I2C_CR1;
- unsigned long time_left;
- u32 mask;
- int ret;
- f4_msg->addr = i2c_8bit_addr_from_msg(msg); /**< 8-bit 地址:最低位即 R/W 位。 */
- f4_msg->buf = msg->buf;
- f4_msg->count = msg->len;
- f4_msg->result = 0;
- f4_msg->stop = is_last;
- reinit_completion(&i2c_dev->complete);
- mask = STM32F4_I2C_CR2_ITEVTEN | STM32F4_I2C_CR2_ITERREN;
- stm32f4_i2c_set_bits(i2c_dev->base + STM32F4_I2C_CR2, mask); /**< 先开事件/错误中断,后续 ADDR 阶段再开 ITBUFEN。 */
- if (is_first) {
- ret = stm32f4_i2c_wait_free_bus(i2c_dev);
- if (ret)
- return ret;
- stm32f4_i2c_set_bits(reg, STM32F4_I2C_CR1_START); /**< 触发起始条件;随后由 SB/ADDR/TXE/RXNE/BTF 推进。 */
- }
- time_left = wait_for_completion_timeout(&i2c_dev->complete,
- i2c_dev->adap.timeout);
- ret = f4_msg->result;
- if (!time_left)
- ret = -ETIMEDOUT;
- return ret;
- }
复制代码
stm32f4_i2c_xfer / stm32f4_i2c_algo / stm32f4_i2c_probe / stm32f4_i2c_remove:适配器算法挂接与平台驱动生命周期(从 i2c_transfer 到硬件)
stm32f4_i2c_xfer:I2C core 的传输入口(为每个 msg 调用 xfer_msg,并在结束时返回 num) - /**
- * @brief I2C core 调用的 xfer 回调:顺序执行 msgs[0..num-1],并返回已完成的消息数。
- *
- * 不直观点:
- * - 这里在每次传输序列开始前 clk_enable(),结束后 clk_disable();
- * - 若某条消息失败,立即终止序列并返回错误码;
- * - 成功路径返回 num(而不是 0),符合 i2c_algorithm->xfer 的约定。
- */
- static int stm32f4_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[],
- int num)
- {
- struct stm32f4_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap); /**< 从适配器取回控制器私有数据。 */
- int ret, i;
- ret = clk_enable(i2c_dev->clk); /**< 传输前使能外设时钟,保证寄存器与状态机可用。 */
- if (ret) {
- dev_err(i2c_dev->dev, "Failed to enable clock\n");
- return ret;
- }
- for (i = 0; i < num && !ret; i++)
- ret = stm32f4_i2c_xfer_msg(i2c_dev, &msgs[i], i == 0,
- i == num - 1); /**< 顺序提交:首条生成 START,末条生成 STOP。 */
- clk_disable(i2c_dev->clk); /**< 传输结束关闭时钟,降低功耗并避免无谓寄存器活动。 */
- return (ret < 0) ? ret : num; /**< 失败返回负 errno;成功返回完成的消息数。 */
- }
复制代码
stm32f4_i2c_func / stm32f4_i2c_algo:向 I2C core 宣告能力并提供算法入口 - /**
- * @brief 向 I2C core 报告该适配器支持的功能集合。
- *
- * @note I2C_FUNC_SMBUS_EMUL 表示可以通过 I2C 原生传输在软件层模拟部分 SMBus 语义。
- */
- static u32 stm32f4_i2c_func(struct i2c_adapter *adap)
- {
- return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
- }
- /**
- * @brief I2C 适配器算法表:I2C core 通过它调用到底层控制器驱动。
- */
- static const struct i2c_algorithm stm32f4_i2c_algo = {
- .xfer = stm32f4_i2c_xfer, /**< 核心传输入口:实现 combined message。 */
- .functionality = stm32f4_i2c_func, /**< 能力回调:用于 i2c_check_functionality 等门禁判断。 */
- };
复制代码
stm32f4_i2c_probe:资源获取、复位、IRQ 注册、硬件配置,并注册 i2c_adapter - /**
- * @brief 平台设备 probe:将设备树描述的 I2C 控制器实例化为一个 i2c_adapter。
- *
- * 不直观点(调用关系核心):
- * - devm_platform_get_and_ioremap_resource:映射寄存器基址到 i2c_dev->base;
- * - irq_of_parse_and_map:从设备树取得 event/error IRQ 并映射;
- * - devm_request_irq:注册两路中断处理函数(事件与错误);
- * - stm32f4_i2c_hw_config:写 CR2/TRISE/CCR/CR1 使能外设;
- * - i2c_add_adapter:把适配器注册到 I2C core,使其可被 /dev/i2c-* 或内核客户端使用。
- */
- static int stm32f4_i2c_probe(struct platform_device *pdev)
- {
- struct device_node *np = pdev->dev.of_node;
- struct stm32f4_i2c_dev *i2c_dev;
- struct resource *res;
- u32 irq_event, irq_error, clk_rate;
- struct i2c_adapter *adap;
- struct reset_control *rst;
- int ret;
- i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL); /**< devm 管理:随设备卸载自动释放。 */
- if (!i2c_dev)
- return -ENOMEM;
- i2c_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); /**< 映射寄存器资源。 */
- if (IS_ERR(i2c_dev->base))
- return PTR_ERR(i2c_dev->base);
- irq_event = irq_of_parse_and_map(np, 0); /**< event IRQ:驱动 SB/ADDR/TXE/RXNE/BTF 等事件。 */
- if (!irq_event) {
- dev_err(&pdev->dev, "IRQ event missing or invalid\n");
- return -EINVAL;
- }
- irq_error = irq_of_parse_and_map(np, 1); /**< error IRQ:驱动 ARLO/AF/BERR 等错误。 */
- if (!irq_error) {
- dev_err(&pdev->dev, "IRQ error missing or invalid\n");
- return -EINVAL;
- }
- i2c_dev->clk = devm_clk_get_enabled(&pdev->dev, NULL); /**< 获取并使能时钟;失败则 probe 终止。 */
- if (IS_ERR(i2c_dev->clk)) {
- dev_err(&pdev->dev, "Failed to enable clock\n");
- return PTR_ERR(i2c_dev->clk);
- }
- rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); /**< 获取复位控制器,确保外设处于已知初态。 */
- if (IS_ERR(rst))
- return dev_err_probe(&pdev->dev, PTR_ERR(rst),
- "Error: Missing reset ctrl\n");
- reset_control_assert(rst);
- udelay(2); /**< 给硬件最小复位保持时间,避免亚稳态。 */
- reset_control_deassert(rst);
- i2c_dev->speed = STM32_I2C_SPEED_STANDARD; /**< 默认 Standard,若设备树频率>=400k 则切换 Fast。 */
- ret = of_property_read_u32(np, "clock-frequency", &clk_rate);
- if (!ret && clk_rate >= I2C_MAX_FAST_MODE_FREQ)
- i2c_dev->speed = STM32_I2C_SPEED_FAST;
- i2c_dev->dev = &pdev->dev;
- ret = devm_request_irq(&pdev->dev, irq_event, stm32f4_i2c_isr_event, 0,
- pdev->name, i2c_dev); /**< 注册事件中断。 */
- if (ret) {
- dev_err(&pdev->dev, "Failed to request irq event %i\n",
- irq_event);
- return ret;
- }
- ret = devm_request_irq(&pdev->dev, irq_error, stm32f4_i2c_isr_error, 0,
- pdev->name, i2c_dev); /**< 注册错误中断。 */
- if (ret) {
- dev_err(&pdev->dev, "Failed to request irq error %i\n",
- irq_error);
- return ret;
- }
- ret = stm32f4_i2c_hw_config(i2c_dev); /**< 写寄存器完成外设初始化与使能。 */
- if (ret)
- return ret;
- adap = &i2c_dev->adap;
- i2c_set_adapdata(adap, i2c_dev); /**< 建立“adapter→private data”关联,供 xfer 回调取回 i2c_dev。 */
- snprintf(adap->name, sizeof(adap->name), "STM32 I2C(%pa)", &res->start);
- adap->owner = THIS_MODULE;
- adap->timeout = 2 * HZ; /**< 供 xfer_msg 的 wait_for_completion_timeout 使用。 */
- adap->retries = 0;
- adap->algo = &stm32f4_i2c_algo; /**< 挂接算法表:xfer/functionality。 */
- adap->dev.parent = &pdev->dev;
- adap->dev.of_node = pdev->dev.of_node;
- init_completion(&i2c_dev->complete); /**< 初始化 completion,同步线程与 ISR。 */
- ret = i2c_add_adapter(adap); /**< 注册到 I2C core:此后可被 i2c_transfer 等路径调用。 */
- if (ret)
- return ret;
- platform_set_drvdata(pdev, i2c_dev); /**< 平台驱动数据绑定,remove 时可取回。 */
- clk_disable(i2c_dev->clk); /**< probe 完成后关闭时钟,等待首次传输再开启。 */
- dev_info(i2c_dev->dev, "STM32F4 I2C driver registered\n");
- return 0;
- }
复制代码
stm32f4_i2c_remove:从 I2C core 注销适配器 - /**
- * @brief 平台设备移除:注销 i2c_adapter。
- *
- * @note 资源释放主要由 devm 机制完成;这里只需撤销 I2C core 注册。
- */
- static void stm32f4_i2c_remove(struct platform_device *pdev)
- {
- struct stm32f4_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
- i2c_del_adapter(&i2c_dev->adap);
- }
复制代码
of_match / platform_driver / module_platform_driver:设备树匹配与模块注册(结构性说明) - static const struct of_device_id stm32f4_i2c_match[] = {
- { .compatible = "st,stm32f4-i2c", },
- {},
- };
- MODULE_DEVICE_TABLE(of, stm32f4_i2c_match);
- static struct platform_driver stm32f4_i2c_driver = {
- .driver = {
- .name = "stm32f4-i2c",
- .of_match_table = stm32f4_i2c_match,
- },
- .probe = stm32f4_i2c_probe,
- .remove = stm32f4_i2c_remove,
- };
- module_platform_driver(stm32f4_i2c_driver);
复制代码
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 规范约束集合 - static struct stm32f7_i2c_spec *stm32f7_get_specs(u32 rate)
- {
- int i;
- for (i = 0; i < ARRAY_SIZE(stm32f7_i2c_specs); i++)
- if (rate <= stm32f7_i2c_specs[i].rate)
- return &stm32f7_i2c_specs[i];
- return ERR_PTR(-EINVAL);
- }
复制代码
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| 最小作为“最优解”。 - #define RATE_MIN(rate) ((rate) * 8 / 10)
- static int stm32f7_i2c_compute_timing(struct stm32f7_i2c_dev *i2c_dev,
- struct stm32f7_i2c_setup *setup,
- struct stm32f7_i2c_timings *output)
- {
- struct stm32f7_i2c_spec *specs;
- u32 p_prev = STM32F7_PRESC_MAX;
- u32 i2cclk = DIV_ROUND_CLOSEST(NSEC_PER_SEC,
- setup->clock_src); /**< I2C 内核时钟周期(ns),用于把“计数值”映射为时间。 */
- u32 i2cbus = DIV_ROUND_CLOSEST(NSEC_PER_SEC,
- setup->speed_freq); /**< 目标 SCL 周期(ns),用于误差度量。 */
- u32 clk_error_prev = i2cbus; /**< 记录当前最优解的周期误差(|tscl - i2cbus|)。 */
- u32 tsync;
- u32 af_delay_min, af_delay_max;
- u32 dnf_delay;
- u32 clk_min, clk_max;
- int sdadel_min, sdadel_max;
- int scldel_min;
- struct stm32f7_i2c_timings *v, *_v, *s;
- struct list_head solutions;
- u16 p, l, a, h;
- int ret = 0;
- specs = stm32f7_get_specs(setup->speed_freq);
- if (specs == ERR_PTR(-EINVAL)) {
- dev_err(i2c_dev->dev, "speed out of bound {%d}\n",
- setup->speed_freq);
- return -EINVAL;
- }
- if ((setup->rise_time > specs->rise_max) ||
- (setup->fall_time > specs->fall_max)) {
- dev_err(i2c_dev->dev,
- "timings out of bound Rise{%d>%d}/Fall{%d>%d}\n",
- setup->rise_time, specs->rise_max,
- setup->fall_time, specs->fall_max);
- return -EINVAL;
- }
- i2c_dev->dnf = DIV_ROUND_CLOSEST(i2c_dev->dnf_dt, i2cclk); /**< 数字滤波宽度(ns)换算为抽头数(dnf)。 */
- if (i2c_dev->dnf > STM32F7_I2C_DNF_MAX) {
- dev_err(i2c_dev->dev,
- "DNF out of bound %d/%d\n",
- i2c_dev->dnf * i2cclk, STM32F7_I2C_DNF_MAX * i2cclk);
- return -EINVAL;
- }
- af_delay_min =
- (i2c_dev->analog_filter ?
- STM32F7_I2C_ANALOG_FILTER_DELAY_MIN : 0); /**< 模拟滤波引入的最小延迟(ns)。 */
- af_delay_max =
- (i2c_dev->analog_filter ?
- STM32F7_I2C_ANALOG_FILTER_DELAY_MAX : 0); /**< 模拟滤波引入的最大延迟(ns)。 */
- dnf_delay = i2c_dev->dnf * i2cclk; /**< 数字滤波延迟近似为 dnf 个时钟周期。 */
- sdadel_min = specs->hddat_min + setup->fall_time -
- af_delay_min - (i2c_dev->dnf + 3) * i2cclk; /**< SDADEL 下界:数据保持时间与滤波/边沿共同约束。 */
- sdadel_max = specs->vddat_max - setup->rise_time -
- af_delay_max - (i2c_dev->dnf + 4) * i2cclk; /**< SDADEL 上界:数据有效窗口与滤波/边沿共同约束。 */
- scldel_min = setup->rise_time + specs->sudat_min; /**< SCLDEL 下界:满足数据建立时间要求。 */
- if (sdadel_min < 0)
- sdadel_min = 0;
- if (sdadel_max < 0)
- sdadel_max = 0;
- dev_dbg(i2c_dev->dev, "SDADEL(min/max): %i/%i, SCLDEL(Min): %i\n",
- sdadel_min, sdadel_max, scldel_min);
- INIT_LIST_HEAD(&solutions);
- /** 先枚举 PRESC/SCLDEL/SDADEL 的可行集合,缩小后续搜索空间。 */
- for (p = 0; p < STM32F7_PRESC_MAX; p++) {
- for (l = 0; l < STM32F7_SCLDEL_MAX; l++) {
- u32 scldel = (l + 1) * (p + 1) * i2cclk; /**< SCLDEL 寄存器字段对应的实际延迟(ns)。 */
- if (scldel < scldel_min)
- continue;
- for (a = 0; a < STM32F7_SDADEL_MAX; a++) {
- u32 sdadel = (a * (p + 1) + 1) * i2cclk; /**< SDADEL 字段对应的实际延迟(ns)。 */
- if (((sdadel >= sdadel_min) &&
- (sdadel <= sdadel_max)) &&
- (p != p_prev)) {
- v = kmalloc(sizeof(*v), GFP_KERNEL);
- if (!v) {
- ret = -ENOMEM;
- goto exit;
- }
- v->presc = p;
- v->scldel = l;
- v->sdadel = a;
- p_prev = p;
- list_add_tail(&v->node,
- &solutions);
- break;
- }
- }
- if (p_prev == p)
- break;
- }
- }
- if (list_empty(&solutions)) {
- dev_err(i2c_dev->dev, "no Prescaler solution\n");
- ret = -EPERM;
- goto exit;
- }
- tsync = af_delay_min + dnf_delay + (2 * i2cclk); /**< 同步项:滤波与内部同步带来的固定时间开销。 */
- s = NULL;
- clk_max = NSEC_PER_SEC / RATE_MIN(setup->speed_freq); /**< 允许的最大周期(ns):对应最低允许速率(0.8*目标)。 */
- clk_min = NSEC_PER_SEC / setup->speed_freq; /**< 允许的最小周期(ns):对应目标速率本身。 */
- /** 在可行(PRESC,SCLDEL,SDADEL)集合中继续搜索(SCLL,SCLH),并以周期误差最小选择最优解。 */
- list_for_each_entry(v, &solutions, node) {
- u32 prescaler = (v->presc + 1) * i2cclk; /**< PRESC 对应的时间量化步进(ns)。 */
- for (l = 0; l < STM32F7_SCLL_MAX; l++) {
- u32 tscl_l = (l + 1) * prescaler + tsync; /**< SCL 低电平持续时间(ns)。 */
- if ((tscl_l < specs->l_min) ||
- (i2cclk >=
- ((tscl_l - af_delay_min - dnf_delay) / 4))) {
- continue;
- }
- for (h = 0; h < STM32F7_SCLH_MAX; h++) {
- u32 tscl_h = (h + 1) * prescaler + tsync; /**< SCL 高电平持续时间(ns)。 */
- u32 tscl = tscl_l + tscl_h +
- setup->rise_time + setup->fall_time; /**< 近似总周期(ns):含上升/下降沿时间。 */
- if ((tscl >= clk_min) && (tscl <= clk_max) &&
- (tscl_h >= specs->h_min) &&
- (i2cclk < tscl_h)) {
- int clk_error = tscl - i2cbus; /**< 与目标周期的偏差,用于择优。 */
- if (clk_error < 0)
- clk_error = -clk_error;
- if (clk_error < clk_error_prev) {
- clk_error_prev = clk_error;
- v->scll = l;
- v->sclh = h;
- s = v;
- }
- }
- }
- }
- }
- if (!s) {
- dev_err(i2c_dev->dev, "no solution at all\n");
- ret = -EPERM;
- goto exit;
- }
- output->presc = s->presc;
- output->scldel = s->scldel;
- output->sdadel = s->sdadel;
- output->scll = s->scll;
- output->sclh = s->sclh;
- dev_dbg(i2c_dev->dev,
- "Presc: %i, scldel: %i, sdadel: %i, scll: %i, sclh: %i\n",
- output->presc,
- output->scldel, output->sdadel,
- output->scll, output->sclh);
- exit:
- list_for_each_entry_safe(v, _v, &solutions, node) {
- list_del(&v->node);
- kfree(v);
- }
- return ret;
- }
复制代码
stm32f7_get_lower_rate:计算“降档”的下一个更低速率档位 - static u32 stm32f7_get_lower_rate(u32 rate)
- {
- int i = ARRAY_SIZE(stm32f7_i2c_specs);
- while (--i)
- if (stm32f7_i2c_specs[i].rate < rate)
- break;
- return stm32f7_i2c_specs[i].rate;
- }
复制代码
stm32f7_i2c_setup_timing:解析固件时序输入并调用 compute_timing 生成最终寄存器字段 - static int stm32f7_i2c_setup_timing(struct stm32f7_i2c_dev *i2c_dev,
- struct stm32f7_i2c_setup *setup)
- {
- struct i2c_timings timings, *t = &timings;
- int ret = 0;
- t->bus_freq_hz = I2C_MAX_STANDARD_MODE_FREQ;
- t->scl_rise_ns = i2c_dev->setup.rise_time;
- t->scl_fall_ns = i2c_dev->setup.fall_time;
- i2c_parse_fw_timings(i2c_dev->dev, t, false); /**< 从设备树/固件读取 bus_freq 与 rise/fall/dnf 等时序参数。 */
- if (t->bus_freq_hz > I2C_MAX_FAST_MODE_PLUS_FREQ) {
- dev_err(i2c_dev->dev, "Invalid bus speed (%i>%i)\n",
- t->bus_freq_hz, I2C_MAX_FAST_MODE_PLUS_FREQ);
- return -EINVAL;
- }
- setup->speed_freq = t->bus_freq_hz;
- i2c_dev->setup.rise_time = t->scl_rise_ns;
- i2c_dev->setup.fall_time = t->scl_fall_ns;
- i2c_dev->dnf_dt = t->digital_filter_width_ns; /**< 数字滤波宽度(ns),后续会换算为抽头数 dnf。 */
- setup->clock_src = clk_get_rate(i2c_dev->clk); /**< I2C 外设时钟源频率(Hz),用于换算寄存器字段对应的时间量。 */
- if (!setup->clock_src) {
- dev_err(i2c_dev->dev, "clock rate is 0\n");
- return -EINVAL;
- }
- if (!of_property_read_bool(i2c_dev->dev->of_node, "i2c-digital-filter"))
- i2c_dev->dnf_dt = STM32F7_I2C_DNF_DEFAULT; /**< 未启用数字滤波时,将宽度视为 0。 */
- do {
- ret = stm32f7_i2c_compute_timing(i2c_dev, setup,
- &i2c_dev->timing);
- if (ret) {
- dev_err(i2c_dev->dev,
- "failed to compute I2C timings.\n");
- if (setup->speed_freq <= I2C_MAX_STANDARD_MODE_FREQ)
- break;
- setup->speed_freq =
- stm32f7_get_lower_rate(setup->speed_freq); /**< 计算失败时按档位降速重试,避免直接失败。 */
- dev_warn(i2c_dev->dev,
- "downgrade I2C Speed Freq to (%i)\n",
- setup->speed_freq);
- }
- } while (ret);
- if (ret) {
- dev_err(i2c_dev->dev, "Impossible to compute I2C timings.\n");
- return ret;
- }
- i2c_dev->analog_filter = of_property_read_bool(i2c_dev->dev->of_node,
- "i2c-analog-filter");
- dev_dbg(i2c_dev->dev, "I2C Speed(%i), Clk Source(%i)\n",
- setup->speed_freq, setup->clock_src);
- dev_dbg(i2c_dev->dev, "I2C Rise(%i) and Fall(%i) Time\n",
- setup->rise_time, setup->fall_time);
- dev_dbg(i2c_dev->dev, "I2C Analog Filter(%s), DNF(%i)\n",
- str_on_off(i2c_dev->analog_filter), i2c_dev->dnf);
- i2c_dev->bus_rate = setup->speed_freq;
- return 0;
- }
复制代码
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 使能外设;这一步对寄存器写入的生效顺序具有控制意义。
- /**
- * @brief 将已计算的 I2C 时序/滤波参数写入硬件寄存器并使能外设。
- *
- * @param i2c_dev 控制器私有数据,包含 timing/dnf/analog_filter/base 等。
- */
- static void stm32f7_i2c_hw_config(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_timings *t = &i2c_dev->timing; /**< timing:由算法求得的离散字段解,直接对应 TIMINGR 各位域。 */
- u32 timing = 0; /**< timing:TIMINGR 最终要写入的位域组合值。 */
- /** TIMINGR 的字段组合必须一次性形成最终值写入,避免逐字段 read-modify-write 带来的中间态。 */
- timing |= STM32F7_I2C_TIMINGR_PRESC(t->presc); /**< PRESC:决定内部时间量化步进。 */
- timing |= STM32F7_I2C_TIMINGR_SCLDEL(t->scldel); /**< SCLDEL:数据建立相关延迟字段。 */
- timing |= STM32F7_I2C_TIMINGR_SDADEL(t->sdadel); /**< SDADEL:数据保持相关延迟字段。 */
- timing |= STM32F7_I2C_TIMINGR_SCLH(t->sclh); /**< SCLH:SCL 高电平计数。 */
- timing |= STM32F7_I2C_TIMINGR_SCLL(t->scll); /**< SCLL:SCL 低电平计数。 */
- writel_relaxed(timing, i2c_dev->base + STM32F7_I2C_TIMINGR);
- /** ANFOFF 语义为“置位=关闭模拟滤波”,因此 enable 模拟滤波时需要清位。 */
- if (i2c_dev->analog_filter)
- stm32f7_i2c_clr_bits(i2c_dev->base + STM32F7_I2C_CR1,
- STM32F7_I2C_CR1_ANFOFF);
- else
- stm32f7_i2c_set_bits(i2c_dev->base + STM32F7_I2C_CR1,
- STM32F7_I2C_CR1_ANFOFF);
- /** DNF 是位域,必须先清 mask 再写入新值,避免旧值残留。 */
- stm32f7_i2c_clr_bits(i2c_dev->base + STM32F7_I2C_CR1,
- STM32F7_I2C_CR1_DNF_MASK);
- stm32f7_i2c_set_bits(i2c_dev->base + STM32F7_I2C_CR1,
- STM32F7_I2C_CR1_DNF(i2c_dev->dnf));
- /** PE:外设使能位。通常在关键配置完成后再置位,避免配置窗口内进入工作态。 */
- stm32f7_i2c_set_bits(i2c_dev->base + STM32F7_I2C_CR1,
- STM32F7_I2C_CR1_PE);
- }
复制代码
stm32f7_i2c_xfer_core:I2C 主机传输的公共核心(支持普通与 atomic 两种驱动方式)
作用与原理
- 负责一次 master_xfer 的高层编排:运行时电源管理、等待总线空闲、发起首个消息、等待完成、错误清理与超时处理。
- atomic 为真时不依赖中断完成量 complete,而是通过轮询函数驱动状态机(避免不可睡眠上下文等待/调度)。
- /**
- * @brief I2C 主机传输公共核心:组织消息队列并等待完成。
- *
- * @param i2c_adap 适配器对象。
- * @param msgs 消息数组。
- * @param num 消息数量。
- *
- * @return 成功返回 num;失败返回负 errno。
- */
- static int stm32f7_i2c_xfer_core(struct i2c_adapter *i2c_adap,
- struct i2c_msg msgs[], int num)
- {
- struct stm32f7_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap);
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg; /**< f7_msg:驱动内部状态机使用的“当前传输上下文”。 */
- struct stm32_i2c_dma *dma = i2c_dev->dma;
- unsigned long time_left;
- int ret;
- i2c_dev->msg = msgs;
- i2c_dev->msg_num = num;
- i2c_dev->msg_id = 0;
- f7_msg->smbus = false;
- ret = pm_runtime_resume_and_get(i2c_dev->dev); /**< 传输前确保时钟/电源就绪,避免访问寄存器无效。 */
- if (ret < 0)
- return ret;
- ret = stm32f7_i2c_wait_free_bus(i2c_dev); /**< 关键前置条件:BUSY 需为 0,否则本次传输不应启动。 */
- if (ret)
- goto pm_free;
- stm32f7_i2c_xfer_msg(i2c_dev, msgs); /**< 启动第 0 个消息:写 CR1/CR2 并置 START。 */
- /** 非 atomic:依赖中断/线程化中断在结束时 complete;atomic:禁止中断后改为轮询驱动状态机。 */
- if (!i2c_dev->atomic)
- time_left = wait_for_completion_timeout(&i2c_dev->complete,
- i2c_dev->adap.timeout);
- else
- time_left = stm32f7_i2c_wait_polling(i2c_dev);
- ret = f7_msg->result;
- if (ret) {
- if (i2c_dev->use_dma)
- dmaengine_synchronize(dma->chan_using); /**< 错误路径:确保 DMA 停止并完成清理语义一致。 */
- /**
- * 错误场景下 TXDR 可能残留“已写入但未实际发送”的字节。
- * 通过置 TXE/flush 手段避免下一次事务误发送旧数据。
- */
- writel_relaxed(STM32F7_I2C_ISR_TXE,
- i2c_dev->base + STM32F7_I2C_ISR);
- goto pm_free;
- }
- if (!time_left) {
- dev_dbg(i2c_dev->dev, "Access to slave 0x%x timed out\n",
- i2c_dev->msg->addr);
- if (i2c_dev->use_dma)
- dmaengine_terminate_sync(dma->chan_using); /**< 超时:同步终止 DMA,防止后台继续写寄存器/内存。 */
- stm32f7_i2c_wait_free_bus(i2c_dev); /**< 超时后尝试恢复/释放总线,避免 BUSY 长期卡死。 */
- ret = -ETIMEDOUT;
- }
- pm_free:
- pm_runtime_put_autosuspend(i2c_dev->dev); /**< 传输结束后允许自动挂起,降低空闲功耗。 */
- return (ret < 0) ? ret : num;
- }
复制代码
stm32f7_i2c_xfer_msg:为单条 i2c_msg 配置 CR1/CR2,并选择 DMA 或 IRQ 驱动数据通道
作用与原理
- /**
- * @brief 配置并启动单条 I2C 消息传输(写 CR1/CR2 并置 START)。
- *
- * @param i2c_dev 控制器私有数据。
- * @param msg Linux I2C 层消息描述。
- */
- static void stm32f7_i2c_xfer_msg(struct stm32f7_i2c_dev *i2c_dev,
- struct i2c_msg *msg)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- void __iomem *base = i2c_dev->base;
- u8 *dma_buf;
- u32 cr1, cr2;
- int ret;
- f7_msg->addr = msg->addr;
- f7_msg->buf = msg->buf;
- f7_msg->count = msg->len;
- f7_msg->result = 0;
- f7_msg->stop = (i2c_dev->msg_id >= i2c_dev->msg_num - 1); /**< 仅最后一条消息生成 STOP,其余通过 TC 触发下一条/重复起始。 */
- reinit_completion(&i2c_dev->complete); /**< 为本条消息重新初始化完成量,由 ISR 在结束/错误时 complete。 */
- cr1 = readl_relaxed(base + STM32F7_I2C_CR1);
- cr2 = readl_relaxed(base + STM32F7_I2C_CR2);
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN;
- if (msg->flags & I2C_M_RD)
- cr2 |= STM32F7_I2C_CR2_RD_WRN; /**< RD_WRN=1 表示读事务,硬件将从 RXDR 出数。 */
- cr2 &= ~(STM32F7_I2C_CR2_HEAD10R | STM32F7_I2C_CR2_ADD10);
- if (msg->flags & I2C_M_TEN) {
- cr2 &= ~STM32F7_I2C_CR2_SADD10_MASK;
- cr2 |= STM32F7_I2C_CR2_SADD10(f7_msg->addr);
- cr2 |= STM32F7_I2C_CR2_ADD10; /**< 10 位地址模式:由 ADD10 指示并写入 SADD10。 */
- } else {
- cr2 &= ~STM32F7_I2C_CR2_SADD7_MASK;
- cr2 |= STM32F7_I2C_CR2_SADD7(f7_msg->addr); /**< 7 位地址需左移到位域位置(bit[7:1]),bit0 为方向位由硬件管理。 */
- }
- cr2 &= ~(STM32F7_I2C_CR2_NBYTES_MASK | STM32F7_I2C_CR2_RELOAD);
- if (f7_msg->count > STM32F7_I2C_MAX_LEN) {
- cr2 |= STM32F7_I2C_CR2_NBYTES(STM32F7_I2C_MAX_LEN);
- cr2 |= STM32F7_I2C_CR2_RELOAD; /**< RELOAD:硬件发送完 NBYTES 后置 TCR,驱动需重装下一段 NBYTES。 */
- } else {
- cr2 |= STM32F7_I2C_CR2_NBYTES(f7_msg->count);
- }
- cr1 |= STM32F7_I2C_CR1_ERRIE | STM32F7_I2C_CR1_TCIE |
- STM32F7_I2C_CR1_STOPIE | STM32F7_I2C_CR1_NACKIE; /**< 事件推进依赖 TC/TCR/STOP/NACK;错误依赖 ERR。 */
- cr1 &= ~(STM32F7_I2C_CR1_RXIE | STM32F7_I2C_CR1_TXIE |
- STM32F7_I2C_CR1_RXDMAEN | STM32F7_I2C_CR1_TXDMAEN); /**< 数据通道只能二选一:IRQ 或 DMA;先清再按策略设置。 */
- i2c_dev->use_dma = false;
- if (i2c_dev->dma && !i2c_dev->atomic) {
- /**
- * DMA 需要满足对齐/缓存一致性等约束,安全缓冲区由 I2C core 提供。
- * STM32F7_I2C_DMA_LEN_MIN 是“值得启用 DMA 的最小长度阈值”,避免短包 DMA 开销反而更大。
- */
- dma_buf = i2c_get_dma_safe_msg_buf(msg, STM32F7_I2C_DMA_LEN_MIN);
- if (dma_buf) {
- f7_msg->buf = dma_buf;
- ret = stm32_i2c_prep_dma_xfer(i2c_dev->dev, i2c_dev->dma,
- msg->flags & I2C_M_RD,
- f7_msg->count, f7_msg->buf,
- stm32f7_i2c_dma_callback,
- i2c_dev);
- if (ret) {
- dev_warn(i2c_dev->dev, "can't use DMA\n");
- i2c_put_dma_safe_msg_buf(f7_msg->buf, msg, false);
- f7_msg->buf = msg->buf; /**< DMA 准备失败:回退到原始缓冲区并采用中断传输。 */
- } else {
- i2c_dev->use_dma = true;
- }
- }
- }
- if (!i2c_dev->use_dma) {
- if (msg->flags & I2C_M_RD)
- cr1 |= STM32F7_I2C_CR1_RXIE; /**< 中断收数:RXNE 触发读取 RXDR。 */
- else
- cr1 |= STM32F7_I2C_CR1_TXIE; /**< 中断发数:TXIS 触发写入 TXDR。 */
- } else {
- if (msg->flags & I2C_M_RD)
- cr1 |= STM32F7_I2C_CR1_RXDMAEN; /**< DMA 收数:由 DMA 搬运 RXDR。 */
- else
- cr1 |= STM32F7_I2C_CR1_TXDMAEN; /**< DMA 发数:由 DMA 搬运 TXDR。 */
- }
- if (i2c_dev->atomic)
- cr1 &= ~STM32F7_I2C_ALL_IRQ_MASK; /**< atomic 模式:不使用中断推进状态机,后续由轮询路径驱动。 */
- cr2 |= STM32F7_I2C_CR2_START; /**< START/重复起始:写 CR2 后硬件开始发送地址与启动传输。 */
- i2c_dev->master_mode = true; /**< 该标志影响 ISR 分流:主机/从机/错误处理路径不同。 */
- writel_relaxed(cr1, base + STM32F7_I2C_CR1);
- writel_relaxed(cr2, base + STM32F7_I2C_CR2);
- }
复制代码
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 字节并递减剩余计数 - /**
- * @brief 发送路径写入 1 字节到 TXDR。
- *
- * @param i2c_dev 控制器私有数据,内部使用 f7_msg 的 buf/count。
- */
- static void stm32f7_i2c_write_tx_data(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg; /**< 驱动内部消息上下文:buf 指向待发送数据,count 为剩余字节数。 */
- void __iomem *base = i2c_dev->base;
- if (f7_msg->count) { /**< 仅当还有待发送数据时才写 TXDR,避免写入无效字节。 */
- writeb_relaxed(*f7_msg->buf++, base + STM32F7_I2C_TXDR); /**< 写 8bit 数据寄存器;buf 自增以指向下一字节。 */
- f7_msg->count--; /**< 发送 1 字节后,剩余字节数递减。 */
- }
- }
复制代码
stm32f7_i2c_read_rx_data:从 RXDR 读取 1 字节或执行接收 FIFO 刷新 - /**
- * @brief 接收路径读取 1 字节到缓冲区;若不期望数据仍读取一次以清空 RXDR。
- *
- * @param i2c_dev 控制器私有数据。
- */
- static void stm32f7_i2c_read_rx_data(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg; /**< buf 指向存放接收数据的位置;count 为剩余待接收字节数。 */
- void __iomem *base = i2c_dev->base;
- if (f7_msg->count) {
- *f7_msg->buf++ = readb_relaxed(base + STM32F7_I2C_RXDR); /**< 读出 1 字节并写入 buf,随后 buf 自增。 */
- f7_msg->count--; /**< 接收 1 字节后剩余计数递减。 */
- } else {
- readb_relaxed(base + STM32F7_I2C_RXDR); /**< 读 RXDR 以清除硬件“RXNE”状态,避免遗留数据影响后续事务。 */
- }
- }
复制代码
stm32f7_i2c_reload:处理 RELOAD 分段传输时重装 NBYTES - /**
- * @brief RELOAD 分段传输重装 NBYTES。
- *
- * @param i2c_dev 控制器私有数据。
- */
- static void stm32f7_i2c_reload(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- u32 cr2;
- if (i2c_dev->use_dma)
- f7_msg->count -= STM32F7_I2C_MAX_LEN; /**< DMA 分段场景:每次装载固定段长后,需要扣除已覆盖的段长度。 */
- cr2 = readl_relaxed(i2c_dev->base + STM32F7_I2C_CR2);
- cr2 &= ~STM32F7_I2C_CR2_NBYTES_MASK;
- if (f7_msg->count > STM32F7_I2C_MAX_LEN) {
- cr2 |= STM32F7_I2C_CR2_NBYTES(STM32F7_I2C_MAX_LEN); /**< 仍超过单段上限:继续维持 RELOAD 并装载下一段最大值。 */
- } else {
- cr2 &= ~STM32F7_I2C_CR2_RELOAD; /**< 最后一段:清 RELOAD,使硬件在段结束后进入 TC 而非 TCR。 */
- cr2 |= STM32F7_I2C_CR2_NBYTES(f7_msg->count); /**< 装载最后一段的实际剩余字节数。 */
- }
- writel_relaxed(cr2, i2c_dev->base + STM32F7_I2C_CR2);
- }
复制代码
stm32f7_i2c_smbus_reload:SMBus 块读类协议按“首字节长度”重装 NBYTES - /**
- * @brief SMBus 块读/块过程调用的 RELOAD:先接收长度字节,再按长度更新 NBYTES。
- *
- * @param i2c_dev 控制器私有数据。
- */
- static void stm32f7_i2c_smbus_reload(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- u32 cr2;
- u8 *val;
- stm32f7_i2c_read_rx_data(i2c_dev); /**< SMBus 块读:首字节为 count,需要先把它接收下来。 */
- val = f7_msg->buf - sizeof(u8); /**< buf 已自增,回退 1 字节定位到刚接收的 count 值。 */
- f7_msg->count = *val; /**< 将接收长度作为后续需要接收的数据字节数。 */
- cr2 = readl_relaxed(i2c_dev->base + STM32F7_I2C_CR2);
- cr2 &= ~(STM32F7_I2C_CR2_NBYTES_MASK | STM32F7_I2C_CR2_RELOAD); /**< SMBus 块读在得知长度后,通常不再需要继续 RELOAD。 */
- cr2 |= STM32F7_I2C_CR2_NBYTES(f7_msg->count);
- writel_relaxed(cr2, i2c_dev->base + STM32F7_I2C_CR2);
- }
复制代码
stm32f7_i2c_handle_isr_errs:错误/异常标志处理与收尾 - /**
- * @brief 处理 I2C 错误类中断标志,设置传输结果并结束本次事务。
- *
- * @param i2c_dev 控制器私有数据。
- * @param status ISR 读出的状态寄存器值。
- *
- * @return IRQ_HANDLED
- */
- static irqreturn_t stm32f7_i2c_handle_isr_errs(struct stm32f7_i2c_dev *i2c_dev, u32 status)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- u16 addr = f7_msg->addr;
- void __iomem *base = i2c_dev->base;
- struct device *dev = i2c_dev->dev;
- if (status & STM32F7_I2C_ISR_BERR) {
- dev_err(dev, "Bus error accessing addr 0x%x\n", addr);
- writel_relaxed(STM32F7_I2C_ICR_BERRCF, base + STM32F7_I2C_ICR); /**< 写 ICR 清 BERR,防止错误标志粘连。 */
- stm32f7_i2c_release_bus(&i2c_dev->adap); /**< 通过禁用/重配恢复控制器,缓解 BUSY 卡死或异常状态。 */
- f7_msg->result = -EIO;
- }
- if (status & STM32F7_I2C_ISR_ARLO) {
- dev_dbg(dev, "Arbitration loss accessing addr 0x%x\n", addr);
- writel_relaxed(STM32F7_I2C_ICR_ARLOCF, base + STM32F7_I2C_ICR);
- f7_msg->result = -EAGAIN; /**< 仲裁丢失通常建议上层重试。 */
- }
- if (status & STM32F7_I2C_ISR_PECERR) {
- dev_err(dev, "PEC error in reception accessing addr 0x%x\n", addr);
- writel_relaxed(STM32F7_I2C_ICR_PECCF, base + STM32F7_I2C_ICR);
- f7_msg->result = -EINVAL;
- }
- if (status & STM32F7_I2C_ISR_ALERT) {
- dev_dbg(dev, "SMBus alert received\n");
- writel_relaxed(STM32F7_I2C_ICR_ALERTCF, base + STM32F7_I2C_ICR);
- i2c_handle_smbus_alert(i2c_dev->alert->ara); /**< ARA 设备负责完成 SMBus Alert 的响应流程。 */
- return IRQ_HANDLED;
- }
- if (!i2c_dev->slave_running) {
- u32 mask;
- if (stm32f7_i2c_is_slave_registered(i2c_dev))
- mask = STM32F7_I2C_XFER_IRQ_MASK;
- else
- mask = STM32F7_I2C_ALL_IRQ_MASK;
- stm32f7_i2c_disable_irq(i2c_dev, mask); /**< 错误收尾:关闭本次事务相关中断源,防止重复进入。 */
- }
- if (i2c_dev->use_dma)
- stm32f7_i2c_dma_callback(i2c_dev); /**< 错误收尾:停止 DMA、解除映射并触发 dma_complete。 */
- i2c_dev->master_mode = false;
- complete(&i2c_dev->complete); /**< 通知等待方本次事务结束(成功或失败)。 */
- return IRQ_HANDLED;
- }
复制代码
stm32f7_i2c_isr_event:顶半部快速处理与线程唤醒判定 - #define STM32F7_ERR_EVENTS (STM32F7_I2C_ISR_BERR | STM32F7_I2C_ISR_ARLO |\
- STM32F7_I2C_ISR_PECERR | STM32F7_I2C_ISR_ALERT)
- /**
- * @brief I2C 事件中断顶半部:做最小化处理并决定是否唤醒线程化 handler。
- *
- * @param irq 中断号。
- * @param data 控制器私有数据指针。
- *
- * @return IRQ_HANDLED 或 IRQ_WAKE_THREAD
- */
- static irqreturn_t stm32f7_i2c_isr_event(int irq, void *data)
- {
- struct stm32f7_i2c_dev *i2c_dev = data;
- u32 status;
- status = readl_relaxed(i2c_dev->base + STM32F7_I2C_ISR);
- /**
- * 非主机模式:说明当前在从机事务或待从机处理;
- * 单 IT 线模式:错误与事件共线,遇到错误标志应交由线程化路径统一处理。
- */
- if (!i2c_dev->master_mode ||
- (i2c_dev->setup.single_it_line && (status & STM32F7_ERR_EVENTS)))
- return IRQ_WAKE_THREAD;
- if (status & STM32F7_I2C_ISR_TXIS)
- stm32f7_i2c_write_tx_data(i2c_dev); /**< TXIS 代表 TXDR 可写,顶半部直接写 1 字节以减少线程切换开销。 */
- if (status & STM32F7_I2C_ISR_RXNE)
- stm32f7_i2c_read_rx_data(i2c_dev); /**< RXNE 代表 RXDR 有数据,顶半部直接读 1 字节。 */
- /**
- * 其余需要“状态机推进”的标志由线程化 handler 处理:
- * NACKF/STOPF/TC/TCR 涉及收尾、分段重装、发 STOP 或启动下一消息。
- */
- if (status &
- (STM32F7_I2C_ISR_NACKF | STM32F7_I2C_ISR_STOPF |
- STM32F7_I2C_ISR_TC | STM32F7_I2C_ISR_TCR))
- return IRQ_WAKE_THREAD;
- return IRQ_HANDLED;
- }
复制代码
stm32f7_i2c_isr_event_thread:线程化事件处理,推进 RELOAD/消息链/STOP/NACK - /**
- * @brief I2C 事件线程化 handler:处理需要完整状态机推进的事件与收尾。
- *
- * @param irq 中断号。
- * @param data 控制器私有数据。
- *
- * @return IRQ_HANDLED
- */
- static irqreturn_t stm32f7_i2c_isr_event_thread(int irq, void *data)
- {
- struct stm32f7_i2c_dev *i2c_dev = data;
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- void __iomem *base = i2c_dev->base;
- u32 status, mask;
- int ret;
- status = readl_relaxed(i2c_dev->base + STM32F7_I2C_ISR);
- if (!i2c_dev->master_mode)
- return stm32f7_i2c_slave_isr_event(i2c_dev, status); /**< 从机模式完整处理在专用路径完成。 */
- if (i2c_dev->setup.single_it_line && (status & STM32F7_ERR_EVENTS))
- return stm32f7_i2c_handle_isr_errs(i2c_dev, status); /**< 单 IT 线:错误交由统一错误处理。 */
- if (status & STM32F7_I2C_ISR_NACKF) {
- dev_dbg(i2c_dev->dev, "<%s>: Receive NACK (addr %x)\n",
- __func__, f7_msg->addr);
- writel_relaxed(STM32F7_I2C_ICR_NACKCF, base + STM32F7_I2C_ICR);
- if (i2c_dev->use_dma)
- stm32f7_i2c_dma_callback(i2c_dev); /**< NACK 发生时停止 DMA,避免继续搬运无意义数据。 */
- f7_msg->result = -ENXIO; /**< NACK 通常代表目标地址/阶段无响应。 */
- }
- if (status & STM32F7_I2C_ISR_TCR) {
- if (f7_msg->smbus)
- stm32f7_i2c_smbus_reload(i2c_dev); /**< SMBus 块读:按首字节长度更新 NBYTES。 */
- else
- stm32f7_i2c_reload(i2c_dev); /**< I2C 普通分段:继续装载下一段 NBYTES。 */
- }
- if (status & STM32F7_I2C_ISR_TC) {
- if (i2c_dev->use_dma && !f7_msg->result) {
- ret = wait_for_completion_timeout(&i2c_dev->dma->dma_complete, HZ);
- if (!ret) {
- dev_dbg(i2c_dev->dev, "<%s>: Timed out\n", __func__);
- stm32f7_i2c_dma_callback(i2c_dev); /**< DMA 未在合理时间内完成,强制终止并标记超时。 */
- f7_msg->result = -ETIMEDOUT;
- }
- }
- if (f7_msg->stop) {
- mask = STM32F7_I2C_CR2_STOP;
- stm32f7_i2c_set_bits(base + STM32F7_I2C_CR2, mask); /**< 最后一条消息:置 STOP 收尾。 */
- } else if (f7_msg->smbus) {
- stm32f7_i2c_smbus_rep_start(i2c_dev); /**< SMBus 需要重复起始的协议:进入 rep-start 专用配置。 */
- } else {
- i2c_dev->msg_id++;
- i2c_dev->msg++;
- stm32f7_i2c_xfer_msg(i2c_dev, i2c_dev->msg); /**< 多消息链:在 TC 点启动下一条消息(重复起始语义由硬件 START 位实现)。 */
- }
- }
- if (status & STM32F7_I2C_ISR_STOPF) {
- if (stm32f7_i2c_is_slave_registered(i2c_dev))
- mask = STM32F7_I2C_XFER_IRQ_MASK;
- else
- mask = STM32F7_I2C_ALL_IRQ_MASK;
- stm32f7_i2c_disable_irq(i2c_dev, mask); /**< STOP 收到后关闭中断源,防止重复触发。 */
- writel_relaxed(STM32F7_I2C_ICR_STOPCF, base + STM32F7_I2C_ICR); /**< 清 STOPF。 */
- i2c_dev->master_mode = false;
- complete(&i2c_dev->complete); /**< 事务结束:唤醒等待方。 */
- }
- return IRQ_HANDLED;
- }
复制代码
stm32f7_i2c_isr_error_thread:独立 error IRQ 线程化处理入口 - /**
- * @brief 独立 error IRQ 的线程化 handler:读取 ISR 后交由统一错误处理。
- */
- static irqreturn_t stm32f7_i2c_isr_error_thread(int irq, void *data)
- {
- struct stm32f7_i2c_dev *i2c_dev = data;
- u32 status;
- status = readl_relaxed(i2c_dev->base + STM32F7_I2C_ISR);
- return stm32f7_i2c_handle_isr_errs(i2c_dev, status);
- }
复制代码
stm32f7_i2c_wait_polling / stm32f7_i2c_dma_callback / stm32f7_i2c_wait_free_bus:atomic 轮询推进与 DMA 完成收尾、总线恢复
stm32f7_i2c_disable_dma_req:关闭外设侧 DMA 请求位 - /**
- * @brief 关闭 I2C 外设的 RX/TX DMA 请求。
- *
- * @param i2c_dev 控制器私有数据。
- */
- static void stm32f7_i2c_disable_dma_req(struct stm32f7_i2c_dev *i2c_dev)
- {
- void __iomem *base = i2c_dev->base;
- u32 mask = STM32F7_I2C_CR1_RXDMAEN | STM32F7_I2C_CR1_TXDMAEN; /**< DMA 请求使能位,需同时清除以避免残留请求。 */
- stm32f7_i2c_clr_bits(base + STM32F7_I2C_CR1, mask);
- }
复制代码
stm32f7_i2c_dma_callback:DMA 结束/中止时的统一收尾(成功与错误路径复用) - /**
- * @brief DMA 传输结束或被迫终止时的收尾回调。
- *
- * @param arg 回调参数,实际为 struct stm32f7_i2c_dev*。
- *
- * 该回调负责:
- * 1) 关闭外设侧 DMA 请求,防止继续触发 DMA;
- * 2) 终止 DMA 通道(异步),避免后台继续搬运;
- * 3) 解除 dma_map_single 的映射;
- * 4) 若使用了 DMA-safe 缓冲区,将其内容回收/拷回;
- * 5) 通过 dma_complete 通知等待方(线程化 ISR 的 TC 分支会等待它)。
- */
- static void stm32f7_i2c_dma_callback(void *arg)
- {
- struct stm32f7_i2c_dev *i2c_dev = arg;
- struct stm32_i2c_dma *dma = i2c_dev->dma;
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- stm32f7_i2c_disable_dma_req(i2c_dev); /**< 必须先停外设请求,否则即便 DMA 通道停了仍可能反复产生请求/标志。 */
- dmaengine_terminate_async(dma->chan_using); /**< 终止 DMA 通道:用于错误/超时场景快速止血。 */
- dma_unmap_single(i2c_dev->dev, dma->dma_buf, dma->dma_len,
- dma->dma_data_dir); /**< 解除映射,保证缓存一致性语义闭合。 */
- if (!f7_msg->smbus)
- i2c_put_dma_safe_msg_buf(f7_msg->buf, i2c_dev->msg, true); /**< 归还 DMA-safe 缓冲区;必要时把数据同步回原 msg->buf。 */
- complete(&dma->dma_complete); /**< 通知“DMA 已结束”,线程化 ISR 在 TC 分支会等待该完成量。 */
- }
复制代码
stm32f7_i2c_wait_polling:atomic 模式下用轮询驱动 ISR 顶半部推进状态机 - /**
- * @brief atomic 模式轮询等待:周期性调用事件处理逻辑推进传输。
- *
- * @param i2c_dev 控制器私有数据。
- *
- * @return 若在超时前完成,返回 1;否则返回 0。
- */
- static int stm32f7_i2c_wait_polling(struct stm32f7_i2c_dev *i2c_dev)
- {
- ktime_t timeout = ktime_add_ms(ktime_get(), i2c_dev->adap.timeout); /**< 使用适配器 timeout 构造绝对超时时刻。 */
- while (ktime_compare(ktime_get(), timeout) < 0) {
- udelay(5); /**< 轮询间隔:避免纯忙等占满总线/CPU,但仍保持足够响应性。 */
- stm32f7_i2c_isr_event(0, i2c_dev); /**< 复用 ISR 顶半部逻辑:读取 ISR 并处理 TXIS/RXNE 等事件。 */
- if (completion_done(&i2c_dev->complete)) /**< 线程化 handler 不参与时,由轮询过程中触发的逻辑最终 complete。 */
- return 1;
- }
- return 0;
- }
复制代码
stm32f7_i2c_release_bus:通过禁用再重新配置外设以恢复可能卡死的总线状态 - /**
- * @brief 释放/恢复 I2C 总线:关闭外设后重新写入配置并重新使能。
- *
- * @param i2c_adap I2C 适配器。
- */
- static void stm32f7_i2c_release_bus(struct i2c_adapter *i2c_adap)
- {
- struct stm32f7_i2c_dev *i2c_dev = i2c_get_adapdata(i2c_adap);
- stm32f7_i2c_clr_bits(i2c_dev->base + STM32F7_I2C_CR1,
- STM32F7_I2C_CR1_PE); /**< 关闭外设,等价于把内部状态机复位到禁用态。 */
- stm32f7_i2c_hw_config(i2c_dev); /**< 重新写 TIMINGR/滤波/DNF 并置 PE,尽量把控制器恢复到已知工作态。 */
- }
复制代码
stm32f7_i2c_wait_free_bus:等待 BUSY 清零,超时则尝试 release_bus 后返回忙 - /**
- * @brief 等待 I2C 总线空闲(BUSY=0)。
- *
- * @param i2c_dev 控制器私有数据。
- *
- * @return BUSY 在超时前清零返回 0;否则尝试恢复控制器并返回 -EBUSY。
- */
- static int stm32f7_i2c_wait_free_bus(struct stm32f7_i2c_dev *i2c_dev)
- {
- u32 status;
- int ret;
- ret = readl_relaxed_poll_timeout(i2c_dev->base + STM32F7_I2C_ISR,
- status,
- !(status & STM32F7_I2C_ISR_BUSY),
- 10, 1000); /**< 轮询 ISR.BUSY,10us 间隔,最大 1000us。 */
- if (!ret)
- return 0;
- stm32f7_i2c_release_bus(&i2c_dev->adap); /**< BUSY 卡住:尝试通过禁用/重配恢复控制器。 */
- return -EBUSY;
- }
复制代码
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 - /**
- * @brief 配置并启动 SMBus 事务的首阶段(通常是写 command,必要时附带写数据)。
- *
- * @param i2c_dev 控制器私有数据。
- * @param flags 客户端标志(例如 I2C_CLIENT_PEC)。
- * @param command SMBus command 字节。
- * @param data SMBus 数据联合体(按 size/read_write 决定是否使用)。
- *
- * @return 0 成功;负 errno 表示不支持或参数非法。
- */
- static int stm32f7_i2c_smbus_xfer_msg(struct stm32f7_i2c_dev *i2c_dev,
- unsigned short flags, u8 command,
- union i2c_smbus_data *data)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- struct device *dev = i2c_dev->dev;
- void __iomem *base = i2c_dev->base;
- u32 cr1, cr2;
- int i, ret;
- f7_msg->result = 0;
- reinit_completion(&i2c_dev->complete);
- cr2 = readl_relaxed(base + STM32F7_I2C_CR2);
- cr1 = readl_relaxed(base + STM32F7_I2C_CR1);
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN;
- if (f7_msg->read_write)
- cr2 |= STM32F7_I2C_CR2_RD_WRN; /* 难点:有些“读协议”的首阶段仍需要先写 command,本函数会在 switch 中改回写方向 */
- cr2 &= ~(STM32F7_I2C_CR2_ADD10 | STM32F7_I2C_CR2_SADD7_MASK);
- cr2 |= STM32F7_I2C_CR2_SADD7(f7_msg->addr);
- f7_msg->smbus_buf[0] = command; /* 难点:SMBus 将 command 作为第一个发送字节,后续阶段可能 repeated-start 切读 */
- switch (f7_msg->size) {
- case I2C_SMBUS_QUICK:
- f7_msg->stop = true;
- f7_msg->count = 0;
- break;
- case I2C_SMBUS_BYTE:
- f7_msg->stop = true;
- f7_msg->count = 1;
- break;
- case I2C_SMBUS_BYTE_DATA:
- if (f7_msg->read_write) {
- f7_msg->stop = false; /* 难点:读 BYTE_DATA 需分两段:先写 command,不发 STOP,后续 repeated-start 再读数据 */
- f7_msg->count = 1;
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN; /* 首阶段强制为写方向(发送 command) */
- } else {
- f7_msg->stop = true;
- f7_msg->count = 2;
- f7_msg->smbus_buf[1] = data->byte;
- }
- break;
- case I2C_SMBUS_WORD_DATA:
- if (f7_msg->read_write) {
- f7_msg->stop = false;
- f7_msg->count = 1;
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN;
- } else {
- f7_msg->stop = true;
- f7_msg->count = 3;
- f7_msg->smbus_buf[1] = data->word & 0xff;
- f7_msg->smbus_buf[2] = data->word >> 8;
- }
- break;
- case I2C_SMBUS_BLOCK_DATA:
- if (f7_msg->read_write) {
- f7_msg->stop = false;
- f7_msg->count = 1;
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN; /* 首阶段仍是写 command,长度字节将在后续读阶段由从机给出 */
- } else {
- f7_msg->stop = true;
- if (data->block[0] > I2C_SMBUS_BLOCK_MAX || !data->block[0]) {
- dev_err(dev, "Invalid block write size %d\n", data->block[0]);
- return -EINVAL;
- }
- f7_msg->count = data->block[0] + 2; /* 难点:block write = command + count + payload */
- for (i = 1; i < f7_msg->count; i++)
- f7_msg->smbus_buf[i] = data->block[i - 1];
- }
- break;
- case I2C_SMBUS_PROC_CALL:
- f7_msg->stop = false; /* 难点:过程调用必须 repeated-start 读回结果 */
- f7_msg->count = 3; /* command + 2 字节数据 */
- f7_msg->smbus_buf[1] = data->word & 0xff;
- f7_msg->smbus_buf[2] = data->word >> 8;
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN; /* 首阶段写 */
- f7_msg->read_write = I2C_SMBUS_READ; /* 为后续阶段标记读方向 */
- break;
- case I2C_SMBUS_BLOCK_PROC_CALL:
- f7_msg->stop = false;
- if (data->block[0] > I2C_SMBUS_BLOCK_MAX - 1) {
- dev_err(dev, "Invalid block write size %d\n", data->block[0]);
- return -EINVAL;
- }
- f7_msg->count = data->block[0] + 2;
- for (i = 1; i < f7_msg->count; i++)
- f7_msg->smbus_buf[i] = data->block[i - 1];
- cr2 &= ~STM32F7_I2C_CR2_RD_WRN;
- f7_msg->read_write = I2C_SMBUS_READ;
- break;
- case I2C_SMBUS_I2C_BLOCK_DATA:
- return -EOPNOTSUPP;
- default:
- dev_err(dev, "Unsupported smbus protocol %d\n", f7_msg->size);
- return -EOPNOTSUPP;
- }
- f7_msg->buf = f7_msg->smbus_buf;
- if ((flags & I2C_CLIENT_PEC) && f7_msg->size != I2C_SMBUS_QUICK) {
- cr1 |= STM32F7_I2C_CR1_PECEN; /* 难点:PEC 由硬件计算,最终值在 PECR 提供 */
- if (!f7_msg->read_write) {
- cr2 |= STM32F7_I2C_CR2_PECBYTE; /* 难点:写方向时可要求硬件自动追加 PEC 字节 */
- f7_msg->count++; /* NBYTES 必须包含 PEC 字节,否则硬件阶段不匹配 */
- }
- } else {
- cr1 &= ~STM32F7_I2C_CR1_PECEN;
- cr2 &= ~STM32F7_I2C_CR2_PECBYTE;
- }
- cr2 &= ~(STM32F7_I2C_CR2_NBYTES_MASK | STM32F7_I2C_CR2_RELOAD);
- cr2 |= STM32F7_I2C_CR2_NBYTES(f7_msg->count);
- cr1 |= STM32F7_I2C_CR1_ERRIE | STM32F7_I2C_CR1_TCIE |
- STM32F7_I2C_CR1_STOPIE | STM32F7_I2C_CR1_NACKIE;
- cr1 &= ~(STM32F7_I2C_CR1_RXIE | STM32F7_I2C_CR1_TXIE |
- STM32F7_I2C_CR1_RXDMAEN | STM32F7_I2C_CR1_TXDMAEN);
- i2c_dev->use_dma = false;
- if (i2c_dev->dma && f7_msg->count >= STM32F7_I2C_DMA_LEN_MIN) {
- ret = stm32_i2c_prep_dma_xfer(i2c_dev->dev, i2c_dev->dma,
- cr2 & STM32F7_I2C_CR2_RD_WRN,
- f7_msg->count, f7_msg->buf,
- stm32f7_i2c_dma_callback,
- i2c_dev);
- if (!ret)
- i2c_dev->use_dma = true;
- else
- dev_warn(i2c_dev->dev, "can't use DMA\n");
- }
- if (!i2c_dev->use_dma) {
- if (cr2 & STM32F7_I2C_CR2_RD_WRN)
- cr1 |= STM32F7_I2C_CR1_RXIE;
- else
- cr1 |= STM32F7_I2C_CR1_TXIE;
- } else {
- if (cr2 & STM32F7_I2C_CR2_RD_WRN)
- cr1 |= STM32F7_I2C_CR1_RXDMAEN;
- else
- cr1 |= STM32F7_I2C_CR1_TXDMAEN;
- }
- cr2 |= STM32F7_I2C_CR2_START;
- i2c_dev->master_mode = true;
- writel_relaxed(cr1, base + STM32F7_I2C_CR1);
- writel_relaxed(cr2, base + STM32F7_I2C_CR2);
- return 0;
- }
复制代码
stm32f7_i2c_smbus_rep_start:SMBus 第二阶段(Repeated-START 切换到读)重新配置 - /**
- * @brief SMBus 需要重复起始的协议:配置第二阶段为读方向并发起 Repeated-START。
- *
- * @param i2c_dev 控制器私有数据。
- */
- static void stm32f7_i2c_smbus_rep_start(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- void __iomem *base = i2c_dev->base;
- u32 cr1, cr2;
- int ret;
- cr2 = readl_relaxed(base + STM32F7_I2C_CR2);
- cr1 = readl_relaxed(base + STM32F7_I2C_CR1);
- cr2 |= STM32F7_I2C_CR2_RD_WRN; /* 难点:第二阶段强制切读,形成 repeated-start 后的读事务 */
- switch (f7_msg->size) {
- case I2C_SMBUS_BYTE_DATA:
- f7_msg->count = 1;
- break;
- case I2C_SMBUS_WORD_DATA:
- case I2C_SMBUS_PROC_CALL:
- f7_msg->count = 2;
- break;
- case I2C_SMBUS_BLOCK_DATA:
- case I2C_SMBUS_BLOCK_PROC_CALL:
- f7_msg->count = 1; /* 难点:块读先读长度字节,随后在 TCR 时由 smbus_reload 更新 NBYTES */
- cr2 |= STM32F7_I2C_CR2_RELOAD; /* 让硬件在读完 1 字节后进入 TCR,驱动再装载后续长度 */
- break;
- }
- f7_msg->buf = f7_msg->smbus_buf;
- f7_msg->stop = true;
- if (cr1 & STM32F7_I2C_CR1_PECEN) {
- cr2 |= STM32F7_I2C_CR2_PECBYTE; /* 难点:读阶段 PECBYTE 仍可能影响 NBYTES 计数与最后一个字节的位置 */
- f7_msg->count++;
- }
- cr2 &= ~(STM32F7_I2C_CR2_NBYTES_MASK);
- cr2 |= STM32F7_I2C_CR2_NBYTES(f7_msg->count);
- cr1 &= ~(STM32F7_I2C_CR1_RXIE | STM32F7_I2C_CR1_TXIE);
- cr1 |= STM32F7_I2C_CR1_RXIE;
- cr1 &= ~(STM32F7_I2C_CR1_RXIE | STM32F7_I2C_CR1_TXIE |
- STM32F7_I2C_CR1_RXDMAEN | STM32F7_I2C_CR1_TXDMAEN);
- i2c_dev->use_dma = false;
- if (i2c_dev->dma && f7_msg->count >= STM32F7_I2C_DMA_LEN_MIN &&
- f7_msg->size != I2C_SMBUS_BLOCK_DATA &&
- f7_msg->size != I2C_SMBUS_BLOCK_PROC_CALL) {
- ret = stm32_i2c_prep_dma_xfer(i2c_dev->dev, i2c_dev->dma,
- cr2 & STM32F7_I2C_CR2_RD_WRN,
- f7_msg->count, f7_msg->buf,
- stm32f7_i2c_dma_callback,
- i2c_dev);
- if (!ret)
- i2c_dev->use_dma = true;
- else
- dev_warn(i2c_dev->dev, "can't use DMA\n");
- }
- if (!i2c_dev->use_dma)
- cr1 |= STM32F7_I2C_CR1_RXIE;
- else
- cr1 |= STM32F7_I2C_CR1_RXDMAEN;
- cr2 |= STM32F7_I2C_CR2_START; /* 难点:再次置 START 生成 repeated-start 序列 */
- writel_relaxed(cr1, base + STM32F7_I2C_CR1);
- writel_relaxed(cr2, base + STM32F7_I2C_CR2);
- }
复制代码
stm32f7_i2c_smbus_check_pec:读取硬件计算 PEC 与接收 PEC 字节比较 - /**
- * @brief SMBus PEC 校验:比较硬件 PECR 与接收缓冲区中的 PEC 字节。
- *
- * @param i2c_dev 控制器私有数据。
- *
- * @return 0 表示 PEC 正确;负 errno 表示协议不支持或 PEC 不一致。
- */
- static int stm32f7_i2c_smbus_check_pec(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- u8 count, internal_pec, received_pec;
- internal_pec = readl_relaxed(i2c_dev->base + STM32F7_I2C_PECR); /* 难点:PECR 是硬件按 SMBus PEC 规则计算的结果 */
- switch (f7_msg->size) {
- case I2C_SMBUS_BYTE:
- case I2C_SMBUS_BYTE_DATA:
- received_pec = f7_msg->smbus_buf[1]; /* 难点:索引取决于该协议的“数据长度与布局” */
- break;
- case I2C_SMBUS_WORD_DATA:
- case I2C_SMBUS_PROC_CALL:
- received_pec = f7_msg->smbus_buf[2];
- break;
- case I2C_SMBUS_BLOCK_DATA:
- case I2C_SMBUS_BLOCK_PROC_CALL:
- count = f7_msg->smbus_buf[0]; /* 块协议首字节为 count */
- received_pec = f7_msg->smbus_buf[count]; /* 难点:PEC 位于块数据末尾,位置由 count 决定 */
- break;
- default:
- dev_err(i2c_dev->dev, "Unsupported smbus protocol for PEC\n");
- return -EINVAL;
- }
- if (internal_pec != received_pec) {
- dev_err(i2c_dev->dev, "Bad PEC 0x%02x vs. 0x%02x\n",
- internal_pec, received_pec);
- return -EBADMSG;
- }
- return 0;
- }
复制代码
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(续) - if (!i2c_dev->use_dma) {
- if (cr2 & STM32F7_I2C_CR2_RD_WRN)
- cr1 |= STM32F7_I2C_CR1_RXIE;
- else
- cr1 |= STM32F7_I2C_CR1_TXIE;
- } else {
- if (cr2 & STM32F7_I2C_CR2_RD_WRN)
- cr1 |= STM32F7_I2C_CR1_RXDMAEN;
- else
- cr1 |= STM32F7_I2C_CR1_TXDMAEN;
- }
- cr2 |= STM32F7_I2C_CR2_START; /* 置 START 触发硬件产生起始条件并锁定 CR2 中的方向/地址/NBYTES 等配置 */
- i2c_dev->master_mode = true; /* 用于 ISR 路径区分主/从状态机 */
- writel_relaxed(cr1, base + STM32F7_I2C_CR1);
- writel_relaxed(cr2, base + STM32F7_I2C_CR2);
- return 0;
- }
复制代码
stm32f7_i2c_smbus_rep_start:SMBus 第二阶段(Repeated-START 切换到读)重新配置并触发 - /**
- * @brief 对需要“先写 command 再读数据”的 SMBus 协议,配置第二阶段为读并触发 Repeated-START。
- *
- * @param i2c_dev 控制器私有数据。
- *
- * 关键点:
- * - 对块读类协议,不知道后续长度,先读 1 字节(长度字节),并启用 RELOAD 让硬件产生 TCR 以便软件更新 NBYTES。
- * - PEC 使能时,读阶段也需要把 PEC 字节计入 NBYTES,使硬件状态机与软件缓冲区布局一致。
- */
- static void stm32f7_i2c_smbus_rep_start(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- void __iomem *base = i2c_dev->base;
- u32 cr1, cr2;
- int ret;
- cr2 = readl_relaxed(base + STM32F7_I2C_CR2);
- cr1 = readl_relaxed(base + STM32F7_I2C_CR1);
- cr2 |= STM32F7_I2C_CR2_RD_WRN; /* 第二阶段固定为读方向 */
- switch (f7_msg->size) {
- case I2C_SMBUS_BYTE_DATA:
- f7_msg->count = 1;
- break;
- case I2C_SMBUS_WORD_DATA:
- case I2C_SMBUS_PROC_CALL:
- f7_msg->count = 2;
- break;
- case I2C_SMBUS_BLOCK_DATA:
- case I2C_SMBUS_BLOCK_PROC_CALL:
- f7_msg->count = 1; /* 先读长度字节 */
- cr2 |= STM32F7_I2C_CR2_RELOAD; /* 读完 1 字节后触发 TCR,驱动再装载真实长度 */
- break;
- }
- f7_msg->buf = f7_msg->smbus_buf;
- f7_msg->stop = true;
- if (cr1 & STM32F7_I2C_CR1_PECEN) {
- cr2 |= STM32F7_I2C_CR2_PECBYTE; /* 使硬件在本阶段按 PEC 规则处理最后一个字节位置 */
- f7_msg->count++;
- }
- cr2 &= ~(STM32F7_I2C_CR2_NBYTES_MASK);
- cr2 |= STM32F7_I2C_CR2_NBYTES(f7_msg->count);
- cr1 &= ~(STM32F7_I2C_CR1_RXIE | STM32F7_I2C_CR1_TXIE);
- cr1 |= STM32F7_I2C_CR1_RXIE;
- cr1 &= ~(STM32F7_I2C_CR1_RXIE | STM32F7_I2C_CR1_TXIE |
- STM32F7_I2C_CR1_RXDMAEN | STM32F7_I2C_CR1_TXDMAEN);
- i2c_dev->use_dma = false;
- if (i2c_dev->dma && f7_msg->count >= STM32F7_I2C_DMA_LEN_MIN &&
- f7_msg->size != I2C_SMBUS_BLOCK_DATA &&
- f7_msg->size != I2C_SMBUS_BLOCK_PROC_CALL) {
- ret = stm32_i2c_prep_dma_xfer(i2c_dev->dev, i2c_dev->dma,
- cr2 & STM32F7_I2C_CR2_RD_WRN,
- f7_msg->count, f7_msg->buf,
- stm32f7_i2c_dma_callback,
- i2c_dev);
- if (!ret)
- i2c_dev->use_dma = true;
- else
- dev_warn(i2c_dev->dev, "can't use DMA\n");
- }
- if (!i2c_dev->use_dma)
- cr1 |= STM32F7_I2C_CR1_RXIE;
- else
- cr1 |= STM32F7_I2C_CR1_RXDMAEN;
- cr2 |= STM32F7_I2C_CR2_START; /* 再次置 START,硬件产生 Repeated-START 并以读方向继续 */
- writel_relaxed(cr1, base + STM32F7_I2C_CR1);
- writel_relaxed(cr2, base + STM32F7_I2C_CR2);
- }
复制代码
stm32f7_i2c_smbus_check_pec:PEC 校验(硬件 PECR vs 接收缓冲区 PEC 字节) - /**
- * @brief SMBus PEC 校验:比较硬件计算值与接收数据末尾的 PEC 字节。
- *
- * @param i2c_dev 控制器私有数据。
- *
- * @return 0 校验通过;-EBADMSG 表示 PEC 不一致;-EINVAL 表示当前协议不支持 PEC 校验布局。
- */
- static int stm32f7_i2c_smbus_check_pec(struct stm32f7_i2c_dev *i2c_dev)
- {
- struct stm32f7_i2c_msg *f7_msg = &i2c_dev->f7_msg;
- u8 count, internal_pec, received_pec;
- internal_pec = readl_relaxed(i2c_dev->base + STM32F7_I2C_PECR); /* 硬件按 SMBus PEC 规则累计得到的校验结果 */
- switch (f7_msg->size) {
- case I2C_SMBUS_BYTE:
- case I2C_SMBUS_BYTE_DATA:
- received_pec = f7_msg->smbus_buf[1]; /* BYTE/ BYTE_DATA:数据区后 1 字节为 PEC */
- break;
- case I2C_SMBUS_WORD_DATA:
- case I2C_SMBUS_PROC_CALL:
- received_pec = f7_msg->smbus_buf[2]; /* WORD/PROC_CALL:2 字节数据后为 PEC */
- break;
- case I2C_SMBUS_BLOCK_DATA:
- case I2C_SMBUS_BLOCK_PROC_CALL:
- count = f7_msg->smbus_buf[0]; /* 块读:第 0 字节为长度 */
- received_pec = f7_msg->smbus_buf[count]; /* PEC 在块尾部:索引由长度决定(依赖上层对缓冲区布局的一致性约束) */
- break;
- default:
- dev_err(i2c_dev->dev, "Unsupported smbus protocol for PEC\n");
- return -EINVAL;
- }
- if (internal_pec != received_pec) {
- dev_err(i2c_dev->dev, "Bad PEC 0x%02x vs. 0x%02x\n",
- internal_pec, received_pec);
- return -EBADMSG;
- }
- return 0;
- }
复制代码
|