首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

RK3588 kernel-6.1/drivers/i2c/muxes 目录功能详细分析

RK3588 kernel-6.1/drivers/i2c/muxes 目录功能详细分析

1. 文档范围

本文分析目录:

  • 源码:rk3588/kernel-6.1/drivers/i2c/muxes
  • 文档:linuxDoc/drivers/i2c/

前提: muxes/ 中的驱动都依赖上层 i2c-mux.c(位于 drivers/i2c/ 提供的多路复用框架。本文先说明框架机制,再逐项分析 muxes/ 下 9 个具体驱动/模块的功能、实现差异与 RK3588 使用场景。


2. 为什么需要 I2C Mux

2.1 问题场景

场景 说明
地址冲突 多条支路挂相同 7 位地址设备(如多个 EEPROM 0x50)
拓扑扩展 一条物理 I2C 需服务多组独立外设(摄像头 SerDes、传感器等)
引脚复用 SoC 同一 I2C 控制器 SDA/SCL 可路由到不同引脚组
多主仲裁 两个主机共享一条从总线,需互斥获取总线
多主机切换 运行时在不同物理 I2C 主控之间切换(demux)

2.2 内核抽象

Linux 不把 mux 做成“透明转发”,而是为每个通道(或逻辑段)注册独立的 i2c_adapter

1
2
3
4
5
6
7
8
物理 I2C 主控 (i2c-2, rk3x)


MUX 芯片 / GPIO / pinctrl / 寄存器

├── 子 adapter: i2c-2-mux (chan 0) → 设备 A、B
├── 子 adapter: i2c-3-mux (chan 1) → 设备 C
└── 子 adapter: i2c-4-mux (chan 2) → 设备 D

用户态可见多个 /dev/i2c-*;client 驱动挂在子 adapter 对应的 DT 子节点下。


3. 与 i2c-mux.c 核心的关系

muxes/ 不包含通用 mux 框架,只实现各硬件/机制的 select / deselect 回调

3.1 核心数据结构(include/linux/i2c-mux.h

1
2
3
4
5
6
7
8
9
struct i2c_mux_core {
struct i2c_adapter *parent; /* 上游物理/父 adapter */
struct device *dev;
int (*select)(struct i2c_mux_core *muxc, u32 chan);
int (*deselect)(struct i2c_mux_core *muxc, u32 chan);
int num_adapters;
int max_adapters;
/* ... flags: LOCKED, ARBITRATOR, GATE ... */
};

3.2 传输路径(i2c-mux.c

每次在子 adapter 上 i2c_transfer() 时:

1
2
3
4
i2c_mux_master_xfer()
→ muxc->select(muxc, chan_id) // muxes/*.c 实现
→ i2c_transfer(parent, msgs) // 在父总线上真正收发
→ muxc->deselect(muxc, chan_id) // 可选,恢复 idle
  • 支持 SMBus 透传(i2c_mux_smbus_xfer);
  • 支持 多级 mux(子 adapter 下再挂 mux);
  • 通过 parent->mux_locklock_ops 避免父子 adapter 重复加锁死锁。

3.3 驱动开发模板

典型 mux 芯片驱动流程:

1
2
3
4
probe
→ i2c_mux_alloc(parent, dev, nchans, priv_size, flags, select, deselect)
→ 对每个 channel: i2c_mux_add_adapter(muxc, force_nr, chan_id, class)
→ DT 子节点 reg = channel 号,core 在子 adapter 上注册 client

4. 目录与构建配置

4.1 文件列表

源文件 Kconfig 模块名
i2c-mux-pca954x.c I2C_MUX_PCA954x i2c-mux-pca954x
i2c-mux-pca9541.c I2C_MUX_PCA9541 i2c-mux-pca9541
i2c-mux-gpio.c I2C_MUX_GPIO i2c-mux-gpio
i2c-mux-pinctrl.c I2C_MUX_PINCTRL i2c-mux-pinctrl
i2c-mux-reg.c I2C_MUX_REG i2c-mux-reg
i2c-mux-gpmux.c I2C_MUX_GPMUX i2c-mux-gpmux
i2c-mux-ltc4306.c I2C_MUX_LTC4306 i2c-mux-ltc4306
i2c-mux-mlxcpld.c I2C_MUX_MLXCPLD i2c-mux-mlxcpld
i2c-demux-pinctrl.c I2C_DEMUX_PINCTRL i2c-demux-pinctrl
i2c-arb-gpio-challenge.c I2C_ARB_GPIO_CHALLENGE i2c-arb-gpio-challenge

依赖:全部需要 CONFIG_I2C_MUXi2c-mux.c 核心)。

4.2 分类概览

1
2
3
4
5
6
7
8
                  i2c-mux.c (核心框架)

┌──────────────────┼──────────────────┐
│ │ │
芯片型 MUX 控制型 MUX 特殊机制
│ │ │
pca954x, pca9541 gpio, reg, arb-gpio
ltc4306, mlxcpld pinctrl, gpmux demux-pinctrl

5. 各驱动功能详细分析

5.1 i2c-mux-pca954x.c — NXP PCA954x/PCA984x(最常用)

硬件: NXP I2C 多路复用器/开关,自身也是 I2C 从设备,通过写 8 位控制寄存器 选通通道。

支持型号:

类型 型号 通道数 行为
Mux PCA9540/42/44/45/47/9548 2~8 互斥,同一时刻仅一路
Switch PCA9543/46/9846~9849 2~8 多路同时 接通(位掩码)

核心实现:

函数 功能
pca954x_reg_write() __i2c_smbus_xfer() 写寄存器,避免 i2c_transfer 二次锁总线
pca954x_select_chan() 计算 regval,若与 last_chan 不同则写入
pca954x_deselect_mux() idle-state:保持 / 断开 / 切到指定通道
pca954x_irq_setup() 部分型号支持 INT,为每通道建 nested irq_domain
idle_state sysfs 运行时配置空闲时通道策略

设备树示例:

1
2
3
4
5
6
7
8
9
pca9548: mux@70 {
compatible = "nxp,pca9548";
reg = <0x70>;
#address-cells = <1>;
#size-cells = <0>;

i2c@0 { reg = <0>; /* 子设备 */ };
i2c@1 { reg = <1>; };
};

RK3588 关联: 车载/多摄板级 DTS 中常见 GPIO 位控制的 i2c-mux 节点(见 §8);若硬件使用 PCA9548,则绑定 nxp,pca9548 由本驱动管理。


5.2 i2c-mux-pca9541.c — NXP PCA9541 总线主选择器

硬件语义: 不是“多通道扩展”,而是 两个 I2C 主机争用一条从总线 的仲裁芯片。

功能要点:

  • 建模为 单通道 muxselect = 获取总线所有权,deselect = 释放;
  • 通过 PCA9541_CONTROL / PCA9541_ISTAT 寄存器协商;
  • 超时:ARB_TIMEOUT(125ms 强制)、ARB2_TIMEOUT(250ms 失败);
  • 读写寄存器使用 __i2c_smbus_xfer,同样避免重入锁。

适用: 双 CPU / 双 BMC 共享传感器总线;RK3588 普通嵌入式板较少使用。


5.3 i2c-mux-gpio.c — GPIO 控制的多路复用

硬件: 用 N 根 GPIO 编码选择 2^N 条 I2C 支路(板级 CPLD/逻辑电路)。

核心实现:

函数 功能
i2c_mux_gpio_set() gpiod_set_array_value_cansleep() 一次性设置所有 GPIO
i2c_mux_gpio_select() chan 作为 GPIO 位图写入
i2c_mux_gpio_deselect() 写入 idle-stateI2C_MUX_GPIO_NO_IDLE 表示不恢复)

设备树:

  • i2c-parent:phandle 指向父 adapter;
  • 子节点 reg = 该通道的 GPIO 编码值;
  • idle-state:空闲时 GPIO 值。

平台: platform_driver,支持 OF 与 ACPI。


5.4 i2c-mux-pinctrl.c — pinctrl 引脚复用型 Mux

场景: SoC 的 I2C 信号可通过 pinctrl 路由到不同引脚组,每组对应一条“逻辑总线”(如 I2C0 走 pinmux A 或 B)。

核心实现:

函数 功能
i2c_mux_pinctrl_select() pinctrl_select_state(pinctrl, states[chan])
i2c_mux_pinctrl_deselect() 切到 states[num_adapters](idle 状态)
i2c_mux_pinctrl_root_adapter() 校验各 state 是否指向同一 root adapter

设备树:

  • i2c-parent:父 adapter;
  • pinctrl-names / pinctrl-0…:每个 channel 对应一个 state;
  • 额外一个 idle state(pinctrl-names 最后一项)。

与 GPIO mux 区别: 切换的是 SoC 引脚复用,不是外部 mux 芯片。


5.5 i2c-mux-reg.c — MMIO 寄存器控制型 Mux

硬件: 板级用一个 内存映射寄存器 的若干位选择 I2C 路由(常见于 PowerPC/QorIQ 等)。

核心实现:

函数 功能
i2c_mux_reg_set() iowrite8/16/32 写通道号,posted bus 上 ioread 确保写完成
i2c_mux_reg_select/deselect() select 写 chan_id;deselect 可选写 idle

设备树: reg 指定寄存器物理地址;little-endian / big-endianidle-in-use + idle


5.6 i2c-mux-gpmux.c — 通用 MUX 子系统接口

设计: 不直接操作 GPIO/寄存器,而是调用 mux_control_select() / mux_control_deselect()(Linux multiplexer 子系统)。

核心实现:

1
2
mux_control_select(mux->control, chan);   // select
mux_control_deselect(mux->control); // deselect

设备树:

1
2
3
compatible = "i2c-mux";
i2c-parent = <&i2c2>;
/* control-mux 由 mux 子系统绑定 */

优点: 硬件选通逻辑可复用于 SPI/I2C 等其他子系统;抽象层次最高


5.7 i2c-mux-ltc4306.c — Analog LTC4305/4306

硬件: ADI I2C mux/switch,带 rise time accelerator、upstream/downstream 加速配置。

功能:

特性 说明
regmap LTC_REG_STATUS/CONFIG/MODE/SWITCH
LTC4306 4 通道 + 2 个可选 GPIO(gpio_chip 导出)
LTC4305 2 通道
select LTC_REG_SWITCH 选通通道

与 pca954x 类似: I2C 控制的专用 mux 芯片,适合长走线、电容负载大的场景。


5.8 i2c-mux-mlxcpld.c — Mellanox CPLD Mux

场景: Mellanox 网卡/服务器主板,CPLD 寄存器控制 I2C 路由。

核心实现:

函数 功能
mlxcpld_mux_reg_write() 1/2 字节寄存器写入选通值
mlxcpld_mux_select_chan() 仅当 last_val != regval 时写入,减少总线流量
平台数据 mlxcpld_mux_plat_data:寄存器地址、宽度

拓扑(驱动注释): i2c-mlxcpld 桥接 → CPLD mux 寄存器 → 多条虚拟 bus → 外设。

RK3588 产品树通常 不包含 此驱动。


5.9 i2c-demux-pinctrl.c — pinctrl 反多路复用(Demux)

与 mux 的本质区别:

对比项 Mux (i2c-mux-*.c) Demux (i2c-demux-pinctrl)
父总线数量 1 个 parent 多个 parent(≥2)
子 adapter 每个 channel 一个,同时存在 仅 1 个 当前 active adapter
切换方式 每次传输 select 通道 运行时 换主控 + OF overlay
用户接口 标准 i2c adapter 编号 sysfs current_master

工作流程:

1
2
3
4
5
6
用户写 sysfs current_master = N
→ i2c_demux_deactivate_master() // 删除旧 adapter
→ of_changeset_apply() // 应用 DT 变更
→ 获取新 parent adapter
→ pinctrl_select_state() // 切换引脚
→ i2c_add_adapter() // 注册新逻辑 adapter

sysfs:

  • available_masters:可读所有候选主控;
  • current_master:读写当前主控索引。

场景: 同一组引脚在“内部 I2C0”与“外部 I2C 扩展”之间动态切换(Renesas 等平台常见)。


5.10 i2c-arb-gpio-challenge.c — GPIO 挑战-应答仲裁

不是通道扩展,而是 多主机互斥

GPIO 作用
our-gpio 本方声明要占用总线
their-gpio 检测对方是否已占用

select 流程:

  1. 拉高 our-gpio
  2. 轮询 their-gpio 是否为低(对方释放);
  3. 超时则放弃并重试;
  4. 成功则返回 0,允许在 parent 上传输。

标志: i2c_mux_alloc(..., I2C_MUX_ARBITRATOR, ...)

场景: 两个主控(如 AP + MCU)共享一条 I2C,无专用 PCA9541 时的软件仲裁方案。


6. Mux 与 Switch 语义对比(以 PCA954x 为代表)

类型 寄存器写法 同时导通
Mux chan | enable 否,仅一路
Switch 1 << chan 是,位掩码可多 bit

驱动在 pca954x_regval() 中统一处理,对上层均暴露为独立 channel adapter。


7. 锁机制与嵌套 Mux

i2c-mux.c 提供两套 lock_ops

模式 行为
默认 子 adapter 锁 parent->mux_lock + 可选 root lock
I2C_MUX_LOCKED mux 自身持有段锁,防止 select 期间父总线被其他客户端抢占

多级 mux 示例:

1
2
3
4
5
i2c-2 (rk3x)
└── pca9548 @0x70
└── channel 2
└── pca9544 @0x71
└── channel 0 → 传感器

传输时依次 select 两级;i2c_adapter_depth() 用于嵌套锁层级。


8. RK3588 平台使用场景

8.1 内置 I2C 控制器

RK3588 自带多路 rockchip,rk3588-i2c不经过 muxes/ 目录驱动,由 i2c-rk3x.c 直接提供 adapter。

8.2 板级 DTS 中的 i2c-mux 节点

在 RK3588 车载/多摄相关 DTS 中大量出现 逻辑名 i2c-mux 的节点,例如:

  • rk3588-vehicle-evb-maxim-max96722-dphy0.dtsi
  • rk3588-vehicle-maxim-cameras-s66.dtsi

典型结构:

1
2
3
4
5
6
7
8
9
10
11
/* 父 I2C 上 */
i2c-mux {
#address-cells = <1>;
#size-cells = <0>;

i2c@0 {
reg = <0>;
/* Serializer / Camera 等 */
};
i2c@1 { reg = <1>; /* ... */ };
};

说明:

  • 节点名 i2c-muxDT 拓扑约定,表示其下每个 i2c@N 对应一条逻辑子总线;
  • 实际选通可能由 板级 GPIO、SerDes 内部逻辑或外部 PCA954x 完成;
  • compatible = "i2c-mux" 且使用 MUX 子系统,内核加载 i2c-mux-gpmux.c
  • 若为 nxp,pca9548 等,加载 i2c-mux-pca954x.c
  • 部分方案可能在 驱动/固件内部 完成通道切换,DT 仅描述拓扑,不绑定上述 mux 驱动之一。

8.3 RK3588 常用驱动优先级建议

硬件实现 推荐驱动
NXP PCA954x/984x 芯片 i2c-mux-pca954x
GPIO 选通 i2c-mux-gpio
SoC 引脚复用多组 I2C 走线 i2c-mux-pinctrl
绑定 mux-controller i2c-mux-gpmux
双主争用 i2c-mux-pca9541i2c-arb-gpio-challenge
运行时切换不同 I2C 主控 i2c-demux-pinctrl

9. 调试与排障

9.1 确认子 adapter

1
2
3
ls /sys/bus/i2c/devices/
# 可见 i2c-N-mux (chan_id M) 或独立编号
ls /dev/i2c-*

9.2 在正确通道上探测

1
2
# 应对子 adapter 编号,而非仅父 bus
i2cdetect -y <子adapter编号>

9.3 常见问题

现象 可能原因
子总线无设备 未 select 到正确 channel;reg 与硬件通道不一致
偶发 -EBUSY 多主仲裁失败(pca9541 / arb-gpio)
仅某 channel 失败 switch 型芯片被配成多通,电气冲突
probe defer i2c-parent phandle 对应 adapter 尚未注册
pca954x idle 异常 检查 idle-state sysfs / DT 属性

9.4 PCA954x 专用

1
2
3
# 查看/设置空闲通道策略(若 sysfs 存在)
cat /sys/.../idle_state
echo -2 > .../idle_state # MUX_IDLE_DISCONNECT

10. 功能对照总表

文件 控制手段 通道模型 典型平台
i2c-mux-pca954x.c I2C 寄存器 2~8 路 mux/switch 通用嵌入式、服务器
i2c-mux-pca9541.c I2C 寄存器 单通道仲裁 双主共享总线
i2c-mux-gpio.c GPIO 位编码 N 路 自定义 CPLD/逻辑
i2c-mux-pinctrl.c pinctrl state N 路引脚组 Tegra 等 SoC 引脚复用
i2c-mux-reg.c MMIO 寄存器 N 路 PowerPC/QorIQ 等
i2c-mux-gpmux.c mux 子系统 N 路 现代 DT 通用方案
i2c-mux-ltc4306.c I2C 寄存器 2/4 路 + 加速 长电缆/大电容负载
i2c-mux-mlxcpld.c CPLD I2C 寄存器 多路 Mellanox 网卡
i2c-demux-pinctrl.c pinctrl + OF 多主控选一 Renesas 等
i2c-arb-gpio-challenge.c GPIO 握手 单通道仲裁 双 GPIO 多主

11. 结论

drivers/i2c/muxes 目录提供 9 种 I2C 拓扑扩展/仲裁实现,均通过 i2c-mux.c 统一为:

  1. select:传输前选通通道或获取总线;
  2. deselect:传输后恢复 idle(可选);
  3. i2c_mux_add_adapter:为每个 channel 导出独立 i2c_adapter

对 RK3588 开发:

  • 物理层i2c-rk3x.c 提供父 adapter;
  • 拓扑层 根据硬件选用 pca954xgpiogpmuxpinctrl 等;
  • 车载多摄 DTSi2c-mux 节点描述子总线结构,需结合具体 compatible 与原理图确认实际加载的 mux 驱动。

12. 推荐阅读顺序

  1. drivers/i2c/i2c-mux.c — 框架与锁
  2. include/linux/i2c-mux.h — API
  3. muxes/i2c-mux-pca954x.c — 最典型芯片实现
  4. muxes/i2c-mux-gpio.ci2c-mux-gpmux.c — 板级常见方案
  5. Documentation/devicetree/bindings/i2c/ 下各 binding
  6. RK3588 板级 DTS 中带 i2c-mux 的 dtsi

13. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/muxes 分析。

RK3588 kernel-6.1/drivers/i2c 目录功能详细分析

RK3588 kernel-6.1/drivers/i2c 目录功能详细分析

1. 文档范围

本文对以下目录做功能级详细分析:

  • 源码路径:rk3588/kernel-6.1/drivers/i2c
  • 文档路径:linuxDoc/drivers/i2c/

分析目标:

  1. 说明该目录在 Linux 内核中的职责与分层结构;
  2. 逐项解释核心文件、算法层、复用器层、总线控制器层的具体功能
  3. 重点说明 RK3588 平台 使用的 Rockchip I2C 控制器驱动(i2c-rk3x.c);
  4. 给出设备树绑定、传输调用链与排障思路。

同目录已有 rk3588-kernel-6.1-i2c总线架构分析.md,侧重架构与调用链;本文在其基础上扩展目录全貌与各模块职责


2. 目录总览

drivers/i2c 共约 177 个源文件,按 Makefile 分为五层:

层级 路径/文件 主要职责
核心层 i2c-core-*.c, i2c-boardinfo.c 总线注册、设备/驱动匹配、传输入口、OF/ACPI、SMBus 协议、从模式框架
用户态接口 i2c-dev.c /dev/i2c-* 字符设备,供 i2c-tools 等用户程序访问
拓扑扩展 i2c-mux.c, muxes/ I2C 多路复用,将每条下游段抽象为独立 adapter
算法层 algos/ 软件位带(bit-banging)等通用传输算法
控制器驱动 busses/(约 140+ 个 .c 文件) 各 SoC/PCI 的 I2C Host Controller 硬件驱动
辅助/测试 i2c-stub.c, i2c-slave-*.c, i2c-smbus.c 测试桩、从设备模拟、SMBus Alert/Host Notify

整体架构可概括为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
用户态 (i2c-tools / 应用)


i2c-dev.c (/dev/i2c-N) 或 内核 client 驱动


i2c-core (i2c_transfer / i2c_smbus_*)

├── i2c-mux.c (可选,多路复用)


adapter->algo->master_xfer()


busses/i2c-xxx.c (硬件控制器,如 i2c-rk3x.c)


物理 SDA/SCL ──► I2C 从设备 (传感器/PMIC/触摸/Codec 等)

3. 构建与 Kconfig 配置体系

3.1 顶层 Makefile

1
2
3
4
5
6
7
8
9
10
obj-$(CONFIG_I2C)           += i2c-core.o
i2c-core-objs := i2c-core-base.o i2c-core-smbus.o
i2c-core-$(CONFIG_ACPI) += i2c-core-acpi.o
i2c-core-$(CONFIG_I2C_SLAVE) += i2c-core-slave.o
i2c-core-$(CONFIG_OF) += i2c-core-of.o

obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-$(CONFIG_I2C_MUX) += i2c-mux.o
obj-y += algos/ busses/ muxes/

说明:

  • i2c-core 是必选核心模块(CONFIG_I2C),其余按 Kconfig 裁剪;
  • algos/busses/muxes/ 始终进入编译树,具体 .o 由各自子目录 Makefile + Kconfig 决定;
  • RK3588 板级配置通常使能:I2CI2C_CHARDEVI2C_MUXOFI2C_RK3X(或等效选项名)。

3.2 主要 Kconfig 选项

选项 功能
I2C 启用 I2C 子系统核心
I2C_CHARDEV 用户态 /dev/i2c-* 接口
I2C_MUX 多路复用核心框架
I2C_SMBUS SMBus Alert / Host Notify 等扩展协议
I2C_SLAVE 内核作为 I2C 从设备(需控制器支持)
I2C_BOARDINFO 静态 board info 注册(非 DT 平台)
I2C_COMPAT 旧版用户态兼容(class 设备)
I2C_STUB 测试用 SMBus 桩驱动
I2C_DEBUG_* 核心/算法/总线调试日志

4. 核心层(Core)功能详解

核心由 i2c-core 复合模块组成,是所有 I2C 操作的统一入口

4.1 i2c-core-base.c — 总线与传输主框架

主要功能:

功能域 代表接口/机制 说明
总线类型 i2c_bus_type 注册 .match.probe.remove,连接 client 与 driver
Adapter 注册 i2c_add_adapter()i2c_add_numbered_adapter()i2c_del_adapter() 控制器向 core 注册/注销
设备注册 i2c_new_client_device()i2c_unregister_device() 在 adapter 上创建 client
驱动注册 i2c_register_driver() client 驱动挂到总线
传输入口 i2c_transfer()__i2c_transfer() 加锁、重试、trace、调用 algo->master_xfer
SMBus 封装 i2c_smbus_read_byte() 等(部分在 core-smbus) 通过 I2C 消息模拟 SMBus
地址检查 i2c_check_7bit_addr_validity_strict() 7 位地址合法性
总线恢复 i2c_recover_bus()i2c_generic_scl_recovery() SCL 卡死时 GPIO/bit-bang 恢复
锁机制 i2c_lock_bus() / i2c_unlock_bus() 根 adapter 与 mux 父子锁
Host Notify i2c_handle_smbus_host_notify() SMBus 主机通知 IRQ 域
时序解析 i2c_parse_fw_timings() 从 DT 读取 clock-frequency、rise/fall 等
Debugfs i2c_debugfs_root 调试节点
Trace trace/events/i2c.h ftrace 跟踪传输

传输调用链(内核 client 驱动路径):

1
2
3
4
5
client 驱动 (如 xxx_probe 里读写寄存器)
→ i2c_smbus_* / i2c_transfer()
→ __i2c_transfer()
→ adap->algo->master_xfer() // 如 rk3x_i2c_xfer
→ 硬件寄存器 + 中断/DMA

4.2 i2c-core-smbus.c — SMBus 协议实现

  • 在 I2C 消息之上实现 SMBus 块读写、PEC(CRC8)、Quick Command 等;
  • 若 adapter 未实现 smbus_xfer,则由 core 用 I2C 消息模拟 SMBus 时序;
  • hwmon、电源管理、传感器等大量驱动依赖。

4.3 i2c-core-of.c — Device Tree 支持

函数 功能
of_i2c_get_board_info() 从 DT 节点解析 reg(地址)、compatible(type)、host-notifywakeup-source
of_i2c_register_devices() adapter 注册后,扫描子节点并 i2c_new_client_device()
i2c_of_match_device() OF 匹配表支持

RK3588 上绝大多数 I2C 从设备(触摸、PMIC、Codec)通过 DT 子节点自动枚举,不依赖 i2c_register_board_info()

4.4 i2c-core-acpi.c — ACPI 支持

  • 在 x86/ACPI 平台解析 ACPI I2C 设备;
  • RK3588 为 ARM64 + DT,通常不编译或未使用此路径。

4.5 i2c-core-slave.c — 从模式框架

  • i2c_slave_register() / i2c_slave_unregister()
  • 要求 adapter->algo->reg_slave 由控制器实现;
  • RK3588 的 i2c-rk3x.c 以实现 Master 为主,从模式非主要用途。

4.6 i2c-boardinfo.c — 静态板级信息

  • i2c_register_board_info(busnum, info, len):在 arch 初始化阶段静态声明设备;
  • 多用于无 DT 的旧 ARM/x86 板;RK3588 以 DT 为主,此路径较少使用。

4.7 i2c-core.h

  • core 内部头文件,声明 __i2c_board_listi2c_devinfo 等仅 core 使用的符号。

5. 用户态接口层

5.1 i2c-dev.c/dev/i2c-*

功能:

  • 每个已注册的 i2c_adapter 对应一个字符设备 i2c-N(N = adap->nr);
  • 通过 ioctl 支持:
    • I2C_RDWR:组合读写;
    • I2C_SMBUS:SMBus 访问;
    • I2C_FUNCS:查询 adapter 能力;
    • I2C_SLAVE / I2C_SLAVE_FORCE:绑定从地址;
    • I2C_RETRIESI2C_TIMEOUT 等;
  • 用户态工具:i2cdetecti2cgeti2cseti2cdump(i2c-tools 包)。

典型用途: bring-up 阶段探测总线、读写 EEPROM、验证硬件连线。


6. SMBus 扩展模块

6.1 i2c-smbus.c

i2c-core-smbus 基础协议之上,增加:

机制 功能
SMBus Alert 从设备拉 Alert# 线,ARA(Alert Response Address)轮询,调用各 driver 的 .alert()
SMBus Host Notify 从设备向主机发通知,通过 host-notify DT 属性与 IRQ 域映射

依赖 CONFIG_I2C_SMBUS,PC 服务器平台更常见;嵌入式 SoC 按需使能。


7. I2C 多路复用(Mux)层

7.1 i2c-mux.c — 复用框架

设计思想: 一条物理 I2C 上挂 MUX 芯片,每个通道对应一条逻辑总线

  • 为每个 channel 创建子 i2c_adapter
  • select(chan) / deselect(chan) 切换硬件通道;
  • 传输时:selecti2c_transfer(parent)deselect
  • 支持 多级 mux(mux 后再挂 mux)。

7.2 muxes/ 目录 — 具体 MUX 芯片驱动

驱动文件 芯片/机制 功能
i2c-mux-pca954x.c NXP PCA954x/984x 最常见 I2C 开关/多路器
i2c-mux-pca9541.c PCA9541 主选择器(双主仲裁场景)
i2c-mux-gpio.c GPIO 用 GPIO 选通通道
i2c-mux-reg.c 寄存器 写寄存器位选通
i2c-mux-pinctrl.c pinctrl 通过引脚复用切换 I2C 信号路由
i2c-demux-pinctrl.c pinctrl 反向:多主机选路
i2c-mux-gpmux.c MUX 子系统 通用 MUX 控制器抽象
i2c-mux-ltc4306.c LTC4306/5 带 rise time 加速的开关
i2c-mux-mlxcpld.c Mellanox CPLD 服务器 CPLD 控制 mux
i2c-arb-gpio-challenge.c GPIO 多主 GPIO 仲裁

RK3588 板子上若多个同地址设备分属不同通道,会在 DTS 中出现 i2c-mux-pca954x 等节点,用户态会看到 i2c-5i2c-6 等多个 adapter。


8. 算法层(algos/

用于没有专用硬件控制器、或硬件仅提供 GPIO 线位的场景。

文件 功能
i2c-algo-bit.c 软件位带:通过 setsda/setscl/getsda/getscl 回调 bit 级操作 SDA/SCL
i2c-algo-pca.c Philips PCA9564 等并行总线桥
i2c-algo-pcf.c PCF8584 适配

常与 i2c-gpio.cbusses/)配合:用两个 GPIO 模拟 I2C 总线。

RK3588 内置硬件 I2C 控制器,正常产品路径不走 algo-bit,但 bring-up 或特殊板可能仍用 GPIO I2C。


9. 总线控制器层(busses/

9.1 规模与分类

  • 140 个 .c 源文件;
  • Kconfig 中约 128 个 config I2C_* 选项;
  • 按平台/总线类型可粗分为:
类别 代表驱动 典型平台
Rockchip i2c-rk3x.c RK3588/RK3399/RK3568 等
Synopsys DesignWare i2c-designware-*.c Intel、部分 ARM SoC
NXP/Freescale i2c-imx.c, i2c-mpc.c i.MX、PowerPC
Samsung i2c-exynos5.c, i2c-s3c2410.c Exynos
Qualcomm i2c-qup.c, i2c-qcom-geni.c 高通
Broadcom i2c-bcm2835.c, i2c-bcm-iproc.c 树莓派等
PCI/ACPI PC i2c-i801.c, i2c-piix4.c x86 芯片组 SMBus
GPIO 模拟 i2c-gpio.c 任意 GPIO
Virtio/测试 i2c-virtio.c, i2c-stub.c 虚拟化/测试

每个控制器驱动典型实现:

  1. platform_driver / pci_driver probe
  2. 映射 MMIO、申请 IRQ、获取 clock/reset/pinctrl;
  3. 填充 struct i2c_adapter + struct i2c_algorithm
  4. i2c_add_numbered_adapter() 注册;
  5. master_xfer() 中完成实际收发。

10. RK3588 平台:Rockchip i2c-rk3x.c 详细分析

10.1 设备树绑定

RK3588 在 rk3588s.dtsi 中定义多个 I2C 控制器,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
i2c0: i2c@fd880000 {
compatible = "rockchip,rk3588-i2c", "rockchip,rk3399-i2c";
reg = <0x0 0xfd880000 0x0 0x1000>;
clocks = <&cru CLK_I2C0>, <&cru PCLK_I2C0>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 317 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c0m0_xfer>;
resets = <&cru SRST_I2C0>, <&cru SRST_P_I2C0>;
reset-names = "i2c", "apb";
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

要点:

  • 主 compatiblerockchip,rk3588-i2c(SoC 特有属性);
  • 回退 compatiblerockchip,rk3399-i2c(驱动 of_match 中已注册,共用 rk3399_soc_data);
  • RK3588S 芯片上约有 8 路 I2C(i2c0i2c7 等,具体以 dtsi 为准);
  • 板级 dts 通过 status = "okay"clock-frequency、子节点使能外设。

10.2 驱动支持的 SoC 列表(rk3x_i2c_match[]

compatible 时序算法 备注
rockchip,rk3066-i2c v0 老 GRF 切换
rockchip,rk3188-i2c v0
rockchip,rk3228-i2c v0
rockchip,rk3288-i2c v0
rockchip,rk3399-i2c v1 RK3588 回退使用
rockchip,rv1108-i2c v0
rockchip,rv1126-i2c v1
rockchip,rv1126b-i2c v1 + DMA

RK3588 无独立 rk3588_soc_data 条目,通过 DT 字符串回退到 rk3399 实现(v1 时序计算、双时钟 i2c+pclk)。

10.3 硬件寄存器与传输模式

主要寄存器:

偏移 名称 功能
0x00 REG_CON 使能、主/从模式、START/STOP、ACK 策略、SDA/STA/STO 时序 tuning
0x04 REG_CLKDIV SCL 高低电平分频
0x08/0x0c MRXADDR/MRXRADDR 组合写读(寄存器地址 + 读数据)
0x10/0x14 MTXCNT/MRXCNT 发送/接收字节数
0x18/0x1c IEN/IPD 中断使能/挂起
0x80+ DMA_* DMA 地址、配置(部分 SoC)
0x228 REG_CON1 Auto-Stop(v5+ 硬件)

传输路径:

模式 条件 说明
FIFO 默认,短消息 TX/RX buffer 在 0x100/0x200,每次最多 32 字节
DMA 较长消息、SoC 支持 dmaengine + bounce buffer,完成后 rk3x_i2c_complete_dma()
组合写读 num>=2 且首包为短写 硬件 REGISTER_TX 模式,一次完成「写寄存器地址 + 读数据」

10.4 状态机与中断

1
2
3
STATE_IDLE → STATE_WRITE / STATE_READ → STATE_STOP → STATE_IDLE
↑__________________________|
IRQ 推进
  • rk3x_i2c_irq():读 REG_IPD,按状态调用 handle_write/read/stop
  • 上层 rk3x_i2c_xfer_common() 通过 wait_event_timeout(i2c->wait, !i2c->busy) 同步;
  • Atomic 路径rk3x_i2c_xfer_polling() 关中断轮询 rk3x_i2c_irq(),用于 atomic 上下文。

10.5 时序计算(v1,RK3399/RK3588 路径)

  • rk3x_i2c_v1_calc_timings():根据 clock-frequency(100k/400k/1M 等)和 DT 中的 scl-rise-nsscl-fall-ns 计算 motion_low/high 分频;
  • 满足 I2C 规范:tHD;statSU;statSU;stotSU;dat 等;
  • rk3x_i2c_clk_notifier_cb():时钟频率变化时重新 rk3x_i2c_adapt_div(),避免 DVFS 后时序违规。

10.6 特殊功能

功能 说明
Auto-Stop IP version ≥ 5 时 autostop_supported,短传输硬件自动发 STOP
GRF 切换 老 SoC 通过 rockchip,grf 切换 I2C 接口模式
Thunder Boot rockchip,amp-shared + CONFIG_ROCKCHIP_THUNDER_BOOT_SERVICE 延迟使能 IRQ
SCL Hold 检测 超时且 REG_INT_SLV_HDSCL 时复位控制器并告警
Restart 兼容 硬件无真 Repeated START,通过复位内部状态 + 普通 START 模拟

10.7 i2c_algorithm 能力

1
2
3
4
5
6
7
static u32 rk3x_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C |
I2C_FUNC_SMBUS_EMUL |
I2C_FUNC_PROTOCOL_MANGLING |
I2C_FUNC_NOSTART;
}

支持标准 I2C、SMBus 模拟、无 START 优化;具体 SMBus 由 core 层模拟完成。

10.8 Probe 流程摘要

1
2
3
4
5
6
7
8
rk3x_i2c_probe()
→ 解析 bus id (of_alias / ACPI)
→ i2c_parse_fw_timings()
→ ioremap、request_irq、clk/reset
→ 可选 GRF、Thunder Boot 回调
→ adap.algo = &rk3x_i2c_algorithm
→ i2c_add_numbered_adapter()
→ of_i2c_register_devices() [core 自动]

11. 辅助与测试模块

文件 功能
i2c-stub.c 模拟 SMBus 从设备,供驱动开发测试
i2c-slave-eeprom.c 内核模拟 I2C EEPROM 从设备
i2c-slave-testunit.c 测试 unit,验证 multi-master、Host Notify 等

生产内核通常不使能这些选项。


12. 与 RK3588 外设的典型关系

1
2
3
4
5
6
7
8
9
10
11
rk3588s.dtsi (i2c0..i2c7)

├── 板级 .dts 使能 &i2c2 等
│ ├── pmic@xx (电源管理)
│ ├── codec@xx (音频)
│ ├── touchscreen@xx (触摸)
│ └── eeprom@xx (配置存储)

└── 可选 i2c-mux@xx
├── channel@0 → 传感器
└── channel@1 → 摄像头 SCCB
  • Client 驱动位于 drivers/i2c/clients 之外的其他目录(如 drivers/input/touchscreendrivers/mfddrivers/hwmon);
  • drivers/i2c 只提供总线基础设施,不包含具体芯片驱动。

13. 调试与排障指南

13.1 确认 Adapter 是否注册

1
2
ls /sys/bus/i2c/devices/
ls /dev/i2c-* # 需 CONFIG_I2C_CHARDEV

13.2 用户态探测

1
2
i2cdetect -y 2         # 扫描 i2c-2 总线
i2cget -y 2 0x50 0x00 # 读设备 0x50 寄存器 0

13.3 内核日志

  • 打开 CONFIG_I2C_DEBUG_CORE / CONFIG_I2C_DEBUG_BUS
  • 关注 timeoutNACKSCL hold by slave(rk3x 特有);
  • trace_event i2c i2c_transfer(若使能 trace)。

13.4 常见问题对照

现象 可能原因 排查方向
/dev/i2c-N adapter 未注册或 CHARDEV 未开 dmesg、status in DT
-ETIMEDOUT 时钟/上拉/地址错误、总线锁死 示波器、降低 clock-frequency
-ENXIO (NACK) 从设备地址错误或未上电 i2cdetect、硬件原理图
仅某通道失败 MUX 未切换 检查 mux 驱动与子 adapter 编号
DVFS 后偶发错误 I2C 时钟变化未适配 rk3x clk notifier、固定频率测试

14. 功能模块对照总表

模块 源文件 对 RK3588 的意义
核心传输 i2c-core-base.c 所有 I2C 访问必经
SMBus i2c-core-smbus.c, i2c-smbus.c 传感器/电源 SMBus 访问
DT 枚举 i2c-core-of.c 板级外设自动加载
用户态 i2c-dev.c 调试与工具链
Mux i2c-mux.c, muxes/* 多设备同板拓扑
算法 algos/* GPIO I2C 备用方案
主机控制器 busses/i2c-rk3x.c RK3588 内置 I2C 硬件驱动
从模式 i2c-core-slave.c 一般不使用
测试 i2c-stub.c, i2c-slave-*.c 开发阶段可选

15. 结论

rk3588/kernel-6.1/drivers/i2c 目录实现的是 Linux 通用 I2C 子系统

  1. Core 定义总线模型、传输 API、OF 枚举、SMBus、恢复与调试基础设施;
  2. i2c-dev 提供用户态访问;
  3. i2c-muxmuxes/ 处理复杂拓扑;
  4. algos/ 提供软件位带等通用算法;
  5. busses/ 包含各平台硬件控制器,其中 RK3588 使用 i2c-rk3x.c,DT 兼容 rockchip,rk3588-i2c + rockchip,rk3399-i2c

对 RK3588 开发与排障:

  • 外设驱动问题 → 查具体 client 驱动与 DT 节点;
  • 整条总线异常 → 重点分析 i2c-rk3x.c 的 xfer 状态机、时钟分频、中断与 pinctrl;
  • 用户态验证i2c-dev + i2c-tools。

16. 推荐阅读顺序

  1. drivers/i2c/MakefileKconfig — 模块组成
  2. i2c-core-base.ci2c_transferi2c_register_adapter
  3. i2c-core-of.c — DT 设备注册
  4. i2c-dev.c — 用户态接口
  5. busses/i2c-rk3x.c — RK3588 硬件实现
  6. arch/arm64/boot/dts/rockchip/rk3588s.dtsi — 控制器与外设节点
  7. i2c-mux.c + 所用 muxes/* — 若板级有 MUX

文档版本:基于 rk3588/kernel-6.1 源码树分析。

RK3588 kernel-6.1/drivers/i2c/i2c-boardinfo.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-boardinfo.c 功能详细分析

1. 文档范围与说明

说明: 用户路径写作「i2c-boardinfo.c 目录」,实际为 单个源文件,位于:

  • rk3588/kernel-6.1/drivers/i2c/i2c-boardinfo.c

并非独立子目录。该文件与 i2c-core-base.c 等共同构成 i2c 子系统,在 Makefile 中按 CONFIG_I2C_BOARDINFO 单独编译为 i2c-boardinfo.o

文档路径: linuxDoc/drivers/i2c/

分析目标:

  • 该文件在 I2C 子系统中的职责;
  • 对外 API、内部数据结构与总线编号协调机制;
  • 与 adapter 注册、设备创建的时序关系;
  • 与 Device Tree(OF)方式的对比;
  • RK3588 平台是否使用及实践建议

2. 功能定位(一句话)

i2c-boardinfo.c 实现 「板级静态 I2C 设备表」的登记与延迟实例化:在 I2C 适配器(adapter)尚未注册之前,由板级/架构初始化代码预先声明「某总线号上会有哪些从设备」;待对应 i2c_adapter 注册成功后,由 core 扫描该表并创建 i2c_client

这是 无 Device Tree 或早期 ARM/PowerPC/MIPS 板级代码 时代的经典机制;现代 RK3588 + DT 产品路径 几乎不调用 本文件提供的 API。


3. 编译与配置

3.1 Makefile

1
2
obj-$(CONFIG_I2C_BOARDINFO) += i2c-boardinfo.o
obj-$(CONFIG_I2C) += i2c-core.o
  • i2c-boardinfo.oi2c-core.o 分离链接,减小 core 模块体积的可选裁剪(理论上可 CONFIG_I2C_BOARDINFO=n,实际默认开启)。

3.2 Kconfig

1
2
3
config I2C_BOARDINFO
bool
default y
  • 无用户可见菜单项,默认始终使能
  • include/linux/i2c.h 中:若未使能,则 i2c_register_board_info() 为空 inline,直接返回 0。

4. 源文件结构概览

整个文件约 94 行,仅包含:

组成部分 内容
全局锁 __i2c_board_lock(读写信号量)
全局链表 __i2c_board_list
动态总线号边界 __i2c_first_dynamic_bus_num
唯一公共 API i2c_register_board_info()

无 probe/remove、无平台驱动、无设备树解析——纯数据登记层


5. 核心数据结构

5.1 struct i2c_devinfoi2c-core.h,内部)

1
2
3
4
5
struct i2c_devinfo {
struct list_head list;
int busnum; /* 目标 I2C 总线号 */
struct i2c_board_info board_info; /* 设备描述模板 */
};
  • i2c core 可访问(不对外导出结构体定义给任意驱动);
  • 每个 i2c_register_board_info() 传入的数组元素对应链表中的一个 i2c_devinfo 节点。

5.2 struct i2c_board_infoinclude/linux/i2c.h,公开)

设备创建模板,主要字段:

字段 含义
type 驱动匹配名 → i2c_client.name
addr 7/10 位 I2C 地址
flags I2C_CLIENT_TEN
platform_data 传给 client 驱动的平台数据
irq 中断号
resources / num_resources 资源数组
of_node / fwnode 固件节点(boardinfo 路径较少用)

便捷宏:

1
2
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)

