[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 控制器驱动,结构大体一致:

  1. probe(初始化)
  • 解析平台资源:MMIO、IRQ、时钟/复位、DMA(可选)

  • 初始化控制器寄存器:时钟分频、FIFO/中断、超时、过滤等

  • 组装 struct i2c_adapter

    • algomaster_xfer/master_xfer_atomic
    • functionality
    • dev.parentnrname
  • i2c_add_adapter()i2c_add_numbered_adapter() 注册到 i2c-core

  1. 传输路径(最关键)
    上层调用最终会落到控制器驱动的传输实现,常见两种风格:
  • 基于 master_xfer() 的自实现:驱动直接实现 i2c_adapter 的传输回调,支持 repeated-start、多 msg;
  • 基于通用算法层 i2c_algorithm:驱动提供底层 bit 操作或简单发送接口,交由算法层组织。

典型需要处理:

  • START/STOP、地址相位、ACK/NACK
  • repeated-start(组合事务:写寄存器地址后紧接读)
  • FIFO 装填/取出,中断触发点
  • 超时、仲裁丢失、总线错误(SCL/SDA 异常)
  1. 并发与上下文
  • I2C 传输通常会睡眠(等待中断/完成),所以多数控制器驱动在进程上下文工作;
  • 有的控制器支持 atomic 传输或用于紧急日志路径,会实现 *_atomic 变体(但要求硬件/实现可在原子上下文完成)。
  1. 错误恢复与总线“卡死”处理
    常见机制:
  • 控制器软复位/清 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#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)并校验硬件允许区间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @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 规范最大上升沿时间离散化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @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 选择)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @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 使能的顺序完成硬件初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @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 前的门禁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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 的单字节收发封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @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)并唤醒等待者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @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 结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @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 的边界区。 */
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* @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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* @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 分发)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* @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 终止传输

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @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 完成(或超时)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* @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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @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 宣告能力并提供算法入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* @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 注销适配器

1
2
3
4
5
6
7
8
9
10
11
/**
* @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:设备树匹配与模块注册(结构性说明)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 版本最大的差异:

  1. 寄存器模型变了(CR2/NBYTES/RELOAD/START/STOP 等)。
  2. 支持更多能力:DMA、SMBus(含 PEC/Alert/Host Notify)、Slave 模式、原子传输(polling)、Runtime PM/系统休眠。
  3. 时序配置从 “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
2
3
4
5
6
7
8
9
10
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_mintscl_h >= h_min 等约束;最终以 |tscl - i2cbus| 最小作为“最优解”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#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:计算“降档”的下一个更低速率档位

1
2
3
4
5
6
7
8
9
10
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 生成最终寄存器字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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 使能外设;这一步对寄存器写入的生效顺序具有控制意义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* @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,而是通过轮询函数驱动状态机(避免不可睡眠上下文等待/调度)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* @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 驱动数据通道

作用与原理

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* @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 字节并递减剩余计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @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 刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @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:错误/异常标志处理与收尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* @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:顶半部快速处理与线程唤醒判定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* @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 线程化处理入口

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @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 请求位

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @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 结束/中止时的统一收尾(成功与错误路径复用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @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 顶半部推进状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @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:通过禁用再重新配置外设以恢复可能卡死的总线状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @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 后返回忙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/**
* @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 切换到读)重新配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* @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 字节比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* @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(续)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	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 切换到读)重新配置并触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* @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 字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* @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;
}