3. I2C控制器驱动:适配器注册、消息传输、中断处理
好,咱们进入I2C控制器驱动的核心环节。说白了,这部分就是让CPU里的I2C外设真正跑起来,能跟外设芯片对话。我刚开始写I2C驱动时,总觉得适配器注册就是个模板代码,后来踩了坑才明白——这里每一步都有讲究。
3.1 适配器注册——让内核认识你的I2C总线
适配器(Adapter)在Linux I2C子系统里,代表一条物理I2C总线。每个I2C控制器对应一个struct i2c_adapter。注册适配器,就是告诉内核:“嘿,我这有条I2C总线,叫I2C-0,能收发消息。”
核心数据结构长这样:
struct i2c_adapter {
struct module *owner;
unsigned int class; // 适配器支持的设备类
const struct i2c_algorithm *algo; // 算法指针,最关键
struct device dev;
struct i2c_bus_recovery_info *bus_recovery_info;
// ... 其他成员
};
注册流程我习惯分三步走:
- 分配并初始化适配器——填充
owner、class、algo等字段。 - 设置算法接口——实现
master_xfer和functionality回调。 - 调用
i2c_add_adapter()——把适配器挂入内核I2C总线。
关键点: i2c_add_adapter() vs i2c_add_numbered_adapter()。前者让内核自动分配总线号,后者你指定号。我建议产品中用后者,避免重启后总线号漂移。
举个例子,一个简化的注册片段:
static struct i2c_algorithm my_i2c_algo = {
.master_xfer = my_i2c_master_xfer,
.functionality = my_i2c_functionality,
};
static int my_i2c_probe(struct platform_device *pdev)
{
struct i2c_adapter *adap;
int ret;
adap = devm_kzalloc(&pdev->dev, sizeof(*adap), GFP_KERNEL);
if (!adap)
return -ENOMEM;
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->algo = &my_i2c_algo;
adap->dev.parent = &pdev->dev;
strscpy(adap->name, "my_i2c_adapter", sizeof(adap->name));
ret = i2c_add_adapter(adap);
if (ret)
return ret;
platform_set_drvdata(pdev, adap);
return 0;
}
嗯,这里要注意:devm_kzalloc自动管理内存,但i2c_add_adapter失败后要手动释放吗?不用,devm_系列会在probe失败时自动清理。但如果你用普通kzalloc,记得在错误路径加kfree。
3.2 消息传输——master_xfer的实现艺术
master_xfer是适配器驱动的灵魂。内核I2C核心层调用它,传入一个struct i2c_msg数组,你要把这些消息通过硬件发出去。
消息结构体:
struct i2c_msg {
__u16 addr; // 从设备地址(7位或10位)
__u16 flags; // I2C_M_RD 等标志
__u16 len; // 数据长度
__u8 *buf; // 数据缓冲区
};
实现master_xfer时,我一般按这个流程走:
- 解析消息数组——判断是读还是写,地址是否匹配。
- 构造硬件传输序列——把消息转换成控制器能理解的启动条件、地址、数据、停止条件。
- 轮询或中断等待完成——短消息用轮询,长消息用中断。
- 返回传输的消息数——成功返回正数,失败返回负错误码。
个人经验: 我遇到过一种情况——从设备在发送地址后不响应ACK。这时master_xfer要能检测到NACK,并立即发送停止条件,然后返回负值。否则总线会一直挂住,后续所有传输都卡死。
一个简化的轮询式传输示例:
static int my_i2c_master_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct my_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
int i, ret;
for (i = 0; i < num; i++) {
// 发送启动条件和从地址
my_i2c_send_start(i2c_dev);
ret = my_i2c_send_addr(i2c_dev, msgs[i].addr, msgs[i].flags);
if (ret < 0)
goto err;
if (msgs[i].flags & I2C_M_RD) {
// 读操作
my_i2c_read_bytes(i2c_dev, msgs[i].buf, msgs[i].len);
} else {
// 写操作
my_i2c_write_bytes(i2c_dev, msgs[i].buf, msgs[i].len);
}
}
my_i2c_send_stop(i2c_dev);
return i;
err:
my_i2c_send_stop(i2c_dev);
return ret;
}
你想想看,如果消息数组里有多个消息,且第一个是写、第二个是读,那就需要重复启动条件(Repeated Start)。很多新手在这里翻车——在两个消息之间错误地发送了停止条件,导致从设备退出传输。
3.3 中断处理——别让CPU空等
轮询方式在短传输时没问题,但一旦数据量大了,CPU就一直在那傻等。我建议产品中都用中断方式。
中断处理的核心逻辑:
- 中断服务程序(ISR)——读取状态寄存器,判断是发送完成、接收完成、还是错误。
- 状态机驱动——每个中断触发一次状态迁移,直到整个消息传输完成。
- 完成量唤醒——传输完成后,用
complete()唤醒等待的master_xfer。
状态机设计我习惯用枚举:
enum my_i2c_state {
STATE_IDLE,
STATE_START,
STATE_ADDR,
STATE_WRITE,
STATE_READ,
STATE_STOP,
STATE_ERROR,
};
ISR里根据当前状态和硬件事件,决定下一步操作。举个例子:
static irqreturn_t my_i2c_isr(int irq, void *dev_id)
{
struct my_i2c_dev *i2c_dev = dev_id;
u32 status = readl(i2c_dev->base + REG_STATUS);
if (status & STATUS_TX_EMPTY) {
// 发送缓冲区空,可以继续发数据
if (i2c_dev->msg_idx < i2c_dev->msg->len) {
writel(i2c_dev->msg->buf[i2c_dev->msg_idx++],
i2c_dev->base + REG_DATA);
} else {
// 数据发完,发停止条件
my_i2c_send_stop(i2c_dev);
complete(&i2c_dev->done);
}
}
if (status & STATUS_NACK) {
dev_err(i2c_dev->dev, "NACK received\n");
i2c_dev->state = STATE_ERROR;
my_i2c_send_stop(i2c_dev);
complete(&i2c_dev->done);
}
return IRQ_HANDLED;
}
我曾经踩过的坑: 中断处理里直接调用了i2c_transfer()。这是大忌!i2c_transfer会尝试获取总线锁,而中断上下文不能睡眠。正确的做法是:ISR只操作硬件寄存器,用complete或工作队列通知上半部。
3.4 知识结构总览
下面这张图把适配器注册、消息传输、中断处理的关系串起来了:
3.5 关键参数对比
不同传输方式的选择,直接影响系统实时性。我整理了一张对比表:
| 特性 | 轮询方式 | 中断方式 | DMA方式 |
|---|---|---|---|
| CPU占用率 | 高(忙等) | 低(仅中断时) | 极低(硬件搬运) |
| 延迟确定性 | 高(无调度抖动) | 中(受中断优先级影响) | 中(DMA完成中断) |
| 适用场景 | 短消息(< 10字节) | 常规传输(10~256字节) | 大数据块(> 256字节) |
| 实现复杂度 | 低 | 中 | 高(需DMA引擎支持) |
| 典型错误 | 超时未处理 | ISR中调用阻塞函数 | DMA缓冲对齐问题 |
我的建议: 在RTOS环境下,如果I2C传输频率不高(比如每秒几十次),中断方式足够。但如果你的系统里I2C是高频数据通道(比如传感器数据流),考虑用DMA+双缓冲,能大幅降低CPU负载。
3.6 避坑指南——我踩过的三个坑
做I2C控制器驱动这些年,有些坑印象特别深:
- 总线锁死恢复——我曾经遇到过从设备异常拉低SCL,导致总线卡死。后来加了
bus_recovery_info,用GPIO模拟9个时钟脉冲强制恢复。这个机制一定要在适配器注册前配好。 - 中断共享冲突——有些SoC里多个外设共享一个中断号。ISR里必须先判断中断是否来自本设备,不是就返回
IRQ_NONE。否则会干扰其他驱动。 - 时钟延展处理——有些慢速从设备会拉低SCL延长时钟。控制器必须支持时钟延展,否则传输会超时。我建议在
master_xfer里加一个超时计数器,避免死等。
嗯,这部分内容就到这里。适配器注册是骨架,消息传输是血肉,中断处理是神经——三者配合好,I2C控制器驱动才算真正立住了。