6. 对外 API:i2c_register_board_info()

6.1 函数原型

1
2
3
int i2c_register_board_info(int busnum,
struct i2c_board_info const *info,
unsigned len);

6.2 参数语义

参数 说明
busnum 目标 adapter 的 固定总线编号(如 0、1、2),须与后续 i2c_add_numbered_adapter()adap->nr 一致
info i2c_board_info 数组指针
len 数组元素个数;可为 0,仅用于占用/预留该 busnum

6.3 调用时机(内核文档要求)

必须在 任何 I2C adapter 驱动注册之前 完成,典型位置:

  • 板级 arch_initcall() / device_initcall() 之前的初始化;
  • arch/*/boards/*/setup.c 中静态表 + 注册函数。

反例: adapter 已 i2c_add_numbered_adapter() 之后再 i2c_register_board_info() — 设备不会自动补注册(除非该 adapter 再次触发扫描,而标准路径只在注册时扫描一次)。

6.4 实现逻辑(逐步)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
down_write(&__i2c_board_lock);

/* 更新「动态分配总线号」的起始边界 */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;

for (; len; len--, info++) {
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
devinfo->busnum = busnum;
devinfo->board_info = *info; /* 结构体浅拷贝 */

/* resources 指针单独深拷贝 */
if (info->resources)
devinfo->board_info.resources = kmemdup(...);

list_add_tail(&devinfo->list, &__i2c_board_list);
}

