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_table
  • i2c_device_probe() → 调用驱动的probe函数

我个人习惯,在调试新板子时,先确认/sys/bus/i2c/devices/下有没有生成对应的设备节点。如果没有,那问题大概率出在设备树或者ACPI表上。

4.2 i2c_add_adapter流程:把控制器注册进系统

接下来是i2c_add_adapter。这个函数的作用,是把你的I2C控制器(adapter)注册到I2C核心中。流程其实不复杂,但每一步都有讲究。

我画了一张流程图,帮你理清脉络:

i2c_add_adapter 核心流程 i2c_add_adapter() i2c_adapter_idr 分配ID device_register(&adap->dev) 扫描已注册的i2c_client列表 对每个设备调用 i2c_device_probe() adapter 注册完成 注意点: • ID分配失败直接返回 • device_register 失败 需清理IDR

流程看着简单,但实际项目中,我遇到过一个问题: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。但最靠谱的还是示波器。

具体步骤:

  1. 抓起始条件:SCL高电平时,SDA从高变低。看t_HD;STA是否满足。
  2. 抓数据位:SCL低电平时改变SDA,SCL高电平时采样。看建立时间和保持时间。
  3. 抓停止条件:SCL高电平时,SDA从低变高。看t_SU;STO是否满足。
  4. 抓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是装控制器,时序适配是调细节。每一步都有坑,但只要你记住:先看内核文档,再看芯片手册,最后用示波器验证,基本不会出大问题。