1. 从总线到设备:I2C驱动分层到底在分什么
很多人第一次接触Linux I2C子系统,脑子里是一团浆糊:一会儿i2c_adapter,一会儿i2c_client,一会儿i2c_driver,还有i2c_algorithm,名字长得像绕口令。我当初啃这块的时候也踩过不少坑,最典型的一个误区就是以为“写I2C驱动”就是写一个文件把读写寄存器全塞进去。后来在几个实际项目里反复折腾才明白,Linux I2C子系统的核心设计思想就一句话:把“怎么发波形”和“发什么数据”彻底拆开。
这个拆分对应到代码里,就是两条清晰的路径。一条是适配器驱动(adapter driver),也叫控制器驱动,它关心的是SoC上那个I2C控制器的寄存器怎么配、时钟怎么分频、中断怎么处理、DMA怎么搬数据。另一条是设备驱动(client driver),它关心的是挂在总线上的那颗从设备——比如一颗温度传感器、一颗EEPROM、一颗触摸屏控制器——它的寄存器地址是什么、上电时序怎么走、数据格式怎么解析。中间由I2C核心层(i2c-core)用一套标准数据结构把两边粘起来。
为什么非要这么分?你想想,同一颗温度传感器,今天焊在某个平台的I2C-1上,明天可能挪到另一个平台的I2C-3上,后天甚至可能挂到一个I2C多路复用器后面。如果设备驱动里写死了控制器寄存器地址,那每换一个平台就得改一遍驱动,这活儿没法干。分层之后,设备驱动只管对着i2c_client发i2c_transfer,底下到底是哪个控制器、用什么方式产生时序,它完全不关心。控制器驱动换了,设备驱动一行不用改。这就是分层的价值,也是理解整个I2C子系统的钥匙。
这一篇我打算把主从两侧的驱动都过一遍,重点放在实际动手时真正会卡住的地方:适配器驱动里i2c_algorithm的master_xfer怎么实现、设备驱动里probe的时序怎么排、设备树匹配为什么老是不生效、传输失败怎么排查。标题里说的“主从设备驱动”,我理解为主控侧(master/adapter)和从设备侧(client)两端的驱动编写,这两块合起来才是一个完整的I2C驱动开发闭环。适合已经能看懂基本内核模块框架、想真正把I2C驱动跑起来的读者,也适合被设备树匹配和传输超时折磨过的朋友对照排查。
2. 主控侧:适配器驱动到底要填哪些坑
2.1 i2c_adapter与i2c_algorithm的关系拆解
先把这个关系说透。i2c_adapter你可以理解成“一个I2C控制器的身份证”,它描述的是硬件实体:这个控制器叫什么名字、属于哪个设备、支持什么传输模式(标准模式100kHz、快速模式400kHz、快速加模式1MHz)、有没有SMBus特性。而i2c_algorithm是“这个控制器干活的方法集”,核心就一个函数指针master_xfer,负责把上层交下来的一串i2c_msg翻译成硬件时序发出去。
我见过不少初学者把这两个结构体的职责搞混,在adapter里塞业务逻辑,或者在algorithm里做设备相关的判断,结果代码越写越乱。正确的姿势是:adapter只做注册和资源管理,algorithm只做纯粹的“消息搬运”。master_xfer的签名长这样:
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);它的语义是:把msgs数组里的num条消息按顺序在总线上完成,成功返回处理的消息条数,失败返回负的错误码。注意这里有个容易忽略的点——返回值是消息条数而不是字节数,我早期就因为这个返回值写错,导致上层一直报-EREMOTEIO,查了半天才发现是自己返回了传输的字节总数。
i2c_msg结构体里几个字段必须吃透:
struct i2c_msg { __u16 addr; /* 从设备7位地址(不含读写位) */ __u16 flags; /* I2C_M_RD表示读,0表示写;还有I2C_M_TEN、I2C_M_NOSTART等 */ __u16 len; /* 数据长度 */ __u8 *buf; /* 数据缓冲区 */ };flags里的I2C_M_NOSTART是个高频坑点。有些从设备要求“写寄存器地址后不产生重复起始条件,直接接着读”,这时候就需要把读消息的I2C_M_NOSTART置上,让控制器在写完地址后不插入STOP+START,而是连续发读命令。如果你的控制器硬件不支持这种组合传输,就得在master_xfer里做拆分或者返回-EOPNOTSUPP,让上层去处理。这一点在对接某些EEPROM和传感器时特别关键,不处理的话读出来的数据全是0xFF。
2.2 master_xfer的实现骨架与中断/轮询取舍
真正写master_xfer的时候,第一个决策是用中断还是用轮询。这个选择不是拍脑袋,得看你的传输频率和数据量。我一般这么判断:如果单次传输数据量小(几十字节以内)、调用频率不高(比如配置阶段偶尔读写),轮询完全够用,代码简单、没有并发复杂度;如果数据量大(比如连续读几百字节的图像传感器配置区)或者调用频繁,就得上中断甚至DMA,否则CPU空转等总线,实时性会受影响。
轮询版本的骨架大致是这样,以常见的寄存器操作为例:
static int my_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct my_i2c *i2c = i2c_get_adapdata(adap); int i, ret = 0; for (i = 0; i < num; i++) { struct i2c_msg *msg = &msgs[i]; ret = my_i2c_xfer_one(i2c, msg); if (ret < 0) return ret; } return num; }my_i2c_xfer_one里要做的事情按顺序是:检查总线是否空闲(读状态寄存器)、配置从地址和读写方向、填发送FIFO或数据寄存器、启动传输、轮询等待完成标志或错误标志、检查ACK/NACK、读回接收数据。每一步都要处理超时,超时时间不能拍脑袋写个死循环,我一般按“最坏情况传输时间×3”来估。比如400kHz下传10个字节,理论时间约(10+1)×9/400k≈250微秒,那超时给1毫秒左右比较稳妥,具体还要看控制器有没有FIFO延迟。
中断版本则要把“启动传输”和“完成处理”拆成两半:master_xfer里启动传输后睡眠等待完成量(wait_for_completion_timeout),中断处理函数里根据状态寄存器判断是ACK、NACK还是仲裁丢失,然后complete唤醒。这里有个经典坑:中断里必须把状态标志清干净,否则会反复进中断。有些控制器需要先读状态再写清除,顺序反了标志清不掉,表现为中断风暴,系统直接卡死。我踩过一次,日志里全是中断计数暴涨,最后靠逐位对照手册才定位到清除顺序问题。
2.3 适配器注册与设备树关联的实操细节
适配器驱动写完后,注册流程是标准的platform driver套路,但有几个细节值得单独拎出来说。首先是i2c_add_adapter和i2c_add_numbered_adapter的区别:前者让内核动态分配总线号,后者要求你指定一个固定编号。产品化项目里我强烈建议用动态分配,因为固定编号在多控制器、多板型共存时极易冲突,一旦冲突注册就失败,而且失败信息不够直观,排查起来很费劲。动态分配后,总线号通过i2c_adapter.nr读出来,用户空间在/sys/bus/i2c/devices/i2c-X/下能看到。
其次是of_node的关联。设备树里控制器节点和驱动匹配靠的是compatible字符串,驱动里要写对应的of_match_table:
static const struct of_device_id my_i2c_of_match[] = { { .compatible = "vendor,my-i2c" }, { } }; MODULE_DEVICE_TABLE(of, my_i2c_of_match);这里最常见的失败原因是compatible字符串和设备树里写的不完全一致,多一个空格、大小写不同、厂商前缀拼错,都会导致probe根本不进。我排查这类问题的习惯是:先看/sys/firmware/devicetree/base/下对应节点在不在,再看/sys/bus/platform/drivers/下驱动有没有绑定成功,两步就能定位是设备树没解析到还是匹配字符串对不上。
还有一个容易忽略的点:适配器注册前要把adap->dev.of_node指向设备树节点,否则后续在这个总线上挂从设备时,从设备的设备树匹配会出问题。这个赋值通常在probe里从pdev->dev.of_node拿。另外adap->retries建议设成2到3,总线偶发干扰时内核会自动重试,比在驱动里自己写重试逻辑干净得多。
3. 从设备侧:client驱动从probe到传输的完整链路
3.1 i2c_driver的注册与probe时机
从设备驱动的入口是i2c_driver,核心是probe、remove和id_table(或of_match_table)。注册用i2c_add_driver,注销用i2c_del_driver,通常包在module_i2c_driver宏里,省去手写init/exit。
static const struct of_device_id my_sensor_of_match[] = { { .compatible = "vendor,my-sensor" }, { } }; MODULE_DEVICE_TABLE(of, my_sensor_of_match); static struct i2c_driver my_sensor_driver = { .driver = { .name = "my-sensor", .of_match_table = my_sensor_of_match, }, .probe = my_sensor_probe, .remove = my_sensor_remove, .id_table = my_sensor_id, }; module_i2c_driver(my_sensor_driver);probe被调用的时机是:I2C核心层发现总线上有一个设备(通过设备树、ACPI或i2c_new_device显式创建),并且它的地址和名字/兼容字符串与某个已注册的i2c_driver匹配上了。这里有个很多人困惑的点:设备树里从设备节点的父节点必须是I2C控制器节点,否则核心层不会把它当成I2C设备来枚举。我见过把传感器节点写到根节点下的,结果驱动死活不probe,查了半天才发现层级放错了。
probe里拿到的struct i2c_client *client就是这颗从设备的抽象,client->addr是7位地址,client->adapter指向它挂的总线。所有后续传输都通过这个client发起。probe里通常要做的事按顺序是:使能电源(如果有独立的regulator)、拉复位脚、等待上电稳定时间、读芯片ID确认通信正常、初始化寄存器、注册字符设备或input设备或hwmon设备等对外接口。读芯片ID这一步千万别省,它是验证“设备树地址对不对、硬件焊接有没有问题、上电时序够不够”的最快手段,没有这一步,后面出问题你连是通信问题还是配置问题都分不清。
3.2 传输API的选择:i2c_transfer还是smbus
内核给从设备驱动提供了两套API:底层的i2c_transfer和上层的SMBus系列函数(i2c_smbus_read_byte_data、i2c_smbus_write_word_data等)。选哪套?我的经验是:如果从设备是标准SMBus协议兼容的(大部分传感器、EEPROM、PMIC都是),优先用SMBus函数,因为它们帮你处理了消息组装,代码短、出错少;如果从设备有非标准的时序要求(比如需要组合传输、需要NOSTART、需要一次发很长的裸数据),那就用i2c_transfer自己拼i2c_msg。
举个对比。读一个8位寄存器地址、8位数据的传感器寄存器,SMBus写法是一行:
ret = i2c_smbus_read_byte_data(client, reg);i2c_transfer写法要拼两条消息:
u8 reg_buf = reg; u8 val; struct i2c_msg msgs[2] = { { .addr = client->addr, .flags = 0, .len = 1, .buf = ®_buf }, { .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = &val }, }; ret = i2c_transfer(client->adapter, msgs, 2);两条消息的写法本质上是“写寄存器地址+重复起始+读数据”的组合传输,绝大多数控制器都支持。但如果你的从设备要求“写完地址后不能有重复起始”,就得在第二条消息上加I2C_M_NOSTART,这时候SMBus函数就无能为力了,必须用i2c_transfer。所以我的建议是:先用SMBus把功能跑通,遇到时序不兼容再降级到i2c_transfer,这样开发效率最高。
3.3 设备树节点的正确写法与地址陷阱
设备树里从设备节点的写法看着简单,坑却不少。一个典型的传感器节点:
&i2c1 { status = "okay"; clock-frequency = <400000>; my_sensor: sensor@48 { compatible = "vendor,my-sensor"; reg = <0x48>; interrupt-parent = <&gpio1>; interrupts = <5 IRQ_TYPE_EDGE_FALLING>; vdd-supply = <®_3v3>; reset-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; }; };几个必须注意的点。第一,reg写的是7位地址,不是8位。有些数据手册给的是8位地址(含读写位),直接抄进去就错了一倍,表现为通信全NACK。判断方法很简单:7位地址范围是0x08到0x77,如果手册给的值大于0x77,那基本就是8位地址,右移一位即可。第二,clock-frequency写在控制器节点下,影响的是整条总线的速率,如果总线上挂了多个速率要求不同的设备,得按最慢的那个来,或者用I2C多路复用器分总线。第三,interrupts和reset-gpios这些可选属性,驱动里要用of_get_named_gpio、platform_get_irq之类的接口去取,取不到要有合理的降级逻辑(比如没有中断就退化成轮询),不能直接返回错误导致probe失败。
还有一个隐蔽的坑:同一个总线上不能有两个reg相同的设备,否则核心层枚举时会报地址冲突,后一个设备创建失败。这种情况在多路复用器场景下尤其要注意,复用器后面的不同通道可以挂相同地址的设备,但复用器本身要正确配置,否则核心层看到的就是同一总线上两个0x48,直接冲突。
4. 传输失败的排查:从NACK到超时的实战路径
4.1 常见错误码的含义与快速定位
I2C传输失败时内核返回的错误码是有明确语义的,读懂它们能省一大半排查时间。我整理了一张速查表,都是实际调试中反复遇到的:
| 错误码 | 含义 | 最常见原因 | 排查动作 |
|---|---|---|---|
| -ENXIO | 地址无应答 | 从设备地址错、设备没上电、焊接虚焊 | 用示波器看SDA/SCL有没有波形,确认地址 |
| -EREMOTEIO | 传输未完成/收到NACK | 从设备忙、寄存器地址越界、时序不满足 | 查手册确认时序,加延时重试 |
| -ETIMEDOUT | 等待完成超时 | 中断没来、时钟没使能、总线被拉死 | 查控制器时钟、看总线电平 |
| -EAGAIN | 总线仲裁丢失 | 多主竞争、SDA被其他设备拉低 | 检查总线上是否有其他主设备 |
| -EOPNOTSUPP | 不支持的操作 | 控制器不支持NOSTART或10位地址 | 换传输方式或换控制器 |
-ENXIO是我遇到最多的,八成是地址问题。有一次调试一颗EEPROM,手册上写地址0x50,我照着写,结果一直ENXIO,最后发现那颗芯片的A0/A1/A2地址脚在板子上被拉高了,实际地址是0x57。地址脚的电平一定要对着原理图确认,别信手册的默认值。
-ETIMEDOUT通常指向控制器侧问题。我遇到过一次,传输偶尔超时,查了半天发现是控制器的时钟门控在低功耗状态下被关了,唤醒后没重新使能。这类问题在带电源管理的产品上很常见,解决办法是在master_xfer开头确保时钟已使能,或者用pm_runtime_get_sync把控制器唤醒。
4.2 用i2c-tools做第一轮验证
在写驱动之前,我强烈建议先用i2c-tools在用户空间把硬件链路验证一遍。这一步能排除掉大量硬件和地址问题,避免在驱动里瞎猜。常用命令:
# 列出所有I2C总线 i2cdetect -l # 扫描某条总线上的设备 i2cdetect -y 1 # 读一个寄存器 i2cget -y 1 0x48 0x00 # 写一个寄存器 i2cset -y 1 0x48 0x01 0x80i2cdetect -y 1的输出里,如果目标地址显示为UU,说明该地址已被内核驱动占用,这是正常的;显示具体数值说明设备在线但没驱动绑定;显示--说明该地址无应答。如果扫描出来全是--,先别怀疑驱动,去查硬件供电和上拉电阻。I2C的SDA/SCL必须接上拉电阻,典型值4.7kΩ,有些模块板载了,有些没有,漏接的话总线根本拉不起来,波形是一潭死水。
4.3 逻辑分析仪抓波形的关键观察点
当软件层面排查不下去时,逻辑分析仪是终极武器。抓I2C波形我重点看四个地方。第一,起始条件是否正常:SCL高电平时SDA由高变低,如果这个沿不干净,可能是上拉太弱或总线电容太大。第二,地址字节的ACK位:第9个时钟周期SDA是否被从设备拉低,没拉低就是NACK,直接对应-ENXIO。第三,时钟频率是否超标:实测频率如果明显高于配置值,可能是分频参数算错,某些从设备对超频很敏感。第四,停止条件后总线是否释放:如果STOP之后SDA一直被拉低,说明有设备把总线锁死了,这种情况通常需要发9个时钟脉冲来解锁,或者断电重启。
我印象最深的一次调试,波形上一切正常,地址ACK了、数据也发了,但从设备就是没反应。最后对照手册发现,那颗芯片要求“写配置寄存器后必须等待至少5毫秒才能读”,而我的驱动写完立刻就读,从设备内部还没准备好,读回来全是无效值。这类时序间隔要求,数据手册里往往藏在小字注释里,一定要逐条抠。
5. 主从协同的几个进阶话题
5.1 中断驱动与轮询的混合策略
从设备驱动里,数据就绪的获取方式直接影响CPU占用和响应延迟。纯轮询简单但浪费CPU,纯中断省CPU但要求硬件支持。我的实际做法是混合:probe时先尝试申请中断,成功就走中断路径,失败则降级到定时轮询,并在日志里明确打印当前模式。这样同一份驱动能适配不同硬件配置,移植性好。
中断路径里要注意中断上下文不能做I2C传输,因为i2c_transfer可能睡眠。正确做法是中断处理函数里只做标记和唤醒(complete或schedule_work),真正的读取放到工作队列或线程化中断里。我见过在硬中断里直接调i2c_smbus_read_byte_data的代码,在部分平台上能跑,换个平台就报“sleeping function called from invalid context”,非常隐蔽。
5.2 多设备共用总线的速率与仲裁
一条I2C总线上挂多个设备时,速率要按最慢的来,这是硬约束。但更麻烦的是地址冲突和总线锁死。地址冲突前面说过,靠复用器或改地址脚解决。总线锁死则是某个设备在传输中途异常复位,把SDA一直拉低,导致整条总线瘫痪。防御手段有两个:一是在master_xfer里加总线恢复逻辑(检测到SDA常低时,手动发时钟脉冲直到释放),二是给关键设备加独立的复位控制,出问题时单独复位它而不是复位整个系统。
5.3 驱动调试的日志与动态调试技巧
内核的dynamic_debug对I2C调试非常有用。打开i2c核心和控制器驱动的调试输出:
echo 'file i2c-core-base.c +p' > /sys/kernel/debug/dynamic_debug/control echo 'file my_i2c.c +p' > /sys/kernel/debug/dynamic_debug/control打开后能看到每次传输的地址、方向、长度、返回值,定位问题比盲猜快得多。另外在master_xfer里临时加dev_dbg打印每条msg的addr/flags/len,配合逻辑分析仪的时间戳对照,能精确到哪一条消息出的问题。这些调试代码上线前记得清掉或降级为dev_vdbg,避免日志刷屏影响性能。
6. 我个人在实际操作中的几点体会
写I2C驱动这些年,最大的感受是:软件问题里有一半其实是硬件和手册没读透。地址脚电平、上拉电阻、上电时序、寄存器写入后的等待时间,这些细节数据手册里都写了,但往往藏在脚注或时序图的小字里,不逐条抠就会在驱动里绕圈子。所以我现在养成的习惯是,动手写代码前先把手册的时序章节和寄存器描述通读一遍,把关键延时和约束列成清单贴在旁边。
另一个体会是分层思维要贯穿始终。适配器驱动里不要出现任何具体设备的业务逻辑,设备驱动里不要假设控制器的任何硬件细节,两边只通过标准接口对话。这样代码才能在不同平台间复用,出问题时也能快速判断是控制器侧还是设备侧。我见过把设备初始化逻辑写进master_xfer的代码,换一颗从设备就得改控制器驱动,维护成本高得离谱。
最后分享一个小技巧:新平台第一次调I2C,先用i2c-tools确认链路通,再写一个最小的适配器驱动只实现master_xfer的轮询版本,用i2cdetect验证能扫到设备,最后才往上叠设备驱动和中断/DMA。这个由简到繁的顺序能让你在每一步都有明确的验证点,出问题时范围缩得很小,比一上来就写全套然后大海捞针高效得多。