up_write(&__i2c_board_lock);
return status; /* 0 或 -ENOMEM */
}

要点:

  1. 浅拷贝 board_infotypeaddrplatform_data 等指针不会复制指向的内容;文档明确警告 __initdata 中的 platform_data 若仅 init 段有效,后续可能失效。
  2. resources 例外:通过 kmemdup 深拷贝,避免原数组释放后悬空。
  3. 失败处理:遇 -ENOMEM 中断循环,已入链节点不会回滚(部分注册)。

6.5 返回值

  • 0:成功(或 len==0 仅更新 bus 号边界);
  • -ENOMEM:分配 i2c_devinfokmemdup 失败。

7. 全局变量与总线号协调

7.1 __i2c_board_list

  • 类型:struct list_head
  • 内容:所有尚未被 adapter 消费掉的 i2c_devinfo(注册后不删除节点,靠 busnum 匹配多次扫描时重复尝试创建,由 i2c_new_client_device 地址冲突检测处理)。

7.2 __i2c_first_dynamic_bus_num

作用: 划分 静态声明的总线号动态分配的总线号

时机 行为
i2c_register_board_info(busnum, ...) busnum >= __i2c_first_dynamic_bus_num,则设为 busnum + 1
i2c_init()(core 初始化) of_alias_get_highest_id("i2c"),若 DT alias 更大则提高该值
i2c_add_adapter() 动态分配 idr_alloc__i2c_first_dynamic_bus_num 起找空闲号

目的: 避免 i2c_add_adapter() 动态分配的 i2c-5 与板级静态声明的 busnum=5 冲突。

7.3 __i2c_board_lock

  • 类型:struct rw_semaphore
  • 写锁:i2c_register_board_info()
  • 读锁:i2c_scan_static_board_info()
  • 写锁:i2c_init() 中调整 __i2c_first_dynamic_bus_num

8. 设备实例化路径(与 core 协作)

本文件只登记;真正创建设备在 i2c-core-base.c

8.1 时序图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[启动早期]
arch/board setup.c
i2c_register_board_info(0, devices[], N)
→ 写入 __i2c_board_list

[稍后]
i2c-rk3x probe / 其他 adapter probe
i2c_add_numbered_adapter(adap) /* adap->nr == 0 */
→ i2c_register_adapter()
→ of_i2c_register_devices(adap) /* DT 子节点,RK3588 主路径 */
→ i2c_acpi_register_devices(adap) /* ACPI */
→ if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap) /* boardinfo 路径 */
→ bus_for_each_drv(...) /* 通知已注册的 i2c_driver */

8.2 i2c_scan_static_board_info()(core 内)

1
2
3
4
5
6
7
8
9
10
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
down_read(&__i2c_board_lock);
list_for_each_entry(devinfo, &__i2c_board_list, list) {
if (devinfo->busnum == adapter->nr &&
IS_ERR(i2c_new_client_device(adapter, &devinfo->board_info)))
dev_err(..., "Can't create device at 0x%02x\n", ...);
}
up_read(&__i2c_board_lock);
}
  • 匹配 devinfo->busnum == adapter->nr
  • 调用 i2c_new_client_device()device_register() → 与 i2c_driver 匹配 probe。

8.3 条件:何时扫描 boardinfo

1
2
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
  • 编号小于动态边界的 adapter 会扫描静态表;
  • 纯动态号 adapter(i2c_add_adapter 且 nr 较大)跳过 boardinfo。

9. 与其他设备注册方式对比

方式 API / 机制 声明时机 RK3588
Boardinfo(本文) i2c_register_board_info() adapter 之前,C 静态表 不使用
Device Tree of_i2c_register_devices() adapter 注册时解析子节点 主路径
ACPI i2c_acpi_register_devices() adapter 注册时
运行时扩展板 i2c_new_client_device() adapter 已存在 内核模块/特殊驱动
地址探测 i2c_new_scanned_device() adapter 已存在,多地址尝试 少见

9.1 RK3588 实际路径

rk3588/kernel-6.1/arch/arm64 i2c_register_board_info() 调用。

典型 DTS(rk3588s.dtsi + 板级 dtsi):

1
2
3
4
5
6
7
8
9
10
&i2c2 {
status = "okay";
clock-frequency = <400000>;

pmic@20 {
compatible = "rockchip,rk806";
reg = <0x20>;
...
};
};

i2c-rk3x 注册 adapter 后, of_i2c_register_devices() 根据子节点创建 client。

9.2 仍使用 boardinfo 的平台(源码树内示例)

架构/路径 示例
SH(SuperH) arch/sh/boards/mach-*/setup.c
PowerPC arch/powerpc/platforms/*/misc.c
MIPS Alchemy arch/mips/alchemy/devboards/*.c
MIPS Sibyte arch/mips/sibyte/swarm/swarm-i2c.c

10. 典型板级用法示例(历史参考)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 设备表 */
static struct i2c_board_info board_i2c_devices[] __initdata = {
I2C_BOARD_INFO("ds1338", 0x68),
{
I2C_BOARD_INFO("eeprom", 0x50),
.platform_data = &eeprom_cfg,
},
};

/* 板级 init */
static int __init board_init(void)
{
i2c_register_board_info(0, board_i2c_devices,
ARRAY_SIZE(board_i2c_devices));
return 0;
}
arch_initcall(board_init);

对应 adapter 须 i2c_add_numbered_adapter()adap->nr == 0


11. 设计约束与注意事项

11.1 platform_data 生命周期

注释明确:不会复制 platform_data 指向的内容。若表在 __initdata,init 结束后内存可能被回收 → client probe 时访问非法。

正确做法: platform_data 指向全局/静态存储,或在 adapter 注册前仍为有效内存。

11.2 无注销接口

  • i2c_unregister_board_info()
  • 链表节点常驻;adapter 删除时不会根据 boardinfo 自动移除 client(client 由 driver model 管理)。

11.3 与 DT 并存

同一 adapter 上可同时:

  1. of_i2c_register_devices() 创建 DT 子节点设备;
  2. i2c_scan_static_board_info() 创建静态表设备。

须避免 地址冲突;RK3588 应只使用 DT 一种来源。

11.4 len == 0 的用途

仅更新 __i2c_first_dynamic_bus_num,用于 预留总线编号,确保后续动态 adapter 不会占用该号。


12. 导出符号策略

1
2
3
4
5
6
7
8
DECLARE_RWSEM(__i2c_board_lock);
EXPORT_SYMBOL_GPL(__i2c_board_lock);

LIST_HEAD(__i2c_board_list);
EXPORT_SYMBOL_GPL(__i2c_board_list);

int __i2c_first_dynamic_bus_num;
EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num);

注释写明:仅 i2c core 使用,不保证给其他模块。模块版本仅限 GPL。

公开给用户/板级代码的只有:

  • i2c_register_board_info()include/linux/i2c.h

13. 调试与验证

13.1 确认 boardinfo 是否参与

1
2
3
4
5
6
# RK3588 预期:无 boardinfo 注册
grep -r i2c_register_board_info arch/arm64/

# 查看某总线上 client 来源
ls /sys/bus/i2c/devices/
cat /sys/bus/i2c/devices/2-0020/of_node/compatible # DT 设备有 of_node

13.2 若移植遗留 boardinfo 代码

  • 确认 busnumaliases / adap->nr 一致;
  • 确认在 i2c-rk3x probe 之前 调用(通常需改 arch init,RK 上不推荐);
  • dmesg 查看 Can't create device at 0x..(地址冲突或 probe 失败)。

14. 功能对照总表

功能 是否由 i2c-boardinfo.c 实现
登记静态 I2C 设备表
分配/管理 adapter 否(core)
创建设备 i2c_client 否(core 调用 i2c_new_client_device
解析 Device Tree 否(i2c-core-of.c
总线传输 否(busses/*
驱动匹配 probe 否(各 i2c_driver
动态总线号边界协调 部分(写 __i2c_first_dynamic_bus_num

15. 结论

i2c-boardinfo.c 是 I2C 子系统中体量很小但历史上很重要的一层:

  1. 职责单一:在 adapter 出现之前,把 (busnum, i2c_board_info) 登记到全局链表;
  2. 延迟绑定:adapter 注册时由 i2c_scan_static_board_info() 实例化 i2c_client
  3. 总线号协调:通过 __i2c_first_dynamic_bus_num 避免与动态分配、DT alias 冲突;
  4. RK3588:产品使用 纯 DTof_i2c_register_devices),不依赖 本文件;阅读 RK I2C Bring-up 可 跳过 boardinfo,重点放在 DTS + i2c-rk3x.c + client 驱动。

若在维护旧架构移植代码时遇到 i2c_register_board_info(),应评估是否可改为 DTS 描述,以与当前 Rockchip 内核一致。


16. 推荐阅读顺序

  1. i2c-boardinfo.c(全文)
  2. drivers/i2c/i2c-core.hi2c_devinfo
  3. include/linux/i2c.hi2c_board_infoi2c_register_board_info
  4. i2c-core-base.ci2c_scan_static_board_infoi2c_register_adapter
  5. i2c-core-of.c — RK3588 实际设备枚举路径

17. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-boardinfo.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-core-acpi.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-core-acpi.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-core-acpi.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-core-acpi.c(约 825 行)

该文件在 Makefile 中作为 i2c-core 复合模块 的可选组成部分编译:

1
i2c-core-$(CONFIG_ACPI) += i2c-core-acpi.o

文档保存路径: linuxDoc/drivers/i2c/

定位:x86/ACPI 固件平台 提供 I2C 子系统的 ACPI 支持:从 ACPI 命名空间解析 I2cSerialBus 资源、枚举从设备、解析 IRQ/总线速率,并可选实现 ACPI Operation Region(固件通过 AML 访问 I2C)

与 RK3588 的关系: Rockchip ARM64 产品内核通常 不启用 CONFIG_ACPIrockchip_linux_defconfig 中无 ACPI 配置),因此 本文件在 RK3588 默认构建中通常不参与链接;设备枚举走 i2c-core-of.c。本文仍完整分析其机制,便于对比 x86 与 ARM/DT 路径。


2. 功能总览

功能模块 主要符号 作用
ACPI 资源解析 i2c_acpi_get_i2c_resource() 从 ACPI 资源中识别 I2cSerialBus
设备枚举 i2c_acpi_register_devices() adapter 注册后扫描 ACPI 命名空间创建 i2c_client
热插拔/重配置 i2c_acpi_notifier 响应 ACPI 设备增删
IRQ 解析 i2c_acpi_get_irq() 从 ACPI/GPIO 获取 client 中断号
总线速率 i2c_acpi_find_bus_speed() 按最慢从设备约束 adapter 时钟
适配器查找 i2c_acpi_find_adapter_by_handle() 按 ACPI handle 找 i2c_adapter
多资源 client i2c_acpi_new_device_by_fwnode() 同一 ACPI 设备多个 I2cSerialBus 资源
D0 探测豁免 i2c_acpi_waive_d0_probe() 特定驱动跳过 D0 电源域探测
Operation Region i2c_acpi_install_space_handler() 固件 AML 经 GSBUS 访问 I2C(CONFIG_ACPI_I2C_OPREGION

3. 编译与配置依赖

3.1 依赖链

1
2
3
4
CONFIG_I2C=y
└── CONFIG_ACPI=y → 编译 i2c-core-acpi.o 并链接进 i2c-core.ko
└── CONFIG_ACPI_I2C_OPREGION=y(默认 y)
→ 启用 Operation Region 代码(文件内 #ifdef 块)

3.2 Kconfig(drivers/i2c/Kconfig

1
2
3
4
config ACPI_I2C_OPREGION
bool "ACPI I2C Operation region support"
depends on I2C=y && ACPI
default y

说明:允许 BIOS/UEFI 固件中的 AML 代码通过 General Serial Bus(GSBUS) 地址空间访问 I2C 从设备(如智能电池)。

3.3 无 ACPI 时的桩函数(i2c-core.h

1
2
3
4
5
6
7
#ifdef CONFIG_ACPI
void i2c_acpi_register_devices(struct i2c_adapter *adap);
int i2c_acpi_get_irq(struct i2c_client *client, bool *wake_capable);
#else
static inline void i2c_acpi_register_devices(struct i2c_adapter *adap) { }
static inline int i2c_acpi_get_irq(...) { return 0; }
#endif

RK3588 无 ACPI 时,上述调用在编译期被优化为空操作。


4. 核心数据结构

4.1 struct i2c_acpi_lookup

查找/填充 i2c_board_info 时的临时上下文:

字段 含义
info 输出的 i2c_board_info
adapter_handle I2cSerialBus 资源中 resource_source 指向的 adapter ACPI handle
device_handle 当前 ACPI 设备 handle
index / n 多资源时选择第 N 个 I2cSerialBus
speed / min_speed / force_speed 总线速率扫描用

4.2 struct i2c_acpi_handler_data(Operation Region)

1
2
3
4
struct i2c_acpi_handler_data {
struct acpi_connection_info info;
struct i2c_adapter *adapter;
};

绑定到 adapter 父设备的 ACPI handle,供 i2c_acpi_space_handler 使用。

4.3 struct gsb_buffer

ACPI GSBUS 访问协议缓冲区布局(status/len/data),与 AML _REG 读写格式对应。


5. ACPI 资源模型:I2cSerialBus

5.1 i2c_acpi_get_i2c_resource()

struct acpi_resource 中筛选:

  1. type == ACPI_RESOURCE_TYPE_SERIAL_BUS
  2. serial_bus.type == ACPI_RESOURCE_SERIAL_TYPE_I2C

成功则得到 struct acpi_resource_i2c_serialbus *,包含:

字段 映射到 Linux
slave_address i2c_board_info.addr
connection_speed 速率(Hz)
access_mode ACPI_I2C_10BIT_MODEI2C_CLIENT_TEN
resource_source 指向 host adapter 的 ACPI 路径

导出符号,供其他驱动(如 DesignWare I2C、平台驱动)解析 ACPI 资源。

5.2 i2c_acpi_client_count()

统计某 acpi_device 的 CRS(Current Resource Settings)中有多少个 I2cSerialBus 资源,用于需要多 client 的驱动。


6. 设备查找与注册流程

6.1 核心查找:i2c_acpi_do_lookup()

对单个 acpi_device 执行:

1
2
3
4
5
6
acpi_bus_get_status()           /* 设备必须 present */
acpi_dev_ready_for_enumeration()
过滤 i2c_acpi_ignored_device_ids /* 如 ACPI_VIDEO_HID 误报 I2C */
acpi_dev_get_resources(..., i2c_acpi_fill_info, ...)
→ 填充 info->addr、adapter_handle、speed
acpi_get_handle(device, resource_source) /* 解析 adapter 连接 */

失败返回 -EINVAL / -ENODEV

6.2 i2c_acpi_get_info()

i2c_acpi_do_lookup 基础上:

  • 若指定 adapter,校验 ACPI_HANDLE(&adapter->dev) == lookup.adapter_handle
  • 否则确认 adapter ACPI 设备存在且 status.present
  • 设置 info->fwnode = acpi_fwnode_handle(adev)
  • acpi_set_modalias() 填充 info->type(用于驱动匹配)。

6.3 Adapter 注册时批量枚举:i2c_acpi_register_devices()

调用点i2c-core-base.ci2c_register_adapter()):

1
2
3
of_i2c_register_devices(adap);
i2c_acpi_install_space_handler(adap);
i2c_acpi_register_devices(adap);

实现逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
void i2c_acpi_register_devices(struct i2c_adapter *adap)
{
if (!has_acpi_companion(&adap->dev))
return;

/* 从 ACPI 根向下 walk,深度最多 32 层 */
acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
I2C_ACPI_MAX_SCAN_DEPTH,
i2c_acpi_add_device, NULL, adap, NULL);

/* 清除父设备依赖,避免 adapter 无法 suspend */
acpi_dev_clear_dependencies(ACPI_COMPANION(adap->dev.parent));
}

i2c_acpi_add_device 对每个 handle:

  1. i2c_acpi_get_info() 得到 i2c_board_info
  2. i2c_acpi_register_device()i2c_new_client_device()
  3. acpi_device_set_enumerated() 标记已枚举,避免重复。

6.4 与 OF 枚举的并行关系

同一 adapter 注册时 同时 执行:

顺序 函数 平台
1 of_i2c_register_devices() ARM/DT(RK3588)
2 i2c_acpi_install_space_handler() x86 + OPREGION
3 i2c_acpi_register_devices() x86/ACPI

三者互不排斥,但同一地址不应在 DT 与 ACPI 中重复声明。

6.5 时序图(x86)

1
2
3
4
5
6
7
PCI/platform I2C adapter probe
→ i2c_add_numbered_adapter()
→ of_i2c_register_devices() [通常为空]
→ i2c_acpi_register_devices()
acpi_walk_namespace → 各 I2C 从设备
i2c_new_client_device()
→ i2c_driver 匹配 → probe

7. 运行时热插拔:i2c_acpi_notifier

i2c_init() 注册:

1
acpi_reconfig_notifier_register(&i2c_acpi_notifier);
事件 行为
ACPI_RECONFIG_DEVICE_ADD 查找 adapter → i2c_acpi_register_device()
ACPI_RECONFIG_DEVICE_REMOVE 找 client → i2c_unregister_device();unbind adapter

用于 ACPI 动态重配置(较少见,多用于服务器/特殊平台)。


8. IRQ 与电源相关

8.1 i2c_acpi_get_irq()

调用点: i2c_device_probe()i2c-core-base.c),当 client 无 DT irq 且存在 ACPI companion 时:

