4. I2C总线驱动编写:从注册到硬件时序适配
好,咱们进入I2C总线驱动的核心环节。这一章我会把i2c_bus_type注册、i2c_add_adapter流程,以及硬件时序适配这些事儿,掰开了揉碎了讲清楚。你可能会问:这些东西网上不都有吗?嗯,但实际项目中踩过的坑,文档里可不会写。
4.1 i2c_bus_type注册:总线驱动的基石
先说说i2c_bus_type。它是Linux I2C子系统的顶层抽象,说白了就是一根“总线类型”的绳子,把所有的I2C控制器和设备串起来。
内核启动时,i2c_init()会调用bus_register(&i2c_bus_type)。这个结构体长什么样?我贴一段核心代码:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
这里最关键的是.match函数。它决定了设备和驱动怎么配对。我见过不少新手,设备树里配对了,驱动也注册了,但就是probe不进去——最后发现是i2c_device_match没走对路径。
核心流程:
bus_register()→ 创建/sys/bus/i2c目录i2c_device_match()→ 先比对of_match_table,再比对id_tablei2c_device_probe()→ 调用驱动的probe函数
我个人习惯,在调试新板子时,先确认/sys/bus/i2c/devices/下有没有生成对应的设备节点。如果没有,那问题大概率出在设备树或者ACPI表上。
4.2 i2c_add_adapter流程:把控制器注册进系统
接下来是i2c_add_adapter。这个函数的作用,是把你的I2C控制器(adapter)注册到I2C核心中。流程其实不复杂,但每一步都有讲究。
我画了一张流程图,帮你理清脉络:
流程看着简单,但实际项目中,我遇到过一个问题:device_register成功了,但扫描设备列表时,某个设备的probe函数里又调用了i2c_add_adapter——这就形成了递归。嗯,内核里对这种嵌套调用是有锁保护的,但如果你自己写的驱动没处理好,很容易死锁。
我的经验:在probe函数里,尽量不要直接或间接调用i2c_add_adapter。如果确实需要,用schedule_work推迟到工作队列里执行。
4.3 硬件时序适配:从波形到代码
时序适配,是I2C驱动里最磨人的部分。你想想看,芯片手册上写的时序参数,和实际硬件跑出来的波形,往往有差距。
先看一个典型的I2C时序参数表:
| 参数 | 符号 | 标准模式(100kHz) | 快速模式(400kHz) |
|---|---|---|---|
| SCL时钟频率 | f_SCL | ≤100 kHz | ≤400 kHz |
| 保持时间(起始条件) | t_HD;STA | ≥4.0 μs | ≥0.6 μs |
| 低电平周期 | t_LOW | ≥4.7 μs | ≥1.3 μs |
| 高电平周期 | t_HIGH | ≥4.0 μs | ≥0.6 μs |
| 数据建立时间 | t_SU;DAT | ≥250 ns | ≥100 ns |
这些参数怎么映射到代码里?以常见的GPIO模拟I2C为例,核心就是控制SCL和SDA的电平翻转时机:
static void i2c_delay(struct i2c_adapter *adap)
{
/* 根据时钟频率计算延时 */
unsigned int ns = 1000000000 / (adap->bus_freq * 2);
ndelay(ns);
}
static void i2c_scl_set(struct i2c_adapter *adap, int val)
{
gpio_set_value(adap->scl_pin, val);
i2c_delay(adap);
}
static void i2c_sda_set(struct i2c_adapter *adap, int val)
{
gpio_set_value(adap->sda_pin, val);
i2c_delay(adap);
}
这里有个坑:ndelay在有些平台上精度不够。我曾经在一个ARM Cortex-A7的板子上,ndelay(250)实际跑出来是1微秒左右。为什么?因为内核的timer精度不够。后来我改用udelay配合循环微调,才把时序校准。
避坑指南:我曾经在调试一款温度传感器时,SCL高电平时间不够,导致传感器偶尔不响应。用示波器一看,t_HIGH只有0.4μs,而传感器要求至少0.6μs。解决办法是在SCL高电平后加一个ndelay(300)。所以,别完全相信代码里的延时函数,一定要用示波器实测。
4.4 时序调试实战:示波器是王道
调试I2C时序,我的工具链就三样:示波器、逻辑分析仪、printk。但最靠谱的还是示波器。
具体步骤:
- 抓起始条件:SCL高电平时,SDA从高变低。看t_HD;STA是否满足。
- 抓数据位:SCL低电平时改变SDA,SCL高电平时采样。看建立时间和保持时间。
- 抓停止条件:SCL高电平时,SDA从低变高。看t_SU;STO是否满足。
- 抓ACK:第9个SCL脉冲时,SDA被从机拉低。如果没拉低,说明从机没响应。
我遇到过最诡异的一次:所有时序都正常,但传感器就是不回ACK。折腾了两天,最后发现是上拉电阻阻值不对——板子上用了10kΩ,但总线电容太大,导致SDA上升沿太慢。换成4.7kΩ就好了。
关键检查点:
- 上拉电阻:一般4.7kΩ~10kΩ,根据总线电容调整
- SCL频率:用示波器测量实际频率,别信代码里的配置值
- ACK/NACK:第9个时钟后检查SDA电平
- 毛刺滤波:如果SDA/SCL上有毛刺,加RC滤波或软件去抖
4.5 总结一下
这一章我们走了三个关键步骤:i2c_bus_type注册是搭架子,i2c_add_adapter是装控制器,时序适配是调细节。每一步都有坑,但只要你记住:先看内核文档,再看芯片手册,最后用示波器验证,基本不会出大问题。