news 2026/4/16 10:50:19

设备树I2C外设注册流程:ARM64平台深度剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设备树I2C外设注册流程:ARM64平台深度剖析

以下是对您提供的博文内容进行深度润色与结构重构后的技术文章。整体风格更贴近一位资深嵌入式Linux内核开发者在技术社区中自然、扎实、有温度的分享——去AI痕迹、强逻辑流、重实战感、轻说教味,同时严格遵循您提出的全部优化要求(无模板化标题、无总结段、无缝融合知识点、口语化专业表达、关键点加粗提示等)。


I2C设备“活”起来之前,Linux内核到底做了什么?

你有没有遇到过这样的场景:
设备树里明明写了codec@1a,驱动也编译进去了,modprobe rt5682也成功了,可dmesg里就是看不到rt5682_probe()的打印,/sys/bus/i2c/devices/下空空如也?
或者更玄学一点:i2c-2是存在的,i2cdetect -y 2能扫出地址,但一读寄存器就超时,示波器上看SCL根本没动?

这不是驱动写错了,也不是硬件焊反了——而是你还没真正看懂:.dts文件里那一行codec@1a { ... };到内核里一个能read_reg()struct i2c_client *client,中间到底发生了多少层“翻译”和“握手”?

今天我们就以 ARM64 平台(RK3588 / i.MX93 / SM8550)为背景,不讲概念复读,不列函数调用栈,而是像拆解一台老式收音机那样,一层层拧开外壳,看看 Linux 内核是怎么把设备树里的静态文本,“点化”成运行时可用的 I2C 外设实例的。


设备树不是配置文件,是“硬件说明书”的二进制快照

很多人把.dts当作类似 U-Boot 环境变量的配置项——改个status = "okay"就完事。这其实是最大的误解。

设备树的本质,是启动早期由 bootloader 加载进内存的一块只读数据区.dtb),它不参与编译,也不被链接进 vmlinux;内核拿到它之后,做的第一件事,是把它“摊平”成一棵struct device_node *组成的树状链表。这个过程叫unflatten_device_tree(),发生在setup_arch()里,比mm_init()还早。

所以,.dts中每个{}块,在内存里都对应一个实实在在的device_node结构体。而&i2c2 { ... }这种写法,本质是告诉内核:“这里有个叫i2c2的节点,它的phandle指向i2c@feac0000,请把它挂到根节点下面”。

⚠️ 关键提醒:&i2c2是引用(reference),不是定义。真正的定义一定在.dtsi里,比如i2c@feac0000 { compatible = "rockchip,rk3588-i2c"; ... };——如果你只改了.dts里的&i2c2,却忘了.dtsi里对应节点的statuscompatible,那整条链就断了。

再来看这段常见代码:

&i2c2 { status = "okay"; clock-frequency = <400000>; eeprom@50 { compatible = "atmel,24c02"; reg = <0x50>; pagesize = <16>; }; };

它其实隐含了三层含义:

  • 第一层:i2c2是个platform device,要走platform_bus匹配;
  • 第二层:eeprom@50i2c2的子节点,但它不是 platform device,而是一个待注册的 I2C client;
  • 第三层:compatible = "atmel,24c02"不是给i2c2看的,是留给i2c_bus匹配用的——也就是说,这个字符串,最终会去和drivers/misc/eeprom/at24.c里那个static const struct i2c_device_id at24_ids[]表做比对。

这就是为什么你不能把atmel,24c02写成atmel,24c02a——少一个字母,匹配就失败,probe()永远不会被调。


I2C控制器怎么“上线”?靠的是 platform_bus,不是 i2c_bus

很多初学者卡在这一步:明明i2c-2没出来,就急着去查at24.ko有没有加载。其实顺序完全反了。

I2C 总线上的设备能不能工作,第一道门槛是控制器本身得先“活”过来

ARM64 上的 I2C 控制器(比如 RK3588 的 I2C2),在设备树里长得像这样:

i2c@feac0000 { compatible = "rockchip,rk3588-i2c"; reg = <0x0 0xfeac0000 0x0 0x1000>; interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH>; #address-cells = <1>; #size-cells = <0>; clocks = <&cru PCLK_I2C2>; clock-names = "i2c"; power-domains = <&power RK3588_PD_PERI>; status = "okay"; };

注意几个关键点:

  • compatible = "rockchip,rk3588-i2c"→ 这是platform_driver的匹配钥匙;
  • reg描述的是控制器寄存器的物理地址范围,不是 I2C 设备地址;
  • #address-cells = <1>#size-cells = <0>是 I2C bus type 的硬性约定,意味着子节点的reg只取一个 u32 值作为设备地址(即0x50),不带长度字段。

当内核解析到这个节点,会把它当作一个platform_device注册到platform_bus上。随后触发匹配流程:

// drivers/i2c/busses/i2c-rk3x.c static const struct of_device_id rk3x_i2c_of_match[] = { { .compatible = "rockchip,rk3399-i2c" }, { .compatible = "rockchip,rk3588-i2c" }, // ← 就是它! { } }; MODULE_DEVICE_TABLE(of, rk3x_i2c_of_match); static struct platform_driver rk3x_i2c_driver = { .probe = rk3x_i2c_probe, .remove = rk3x_i2c_remove, .driver = { .name = "rk3x-i2c", .of_match_table = rk3x_i2c_of_match, // ← platform_bus 用它匹配 }, };

rk3x_i2c_probe()干了四件大事:

  1. devm_ioremap_resource():把0xfeac0000映射成虚拟地址;
  2. clk_prepare_enable():打开时钟,否则寄存器读写全返回 0;
  3. devm_request_irq():申请中断(有些 I2C 控制器用轮询,但 RK 系列基本都用中断);
  4. i2c_add_numbered_adapter():这才是最关键的一步——它把struct i2c_adapter注册进 I2C core,生成/sys/class/i2c-adapter/i2c-2,并触发后续 client 创建。

💡 小技巧:如果i2c-2没出现,先dmesg | grep -i "rk3x\|i2c"看 probe 是否执行;如果没输出,说明 platform_driver 根本没匹配上——这时候回头检查compatible拼写、status状态、clocks 是否 enable。


client 不是“生”出来的,是 I2C core “造”出来的

很多人以为:我写了eeprom@50,内核就会自动创建一个i2c_client。但真相是:client 是 I2C core 主动构造的,不是设备树“生”出来的。

这个动作发生在i2c_add_numbered_adapter()返回之后,由 I2C core 主动调用of_i2c_register_devices(adap)完成。

我们来还原一下这个过程:

  1. i2c_add_numbered_adapter()注册完adap后,会遍历它的dev.of_node->child链表;
  2. 对每个子节点(比如eeprom@50),调用of_i2c_register_device()
  3. 该函数内部:
    - 解析reg = <0x50>→ 得到info.addr = 0x50
    - 解析compatible = "atmel,24c02"→ 填入info.type
    - 解析interrupts→ 调用of_irq_get()获取 Linux IRQ 编号;
    - 最终调用i2c_new_client_device(adap, &info),生成struct i2c_client *client
  4. i2c_new_client_device()内部会:
    - 分配client内存;
    - 设置client->adapter = adap
    - 调用device_register(&client->dev),将它挂到i2c_bus_type上;
    - 触发i2c_bus.match(),开始找 driver。

看到没?整个过程里,设备树只是提供原始参数,真正干活的是 I2C core 的 C 代码。这也是为什么你可以用i2c_new_dummy_device()在 runtime 动态创建 client——设备树只是最常用的一种初始化方式,不是唯一方式。

🔍 调试线索:如果i2c-2存在,但/sys/bus/i2c/devices/2-0050/没出现,说明of_i2c_register_devices()没执行或执行失败。此时可以加一句pr_info("creating client for %pOF\n", child);of_i2c_register_devices()里确认是否走到这步。


为什么要有两套总线?platform_bus 和 i2c_bus 不是重复造轮子

这个问题问到了 Linux 设备模型的底层哲学。

简单说:platform_bus 管“谁来管总线”,i2c_bus 管“总线上挂谁”。它们服务的对象、生命周期、匹配逻辑,完全不同。

维度platform_busi2c_bus
设备类型SoC 内部集成模块(UART / I2C 控制器 / PWM)外挂芯片(EEPROM / Codec / Sensor)
设备来源of_platform_populate()扫描根节点及其子树of_i2c_register_devices()扫描 I2C controller 的子节点
驱动匹配依据.of_match_table(匹配compatible.id_table(匹配info.type,即compatible字符串)
生命周期内核启动期静态注册,不可热插拔可热插拔(需 mux 支持),也可动态创建
地址空间无统一地址空间(每个 controller 自己管)全局 I2C 地址空间(0x00–0x7F),需避免冲突

举个现实例子:你在 RK3588 板子上换了一颗新的 PMIC,型号从rk806换成rk809
你只需要改设备树里pmic@20节点的compatible = "rockchip,rk809",并确保rk809驱动已编译——不用碰 I2C controller 驱动,也不用改任何 client 驱动。因为rk809依然是挂在i2c-1上的一个 client,匹配逻辑完全走i2c_bus

反过来,如果你把 RK3588 换成 i.MX93,I2C controller 的 IP 核变了,那你只需更新platform_driver(比如换成imx-lpi2c),所有挂在它上面的 client 驱动——at24rt5682bme280——全都原封不动继续用。

这就是两级总线设计的真正价值:解耦。不是为了炫技,而是为了在芯片迭代、硬件变更、驱动维护时,把改动控制在最小范围内。


实战排障:三个高频问题,一句命令定位根源

❌ 问题1:dmesg里看不到at24_probe(),但i2c-2是存在的

→ 先跑这句:

dmesg | grep -E "(at24|2-0050|i2c.*[0-9]-[0-9a-f]{3})"
  • 如果看到i2c i2c-2: new_device: can't create device at 0x50,说明地址冲突(另一个设备也在用 0x50);
  • 如果看到at24 2-0050: failed to get page size,说明pagesize属性没被正确解析(检查.dts是否拼错);
  • 如果啥都没看到,说明of_i2c_register_devices()根本没执行 → 检查i2c2节点下是否有status = "okay",且没有被其他节点 disable。

❌ 问题2:i2cdetect -y 2能扫出地址,但i2cget -y 2 0x50 0x00返回Error: Read failed

→ 这是典型的电气或时序问题。别急着改驱动,先看设备树:

&i2c2 { clock-frequency = <100000>; // 先降频试试 i2c-scl-falling-time-ns = <30>; i2c-scl-rising-time-ns = <150>; i2c-sda-falling-time-ns = <30>; };

RK 系列驱动支持这些属性,会自动配置CON,CLKDIVL,CLKDIVH寄存器。如果硬件上用了 10k 上拉、线长 15cm,400kHz 很可能不稳定。

❌ 问题3:modprobe at24成功,但/sys/bus/i2c/devices/2-0050/eeprom不可读

→ 检查at24驱动是否启用了CONFIG_MISC_DEVICESCONFIG_EEPROM_AT24;更重要的是,确认at24id_table是否包含"atmel,24c02"

static const struct i2c_device_id at24_ids[] = { { "24c01", AT24_DEVICE_MAGIC(1, 8) }, { "24c02", AT24_DEVICE_MAGIC(2, 8) }, // ← 必须有这一行! { "atmel,24c02", AT24_DEVICE_MAGIC(2, 8) }, // ← 更推荐带 vendor 的写法 { } };

很多旧版驱动只写了"24c02",不认"atmel,24c02",就会匹配失败。


最后一句实在话

设备树不是魔法,I2C 注册也不是黑箱。它是一套高度工程化的协作机制:
设备树负责“说清楚”,platform_bus 负责“搭好桥”,i2c_bus 负责“接上人”,I2C core 负责“发号施令”,而驱动,只是听命行事的那个“人”。

当你再遇到 probe 不执行、client 不创建、通信失败这些问题时,别急着翻驱动源码——先dmesg | grep i2c,再ls /sys/bus/i2c/devices/,然后打开.dtsdrivers/i2c/对着看。你会发现,那些曾经神秘的宏、结构体、匹配逻辑,其实都有迹可循,有据可依。

如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 6:52:16

TurboDiffusion部署教程:清华视频生成加速框架一键上手指南

TurboDiffusion部署教程&#xff1a;清华视频生成加速框架一键上手指南 1. 这不是普通视频生成工具&#xff0c;是真正能“秒出片”的加速器 你有没有试过等一个视频生成完成&#xff0c;盯着进度条看了三分钟&#xff0c;结果发现画面模糊、动作卡顿、细节糊成一片&#xff…

作者头像 李华
网站建设 2026/4/15 15:11:59

Android TV媒体播放器SmartTube完整配置指南

Android TV媒体播放器SmartTube完整配置指南 【免费下载链接】SmartTube SmartTube - an advanced player for set-top boxes and tv running Android OS 项目地址: https://gitcode.com/GitHub_Trending/smar/SmartTube 在智能电视应用生态中&#xff0c;Android TV媒体…

作者头像 李华
网站建设 2026/4/14 6:26:07

告别复杂配置,Emotion2Vec+镜像实现语音情绪快速检测

告别复杂配置&#xff0c;Emotion2Vec镜像实现语音情绪快速检测 1. 为什么你需要一个“开箱即用”的语音情绪识别工具&#xff1f; 你是否遇到过这样的场景&#xff1a; 客服质检团队每天要听上百通录音&#xff0c;靠人工判断客户情绪是否愤怒、焦虑或满意&#xff0c;效率…

作者头像 李华
网站建设 2026/4/12 9:42:41

Open-AutoGLM能识别中文界面吗?实测告诉你答案

Open-AutoGLM能识别中文界面吗&#xff1f;实测告诉你答案 最近在技术圈刷到一个让人眼前一亮的项目&#xff1a;Open-AutoGLM——智谱开源的手机端AI Agent框架。它宣称能“看懂”手机屏幕&#xff0c;听懂你的中文指令&#xff0c;比如“打开小红书搜美食”&#xff0c;就能…

作者头像 李华
网站建设 2026/4/15 15:04:19

DeepSeek-OCR对比Glyph:谁更适合你?

DeepSeek-OCR对比Glyph&#xff1a;谁更适合你&#xff1f; 在处理超长文本时&#xff0c;传统大语言模型&#xff08;LLM&#xff09;常被上下文窗口限制卡住脖子——序列越长&#xff0c;计算开销呈平方级增长&#xff0c;显存吃紧、推理变慢、部署成本飙升。近两年&#xf…

作者头像 李华
网站建设 2026/4/11 1:20:07

Paraformer-large适合哪些场景?教育/医疗/会议应用解析

Paraformer-large适合哪些场景&#xff1f;教育/医疗/会议应用解析 1. 这不是普通语音转文字&#xff0c;而是能“听懂”长对话的离线ASR系统 你有没有遇到过这些情况&#xff1a; 教师录了一节45分钟的公开课&#xff0c;想快速生成逐字稿做教学反思&#xff0c;但在线工具…

作者头像 李华