1
2
3
4
5
} else if (ACPI_COMPANION(dev)) {
irq = i2c_acpi_get_irq(client, &wake_capable);
if (irq > 0 && wake_capable)
client->flags |= I2C_CLIENT_WAKE;
}

解析顺序:

  1. 扫描 ACPI 资源中的中断资源 → i2c_dev_irq_from_resources()
  2. 若无,尝试 acpi_dev_gpio_irq_wake_get()(ACPI GPIO 中断扩展);
  3. 返回 IRQ 号或负错误码。

8.2 i2c_acpi_waive_d0_probe()

i2c_driver 设置 I2C_DRV_ACPI_WAIVE_D0_PROBE 且 ACPI 设备电源状态允许时,跳过 dev_pm_domain_attach() 的 D0 探测,避免某些固件枚举顺序导致的 probe 失败。


9. 总线速率:i2c_acpi_find_bus_speed()

用途: 部分 I2C adapter 驱动(如 i2c-designware)在 probe 时若 DT 未给 clock-frequency,则调用此函数从 ACPI 推断速率。

算法:

  1. acpi_walk_namespace 扫描所有 I2C 从设备;
  2. 仅统计连接到当前 adapter handle 的设备;
  3. 最慢connection_speed 作为 min_speed
  4. 特殊 quirk:存在 MSSL1680(Silead 触摸)时 强制 400kHzi2c_acpi_force_400khz_device_ids)。

导出符号,供 i2c-designware-common.ci2c-amd-mp2-plat.c 等使用。


10. 辅助 API

10.1 i2c_acpi_find_adapter_by_handle()

通过 bus_find_device(..., device_match_acpi_handle)i2c_bus_type 上查找 adapter。用于 ACPI 设备需绑定到特定 adapter 的场景(如 i2c-mux-gpio ACPI 路径)。

10.2 i2c_acpi_new_device_by_fwnode()

当单个 ACPI 设备含有 多个 I2cSerialBus 资源时,默认枚举只创建第一个;本 API 按 index 创建第 N 个资源的 client。

使用示例: Intel Cherry Trail 平台 intel/chtwc_int33fe.c(电池、FUSB302 等)、serial-multi-instantiate 驱动。

10.3 忽略列表 i2c_acpi_ignored_device_ids

1
{ ACPI_VIDEO_HID, 0 },  /* 显卡 ACPI 设备误带 I2C 资源,忽略 */

11. ACPI Operation Region(CONFIG_ACPI_I2C_OPREGION

11.1 目的

UEFI/BIOS 中的 AML 字节码 可通过 Operation Region 在运行时访问硬件。本驱动在 adapter 父设备 的 ACPI handle 上安装 GSBUS(General Serial Bus) 地址空间处理器,使固件能经 I2C adapter 读写从设备(典型:智能电池 SMBus)。

11.2 安装与拆除

1
2
i2c_acpi_install_space_handler(adapter);   /* adapter 注册时 */
i2c_acpi_remove_space_handler(adapter); /* adapter 删除时 */
  • 父设备必须有 ACPI companion;
  • acpi_install_address_space_handler(..., ACPI_ADR_SPACE_GSBUS, i2c_acpi_space_handler, ...)

11.3 i2c_acpi_space_handler()

根据 AML 访问类型转发到 Linux I2C/SMBus API:

ACPI GSB 访问类型 Linux 调用
ACPI_GSB_ACCESS_ATTRIB_SEND_RCV i2c_smbus_read_byte / write_byte
ACPI_GSB_ACCESS_ATTRIB_BYTE read_byte_data / write_byte_data
ACPI_GSB_ACCESS_ATTRIB_WORD read_word_data / write_word_data
ACPI_GSB_ACCESS_ATTRIB_BLOCK read_block_data / write_block_data
ACPI_GSB_ACCESS_ATTRIB_MULTIBYTE acpi_gsb_i2c_read_bytes / write_bytesi2c_transfer

每次访问临时构造 i2c_client(仅 addr/flags/adapter),不注册到驱动模型。

11.4 与 core 文档的对应

顶层 Kconfig 说明:

Operation Regions allow firmware (BIOS) code to access I2C slave devices, such as smart batteries through an I2C host controller driver.


12. 与 i2c-core-base.c 的集成点汇总

时机 ACPI 相关调用
i2c_init() acpi_reconfig_notifier_register(&i2c_acpi_notifier)
i2c_register_adapter() i2c_acpi_install_space_handler() + i2c_acpi_register_devices()
i2c_del_adapter() i2c_acpi_remove_space_handler()
i2c_device_probe() i2c_acpi_get_irq()i2c_acpi_waive_d0_probe()
驱动匹配 acpi_driver_match_device() 可作为无 id_table 时的匹配依据
i2c_exit() 注销 notifier

13. 导出符号一览

符号 说明
i2c_acpi_get_i2c_resource 解析 I2cSerialBus 资源
i2c_acpi_client_count 统计 I2cSerialBus 数量
i2c_acpi_find_bus_speed 推断 adapter 速率
i2c_acpi_find_adapter_by_handle 按 handle 找 adapter
i2c_acpi_new_device_by_fwnode 按资源索引创建 client
i2c_acpi_waive_d0_probe D0 探测豁免判断
i2c_acpi_notifier 重配置通知块(非 export,core 内用)

内部函数(i2c-core.h 声明给 core):i2c_acpi_register_devicesi2c_acpi_get_irqi2c_acpi_install/remove_space_handler


14. RK3588 平台说明

14.1 默认是否编译

配置项 RK3588 典型值
CONFIG_ACPI 未启用(无 ACPI 表)
i2c-core-acpi.o 不链接
设备枚举 of_i2c_register_devices()i2c-core-of.c
IRQ of_irq_get / of_irq_get_byname
总线速率 DT clock-frequency(如 100k/400k)

14.2 何时需要了解本文件

  • 维护 跨平台 驱动(同一驱动同时支持 ACPI 与 OF);
  • 阅读 Intel/AMD x86 上 I2C 问题(枚举缺失、速率错误、电池 OP region);
  • 内核配置误开 ACPI 时的行为分析(ARM 上一般应关闭)。

14.3 与 RK3588 相关的唯一间接引用

i2c-rk3x.c 中含 rk3x_i2c_acpi_get_bus_id(),用于 ACPI 平台 下从 ACPI UID 获取 bus number;在纯 DT 的 RK3588 上使用 of_alias_get_id()不经过 i2c-core-acpi.c 的枚举逻辑。


15. 三种设备声明方式对比(复习)

方式 实现文件 RK3588
Device Tree i2c-core-of.c 使用
Boardinfo 静态表 i2c-boardinfo.c 不使用
ACPI 命名空间 i2c-core-acpi.c 不使用(无 ACPI)

16. 调试建议(x86/ACPI 平台)

1
2
3
4
5
6
7
8
9
10
# 查看 ACPI companion
ls -l /sys/bus/i2c/devices/*/firmware_node

# 查看已枚举 client
ls /sys/bus/i2c/devices/

# 内核日志
dmesg | grep -i "i2c.*acpi\|enumerate I2C slaves"

# 禁用 ACPI I2C 枚举(调试):通常通过不编译 CONFIG_ACPI 或修复 DSDT

17. 结论

i2c-core-acpi.c 是 Linux I2C 子系统在 ACPI/UEFI 固件体系 下的核心适配层,主要完成:

  1. 从 ACPI I2cSerialBus 资源解析地址、adapter 关联、速率、10 位地址;
  2. I2C adapter 注册 时遍历 ACPI 命名空间 自动创建 i2c_client
  3. 支持 热插拔重配置多资源 clientIRQ/GPIO 唤醒
  4. 可选 Operation Region 让固件 AML 经 GSBUS 访问 I2C;
  5. 为 adapter 驱动提供 总线速率推断 等辅助能力。

RK3588:默认 无 ACPI,I2C 完全依赖 Device Tree + i2c-rk3x.c;本文件属于架构对比与 x86 维护知识,而非 RK 产品路径的必读本。


18. 推荐阅读顺序

  1. i2c-core-acpi.ci2c_acpi_register_devicesi2c_acpi_do_lookup
  2. i2c-core-base.ci2c_register_adapteri2c_device_probe
  3. include/linux/i2c.hI2C_DRV_ACPI_WAIVE_D0_PROBE
  4. drivers/i2c/busses/i2c-designware-common.ci2c_acpi_find_bus_speed 使用示例
  5. Documentation/firmware-guide/acpi/ — I2cSerialBus 规范(内核文档)

19. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-core-acpi.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-core-base.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-core-base.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-core-base.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-core-base.c(约 2723 行)

编译关系: 作为 i2c-core 复合模块的主干始终参与链接CONFIG_I2C=y 时):

1
2
3
4
i2c-core-objs := i2c-core-base.o i2c-core-smbus.o
i2c-core-$(CONFIG_ACPI) += i2c-core-acpi.o
i2c-core-$(CONFIG_OF) += i2c-core-of.o
i2c-core-$(CONFIG_I2C_SLAVE) += i2c-core-slave.o

文档保存路径: linuxDoc/drivers/i2c/

定位: Linux I2C 子系统的 核心实现:总线/设备模型、adapter 与 client 生命周期、i2c_transfer 传输路径、总线锁、总线恢复、驱动注册与(遗留)地址探测、Host Notify、debugfs/sysfs 等。SMBus 高层 API 在 i2c-core-smbus.c;DT 设备枚举在 i2c-core-of.c;ACPI 在 i2c-core-acpi.c

与 RK3588 的关系: 本文件是 RK3588 I2C 栈的必经核心i2c-rk3x.c 通过 i2c_add_numbered_adapter() 注册 adapter,经 of_i2c_register_devices() 创建 client,应用/驱动最终调用 i2c_transfer()rk3x_i2c_xfer()


2. 功能总览(按模块)

模块 行号区间(约) 主要符号 作用
跟踪与工具 73–118 i2c_freq_mode_string, i2c_match_id 频率模式字符串、驱动 ID 匹配
总线恢复 179–471 i2c_generic_scl_recovery, i2c_recover_bus SCL 位操作/GPIO 恢复卡死总线
设备 probe 489–621 i2c_device_probe/remove client 绑定驱动、IRQ、PM、时钟
总线类型 700–714 i2c_bus_type, i2c_client_type driver core 总线与设备类型
地址校验 736–845 i2c_check_addr_*, i2c_lock_addr 7/10 位地址、冲突检测、实例化锁
Client 生命周期 965–1230 i2c_new_client_device, dummy/ancillary 创建/注销 client
Adapter 生命周期 1522–1860 i2c_register_adapter, i2c_del_adapter 注册/删除 adapter、枚举子设备
Host Notify 1449–1519 i2c_handle_smbus_host_notify SMBus Host Notify → 虚拟 IRQ
Adapter sysfs 1264–1400 new_device, delete_device 用户空间动态创建设备
驱动注册 2014–2062 i2c_register_driver, i2c_del_driver I2C 驱动注册与 adapter 遍历
子系统初始化 2123–2189 i2c_init / i2c_exit postcore_initcall 早期初始化
数据传输 2196–2410 i2c_transfer, __i2c_transfer 核心 master 传输与 quirk 检查
遗留地址探测 2456–2590 i2c_detect, i2c_default_probe detect 回调自动发现设备
Adapter 引用 2638–2667 i2c_get_adapter, i2c_put_adapter 按总线号获取 adapter
DMA 安全缓冲 2681–2718 i2c_get/put_dma_safe_msg_buf 非 DMA 安全 buf 的 bounce buffer
DT 时序 1933–1988 i2c_parse_fw_timings 从 DT 解析 I2C 时序参数

3. 源码在 I2C 子系统中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
           ┌─────────────────────────────────────┐
│ 用户空间 / 其它内核模块 │
│ i2c-dev.c │ 各 i2c_driver 驱动 │
└─────────────┬───────────────────────┘

┌─────────────▼───────────────────────┐
│ i2c-core-base.c(本文) │
│ bus_type、adapter/client、transfer │
└─┬─────────┬─────────┬───────────────┘
│ │ │
i2c-core-smbus.c i2c-core-of.c i2c-core-acpi.c
(SMBus API) (DT 枚举) (ACPI 枚举, x86)

┌────────────▼────────────┐
│ busses/i2c-rk3x.c 等 │
│ algo->master_xfer │
└─────────────────────────┘

4. 子系统初始化(i2c_init

入口: postcore_initcall(i2c_init) — 必须 早于 部分 subsys_initcall 中注册的 I2C 驱动。

主要步骤:

  1. of_alias_get_highest_id("i2c") — 根据 DT alias 调整动态总线号起点 __i2c_first_dynamic_bus_num
  2. bus_register(&i2c_bus_type) — 注册 i2c 总线;
  3. debugfs_create_dir("i2c", NULL) — 创建 /sys/kernel/debug/i2c/
  4. 注册 dummy_driver(占位,保证总线上有驱动可做匹配);
  5. 注册 OF/ACPI 重配置 notifier(动态 DT/ACPI 热插拔)。

全局状态:

  • static DEFINE_MUTEX(core_lock) — 保护 i2c_adapter_idr 与探测序列化;
  • static DEFINE_IDR(i2c_adapter_idr) — 总线号 → i2c_adapter 映射;
  • static bool is_registered — 防止 adapter 在 bus 注册前添加。

5. 设备模型:i2c_bus_type 与 probe 路径

5.1 总线定义

1
2
3
4
5
6
7
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match, /* OF / ACPI / i2c_device_id */
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};

匹配顺序(i2c_device_match): OF 表 → ACPI 表 → i2c_device_id 表 → 按设备名匹配。

5.2 i2c_device_probe() 要点(RK3588 走 DT 分支)

步骤 说明
IRQ of_irq_get / of_irq_get_byname;或 ACPI i2c_acpi_get_irq;或 Host Notify
驱动匹配 要求 id_table 或 OF/ACPI 匹配之一
唤醒 I2C_CLIENT_WAKE + wakeup GPIO/IRQ
时钟 of_clk_set_defaults()
电源域 dev_pm_domain_attach()(ACPI 可 i2c_acpi_waive_d0_probe 豁免)
驱动 probe driver->probe()probe_new()

Host Notify: I2C_CLIENT_HOST_NOTIFY 时通过 adapter 上 irq_domain 将 SMBus 通知映射为 client 的 client->irq


6. Adapter 注册与删除

6.1 两种注册 API

