3. I2C总线驱动编写:适配器注册与master_xfer实现

好,咱们今天聊聊I2C总线驱动的核心部分。说白了,就是怎么让你的Linux内核认识你的I2C控制器,并且能真正干活——收发数据。

我个人习惯把I2C驱动分成两层来看:一是适配器驱动,二是设备驱动。今天咱们重点讲适配器驱动。你想想看,适配器就是那个“管家”,负责管理总线上所有的设备。没有它,设备驱动就是无根之萍。

3.1 I2C适配器驱动注册与注销

适配器驱动的核心数据结构是 struct i2c_adapter。我刚开始写驱动时,总觉得这结构体字段太多,后来发现其实就几个关键点。

struct i2c_adapter {
    struct module *owner;
    unsigned int class;
    const struct i2c_algorithm *algo;
    void *algo_data;
    struct device dev;
    struct i2c_lock_operations *lock_ops;
    // ... 其他字段
};

注册一个适配器,流程其实很清晰:

  1. 分配并初始化 i2c_adapter 结构体
  2. 设置算法(就是那个 master_xfer 函数)
  3. 调用 i2c_add_adapter() 或 i2c_add_numbered_adapter()

关键区别:i2c_add_adapter() 让内核自动分配总线号,而 i2c_add_numbered_adapter() 由你指定。我在项目中遇到过,如果硬件设计上总线号有固定要求,一定要用后者,否则设备树里配的地址全对不上,排查起来很痛苦。

static struct i2c_adapter my_i2c_adapter = {
    .owner = THIS_MODULE,
    .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
    .algo = &my_i2c_algorithm,
    .dev.parent = &pdev->dev,
    .name = "my-i2c-bus",
};

// 注册
ret = i2c_add_adapter(&my_i2c_adapter);
if (ret) {
    dev_err(&pdev->dev, "Failed to add I2C adapter\n");
    return ret;
}

// 注销
i2c_del_adapter(&my_i2c_adapter);

小技巧:注销时记得先确保总线上没有正在进行的传输。我曾经在驱动卸载时没处理好,结果设备还在读写,直接内核崩溃。建议在 remove 函数里先调用 i2c_lock_adapter() 锁住总线,再执行注销。

3.2 实现master_xfer函数——核心中的核心

master_xfer 是适配器驱动的灵魂。内核通过它来发起实际的I2C传输。它的原型长这样:

static int my_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

参数解释:

  • adap:指向你的适配器
  • msgs:消息数组,一个传输可能包含多条消息
  • num:消息数量

返回值:成功传输的消息数量,失败返回负值。

嗯,这里要注意。很多新手会直接返回 num 表示全部成功,但如果你只成功传了前两条,第三条失败了,应该返回2。内核会根据返回值判断哪些消息已经处理了。

static int my_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    int i;
    struct my_i2c_dev *i2c_dev = adap->algo_data;

    for (i = 0; i < num; i++) {
        struct i2c_msg *msg = &msgs[i];
        int ret;

        // 设置从设备地址
        my_i2c_set_slave_addr(i2c_dev, msg->addr);

        if (msg->flags & I2C_M_RD) {
            // 读操作
            ret = my_i2c_read(i2c_dev, msg->buf, msg->len);
        } else {
            // 写操作
            ret = my_i2c_write(i2c_dev, msg->buf, msg->len);
        }

        if (ret < 0) {
            dev_err(&adap->dev, "Transfer failed at msg %d\n", i);
            return i;  // 返回已成功传输的消息数
        }
    }

    return num;
}

避坑指南:我曾经在实现master_xfer时,忽略了硬件FIFO的深度限制。一次传输超过FIFO大小,数据就乱了。后来我加了个循环,每次只发FIFO能容纳的大小,问题才解决。记得检查你的硬件手册,看看单次传输最大长度是多少。

3.3 I2C传输消息格式——i2c_msg详解

struct i2c_msg 是I2C传输的基本单元。它长这样:

struct i2c_msg {
    __u16 addr;    // 从设备地址(7位或10位)
    __u16 flags;   // 传输标志
    __u16 len;     // 数据长度
    __u8 *buf;     // 数据缓冲区
};

flags字段常用的有:

标志 含义
I2C_M_RD 读操作。不设置就是写操作
I2C_M_TEN 10位地址模式
I2C_M_NOSTART 不发送起始条件,用于组合传输
I2C_M_REV_DIR_ADDR 反转读写方向(很少用)

你想想看,一个典型的I2C读操作,其实是两个消息的组合:

  1. 先发一个写消息,告诉设备要读哪个寄存器
  2. 再发一个读消息,读取数据
// 读取设备寄存器0x10的值
struct i2c_msg msgs[2];
u8 reg_addr = 0x10;
u8 value;

msgs[0].addr = 0x50;        // 设备地址
msgs[0].flags = 0;          // 写操作
msgs[0].len = 1;
msgs[0].buf = &reg_addr;

msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD;   // 读操作
msgs[1].len = 1;
msgs[1].buf = &value;

// 发起传输
ret = i2c_transfer(adapter, msgs, 2);

重点:i2c_transfer() 是内核提供的上层接口,它会调用你的 master_xfer。但要注意,i2c_transfer() 可能会睡眠,所以不能在中断上下文调用。我在项目中就踩过这个坑,在定时器中断里调了 i2c_transfer,结果系统直接死锁。

3.4 知识体系总览

为了让你更直观地理解整个流程,我画了一张图:

I2C适配器驱动核心流程 用户空间 (应用程序 / i2c-tools) 内核I2C核心层 (i2c-core) I2C适配器驱动 i2c_add_adapter() master_xfer() i2c_msg解析 硬件I2C控制器 (寄存器操作) 图1: I2C适配器驱动从用户空间到硬件的完整调用链

从这张图你能看到,适配器驱动处于中间层。它向上对接内核核心层,向下操作硬件寄存器。说白了,你的任务就是把 master_xfer 里的 i2c_msg 翻译成硬件能理解的时序。

个人经验:调试I2C驱动时,我建议先用逻辑分析仪抓波形。别急着怀疑代码,先看看SCL和SDA上的时序对不对。我曾经花了两天查一个bug,最后发现是硬件上拉电阻焊错了。嗯,从那以后我养成了先看波形的习惯。

好了,关于I2C适配器驱动的注册、master_xfer 的实现以及消息格式,就讲到这里。这些是I2C驱动开发的基础,理解了它们,后面的设备驱动就水到渠成了。


可可精品资料库