4. I2C控制器驱动开发(Linux):I2C核心层、I2C总线驱动、I2C设备驱动、adapter注册、client注册
好,咱们今天聊聊I2C控制器驱动。这部分内容,说白了就是Linux内核怎么把I2C总线管起来的。很多初学者一上来就对着datasheet猛写寄存器,结果发现驱动加载了,设备就是不通。为什么?因为没搞懂内核那套分层机制。
我个人习惯,先把框架看清楚再动手。I2C子系统在内核里分了三层:核心层、总线驱动层、设备驱动层。这三层各司其职,你想想看,就像公司里的三个部门——核心层是行政,管注册、管匹配;总线驱动是硬件接口,负责跟I2C控制器寄存器打交道;设备驱动是业务部门,只管跟具体传感器、EEPROM通信。
4.1 I2C核心层:承上启下的枢纽
核心层代码主要在drivers/i2c/i2c-core.c里。它不直接操作硬件,但所有I2C驱动的注册、注销、设备枚举、总线匹配,都得经过它。
核心层提供了几个关键结构体:
| 结构体 | 作用 | 我常用的场景 |
|---|---|---|
i2c_adapter |
代表一个I2C控制器 | 每个硬件I2C接口对应一个adapter |
i2c_client |
代表一个I2C从设备 | 挂在adapter上的具体芯片 |
i2c_algorithm |
控制器的通信方法 | 实现master_xfer函数 |
i2c_driver |
设备驱动的抽象 | 匹配client并实现probe |
核心层还维护了一个全局的adapter链表和driver链表。每次新注册一个adapter,核心层就会遍历所有已注册的driver,看看有没有能匹配上的。反过来,注册driver时也会扫描所有adapter。这个匹配机制,我当年调试的时候踩过坑——设备树里地址写错了,核心层匹配不上,驱动probe死活不调用。
关键函数调用链:
i2c_add_adapter() → i2c_register_adapter() → of_i2c_register_devices() → 创建设备节点 → 触发driver匹配
4.2 I2C总线驱动:跟硬件打交道的人
总线驱动,也叫控制器驱动。它负责实现i2c_algorithm里的master_xfer()函数。这个函数就是I2C通信的底层实现——怎么发start信号、怎么传地址、怎么收ACK、怎么发stop信号。
我在项目中遇到过一个问题:某款芯片的I2C控制器,发送地址后硬件不自动检查ACK,需要软件轮询状态寄存器。当时我按标准写法实现了master_xfer,结果通信成功率只有70%。后来加了重试机制和超时处理,才算稳定。
总线驱动的注册流程大致如下:
- 分配
i2c_adapter结构体 - 填充
i2c_algorithm,主要是master_xfer和functionality - 设置adapter的
dev.of_node(如果使用设备树) - 调用
i2c_add_adapter()或i2c_add_numbered_adapter()
这里有个细节:i2c_add_numbered_adapter()可以指定总线编号,比如i2c-0、i2c-1。而i2c_add_adapter()让内核自动分配。我个人习惯用带编号的版本,方便用户空间工具定位。
避坑指南:我曾经在注册adapter时忘了设置class字段,结果用户空间的i2cdetect工具扫不到设备。后来查了内核文档才发现,adapter->class = I2C_CLASS_HWMON这类标志位必须显式设置,否则核心层不会把设备暴露给用户空间。
4.3 I2C设备驱动:只管业务逻辑
设备驱动就简单多了。它不需要知道I2C控制器怎么发start信号,只需要调用i2c_transfer()或i2c_smbus_read_byte_data()这类API。核心层和总线驱动会帮你搞定底层时序。
设备驱动的核心是i2c_driver结构体:
static const struct i2c_device_id xxx_id[] = {
{ "xxx_sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, xxx_id);
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "vendor,xxx-sensor" },
{ }
};
MODULE_DEVICE_TABLE(of, xxx_of_match);
static struct i2c_driver xxx_driver = {
.driver = {
.name = "xxx_sensor",
.of_match_table = xxx_of_match,
},
.probe = xxx_probe,
.remove = xxx_remove,
.id_table = xxx_id,
};
module_i2c_driver(xxx_driver);
注意module_i2c_driver()这个宏,它把i2c_add_driver()和i2c_del_driver()封装好了。我见过有人手写module_init和module_exit,结果忘了调用i2c_add_driver,驱动加载了但probe不执行。
4.4 adapter注册:让内核认识你的控制器
adapter注册是总线驱动的最后一步。核心函数是i2c_register_adapter(),它做了几件事:
- 分配总线编号(如果没指定)
- 创建
/dev/i2c-N设备节点 - 扫描设备树,为每个子节点创建
i2c_client - 触发driver匹配
设备树中adapter的典型写法:
&i2c1 {
status = "okay";
clock-frequency = <100000>;
temp_sensor: temperature@48 {
compatible = "vendor,temp-sensor";
reg = <0x48>;
};
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
内核解析设备树时,会为temp_sensor和eeprom各创建一个i2c_client。这里要注意reg地址必须跟硬件一致,我曾经把0x48写成0x49,结果驱动probe了但读写全是错误数据。
重要提醒:adapter注册成功后,i2c-core会调用of_i2c_register_devices()。如果你的设备树里没有子节点,或者status = "disabled",那么不会创建任何client。很多新手在设备树里写了节点但忘了加status = "okay",折腾半天找不到原因。
4.5 client注册:设备驱动的灵魂
i2c_client代表一个具体的I2C从设备。它通常在两种情况下被创建:
- 设备树解析:adapter注册时自动创建
- 板级代码:通过
i2c_new_device()手动创建
client注册后,核心层会遍历所有i2c_driver,调用i2c_match_id()或of_match_node()进行匹配。匹配成功就调用driver的probe函数。
probe函数里,你拿到的是struct i2c_client *client,通过它你可以:
- 读取设备地址:
client->addr - 获取adapter:
client->adapter - 发起通信:
i2c_master_send()、i2c_master_recv()
我习惯在probe里先读一下设备的ID寄存器,确认硬件确实在线。如果读不到,就返回-ENODEV。这个习惯帮我避免了好几次硬件虚焊导致的问题。
4.6 整体框架图
下面这张图展示了I2C子系统的分层结构和注册流程。我画的时候特意把核心层放在中间,因为它确实是整个系统的枢纽。
4.7 调试技巧与常见问题
最后分享几个我实际调试中总结的经验:
- adapter注册失败:检查
dmesg,看是不是中断号冲突或IO内存申请失败。我遇到过platform_get_resource返回NULL的情况,原因是设备树里reg属性写错了。 - probe不调用:先确认
i2cdetect -l能看到adapter,再用i2cdetect -y N扫描总线。如果设备地址显示UU,说明驱动已绑定;如果显示--,说明硬件没应答。 - 通信超时:检查时钟频率是否匹配。有些从设备只支持100kHz,你设成400kHz就会超时。另外,上拉电阻阻值不对也会导致时序异常。
调试利器:我习惯在probe函数里加dev_info(&client->dev, "probe success, addr=0x%02x\n", client->addr);。别小看这一行,它能帮你快速定位是驱动没加载,还是probe失败了。