API 总线号 RK3588 使用
i2c_add_numbered_adapter() 固定 adap->nr(DT aliases i2c-rk3x.c 使用
i2c_add_adapter() 动态分配或 DT alias USB/PCI 等

i2c_add_adapter() 若 adapter 的 dev.of_nodei2c alias,则转为 numbered 注册。

6.2 i2c_register_adapter() 流程

1
2
3
4
5
6
7
8
9
10
11
12
i2c_register_adapter(adap)
├─ 校验 adap->algo、设置 lock_ops、timeout
├─ i2c_setup_host_notify_irq_domain()
├─ device_register() → sysfs: /sys/bus/i2c/devices/i2c-N
├─ debugfs_create_dir()
├─ i2c_setup_smbus_alert() [i2c-core-smbus.c]
├─ pm_runtime_enable()
├─ i2c_init_recovery() [GPIO / 通用 SCL 恢复]
├─ of_i2c_register_devices() [DT 子设备, RK3588 主路径]
├─ i2c_acpi_*() [x86, 通常无]
├─ i2c_scan_static_board_info() [CONFIG_I2C_BOARDINFO, RK 通常无]
└─ bus_for_each_drv → i2c_do_add_adapter → i2c_detect()

6.3 i2c_del_adapter() 流程

注销 adapter 前:卸载所有 client(含 dummy)、移除 ACPI OP region handler、注销设备、释放 Host Notify domain、从 idr 删除。

6.4 devm_i2c_add_adapter()

设备资源管理封装:adapter 随 parent dev 自动 i2c_del_adapter


7. Client 创建与注销

7.1 i2c_new_client_device()

调用者: of_i2c_register_devices()i2c_acpi_register_devices()、驱动 detect、sysfs new_device 等。

流程摘要:

  1. i2c_board_info 填充 addrflagsnamefwnodeof_node
  2. i2c_check_addr_validity() — 7 位禁止 0x00,10 位 ≤ 0x3ff;
  3. i2c_lock_addr() — 防止并发重复实例化同一 7 位地址;
  4. i2c_check_addr_ex() — 统计同地址已有 client 数量(用于命名后缀);
  5. device_register() → 触发 i2c_device_probe()
  6. 设备名规则:i2c-%04xbus-addr-index(见 i2c_dev_set_name)。

7.2 其它 client API

函数 用途
i2c_unregister_device() 对称注销;清除 OF OF_POPULATED、ACPI enumerated
i2c_find_device_by_fwnode() 按 fwnode 查找已注册 client
i2c_new_dummy_device() / devm_* 无驱动的占位 client(mux、临时访问)
i2c_new_ancillary_device() 主 client 下的附属设备(如 codec 子器件)

7.3 Dummy 驱动

dummy_driver 在 core 初始化时注册,使未绑定具体驱动的 client 仍可存在于总线上(供 i2c_transfer 直接访问或 mux 使用)。


8. 数据传输核心:i2c_transfer

8.1 调用链(RK3588 典型)

1
2
3
4
5
6
7
8
9
驱动 i2c_master_send() / i2c_transfer()
→ i2c_transfer(adap, msgs, num)
→ __i2c_lock_bus_helper(adap) /* rt_mutex, 支持 mux 树深度 */
→ __i2c_transfer(adap, msgs, num)
→ __i2c_check_suspended()
→ i2c_check_for_quirks() /* adap->quirks */
→ adap->algo->master_xfer() /* rk3x: rk3x_i2c_xfer */
→ 失败 -EAGAIN 时按 adap->retries/timeout 重试
→ i2c_unlock_bus()

8.2 __i2c_transfer 细节

  • 要求 adap->algo->master_xfer 存在,否则 -EOPNOTSUPP
  • 原子上下文:i2c_in_atomic_xfer_mode() 且存在 master_xfer_atomic,则走原子路径(RK3x:rk3x_i2c_xfer_polling);
  • Tracepoint: i2c_transfer trace 启用时记录 read/write/reply;
  • 仲裁丢失: 返回 -EAGAIN 时在 adap->timeout 内重试。

8.3 Adapter Quirks(i2c_check_for_quirks

限制单次传输:max_num_msgsmax_read_lenmax_write_len、组合消息 I2C_AQ_COMB 等。主机驱动可在 adap->quirks 中声明硬件限制。

8.4 i2c_transfer_buffer_flags()

对单条消息的便捷封装:根据 I2C_M_RD 设置读写方向。

8.5 DMA 安全缓冲

i2c_get_dma_safe_msg_buf() / i2c_put_dma_safe_msg_buf()

  • msg->buf 非 DMA 安全且长度 ≥ threshold 时分配 bounce buffer
  • 读完成后 memcpy 回原始 buffer;
  • RK3x 等 DMA 主机驱动在传输前可调用以避免 DMA 映射失败。

9. 总线锁与 MUX 树

9.1 默认锁实现

1
2
3
4
5
static const struct i2c_lock_operations i2c_adapter_lock_ops = {
.lock_bus = i2c_adapter_lock_bus, /* rt_mutex_lock_nested */
.trylock_bus = i2c_adapter_trylock_bus,
.unlock_bus = i2c_adapter_unlock_bus,
};

i2c_adapter_depth() 沿 i2c_parent_is_i2c_adapter() 向上计数,用于 lockdep 嵌套子类,避免 mux 父子 adapter 死锁。

9.2 地址占用检查

  • i2c_check_addr_busy() — 本 adapter 及 mux 父/子 链上是否已有该地址;
  • i2c_encode_flags_to_addr() — 10 位地址、I2C_CLIENT_SLAVE 等标志编码到唯一键值。

10. 总线恢复(Bus Recovery)

当 SDA 被拉低导致总线挂死时,adapter 可配置 adap->bus_recovery_info

机制 函数
通用 SCL 翻转 i2c_generic_scl_recovery() — 9 个时钟脉冲 + STOP 条件
GPIO 自动配置 DT scl-gpios / sda-gpiosi2c_gpio_init_generic_recovery()
Pinctrl recovery / gpio state 切换
入口 i2c_recover_bus(adap) — 调用 recover_bus 回调

初始化: i2c_register_adapter()i2c_init_recovery();失败则清除 bus_recovery_info(非致命)。

RK3588 i2c-rk3x 是否在 DT 中启用 recovery 取决于板级 dtsi;core 提供通用框架。


11. Host Notify 与 SMBus Alert

功能 位置 说明
Host Notify IRQ domain i2c_setup_host_notify_irq_domain() 为 7 位地址各建虚拟 IRQ
中断转发 i2c_handle_smbus_host_notify(adap, addr) 总线驱动 ISR 中调用
SMBus Alert i2c_setup_smbus_alert() i2c-core-smbus.c,adapter 注册时调用

12. 驱动注册与遗留探测

12.1 i2c_register_driver() / i2c_del_driver()

  • i2c_driver 挂到 i2c_bus_type
  • 对已存在的每个 adapter 调用 i2c_do_add_adapter()i2c_detect()

12.2 i2c_detect()(遗留机制)

当驱动提供 address_list + detect() 时:

  1. 对列表中每个 7 位地址创建临时 client;
  2. i2c_default_probe() — SMBus Quick Write 或 Byte Read(避免损坏 EEPROM);
  3. 调用 driver->detect() 填充 i2c_board_info
  4. i2c_new_client_device() 实例化。

RK3588: 现代平台 几乎不用 class-based detect;设备由 DT 声明。仍保留以兼容旧 x86/工业板。

12.3 i2c_clients_command()

向 adapter 下所有已绑定 client 广播 driver->command()(电源管理等)。


13. Sysfs 与 Debugfs

13.1 Adapter 属性(/sys/bus/i2c/devices/i2c-N/

属性 权限 功能
name RO adapter 名称
new_device WO echo name addr > new_device 动态创建 client
delete_device WO 删除 userspace 创建的 client
uevent 导出 modalias 等

13.2 Client 属性

namemodalias — 支持 udev 冷插拔 modprobe

13.3 Debugfs

/sys/kernel/debug/i2c/i2c-N/ — 各 adapter 子目录(具体节点由主机驱动或 core 扩展添加)。


14. 设备树时序解析

i2c_parse_fw_timings(dev, &timings, use_defaults) 从 DT 读取:

  • clock-frequencyi2c-scl-falling-timei2c-scl-rising-time 等;
  • 主机驱动(如 DesignWare、部分 SoC)计算分频,非 RK3x 专用

RK3x 通常在驱动内直接读 clock-frequency;若需统一时序可调用此 API。


15. 与兄弟文件的分工

文件 职责 RK3588
i2c-core-base.c 模型、transfer、adapter/client 核心,必用
i2c-core-smbus.c i2c_smbus_*i2c_smbus_xfer 传感器/PMIC 常用
i2c-core-of.c of_i2c_register_devices、OF notifier 设备枚举主路径
i2c-core-acpi.c ACPI 枚举、OP region 通常不编译
i2c-core-slave.c I2C slave 模式 视配置
i2c-boardinfo.c 静态 boardinfo 表 通常不用

16. RK3588 端到端路径示例

i2c-rk3x + 摄像头传感器为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. platform_driver probe (i2c-rk3x.c)
adap->algo->master_xfer = rk3x_i2c_xfer
i2c_add_numbered_adapter(&i2c->adap) // nr 来自 DT alias

2. i2c_register_adapter() [i2c-core-base.c]
of_i2c_register_devices(adap) [i2c-core-of.c]
→ 解析 i2cN 下子节点
→ i2c_new_client_device() [i2c-core-base.c]

3. i2c_device_probe() [i2c-core-base.c]
of_irq_get → client->irq
sensor i2c_driver->probe()

4. 传感器驱动读写
i2c_transfer(adap, msgs, 2)
→ rk3x_i2c_xfer() [i2c-rk3x.c 硬件寄存器]

关键配置: CONFIG_I2C=yCONFIG_I2C_RK3X=yCONFIG_I2C_CHARDEV(可选 /dev/i2c-N)。


17. 主要导出符号分类

17.1 传输与适配器

i2c_transfer, __i2c_transfer, i2c_transfer_buffer_flags, i2c_get_adapter, i2c_put_adapter, i2c_recover_bus, i2c_generic_scl_recovery, i2c_handle_smbus_host_notify, i2c_get_dma_safe_msg_buf, i2c_put_dma_safe_msg_buf

17.2 设备生命周期

i2c_add_adapter, i2c_add_numbered_adapter, i2c_del_adapter, devm_i2c_add_adapter, i2c_new_client_device, i2c_unregister_device, i2c_new_dummy_device, devm_i2c_new_dummy_device, i2c_new_ancillary_device, i2c_find_device_by_fwnode, i2c_find_adapter_by_fwnode, i2c_get_adapter_by_fwnode

17.3 驱动模型

i2c_bus_type, i2c_client_type, i2c_adapter_type, i2c_register_driver, i2c_del_driver, i2c_verify_client, i2c_verify_adapter, i2c_match_id, i2c_get_match_data, i2c_for_each_dev, i2c_clients_command

17.4 其它

i2c_freq_mode_string, i2c_check_7bit_addr_validity_strict, i2c_dev_irq_from_resources, i2c_adapter_depth, i2c_parse_fw_timings, i2c_probe_func_quick_read, i2c_get_device_id


18. 调试建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看总线与设备
ls /sys/bus/i2c/devices/
cat /sys/bus/i2c/devices/i2c-*/name

# 查看 client 与驱动绑定
ls -l /sys/bus/i2c/devices/*-*

# 内核日志
dmesg | grep -iE 'i2c-core|i2c-[0-9]|rk3x'

# 启用 transfer 跟踪(需 CONFIG_EVENT_TRACING)
# echo 1 > /sys/kernel/debug/tracing/events/i2c/enable

# debugfs
ls /sys/kernel/debug/i2c/

常见问题定位:

现象 可能涉及 base.c 逻辑
无 client 设备 of_i2c_register_devices 未执行或 DT 无子节点
probe defer adapter 未注册、EPROBE_DEFER on GPIO/recovery
地址冲突 i2c_check_addr_ex / mux 父子地址检查
传输超时 i2c_transfer 重试、adap->timeout
总线卡死 i2c_recover_bus、recovery info 未配置

19. 结论

i2c-core-base.c 是 Linux I2C 子系统的 中枢实现,涵盖:

  1. 子系统与总线注册postcore_initcall);
  2. Adapter/Client 设备模型 与 driver core 集成;
  3. i2c_transfer 统一传输入口 及锁、quirk、trace、重试;
  4. 总线恢复、Host Notify、DMA bounce buffer
  5. 驱动注册与遗留 detect、sysfs 动态设备、debugfs;
  6. OF/ACPI/SMBus 子模块的衔接调用点。

RK3588:所有 SoC 内置 I2C(i2c-rk3x)与 DT 声明的外设均 必须经过本文件;理解 i2c_register_adapterof_i2c_register_devicesi2c_new_client_devicei2c_transfer 链路是分析板级 I2C 问题的关键。


20. 推荐阅读顺序

  1. i2c-core-base.ci2c_register_adapteri2c_transferi2c_new_client_device
  2. i2c-core-of.c — RK3588 设备枚举
  3. busses/i2c-rk3x.c — 硬件 master_xfer
  4. include/linux/i2c.h — 公共 API 与结构体
  5. Documentation/i2c/ — 实例化设备、总线恢复说明

21. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-core-base.c(约 2723 行)分析。

RK3588 kernel-6.1/drivers/i2c/i2c-core-of.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-core-of.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-core-of.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-core-of.c(约 220 行)

编译关系: 在启用 Open Firmware / Device Tree 支持时编入 i2c-core.ko

1
i2c-core-$(CONFIG_OF) += i2c-core-of.o

ARM64 Rockchip 内核 必然启用 CONFIG_OF,故本文件在 RK3588 上 始终参与链接

文档保存路径: linuxDoc/drivers/i2c/

定位: I2C 子系统的 Device Tree 支持层:将 DTS 中挂在 I2C adapter 下的子节点解析为 i2c_board_info,并调用 i2c_new_client_device() 实例化 i2c_client;同时提供 OF 匹配辅助 与(可选)动态 DT 热插拔 通知。

与 RK3588 的关系: 这是 RK3588 上 I2C 从设备枚举的主路径i2c-rk3x.c 注册 adapter 后,板级 dtsi 中 &i2cN { ... } 下的 compatible + reg 子节点均经本文件创建 client,再与对应 i2c_driver 匹配 probe。


2. 功能总览

功能模块 符号 可见性 作用
DT → board_info of_i2c_get_board_info() 导出 device_node 填充 i2c_board_info
单设备注册 of_i2c_register_device() static 解析节点并 i2c_new_client_device()
批量枚举 of_i2c_register_devices() core 内 adapter 注册时遍历子节点创建设备
驱动匹配 i2c_of_match_device() 导出 OF compatible 匹配(含 sysfs 回退)
动态 DT i2c_of_notifier / of_i2c_notify() CONFIG_OF_DYNAMIC 运行时增删 I2C 设备节点

3. 在 I2C 栈中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
板级 DTS (&i2c2 { hym8563@51 { compatible; reg; }; })


i2c-rk3x probe → i2c_add_numbered_adapter()


i2c_register_adapter() [i2c-core-base.c]

├── of_i2c_register_devices() [i2c-core-of.c] ← 本文
│ for_each_available_child_of_node
│ → of_i2c_get_board_info()
│ → i2c_new_client_device() [i2c-core-base.c]

└── i2c_device_probe() [驱动绑定]

ACPIi2c_acpi_register_devices)和 boardinfoi2c_scan_static_board_info)并列,RK3588 仅走 OF 路径


4. of_i2c_get_board_info() — DT 节点 → i2c_board_info

1
2
int of_i2c_get_board_info(struct device *dev, struct device_node *node,
struct i2c_board_info *info);

4.1 解析流程

步骤 DT 属性 / API 填入字段
清零 memset(info, 0, ...)
设备类型名 of_modalias_node() info->type
I2C 地址 reg(u32) info->addr + 标志位
节点引用 info->of_nodeinfo->fwnode
Host Notify host-notify(bool) I2C_CLIENT_HOST_NOTIFY
唤醒源 wakeup-source I2C_CLIENT_WAKE

4.2 of_modalias_node() 行为

compatible 属性 第一个 字符串;若含 ,,则 modalias 为 逗号后 部分(去掉厂商前缀)。

示例:

1
compatible = "haoyu,hym8563";

info->type = "hym8563"(与 MODULE_DEVICE_TABLE(of, ...) 中条目或驱动名对应)。

4.3 reg 属性与地址标志(dt-bindings/i2c/i2c.h

1
2
#define I2C_TEN_BIT_ADDRESS    (1 << 31)   /* 10 位寻址 */
#define I2C_OWN_SLAVE_ADDRESS (1 << 30) /* 本机为 slave 设备 */
reg 高位 处理
I2C_TEN_BIT_ADDRESS 清除高位 → info->flags |= I2C_CLIENT_TEN
I2C_OWN_SLAVE_ADDRESS 清除高位 → info->flags |= I2C_CLIENT_SLAVE
低 7/10 位 实际从机地址

RK3588 板级 DTS 通常仅使用 7 位地址,例如 reg = <0x51>

4.4 错误返回

条件 返回值
modalias 失败 -EINVAL
缺少/非法 reg -EINVALof_property_read_u32 错误码

5. of_i2c_register_devices() — 核心枚举入口

1
void of_i2c_register_devices(struct i2c_adapter *adap);

调用点: i2c_register_adapter()i2c-core-base.c),在 adapter device_register() 之后、ACPI/boardinfo 扫描之前执行。

5.1 前置条件

1
2
if (!adap->dev.of_node)
return;

Adapter 的 struct device 必须已关联 DT 节点(i2c-rk3x 在 probe 中设置 adap.dev.of_node = pdev->dev.of_node)。无 of_node不枚举任何子设备

5.2 遍历哪一层子节点?

1
2
3
4
5
bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
if (!bus)
bus = of_node_get(adap->dev.of_node);

for_each_available_child_of_node(bus, node) { ... }
情况 遍历根节点
存在子节点 i2c-bus adapter/i2c-bus/ 下的 available 子节点
不存在 直接 adapter 节点下的子节点(RK3588 常用)

for_each_available_child_of_node 会跳过:

  • status = "disabled" 的节点;
  • 未通过 OF 可用性检查的节点。

5.3 OF_POPULATED 防重复

1
2
if (of_node_test_and_set_flag(node, OF_POPULATED))
continue;

已实例化的节点不会再次注册。注销时 i2c_unregister_device()of_node_clear_flag(..., OF_POPULATED)

注册失败时清除标志:

1
of_node_clear_flag(node, OF_POPULATED);

5.4 与 RK3588 DTS 的对应关系

SoC dtsi 中控制器模板(rk3588s.dtsi):

1
2
3
4
5
6
i2c0: i2c@fd880000 {
compatible = "rockchip,rk3588-i2c", "rockchip,rk3399-i2c";
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};

板级 overlay(rk3588s-tablet.dtsi):

1
2
3
4
5
6
7
8
9
10
&i2c2 {
status = "okay";

hym8563: hym8563@51 {
compatible = "haoyu,hym8563";
reg = <0x51>;
wakeup-source;
...
};
};

枚举结果:

  • i2c_new_client_device()i2c-2 总线上创建 client;
  • sysfs 设备名类似 2-0051(bus 号 + 地址);
  • client->dev.of_node 指向 hym8563@51 节点。

6. of_i2c_register_device() — 单节点实例化

1
2
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,
struct device_node *node);
  1. of_i2c_get_board_info(&adap->dev, node, &info)
  2. i2c_new_client_device(adap, &info) → 进入 driver core,触发 i2c_device_probe()

of_i2c_register_devices()动态 notifier 共用。


7. i2c_of_match_device() — 驱动与 client 匹配

1
2
3
const struct of_device_id *
i2c_of_match_device(const struct of_device_id *matches,
struct i2c_client *client);

7.1 匹配顺序

  1. of_match_device(matches, &client->dev) — 标准 OF 匹配(client->dev.of_node 存在时);
  2. i2c_of_match_device_sysfs() — 回退:通过 sysfs 手动 new_device 创建的 client 无 of_node,用 client->namecompatible 字符串比较(支持 vendor,model 的短名匹配)。

7.2 在 core 中的使用

位置 用途
i2c_device_match() 总线 match 回调
i2c_device_probe() id_table 时要求 OF/ACPI 匹配之一

RK3588 驱动应提供 of_match_table,例如:

1
2
3
4
5
static const struct of_device_id rk3x_i2c_of_match[] = {
{ .compatible = "rockchip,rk3588-i2c" },
{ .compatible = "rockchip,rk3399-i2c" },
{ }
};

8. 动态 Device Tree(CONFIG_OF_DYNAMIC

当内核启用 动态 OF(运行时 overlay / 配置扩展)时,编译 of_i2c_notify() 与全局 i2c_of_notifier

注册: i2c_init()of_reconfig_notifier_register(&i2c_of_notifier)

8.1 OF_RECONFIG_CHANGE_ADD

1
2
3
of_find_i2c_adapter_by_node(rd->dn->parent)  /* 父节点必须是 adapter 的 of_node */
→ 清除 FWNODE_FLAG_NOT_DEVICE(便于 fw_devlink)
→ of_i2c_register_device()

要求新节点的 父 DT 节点 对应已注册的 i2c_adapter

8.2 OF_RECONFIG_CHANGE_REMOVE

1
2
of_find_i2c_device_by_node(rd->dn)
→ i2c_unregister_device()

8.3 RK3588 说明

产品 rockchip_linux_defconfig 通常 不强调 CONFIG_OF_DYNAMIC;量产固件多在启动时加载完整 DTB。该路径主要用于 开发/测试 overlay,了解即可。


9. 与 I2C MUX 的协作

i2c-mux.ci2c_mux_add_adapter() 中为每个 channel 创建子 adapter,并设置:

1
priv->adap.dev.of_node = child;  /* mux 节点下对应 channel 的子节点 */

随后 i2c_add_numbered_adapter() / i2c_add_adapter() 同样会调用 of_i2c_register_devices(),从而枚举 该 channel 下 DT 子设备。

典型 DTS 结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
&i2c4 {
i2c-mux@70 {
compatible = "nxp,pca9548";
reg = <0x70>;
#address-cells = <1>;
#size-cells = <0>;

i2c@0 {
reg = <0>;
sensor@48 { compatible = "..."; reg = <0x48>; };
};
};
};

MUX 框架负责 channel adapter 的 of_node 指向 i2c@N 节点;具体传感器仍由 i2c-core-of.c 枚举


10. 与 i2c-core-base.c 的衔接

base.c 逻辑 of.c 参与点
i2c_register_adapter() 调用 of_i2c_register_devices(adap)
i2c_device_probe() of_irq_get / of_clk_set_defaults(非 of.c,但依赖 client->dev.of_node
i2c_unregister_device() 清除 OF_POPULATED
i2c_init() 注册 i2c_of_notifier(若 CONFIG_OF_DYNAMIC
i2c_add_adapter() of_alias_get_id(dev->of_node, "i2c") 决定 bus 号(在 base.c,非 of.c)

IRQ / 时钟 / pinctrl 不在 i2c-core-of.c 解析,由 i2c_driver 的 probe 通过 dev->of_node 自行读取(如 devm_request_irqdevm_clk_get)。


11. 三种实例化方式对比(RK3588)

方式 实现 RK3588
Device Tree i2c-core-of.c 主路径
ACPI i2c-core-acpi.c 不使用
静态 boardinfo i2c-boardinfo.c 不使用
sysfs new_device i2c-core-base.c 调试可选
遗留 detect i2c-core-base.c 基本不用

12. RK3588 端到端示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
启动
rk3x_i2c_probe(i2c@fea90000) /* i2c1 */
adap.dev.of_node = pdev->dev.of_node
i2c_add_numbered_adapter() /* nr=1, 来自 aliases */

i2c_register_adapter()
of_i2c_register_devices(adap)
节点: &i2c1 下无子设备 → 无 client

rk3x_i2c_probe(i2c@feaa00000) /* i2c2, tablet dtsi 有子节点 */
of_i2c_register_devices()
hym8563@51 → client 2-0051, type "hym8563"
cw2015@62 → client 2-0062
bq25703@6b → client 2-006b

hym8563 i2c_driver probe
of_match_device("haoyu,hym8563")
of_irq_get / clk / pinctrl from of_node

Aliasesrk3588s.dtsi):

1
2
3
4
5
aliases {
i2c0 = &i2c0;
...
i2c8 = &i2c8;
};

保证 i2c_add_numbered_adapteradap->nr 与 DTS i2cN 编号一致。


13. 导出符号与桩函数

符号 CONFIG_OF 开启 关闭
of_i2c_get_board_info 实现 inline 空/错误
of_i2c_register_devices 实现 inline 空
i2c_of_match_device 实现 NULL
of_find_i2c_*_by_node include/linux/i2c.h inline 返回 NULL

14. 调试与常见问题

14.1 检查枚举结果

1
2
3
4
5
ls /sys/bus/i2c/devices/
# 例如 2-0051 -> ../../../devices/platform/feaa0000.i2c/i2c-2/2-0051

cat /sys/bus/i2c/devices/2-0051/name
cat /sys/bus/i2c/devices/2-0051/of_node/name # 需 CONFIG_SYSFS 关联

14.2 设备未出现

可能原因 检查
adapter status = "disabled" 父节点 &i2cN { status = "okay"; }
子设备 status = "disabled" for_each_available_child_of_node 会跳过
adapter 无 of_node 主机驱动是否正确设置 adap.dev.of_node
reg 缺失 dmesg: of_i2c: invalid reg
compatible 问题 dmesg: of_i2c: modalias failure
重复枚举 OF_POPULATED 已置位
地址冲突 i2c_new_client_device 失败日志

14.3 驱动未 probe

  • i2c_of_match_device 失败:检查 compatible 与驱动 of_match_table
  • 模块未加载:modalias / depmod
  • probe defer:依赖时钟/_regulator 未就绪(与 of.c 无直接关系)。

14.4 内核日志

1
dmesg | grep -iE 'of_i2c|i2c-[0-9]|Failed to create I2C device'

15. 源码结构速查(全文约 220 行)

1
2
3
4
5
L22-63   of_i2c_get_board_info()      导出:DT → i2c_board_info
L65-83 of_i2c_register_device() 单设备注册
L85-114 of_i2c_register_devices() adapter 子节点批量枚举
L116-160 i2c_of_match_device*() OF/sysfs 匹配
L162-220 of_i2c_notify / i2c_of_notifier [CONFIG_OF_DYNAMIC]

16. 结论

i2c-core-of.c 体量小,却是 ARM/DT 平台 I2C 设备发现的枢纽

  1. 将 DTS 子节点的 compatible + reg 转为 i2c_board_info
  2. adapter 注册时 自动创建 i2c_client
  3. 通过 OF_POPULATED 避免重复实例化;
  4. 支持可选的 动态 DT 热插拔
  5. 增强 i2c_of_match_device 以兼容 sysfs 创建设备。

RK3588:板级所有 I2C 外设(RTC、PMIC、Codec、触摸、传感器、MUX 下设备等)均依赖本文件与 i2c-core-base.c 的配合;分析硬件 bring-up 问题时应 优先核对 DTS 与 dmesg 中 of_i2c 相关日志


17. 推荐阅读顺序

  1. i2c-core-of.cof_i2c_register_devicesof_i2c_get_board_info
  2. i2c-core-base.ci2c_register_adapteri2c_new_client_device
  3. arch/arm64/boot/dts/rockchip/*.dtsi — 板级 &i2cN 子节点
  4. busses/i2c-rk3x.c — adapter 注册与 of_node 绑定
  5. Documentation/devicetree/bindings/i2c/ — 绑定文档

18. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-core-of.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-core-slave.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-core-slave.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-core-slave.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-core-slave.c(约 128 行)

编译关系: 仅在启用 CONFIG_I2C_SLAVE 时编入 i2c-core.ko

1
i2c-core-$(CONFIG_I2C_SLAVE) += i2c-core-slave.o

文档保存路径: linuxDoc/drivers/i2c/

定位: I2C 子系统的 从机(Slave)模式核心薄层:在已有 i2c_clienti2c_adapter 框架上,提供 注册/注销从机回调向从机后端投递总线事件、以及 从 DT 检测控制器是否应工作在从机模式 的通用 API。具体从机行为由 后端驱动(如 i2c-slave-eeprom.c)和 主机控制器驱动的 reg_slave/unreg_slave 实现。

与 RK3588 的关系:

项目 RK3588 典型情况
CONFIG_I2C_SLAVE Rockchip arm64 defconfig 通常未启用 → 本文件 默认不链接
i2c-rk3x.c 未实现 algo->reg_slave / unreg_slave
产品角色 SoC I2C 控制器作 主设备(Master) 访问传感器/PMIC 等

若需让 RK3588 Linux 作为 I2C 从设备 被外部主机访问,除打开 CONFIG_I2C_SLAVE 外,还须 硬件与 i2c-rk3x 驱动支持从机模式(当前树中未提供)。本文仍完整说明本文件机制及与 DT、其它驱动的衔接。


2. 功能总览

符号 导出 作用
i2c_slave_register() GPL 绑定 slave_cb,调用 adapter reg_slave
i2c_slave_unregister() GPL 调用 adapter unreg_slave,清除回调
i2c_slave_event() GPL 主机驱动收到总线活动时调用,转发至 slave_cb
i2c_detect_slave_mode() GPL 扫描 DT 子节点 reg 是否含 I2C_OWN_SLAVE_ADDRESS

Tracepoint: trace/events/i2c_slave.htrace_i2c_slave),在 i2c_slave_event() 中可选记录。


3. I2C 从机模式架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
外部 I2C Master                    Linux SoC
───────────────── ─────────
│ │
│ START + 从机地址 + R/W │
▼ ▼
┌─────────┐ IRQ/DMA ┌──────────────────┐
│ 总线 │──────────────►│ bus driver │
└─────────┘ │ (reg_slave 实现) │
│ i2c_slave_event │
└────────┬─────────┘

┌────────▼─────────┐
│ i2c-core-slave.c │ ← 本文(转发)
└────────┬─────────┘
│ slave_cb
┌────────▼─────────┐
│ 后端驱动 │
│ slave-eeprom 等 │
└────────┬─────────┘
│ sysfs 等
┌────────▼─────────┐
│ 用户空间 / 内核 │
└──────────────────┘

内核文档:Documentation/i2c/slave-interface.rst

要点: i2c-core-slave.c 不处理硬件寄存器;仅协调 adapter 算法从机后端回调


4. 数据结构与事件类型(include/linux/i2c.h

4.1 回调类型

1
2
typedef int (*i2c_slave_cb_t)(struct i2c_client *client,
enum i2c_slave_event event, u8 *val);

struct i2c_client 含成员 i2c_slave_cb_t slave_cbCONFIG_I2C_SLAVE 时有效)。

4.2 事件枚举

事件 含义(简述)
I2C_SLAVE_WRITE_REQUESTED 主机发起写:地址+写位已匹配,尚未收数据
I2C_SLAVE_WRITE_RECEIVED 收到一字节写数据(val 为数据)
I2C_SLAVE_READ_REQUESTED 主机发起读:应填充首字节到 *val
I2C_SLAVE_READ_PROCESSED 上一读字节已发出,准备下一字节
I2C_SLAVE_STOP 检测到 STOP(或等效结束)

后端在 READ_REQUESTED / READ_PROCESSED 中通过 *val 提供读出数据;WRITE_RECEIVED*val 取写入数据。详见 slave-interface.rst 中 mandatory/optional 说明。


5. i2c_slave_register() — 注册从机

1
int i2c_slave_register(struct i2c_client *client, i2c_slave_cb_t slave_cb);

5.1 流程

1
2
3
4
5
6
7
8
参数检查 (client, slave_cb)
→ 7 位地址: i2c_check_7bit_addr_validity_strict()
→ 检查 client->adapter->algo->reg_slave 非空
→ client->slave_cb = slave_cb
→ i2c_lock_bus(ROOT_ADAPTER)
→ ret = adap->algo->reg_slave(client)
→ i2c_unlock_bus()
→ 失败则 slave_cb = NULL

5.2 注意事项

检查 行为
未设置 I2C_CLIENT_SLAVE 仅警告,仍继续注册(可能与其它 master 地址冲突)
reg_slave -EOPNOTSUPP
10 位地址 跳过 strict 7 位检查

谁调用: 从机后端在 probe 中调用,例如 i2c-slave-eeprom.ci2c-slave-testunit.ci2c-smbus.c 中 Host Notify 从机路径(CONFIG_I2C_SLAVE)。


6. i2c_slave_unregister() — 注销从机

1
int i2c_slave_unregister(struct i2c_client *client);

对称调用 adap->algo->unreg_slave(client),成功则 client->slave_cb = NULL。无 unreg_slave 时返回 -EOPNOTSUPP

调用时机: 后端驱动 remove、或 Host Notify 客户端拆除时。


7. i2c_slave_event() — 事件投递

1
2
int i2c_slave_event(struct i2c_client *client,
enum i2c_slave_event event, u8 *val);
  • 调用 client->slave_cb(client, event, val)
  • 若启用 trace,记录 trace_i2c_slave(client, event, val, ret)
  • 返回值供 总线驱动 决定 ACK/NACK 等(依控制器实现)。

调用者:实现了从机模式的 I2C 主机驱动(如 i2c-stm32f7.ci2c-imx.ci2c-rcar.c 等在 ISR/状态机中调用)。

示例(STM32):

1
2
3
i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
i2c_slave_event(i2c_dev->slave_running, I2C_SLAVE_WRITE_RECEIVED, &val);
i2c_slave_event(i2c_dev->slave_running, I2C_SLAVE_STOP, &val);

8. i2c_detect_slave_mode() — DT 从机模式检测

1
bool i2c_detect_slave_mode(struct device *dev);

8.1 逻辑

dev->of_node 存在,遍历其 直接子节点,读取 reg

1
2
if (reg & I2C_OWN_SLAVE_ADDRESS)
return true;

I2C_OWN_SLAVE_ADDRESS 定义于 include/dt-bindings/i2c/i2c.h(1 << 30)

8.2 与 i2c-core-of.c 的配合

of_i2c_get_board_info() 解析子设备时:

1
2
3
4
if (addr & I2C_OWN_SLAVE_ADDRESS) {
addr &= ~I2C_OWN_SLAVE_ADDRESS;
info->flags |= I2C_CLIENT_SLAVE;
}
  • 子设备I2C_OWN_SLAVE_ADDRESS → 创建的 client 带 I2C_CLIENT_SLAVE
  • i2c_detect_slave_mode(dev)控制器设备 dev 上扫描子节点,判断整路 I2C 是否配置为从机控制器模式(任一子节点声明 own-slave 地址即返回 true)。

8.3 使用方

驱动 用途
i2c-designware-core i2c_dw_configure() 选择 master/slave 硬件配置
i2c-at91-core dev->slave_detected 标志

RK3588 i2c-rk3x 源码中 未调用 i2c_detect_slave_mode()

8.4 ACPI

1
2
3
} else if (IS_BUILTIN(CONFIG_ACPI) && ACPI_HANDLE(dev)) {
dev_dbg(dev, "ACPI slave is not supported yet\n");
}

ACPI 从机检测 尚未实现,恒返回 false。


9. Kconfig 与相关模块

9.1 CONFIG_I2C_SLAVE

1
2
3
4
5
config I2C_SLAVE
bool "I2C slave support"
help
This enables Linux to act as an I2C slave device. Note that your I2C
bus master driver also needs to support this functionality.

依赖链:

1
2
3
4
5
CONFIG_I2C_SLAVE=y
→ i2c-core-slave.o 链接进 i2c-core.ko
→ 可选:
CONFIG_I2C_SLAVE_EEPROM (i2c-slave-eeprom.ko)
CONFIG_I2C_SLAVE_TESTUNIT (i2c-slave-testunit.ko)

9.2 从机后端驱动(同目录,非本文)

文件 作用
i2c-slave-eeprom.c 模拟 I2C EEPROM,用户空间经 sysfs 读写“存储”
i2c-slave-testunit.c 测试用从机,配合总线 master 测试多主机、Host Notify 等

9.3 i2c-smbus.c 中的从机用法

启用 CONFIG_I2C_SLAVE 时,SMBus Host Notify 可注册从机回调 i2c_slave_host_notify_cb,在收到主机写通知时置位 status->host_notifywake_up


10. 主机驱动支持情况(本树)

algo->reg_slave / unreg_slave 在部分 busses/ 驱动中实现,例如:

  • i2c-stm32f7.c
  • i2c-imx.c
  • i2c-rcar.c
  • i2c-pxa.c
  • i2c-npcm7xx.c
  • i2c-mlxbf.c
  • i2c-emev2.c
  • DesignWare 系列(配合 i2c_detect_slave_mode 配置 master/slave)

未实现(RK3588 相关):

  • i2c-rk3x.c reg_slave / unreg_slave

因此即使打开 CONFIG_I2C_SLAVE,在 RK3588 上对 rk3x 控制器 调用 i2c_slave_register() 仍会得到 -EOPNOTSUPP


11. Device Tree 与实例化

11.1 从机后端设备节点

从机后端仍是标准 I2C client,在 DT 中挂在某条 I2C 总线下,且 reg 需体现从机地址。文档说明实例化时地址空间需加 0x1000 偏移(历史约定,与 I2C_OWN_SLAVE_ADDRESS 标志配合使用)。

用户空间示例(slave-interface.rst):

1
echo slave-24c02 0x1064 > /sys/bus/i2c/devices/i2c-1/new_device

0x1064 = 0x1000 + 7 位地址 0x64)

11.2 控制器“从机模式”检测示例

若某 DT 设计让 Linux 控制器监听固定地址,子节点可写:

1
2
3
4
linux-i2c-slave@50 {
reg = <0x50 | (1 << 30)>; /* I2C_OWN_SLAVE_ADDRESS */
compatible = "...";
};

i2c_detect_slave_mode(&controller->dev) 返回 true → DesignWare 等驱动配置为 硬件从机

RK3588 产品 DTS 极少 这样配置;常见为 SoC 作 master 访问外设。


12. 与 Master 路径对比

维度 Master(RK3588 常态) Slave(本文件)
配置 默认 CONFIG_I2C_SLAVE
传输 API i2c_transfer() / SMBus 无主动 transfer;被动 i2c_slave_event
Adapter algo master_xfer reg_slave / unreg_slave
DT 标志 reg = <0xNN> reg 含 bit30 或实例化 0x1xxx
RK3x 支持 不支持

13. 源码全文结构(约 128 行)

1
2
3
4
5
L17-18   trace/events/i2c_slave.h
L20-57 i2c_slave_register()
L60-83 i2c_slave_unregister()
L85-95 i2c_slave_event()
L97-127 i2c_detect_slave_mode()

无其它隐藏逻辑;文件刻意保持 薄封装,便于总线驱动与后端解耦。


14. 启用与调试(非 RK3588 默认)

14.1 内核配置

1
2
CONFIG_I2C_SLAVE=y
CONFIG_I2C_SLAVE_EEPROM=m # 可选

重新编译 i2c-core 及所需 bus 驱动、后端模块。

14.2 验证 API 是否编入

1
grep i2c_slave_register /proc/kallsyms   # 需 CONFIG_KALLSYMS

14.3 Trace

1
2
# 若配置了 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/i2c_slave/enable

14.4 常见错误

现象 原因
-EOPNOTSUPP on register adapter 无 reg_slaverk3x
地址冲突警告 未设 I2C_CLIENT_SLAVE 且与其它 client 同址
invalid address 7 位保留地址未通过 strict 检查

15. RK3588 结论与扩展方向

  1. i2c-core-slave.c 提供 Linux I2C 从机框架的 4 个核心 API,本身仅约 128 行。
  2. 默认 Rockchip 内核 通常 不启用 CONFIG_I2C_SLAVE,且 rk3x 无硬件从机钩子,故 RK3588 量产路径 不依赖 本文件。
  3. 若产品需要 SoC 作 I2C 从设备(例如被 MCU/FPGA 访问),需要:
    • 确认 RK3588 I2C IP 是否支持 slave 模式(数据手册);
    • i2c-rk3x.c 中实现 reg_slave/unreg_slave 及中断路径中调用 i2c_slave_event
    • 打开 CONFIG_I2C_SLAVE 并编写或复用从机后端驱动。

16. 推荐阅读顺序

  1. i2c-core-slave.c — 四个导出函数
  2. Documentation/i2c/slave-interface.rst — 事件语义与后端编写
  3. i2c-slave-eeprom.c — 完整后端示例
  4. drivers/i2c/busses/i2c-stm32f7.creg_slave + i2c_slave_event 调用示例
  5. i2c-core-of.cI2C_OWN_SLAVE_ADDRESS 解析

17. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-core-slave.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-core-smbus.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-core-smbus.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-core-smbus.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-core-smbus.c(约 723 行)

编译关系: 作为 i2c-core 复合模块的固定组成部分始终参与链接CONFIG_I2C=y 时):

1
i2c-core-objs := i2c-core-base.o i2c-core-smbus.o

i2c-smbus.c(独立模块 CONFIG_I2C_SMBUS)分工不同:本文件实现 SMBus 协议栈与 I2C 模拟i2c-smbus.ko 实现 SMBus Alert / Host Notify 等扩展协议的处理逻辑

文档保存路径: linuxDoc/drivers/i2c/

定位: Linux I2C 核心的 SMBus 层:PEC/CRC、各类 SMBus 读写 API、统一入口 i2c_smbus_xfer、在 adapter 无原生 SMBus 时通过 i2c_transfer 模拟,以及 adapter 注册时的 SMBus Alert 设备创建入口

与 RK3588 的关系: i2c-rk3x.c 仅声明 I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL algo->smbus_xfer,故 RK3588 上几乎所有 SMBus 访问均走 i2c_smbus_xfer_emulated()__i2c_transfer()rk3x_i2c_xfer()。PMIC(如 rk808)、部分传感器驱动大量使用 i2c_smbus_read_byte_data() 等 API。


2. 功能总览

模块 行号(约) 主要符号 作用
PEC/CRC8 28–93 i2c_smbus_pec, i2c_smbus_add_pec SMBus 分组错误校验
便捷读写 API 95–300 i2c_smbus_read/write_* Byte/Word/Block/I2C-Block 协议封装
I2C 模拟 322–520 i2c_smbus_xfer_emulated 将 SMBus 转为 1~2 条 i2c_msg
统一传输 535–619 i2c_smbus_xfer, __i2c_smbus_xfer 锁总线、选原生/模拟、重试、trace
块读回退 640–676 i2c_smbus_read_i2c_block_data_or_emulated 无 I2C block 时用 byte/word 拼读
SMBus Alert 入口 693–722 i2c_new_smbus_alert_device, i2c_setup_smbus_alert 创建 ARA(0x0c) client(需 CONFIG_I2C_SMBUS

3. 在 I2C 栈中的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
驱动 (rk808, 传感器, hwmon...)
│ i2c_smbus_read_byte_data() 等

i2c-core-smbus.c ← 本文
│ i2c_smbus_xfer()
│ ├─ algo->smbus_xfer (若有)
│ └─ i2c_smbus_xfer_emulated()
│ └─ __i2c_transfer()

i2c-core-base.c
│ i2c_transfer() → rk3x_i2c_xfer

i2c-rk3x.c (硬件)

可选并行路径:
CONFIG_I2C_SMBUS → i2c-smbus.ko (Alert IRQ、Host Notify 从机等)

文件头注释:

SMBus specific extensions (e.g. smbalert) are handled in a separate i2c-smbus module.


4. SMBus 协议类型与便捷 API

所有便捷函数最终调用 i2c_smbus_xfer(adapter, addr, flags, read_write, command, protocol, data)

protocol 常量 便捷 API 协议说明
I2C_SMBUS_QUICK (内部/探测) Quick Command,无数据字节
I2C_SMBUS_BYTE read_byte / write_byte Receive/Send Byte
I2C_SMBUS_BYTE_DATA read_byte_data / write_byte_data 命令字节 + 1 数据字节
I2C_SMBUS_WORD_DATA read_word_data / write_word_data 命令 + 16 位字(小端)
I2C_SMBUS_BLOCK_DATA read_block_data / write_block_data 首字节为长度,最多 32 字节
I2C_SMBUS_I2C_BLOCK_DATA read/write_i2c_block_data I2C 风格块传输(长度由主机指定)
I2C_SMBUS_PROC_CALL (经 xfer) 写 word 再读 word
I2C_SMBUS_BLOCK_PROC_CALL (经 xfer) 写 block 再读 block

数据容器: union i2c_smbus_datainclude/linux/i2c.h),block 数据存放在 data.block[]block[0] 为长度。


5. PEC(Packet Error Code)

5.1 i2c_smbus_pec()

对缓冲区做 增量 CRC-8(多项式 0x1070<<3),用于 SMBus 2.0 PEC。

5.2 写路径 i2c_smbus_add_pec()

msg->buf 末尾追加一字节 PEC(msg->len++)。

5.3 读路径 i2c_smbus_check_pec()

读完成后校验最后一字节;失败返回 -EBADMSG,并缩短 msg->len 隐藏 PEC 字节。

5.4 启用条件

client->flags & I2C_CLIENT_PEC 且在模拟路径中 wants_pec 为真(Quick 与 I2C_BLOCK_DATA 的 I2C-block 类型除外)。


6. i2c_smbus_xfer_emulated() — I2C 模拟核心

当 adapter 没有 可用的 smbus_xfer(或返回 -EOPNOTSUPP 且无 master_xfer)时,由 __i2c_smbus_xfer 调用本函数。

6.1 基本思路

将 SMBus 操作拆成 1 或 2 条 struct i2c_msg

操作类型 典型 msg 布局
Quick 1 条,无数据或仅 R/W 位
Send/Receive Byte 1 条读或写
Byte/Word Data 写:1 条(cmd[+data]);读:写 cmd + 读 data
Block Data 读 写 cmd + 读(I2C_M_RECV_LEN,首字节为长度)
I2C Block 写 cmd+data 或 写 cmd + 读固定长度

最后调用 __i2c_transfer(adapter, msg, nmsgs)(不再重复加 bus 锁,由上层 i2c_smbus_xfer 已加锁)。

6.2 DMA 安全缓冲

i2c_smbus_try_get_dmabuf():对 block 类传输分配 I2C_M_DMA_SAFE 缓冲区,避免栈上 msgbuf 无法 DMA;结束后 kfree

6.3 I2C_M_RECV_LEN

SMBus Block Read 模拟时在读消息上设置 I2C_M_RECV_LEN,由 主机驱动 根据从机返回的第一个字节扩展读长度。若驱动不支持(RK3x 未专门处理该标志),Block Read 模拟可能失败;RK3588 上常见驱动多用 Byte/Word Data,一般不受影响。


7. i2c_smbus_xfer()__i2c_smbus_xfer()

7.1 i2c_smbus_xfer()

1
2
3
res = __i2c_lock_bus_helper(adapter);
res = __i2c_smbus_xfer(...);
i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);

