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 分析。

文章互动

阅读 --

留言

0 条留言

正在加载留言…