3. I2C从设备驱动开发:从设备驱动模型、probe流程、数据传输接口
好,咱们进入第三章。这一章我打算聊聊I2C从设备驱动到底怎么写。很多刚入行的朋友总觉得写驱动就是填几个结构体,注册一下就完事了。其实不然,驱动模型、probe流程、数据传输接口,这三块是环环相扣的。任何一个环节出问题,设备都跑不起来。
我个人习惯把从设备驱动拆成三个层次来看:驱动模型层(怎么注册)、probe流程层(怎么匹配)、数据传输层(怎么读写)。咱们一个一个说。
3.1 从设备驱动模型:结构体与注册
在Linux内核里,I2C从设备驱动主要围绕两个结构体展开:i2c_driver 和 i2c_client。前者代表驱动本身,后者代表设备实例。说白了,驱动是“会干活的人”,设备是“需要被干活的硬件”。
| 结构体 | 作用 | 关键字段 |
|---|---|---|
i2c_driver |
描述驱动能力,注册到I2C核心 | probe, remove, id_table |
i2c_client |
描述一个具体的I2C从设备 | addr, adapter, name |
i2c_device_id |
驱动与设备的匹配表 | name, driver_data |
注册驱动的代码模板大概是这样的:
static const struct i2c_device_id mydevice_id[] = {
{ "my_i2c_sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, mydevice_id);
static struct i2c_driver mydevice_driver = {
.driver = {
.name = "my_i2c_sensor",
.of_match_table = of_match_ptr(mydevice_of_match),
},
.probe = mydevice_probe,
.remove = mydevice_remove,
.id_table = mydevice_id,
};
module_i2c_driver(mydevice_driver);
这里有个细节:module_i2c_driver 是个宏,它帮你完成了 i2c_add_driver 和 i2c_del_driver 的注册与注销。我刚开始写驱动时,总喜欢手动调用这两个函数,后来发现用宏更干净,还不会漏掉错误处理。
of_match_table。否则probe可能不会被调用。我曾经在一个项目里忘了加,结果设备树配得再完美,驱动就是纹丝不动。
3.2 probe流程:从匹配到初始化
probe函数是驱动的“入口”。当I2C核心发现一个 i2c_client 与你的 i2c_driver 匹配时,就会调用probe。匹配的依据通常是设备树里的 compatible 字符串,或者ACPI表,或者板级代码里的 i2c_board_info。
probe里一般做三件事:
- 获取资源:从设备树或平台数据里读取配置参数。
- 初始化硬件:通过I2C读写寄存器,设置设备工作模式。
- 注册子系统:比如注册为输入设备、传感器、或者字符设备。
一个典型的probe函数长这样:
static int mydevice_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct mydevice_data *data;
int ret;
// 1. 分配私有数据结构
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
i2c_set_clientdata(client, data);
// 2. 读取设备树属性
if (of_property_read_u32(client->dev.of_node, "vref-mv", &data->vref))
data->vref = 3300; // 默认值
// 3. 初始化硬件
ret = mydevice_hw_init(client);
if (ret)
return ret;
// 4. 注册到子系统
ret = mydevice_register_subsystem(data);
if (ret)
return ret;
dev_info(&client->dev, "probe success, addr=0x%02x\n", client->addr);
return 0;
}
我曾经遇到过一个坑:probe里调用了 i2c_smbus_read_byte_data,但此时I2C控制器还没完全准备好,结果返回了-ENXIO。后来我在probe开头加了一个 msleep(10) 才稳定。嗯,硬件有时就是这么不讲道理。
3.3 数据传输接口:SMBus vs. 原生I2C
数据传输是驱动里最核心的部分。Linux内核提供了两套API:SMBus兼容接口 和 原生I2C消息接口。SMBus接口更简单,但功能有限;原生接口更灵活,但代码量稍大。
| 接口类型 | 函数 | 适用场景 |
|---|---|---|
| SMBus读字节 | i2c_smbus_read_byte_data(client, reg) |
单寄存器读取 |
| SMBus写字节 | i2c_smbus_write_byte_data(client, reg, val) |
单寄存器写入 |
| SMBus读块 | i2c_smbus_read_block_data(client, cmd, buf) |
连续读取多个字节 |
| 原生I2C消息 | i2c_transfer(adapter, msgs, num) |
复杂传输,如重复起始条件 |
我个人建议:能用SMBus就用SMBus。代码简洁,可读性强。但如果你需要发送重复起始条件(Repeated Start),或者需要同时读写不同长度的数据,那就得上原生接口了。
原生I2C传输的例子:
static int mydevice_read_block(struct i2c_client *client,
u8 reg, u8 *buf, int len)
{
struct i2c_msg msgs[2];
int ret;
// 第一条消息:写寄存器地址
msgs[0].addr = client->addr;
msgs[0].flags = 0; // 写
msgs[0].len = 1;
msgs[0].buf = ®
// 第二条消息:读数据
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; // 读
msgs[1].len = len;
msgs[1].buf = buf;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret < 0)
return ret;
return 0;
}
i2c_transfer 返回的是成功传输的消息数量,不是字节数。所以判断错误时,要检查返回值是否等于消息数(这里是2),而不是大于0。
3.4 知识结构图:从设备驱动核心流程
下面这张图把整个从设备驱动的核心流程串起来了。从驱动注册到probe,再到数据传输,一目了然。
3.5 避坑指南:我踩过的几个雷
写I2C从设备驱动,有几个地方特别容易出问题。我把自己踩过的坑列出来,你遇到了可以少走弯路。
- 地址不匹配:设备树里写的地址是7位还是10位?很多传感器手册给的是8位地址(含读写位),但内核用的是7位地址。记得右移一位。我曾经因为这个折腾了一下午。
- probe重复调用:如果设备树里配了多个节点,或者驱动支持多个设备ID,probe可能会被调用多次。确保你的初始化代码是幂等的,或者做好引用计数。
- 数据传输超时:I2C总线有默认超时时间(通常1秒)。如果你的从设备响应慢,考虑用
i2c_transfer的I2C_M_IGNORE_NAK标志,或者调整时钟频率。 - 忘记释放资源:probe里用了
devm_kzalloc可以自动释放,但如果你手动申请了GPIO、中断或DMA,记得在remove里释放。否则热插拔几次,系统就崩了。
i2c_transfer!I2C传输可能睡眠,而中断上下文不允许睡眠。如果非要在中断里读写,考虑使用 threaded IRQ 或 workqueue。