对调用者提供 已加总线锁 的 SMBus 传输(与 i2c_transfer 锁语义一致)。

7.2 __i2c_smbus_xfer() 决策流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__i2c_check_suspended()
trace_smbus_write / trace_smbus_read
flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB

xfer_func = adapter->algo->smbus_xfer
[原子上下文: 优先 smbus_xfer_atomic,否则可能 NULL → 模拟]

若 xfer_func 存在:
重试循环 (EAGAIN + timeout)
若 res != -EOPNOTSUPP 或 无 master_xfer → 返回

否则:
i2c_smbus_xfer_emulated()

trace_smbus_reply / trace_smbus_result

RK3588: rk3xsmbus_xfer始终进入 i2c_smbus_xfer_emulated

7.3 Tracepoint

trace/events/smbus.hsmbus_writesmbus_readsmbus_replysmbus_result


8. i2c_smbus_read_i2c_block_data_or_emulated()

当 adapter 不支持 I2C_FUNC_SMBUS_READ_I2C_BLOCK 时:

  1. 若支持 I2C block,直接 i2c_smbus_read_i2c_block_data
  2. 否则用 word data(每次 2 字节)和 byte data(逐字节)从 command + i 线性地址读取。

前提: 从设备寄存器布局必须 线性映射(文档已警告)。用于兼容老 adapter/简化硬件。


9. SMBus Alert 相关(与 i2c-smbus.ko 分工)

9.1 i2c_new_smbus_alert_device()

创建 Alert Response Address (ARA) 设备:

1
I2C_BOARD_INFO("smbus_alert", 0x0c)

返回 i2c_client *,供 i2c_handle_smbus_alert() 等使用(实现在 i2c-smbus.c 或 core 其它处)。

9.2 i2c_setup_smbus_alert()#if IS_ENABLED(CONFIG_I2C_SMBUS)

i2c_register_adapter() 中调用(i2c-core-base.c):

  • 检查 adapter 父设备是否有 interrupt-names = "smbus_alert"
  • 若有则 i2c_new_smbus_alert_device(adapter, NULL)

未配置 CONFIG_I2C_SMBUS 时,i2c-core.h 提供 空 inline,直接返回 0。

9.3 Host Notify

i2c-core-base.cI2C_CLIENT_HOST_NOTIFYi2c_smbus_host_notify_to_irq();从机侧 Host Notify 在 CONFIG_I2C_SLAVE 时由 i2c-core-smbus.c 后部或 i2c-smbus.c 协作(本树 i2c-core-smbus.c 在 705 行后仅保留 alert setup,Host Notify 从机代码在 #if CONFIG_I2C_SLAVE 块,需对照当前文件——我读到的文件在 705 行结束是 #endif,Host Notify 可能在之前被移走)

让我再确认 i2c-core-smbus.c 是否有 host notify - from first read, lines 250-350 in i2c-smbus.c had host notify with CONFIG_I2C_SLAVE. The grep showed i2c-core-smbus.c had host notify at 250+ but my read of offset 575+ ended at 724 with only smbus alert. The initial grep was:

1
2
rk3588/kernel-6.1/drivers/i2c/i2c-smbus.c
250:#if IS_ENABLED(CONFIG_I2C_SLAVE)

So Host Notify slave is in i2c-smbus.c not i2c-core-smbus.c. Good.


10. 与 i2c-core-base.c 的集成

调用点 使用的 SMBus API
i2c_register_adapter() i2c_setup_smbus_alert(adap)
i2c_device_probe() Host Notify → i2c_smbus_host_notify_to_irq(base.c,非 smbus.c 核心)
i2c_default_probe() i2c_smbus_xfer Quick/Byte Read
i2c_get_device_id() SMBus Read Block 设备 ID
i2c_probe_func_quick_read() Quick read

11. 与 i2c-rk3x / RK3588

11.1 能力位

1
2
// i2c-rk3x.c
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
标志 含义
I2C_FUNC_I2C 支持 i2c_transfer
I2C_FUNC_SMBUS_EMUL SMBus 可通过 I2C 消息模拟(本文件路径)
I2C_FUNC_SMBUS_READ_BLOCK_DATA 不宣称原生 SMBus block

11.2 典型驱动调用链(PMIC)

1
2
3
4
5
6
rk808_probe()
→ i2c_smbus_read_byte_data(client, RK808_CHIPID1)
→ i2c_smbus_xfer()
→ i2c_smbus_xfer_emulated() // 2 msg: 写 reg + 读 1 字节
→ __i2c_transfer()
→ rk3x_i2c_xfer()

11.3 使用建议(RK3588)

  • Byte/Word Data:完全支持,无需担心。
  • Block Data 读:依赖 I2C_M_RECV_LEN;RK3x 未明确实现时,优先在驱动中用 I2C block多次 byte read;或使用 i2c_smbus_read_i2c_block_data_or_emulated()
  • PEC:若外设要求 PEC,设置 I2C_CLIENT_PEC(DT 或 board info)。

12. i2c-core-smbus.c vs i2c-smbus.ko

项目 i2c-core-smbus.c drivers/i2c/i2c-smbus.c
链接 始终进 i2c-core.ko 可选 CONFIG_I2C_SMBUS 模块
内容 协议、模拟、PEC、便捷 API SMBus Alert IRQ/workqueue、i2c_handle_smbus_alert
RK3588 必用(凡用 SMBus API) 仅当需要 Alert 线且配置 Kconfig

13. 导出符号分类

PEC: i2c_smbus_pec

便捷 API: i2c_smbus_read_byte, write_byte, read_byte_data, write_byte_data, read_word_data, write_word_data, read_block_data, write_block_data, read_i2c_block_data, write_i2c_block_data, read_i2c_block_data_or_emulated

核心: i2c_smbus_xfer, __i2c_smbus_xfer

Alert: i2c_new_smbus_alert_devicei2c_setup_smbus_alert(非 export,core 内用)


14. 调试

1
2
3
4
5
6
7
8
9
# 查看某总线是否注册 smbus_alert
ls /sys/bus/i2c/devices/i2c-*/ | grep smbus

# trace(需 CONFIG_EVENT_TRACING)
echo 1 > /sys/kernel/debug/tracing/events/smbus/enable
cat /sys/kernel/debug/tracing/trace

# 功能位(debugfs 或 驱动打印)
# adapter 应含 SMBUS_EMUL
错误码 常见原因
-EBADMSG PEC 校验失败
-EPROTO Block 长度非法
-EOPNOTSUPP 模拟路径不支持的 protocol
-EIO __i2c_transfer 返回 msg 数不足

15. 源码结构速查(约 723 行)

1
2
3
4
5
6
7
8
L28-93    CRC8 / PEC
L95-300 便捷 SMBus API
L302-316 DMA 缓冲辅助
L322-520 i2c_smbus_xfer_emulated
L535-551 i2c_smbus_xfer
L553-619 __i2c_smbus_xfer
L640-676 read_i2c_block_data_or_emulated
L693-722 smbus alert 设备创建 / i2c_setup_smbus_alert

16. 结论

i2c-core-smbus.c 是 Linux I2C 子系统中 SMBus 与 I2C 之间的桥梁

  1. 为驱动提供 标准 SMBus 读写 API
  2. 通过 i2c_smbus_xfer_emulated 使仅实现 master_xfer 的控制器(含 RK3588 rk3x)也能运行 SMBus 客户端;
  3. 支持 PECtrace总线锁重试
  4. 在 adapter 注册时可选挂载 SMBus Alert 客户端(配合 i2c-smbus.ko)。

RK3588 平台上 PMIC、传感器等驱动的寄存器访问大量依赖本文件;理解 i2c_smbus_xfer → 模拟 → rk3x_i2c_xfer 是分析 I2C 寄存器读写问题的关键路径之一。


17. 推荐阅读顺序

  1. i2c-core-smbus.c__i2c_smbus_xferi2c_smbus_xfer_emulated
  2. include/linux/i2c.hI2C_SMBUS_*i2c_smbus_data
  3. busses/i2c-rk3x.cfunctionalityrk3x_i2c_xfer
  4. drivers/mfd/rk808.c — SMBus 读芯片 ID 示例
  5. i2c-smbus.c — Alert 协议(若板级有 smbus_alert 中断)
  6. Documentation/i2c/smbus-protocol.rst(若存在)或 SMBus 规范

18. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-core-smbus.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-dev.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-dev.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-dev.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-dev.c(约 782 行)

编译关系: 可选模块,由 CONFIG_I2C_CHARDEV 控制:

1
obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o

Rockchip RK3588 产品 defconfig 中通常为 CONFIG_I2C_CHARDEV=y(如 rockchip_linux_defconfig),即 内置 i2c-dev.ko 或编进内核,用户空间可通过 /dev/i2c-N 访问各条 I2C 总线。

文档保存路径: linuxDoc/drivers/i2c/

定位: 为每条已注册的 i2c_adapter 创建字符设备节点,使用户态程序(i2c-toolslibi2c、自研工具)通过 read/write/ioctl 在该总线上与任意从地址通信,不经过 内核中已绑定的 i2c_driver(除非地址冲突检查失败)。

与内核驱动的关系: i2c-dev 打开的是 匿名 i2c_client(未注册到 driver core),与 DT 枚举的正式 client 并行存在;对已被内核驱动占用的地址,I2C_SLAVE 会返回 -EBUSYI2C_SLAVE_FORCE 可强制覆盖,有破坏内核驱动风险)。


2. 功能总览

模块 主要符号 作用
设备管理 struct i2c_dev、链表 每个 adapter 对应一个 cdev + sysfs 设备
总线通知 i2cdev_notifier adapter 增删时创建/销毁 /dev/i2c-N
打开/关闭 i2cdev_open / release 绑定 adapter、分配匿名 client
读写 i2cdev_read / write 需先 I2C_SLAVE,调用 i2c_master_recv/send
I2C_RDWR i2cdev_ioctl_rdwr 用户态 i2c_msg 批量传输 → i2c_transfer
I2C_SMBUS i2cdev_ioctl_smbus SMBus ioctl → i2c_smbus_xfer
其它 ioctl i2cdev_ioctl SLAVE、FUNCS、PEC、RETRIES、TIMEOUT 等
32 位兼容 compat_i2cdev_ioctl CONFIG_COMPAT 下 arm32 用户态
模块初始化 i2c_dev_init / exit 主设备号 89、class、notifier

3. 架构与数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
用户空间 (i2cget / i2cset / 自定义程序)
│ open("/dev/i2c-2")

i2cdev_open → i2c_get_adapter(2) → 匿名 i2c_client
│ ioctl(I2C_SLAVE, 0x51) // 可选,read/write 需要
│ ioctl(I2C_RDWR, ...) // 或
│ read() / write()

i2c_transfer / i2c_master_* / i2c_smbus_xfer

i2c-core-base.c + i2c-core-smbus.c

i2c-rk3x.c (rk3588)

设备节点:

  • 主设备号 I2C_MAJOR = 89include/linux/i2c-dev.h
  • 次设备号 = adap->nr(与 i2c-2 总线号一致)
  • 路径:/dev/i2c-0/dev/i2c-8(RK3588 最多 9 路内置 I2C,视 DTS 启用情况而定)
  • sysfs class:i2c-dev,设备下可读 name 属性(adapter 名称)

4. 核心数据结构

4.1 struct i2c_dev

1
2
3
4
5
6
struct i2c_dev {
struct list_head list;
struct i2c_adapter *adap;
struct device dev; /* sysfs: /sys/class/i2c-dev/i2c-N */
struct cdev cdev;
};

全局链表 i2c_dev_list + 自旋锁 i2c_dev_list_lock,按 adap->nr 查找。

4.2 打开文件后的 file->private_data

指向 堆上分配的匿名 struct i2c_client

  • client->adapter = 打开的 adapter;
  • client->addr = 由 I2C_SLAVE / I2C_SLAVE_FORCE 设置;
  • client->flags = I2C_M_TENI2C_CLIENT_PEC 等可由 ioctl 修改;
  • 调用 i2c_new_client_device()不出现在 /sys/bus/i2c/devices/ 的正式 client 列表中(名称为 "i2c-dev N" 仅内部使用)。

5. Adapter 注册与 /dev 节点生命周期

5.1 i2c_dev_init()

  1. register_chrdev_region(MKDEV(89, 0), I2C_MINORS, "i2c")
  2. class_create(..., "i2c-dev")
  3. bus_register_notifier(&i2c_bus_type, &i2cdev_notifier)
  4. i2c_for_each_dev(NULL, i2cdev_attach_adapter) — 绑定 已存在 的 adapter。

5.2 i2cdev_attach_adapter()

i2c_bus_type 上新增 adapter 类型 设备时:

1
2
3
4
get_free_i2c_dev(adap)
cdev_init(&i2cdev_fops)
dev_set_name("i2c-%d", adap->nr)
cdev_device_add() → /dev/i2c-N 与 /sys/class/i2c-dev/i2c-N

5.3 i2cdev_detach_adapter()

adapter 删除时 cdev_device_del,从链表移除并释放 i2c_dev

与 RK3588: i2c-rk3x probe 成功并 i2c_add_numbered_adapter() 后,自动出现对应 /dev/i2c-N(若 CONFIG_I2C_CHARDEV=y)。


6. 文件操作

6.1 read() / write()

项目 说明
前提 必须先 ioctl(I2C_SLAVE, addr) 设置从机地址
实现 i2c_master_recv / i2c_master_send(内部 i2c_transfer
长度上限 单次最多 8192 字节
缓冲区 内核 kmalloc / memdup_user,再拷贝到用户空间

适用于简单连续读写的用户态工具;复杂事务应用 I2C_RDWR

6.2 ioctl() 命令一览

定义于 include/uapi/linux/i2c-dev.h

ioctl 含义
I2C_SLAVE (0x0703) 设置 7/10 位从地址;若已有 带驱动 的正式 client 占址 → -EBUSY
I2C_SLAVE_FORCE (0x0706) 强制设置地址,不检查 是否已有驱动
I2C_TENBIT (0x0704) 启用 10 位地址(UAPI 注释称历史代码对 10 位支持不完整)
I2C_FUNCS (0x0705) 返回 i2c_get_functionality(adapter) 功能掩码
I2C_RDWR (0x0707) 提交 i2c_rdwr_ioctl_data(最多 42 条 msg)
I2C_PEC (0x0708) 匿名 client 的 PEC 标志(不影响 已注册内核 client)
I2C_SMBUS (0x0720) i2c_smbus_ioctl_datai2c_smbus_xfer
I2C_RETRIES (0x0701) 修改 adapter->retries(影响整条总线)
I2C_TIMEOUT (0x0702) 修改 adapter->timeout(单位 10ms,转 jiffies)

7. I2C_RDWRi2cdev_ioctl_rdwr()

7.1 流程

  1. 从用户空间复制 struct i2c_msg 数组(memdup_array_user);
  2. 每条消息:len ≤ 8192bufmemdup_user 拷入内核,置 I2C_M_DMA_SAFE
  3. I2C_M_RECV_LEN:校验读方向与 buf[0] 长度,并设置 msgs[i].len = buf[0]
  4. i2c_transfer(client->adapter, msgs, nmsgs)注意:使用 adapter,msg 内自带 addr不依赖 I2C_SLAVE 设置的 client->addr
  5. 读消息:copy_to_user 回传数据;释放内核缓冲区。

7.2 与 I2C_SLAVE 的关系

注释说明:打开后即可用 I2C_RDWR 向任意地址发消息(地址在每条 i2c_msg 中)。read/writeI2C_SMBUS 则需要先 I2C_SLAVE

7.3 RK3588

最终进入 rk3x_i2c_xfer(),支持组合读写(与 i2c_transfer 路径一致)。


8. I2C_SMBUSi2cdev_ioctl_smbus()

  • 校验 sizeread_write 合法;
  • Quick / Send Byte 等无 data 指针情况直接 i2c_smbus_xfer(..., NULL)
  • 其它从用户空间复制 union i2c_smbus_data,调用 i2c_smbus_xfer,读操作再 copy_to_user
  • 支持 I2C_SMBUS_I2C_BLOCK_BROKEN 兼容旧 ABI。

用户态 i2c_smbus_* 库函数通常封装此 ioctl。RK3588 上经 i2c_smbus_xfer_emulated 模拟(见 i2c-core-smbus.c 文档)。


9. 地址占用检查:i2cdev_check_addr()

i2c-core-base.ci2c_check_addr_busy() 的差异(文件注释):

检查逻辑 i2c-core i2c-dev
已有 client + 已绑定 driver 忙(I2C_SLAVE 失败)
已有 client + 无 driver 不忙(允许用户态访问)

递归检查 MUX 父子 adapter 上的同名地址(i2cdev_check_mux_parents / _children)。

I2C_SLAVE_FORCE 跳过 BUSY 检查,可能导致与内核驱动 同时访问同一从设备,仅调试时使用。


10. 与内核 I2C 子系统其它部分的关系

组件 关系
i2c-core-base.c i2c_transferi2c_get_adapteri2c_master_send/recv
i2c-core-smbus.c i2c_smbus_xfer
i2c-core-of.c DT 创建的正式 client;与 i2c-dev 匿名 client 独立
i2c-rk3x.c 实际硬件传输
adapter sysfs new_device 另一条 用户态创建设备路径(创建 正式 client)

生产环境建议: 正常外设由 内核驱动 + DT 管理;/dev/i2c-* 主要用于 调试、校准、产测、无驱动芯片的临时访问


11. RK3588 使用说明

11.1 配置

1
2
3
CONFIG_I2C_CHARDEV=y
CONFIG_I2C=y
CONFIG_I2C_RK3X=y

11.2 用户空间示例

1
2
3
4
5
6
7
8
9
10
# 列出总线
ls -l /dev/i2c-*

# i2c-tools(需交叉编译或板端安装)
i2cdetect -y 2
i2cget -y 2 0x51 0x00
i2cset -y 2 0x51 0x00 0xab

# 查看 adapter 名称
cat /sys/class/i2c-dev/i2c-2/name

总线号 N 与 DTS aliases { i2cN = &i2cN; }i2c_add_numbered_adapteradap->nr 一致。

11.3 与板级外设共存

例如 rk3588s-tablet.dtsi&i2c2 上的 hym8563@51 已被 hym8563 驱动 占用时:

  • ioctl(I2C_SLAVE, 0x51)-EBUSY
  • I2C_SLAVE_FORCE 可强制访问,但可能与 RTC 驱动冲突,导致系统异常。

11.4 权限

设备节点通常属 root:i2cchmod 666 或由 udev 规则赋权;嵌入式系统常在 init 脚本中 chmod 以便应用访问。


12. 安全与限制

限制 说明
单次传输 read/write/ioctl 单条 msg ≤ 8192 字节
I2C_RDWR 条数 ≤ 42I2C_RDWR_IOCTL_MAX_MSGS
次设备号 adap->nr < I2C_MINORS(通常 256)
10 位地址 UAPI 注明历史实现问题,慎用
修改 retries/timeout 影响 整条 adapter,非单文件描述符私有

用户态对 /dev/i2c-* 的访问等价于 root 级硬件控制,产线工具需防止误写 PMIC 等关键器件。


13. 模块初始化与卸载

1
2
module_init(i2c_dev_init);
module_exit(i2c_dev_exit);
  • =y 内置=m 模块 i2c-dev.ko
  • 卸载模块会注销 notifier 并删除所有 i2c-dev 字符设备(需无打开的文件描述符)。

14. 源码结构速查(约 782 行)

1
2
3
4
5
6
7
8
9
10
11
L42-96    i2c_dev 链表管理
L98-113 sysfs name 属性
L134-178 read / write
L180-233 地址检查(含 mux)
L235-309 i2cdev_ioctl_rdwr
L311-390 i2cdev_ioctl_smbus
L392-496 i2cdev_ioctl
L498-583 CONFIG_COMPAT
L585-635 open / release / file_operations
L649-724 attach/detach + bus notifier
L732-774 init / exit

15. 结论

i2c-dev.c 实现 I2C 子系统的 用户空间字符设备接口

  1. 每条 i2c_adapter 对应 /dev/i2c-N(主设备号 89);
  2. 通过 匿名 client 提供 I2C_SLAVE + read/writeI2C_RDWR / I2C_SMBUS
  3. 底层统一落到 i2c_transfer / i2c_smbus_xfer,RK3588 上即 rk3x 硬件驱动
  4. DT 内核驱动 并行,地址冲突时 I2C_SLAVE 保护已绑定设备;
  5. Rockchip 产品内核 默认启用 CONFIG_I2C_CHARDEV,调试与工具链普遍依赖本模块。

理解本文件有助于分析 用户态能否访问某 I2C 地址i2cdetect 与内核驱动冲突、以及 产测程序直接读写总线 的实现路径。


16. 推荐阅读顺序

  1. i2c-dev.ci2cdev_ioctli2cdev_open
  2. include/uapi/linux/i2c-dev.h — ioctl 编号与结构体
  3. Documentation/i2c/dev-interface.rst — 官方用户接口说明
  4. i2c-core-base.ci2c_transferi2c_get_adapter
  5. i2c-core-smbus.c — SMBus ioctl 底层

17. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-dev.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-mux.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-mux.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-mux.c 目录」,实际为 单个源文件(I2C MUX 框架核心):

  • rk3588/kernel-6.1/drivers/i2c/i2c-mux.c(约 467 行)

编译关系:CONFIG_I2C_MUX 控制,编译为模块或内置 i2c-mux.ko

1
obj-$(CONFIG_I2C_MUX) += i2c-mux.o

具体硬件实现位于 drivers/i2c/muxes/(PCA954x、GPIO mux、pinctrl 等),均依赖本文件提供的 API。

文档保存路径: linuxDoc/drivers/i2c/

定位:一条物理 I2C 总线 通过外部 MUX/开关/仲裁逻辑,抽象为 多条逻辑 i2c_adapter。每次传输前 select 通道,在 父 adapter 上执行 i2c_transfer / i2c_smbus_xfer,可选 deselect。支持 多级 mux(mux 下再挂 mux)。

与 RK3588 的关系: 车载/多摄等 RK3588 板级 DTS 中常见 i2c-mux { i2c@0 { ... }; i2c@1 { ... }; }(如 Maxim SerDes 方案),由 i2c-mux-pinctrl 等驱动调用本框架;SoC 内置 i2c-rk3x 仍为 父总线,子总线由本文件注册。

相关文档: 各具体 MUX 芯片驱动见 rk3588-kernel-6.1-drivers-i2c-muxes目录功能详细分析_zh.md


2. 功能总览

模块 符号 作用
传输代理 i2c_mux_master_xfer / __i2c_mux_master_xfer select → 父总线 i2c_transfer → deselect
SMBus 代理 i2c_mux_smbus_xfer / __i2c_mux_smbus_xfer select → i2c_smbus_xfer → deselect
功能查询 i2c_mux_functionality 继承父 adapter 的 functionality
总线锁 i2c_mux_lock_ops / i2c_parent_lock_ops 协调 mux_lockbus_lock,防死锁
根 adapter i2c_root_adapter 沿 device 树向上找最顶层 I2C adapter
分配 core i2c_mux_alloc 分配 i2c_mux_core + 私有数据 + select/deselect
注册子总线 i2c_mux_add_adapter 为每个 channel 创建子 i2c_adapter + DT 绑定
注销 i2c_mux_del_adapters 删除所有子 adapter 与 sysfs 链接

3. 架构示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌─────────────────────────────────┐
│ i2c-mux.c(本文) │
│ i2c_mux_add_adapter() │
│ 子 adapter algo → mux_xfer │
└───────────────┬─────────────────┘
│ select/deselect 回调
┌───────────────▼─────────────────┐
│ muxes/i2c-mux-pca954x.c 等 │
│ 写 MUX 芯片寄存器 / GPIO / pinctrl │
└───────────────┬─────────────────┘

┌───────────────▼─────────────────┐
│ 父 i2c_adapter (如 i2c-4, rk3x) │
└───────────────┬─────────────────┘

SDA / SCL 物理总线

用户/内核视角:

1
2
3
4
5
i2c-4 (rk3x 物理总线)
└── MUX 设备 @0x70 (client 驱动,如 pca9548)
├── 子 adapter i2c-5 (chan 0) → cam0@31, ser@41 ...
├── 子 adapter i2c-6 (chan 1) → ...
└── ...

4. 核心数据结构

4.1 struct i2c_mux_coreinclude/linux/i2c-mux.h

字段 含义
parent 上游 i2c_adapter(物理总线或上一级 mux 子总线)
dev MUX 控制器对应的 struct device(常为 i2c_client->dev
select / deselect 硬件相关,由 muxes/*.c 实现
mux_locked 使用 I2C_MUX_LOCKED:子 adapter 自带锁语义
arbitrator I2C_MUX_ARBITRATOR:DT 子节点名 i2c-arb
gate I2C_MUX_GATE:DT 子节点名 i2c-gate
priv 芯片驱动私有数据(i2c_mux_priv(muxc)
adapter[] 已注册的子 adapter 指针数组

4.2 struct i2c_mux_priv(本文件内 static)

每个 通道 对应一个子 adapter 的私有数据:

1
2
3
4
5
6
struct i2c_mux_priv {
struct i2c_adapter adap; /* 对外暴露的子总线 */
struct i2c_algorithm algo; /* 动态填充 master_xfer / smbus_xfer */
struct i2c_mux_core *muxc;
u32 chan_id; /* 传给 select/deselect */
};

5. 传输路径详解

5.1 Master 传输(i2c_mux_master_xfer

1
2
3
4
5
ret = muxc->select(muxc, priv->chan_id);
if (ret >= 0)
ret = i2c_transfer(parent, msgs, num); /* 或 __i2c_transfer */
if (muxc->deselect)
muxc->deselect(muxc, priv->chan_id);
变体 使用条件 父总线调用
i2c_mux_master_xfer I2C_MUX_LOCKED i2c_transfer(含锁)
__i2c_mux_master_xfer 默认 __i2c_transfer(调用方已持锁)

子 adapter 的 master_xferi2c_mux_add_adapter() 中按父 adapter 是否提供 master_xfer / master_xfer_atomic 动态赋值

5.2 SMBus 传输(i2c_mux_smbus_xfer

逻辑与 master 相同,父侧为 i2c_smbus_xfer / __i2c_smbus_xfer

RK3588 rk3x: 父 adapter smbus_xfer,子 adapter 也不会 设置 smbus_xfer;子总线上的 SMBus 仍通过父的 master_xfer 模拟(与 i2c-core-smbus.c 一致)。

5.3 i2c_mux_functionality()

直接返回 parent->algo->functionality(parent),子总线能力与父总线一致(如 I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL)。


6. 总线锁与多级 MUX

6.1 问题

子 adapter 传输时会调用父 adapter 的 i2c_transfer,而 i2c_transfer 会再次对 子 adapter 加锁 → 若使用默认 i2c_adapter_lock_ops重复加锁死锁

6.2 两套 lock_ops

模式 lock_ops 行为
默认 i2c_parent_lock_ops 先锁 parent->mux_lock(嵌套深度 i2c_adapter_depth),再按需锁 parent->bus_lock
I2C_MUX_LOCKED i2c_mux_lock_ops 主要操作 parent->mux_lockI2C_LOCK_ROOT_ADAPTER 时才锁根 bus_lock

mux_locki2c_register_adapter()i2c-core-base.c)中对每个 adapter 初始化。

6.3 多级 MUX

子 adapter 的 parent 可以是 上一级 mux 的子 adapter,形成:

1
rk3x (i2c-4) → MUX-A chan0 (i2c-5) → MUX-B chan1 (i2c-6) → 传感器

i2c_root_adapter(dev) 向上遍历 i2c_parent_is_i2c_adapter() 直到最顶层物理 adapter。

i2c-core-base.c 中地址冲突检查也会 递归 mux 父子树(与 i2c-dev.c 类似)。


7. i2c_mux_alloc()

1
2
3
4
5
6
7
8
struct i2c_mux_core *i2c_mux_alloc(
struct i2c_adapter *parent,
struct device *dev,
int max_adapters,
int sizeof_priv,
u32 flags, /* I2C_MUX_LOCKED | ARBITRATOR | GATE */
int (*select)(...),
int (*deselect)(...));
  • 使用 devm_kzalloc,与 MUX 设备生命周期绑定;
  • adapter[] 柔性数组,max_adapters 为最大通道数;
  • sizeof_priv > 0 时,priv 指向紧跟数组后的私有区(pca954xgpio 等状态)。

典型调用(pca954x):

1
2
muxc = i2c_mux_alloc(adap, dev, PCA954X_MAX_NCHANS, sizeof(*data), 0,
pca954x_select_chan, pca954x_deselect_mux);

8. i2c_mux_add_adapter() — 注册子总线

1
2
3
int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
u32 force_nr, u32 chan_id,
unsigned int class);

8.1 主要步骤

  1. 分配 i2c_mux_priv,设置 chan_id
  2. 根据父 algo 填充子 i2c_algorithm
  3. 设置子 adapter 名称:"i2c-%d-mux (chan_id %d)"
  4. 继承父的 retriestimeoutquirks
  5. DT of_node 绑定(见下节);
  6. ACPI: acpi_preset_companion(若适用);
  7. i2c_add_numbered_adapterforce_nr != 0)或 i2c_add_adapter
  8. 创建 sysfs 符号链接:mux_devicechannel-N
  9. of_i2c_register_devices() — 在 子 adapter 注册时 自动枚举 i2c@N 下子设备

8.2 Device Tree 节点匹配逻辑

muxc->dev->of_node 存在时:

条件 查找的子节点容器
I2C_MUX_ARBITRATOR i2c-arb
I2C_MUX_GATE i2c-gate
默认 i2c-mux

在容器下查找 reg == chan_id 的子节点(如 i2c@0 { reg = <0>; ... }),将其设为 priv->adap.dev.of_node

i2c-mux 节点自身带 reg(旧式 DT),则回退到 MUX 设备节点本身。

RK3588 车载 DTS 示例rk3588-vehicle-evb-maxim-max96722-dphy3.dtsi):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
i2c-mux {
#address-cells = <1>;
#size-cells = <0>;

i2c@0 {
reg = <0>;
max9295@41 { ... };
ov2311@31 { ... };
};
i2c@1 {
reg = <1>;
...
};
};

i2c_mux_add_adapter(muxc, 0, 0, 0)i2c@0 绑到 chan 0 的子 adapter,随后 of_i2c_register_devices 创建 camera/serializer 等 client。

8.3 class 参数

用于遗留 class-based detect;若与祖先 adapter 的 class 冲突会 dev_err 警告。现代 RK 平台主要靠 DT + OF 匹配,通常传 0


9. i2c_mux_del_adapters()

按注册逆序:

  • 删除 sysfs 链接;
  • i2c_del_adapter(卸载子节点上所有 client);
  • of_node_put
  • kfree(priv)

MUX 驱动 remove 中必须调用,与 probe 中多次 i2c_mux_add_adapter 成对。


10. i2c_root_adapter()

1
struct i2c_adapter *i2c_root_adapter(struct device *dev);

从任意 dev 向上找 type == i2c_adapter_type 的祖先,再沿 i2c_parent_is_i2c_adapter 走到 最顶层 adapter。

用途: 时钟/电源、全局总线配置、部分驱动需要区分“物理根总线”与“mux 子总线”。


11. 与 I2C 子系统其它模块的关系

模块 关系
i2c-core-base.c i2c_add_adapter / i2c_del_adaptermux_locki2c_adapter_depth
i2c-core-of.c 子 adapter 的 of_node 绑定后枚举子设备
i2c-core-smbus.c 父有 smbus_xfer 时子 adapter 可透传 SMBus
i2c-dev.c 每个子 adapter 可有 /dev/i2c-N
muxes/*.c 实现 select/deselect,调用本框架 API

12. 导出 API 一览

符号 说明
i2c_mux_alloc 分配 mux core
i2c_mux_add_adapter 添加一个通道的子 adapter
i2c_mux_del_adapters 删除所有子 adapter
i2c_root_adapter 查找根物理 adapter

头文件辅助:i2c_mux_priv()I2C_MUX_* flags。


13. RK3588 配置与使用

13.1 Kconfig

1
2
config I2C_MUX
tristate "I2C bus multiplexing support"

CONFIG_I2C_MUX=y/m,并启用具体驱动,例如:

  • CONFIG_I2C_MUX_PINCTRL — 引脚复用型(车载 SerDes 常见)
  • CONFIG_I2C_MUX_PCA954x — NXP PCA954x/9548
  • CONFIG_I2C_MUX_GPIO — GPIO 控制通道

(具体是否在 rockchip_linux_defconfig 中启用取决于产品方案,多摄/车载 defconfig 通常会打开相应项。)

13.2 典型拓扑

1
2
3
4
5
6
7
&i2c4 {
status = "okay";
maxim_serdes: max96722@29 {
...
i2c-mux { ... }; /* 逻辑子总线在 SerDes 驱动/子节点下 */
};
};

物理 i2c-rk3x 仅一条;摄像头、serializer 挂在 mux 子 adapter 对应的 DT 子树下,驱动 probe 时 client->adapter 为子总线号。

13.3 调试

1
2
3
4
5
6
7
8
9
10
# 查看总线拓扑
ls /sys/bus/i2c/devices/
i2cdetect -l

# 查看 mux 与子通道链接
ls -l /sys/bus/i2c/devices/i2c-*/mux_device
ls -l /sys/bus/i2c/devices/*/channel-*

# 对子总线探测(N 为子 adapter 号)
i2cdetect -y N

14. 设计要点与注意事项

要点 说明
每传输切换通道 select 在每次 i2c_transfer 前调用,有开销;deselect 可选(idle 功耗/总线释放)
通道互斥 同一父总线上多子 adapter 共享物理线,mux_lock 保证并发安全
地址空间独立 不同 channel 可有相同 7 位地址(不同子 adapter)
勿在 select 中长阻塞 阻塞会持有父总线锁,影响其它 channel
DT regchan_id 必须一致,否则 of_node 绑定错误、子设备无法枚举

15. 源码结构速查(约 467 行)

1
2
3
4
5
6
7
8
L32-37   struct i2c_mux_priv
L39-119 mux master/smbus xfer(locked / unlocked)
L122-141 functionality / parent_classes
L143-211 lock_ops(mux / parent)
L213-237 i2c_root_adapter
L239-269 i2c_mux_alloc
L283-438 i2c_mux_add_adapter(含 DT/ACPI/sysfs)
L441-461 i2c_mux_del_adapters

16. 结论

i2c-mux.c 是 Linux I2C 多路复用子系统的核心框架(非具体芯片驱动):

  1. 将每个 MUX 通道 呈现为独立 i2c_adapter
  2. 在传输路径中插入 select / deselect 钩子,复用父总线硬件驱动(RK3588 上为 rk3x);
  3. 通过 mux_lock / 专用 lock_ops 支持多级 mux 与并发安全;
  4. Device Treei2c-mux / i2c@N 结构紧密配合,驱动 of_i2c_register_devices 枚举支路外设。

RK3588 多摄像头、SerDes 等复杂 I2C 拓扑依赖本文件 + muxes/ 驱动;分析板级 I2C 问题时需区分 物理总线号mux 子总线号,并确认 chan_id 与 DTS reg 一致


17. 推荐阅读顺序

  1. i2c-mux.ci2c_mux_add_adapteri2c_mux_master_xfer
  2. include/linux/i2c-mux.h — API 与 flags
  3. muxes/i2c-mux-pinctrl.ci2c-mux-pca954x.c — select/deselect 实现
  4. i2c-core-of.c — 子 adapter 设备枚举
  5. 板级 DTS 中 i2c-mux 节点

18. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-mux.c 分析。