上周调一块传感器板子,I2C死活读不出数据。示波器抓波形一看,SCL时钟频率飘忽不定,高电平宽度时宽时窄。第一反应是上拉电阻问题,换了几个值依旧无解。最后发现是适配器驱动里的时钟分频计算写崩了------这让我重新审视了Linux I2C子系统里最核心的两个结构:i2c_adapter和i2c_algorithm。
适配器:硬件载体的抽象
在Linux的I2C框架里,i2c_adapter代表一个物理I2C控制器。它可以是SoC内置的I2C控制器,也可以是GPIO模拟的软实现。关键字段就这几个:
c
struct i2c_adapter {
struct module *owner;
const struct i2c_algorithm *algo; // 核心!算法操作集
void *algo_data; // 算法私有数据
int nr; // 适配器编号(i2c-0、i2c-1那个数字)
char name[48]; // 适配器名字,dmesg里能看到
struct device dev; // 设备模型基础
// ... 其他省略
};
注册适配器时最常掉坑的是nr字段。如果填-1,内核会自动分配编号,但有些老驱动硬编码了编号,结果系统里多个适配器编号冲突,/dev/i2c-*对不上号。建议新驱动都让内核自动分配,通过of_i2c_get_adapter()之类接口去获取适配器指针。
算法:硬件操作的灵魂
i2c_algorithm才是真正干活的。它定义了如何在这个硬件上发起I2C传输:
c
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num);
int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality)(struct i2c_adapter *adap);
};
master_xfer是必须实现的,SMBus那个可以留空。重点是这个functionality函数,它告诉上层这个控制器支持哪些特性:
c
static u32 my_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | // 支持标准I2C协议
I2C_FUNC_SMBUS_QUICK | // 支持SMBus快速命令
I2C_FUNC_10BIT_ADDR; // 支持10位地址模式
}
这里踩过大坑:有一次没声明I2C_FUNC_PROTOCOL_MANGLING,结果上层想发重复START条件时直接失败。内核的I2C核心会根据这个标志决定是否帮你处理特殊时序,硬件不支持就得自己想办法。
实现一个GPIO模拟的I2C适配器
用GPIO模拟是最直观的理解方式。关键在实现master_xfer:
c
static int gpio_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct gpio_i2c_data *data = adap->algo_data;
int ret, i;
// 每个msg可能代表一次读或写操作
for (i = 0; i < num; i++) {
// 发START条件:SCL高时SDA拉低
gpiod_set_value(data->sda_gpio, 1);
gpiod_set_value(data->scl_gpio, 1);
udelay(5); // 这里要严格满足时序要求,太短设备不认
gpiod_set_value(data->sda_gpio, 0);
udelay(5);
gpiod_set_value(data->scl_gpio, 0);
// 发地址+读写位
ret = gpio_i2c_send_byte(data, msgs[i].addr << 1 |
(msgs[i].flags & I2C_M_RD ? 1 : 0));
if (ret < 0) {
gpio_i2c_stop(data); // 出错记得发STOP
return ret;
}
// 处理数据段
if (msgs[i].flags & I2C_M_RD) {
ret = gpio_i2c_read_bytes(data, msgs[i].buf, msgs[i].len);
} else {
ret = gpio_i2c_write_bytes(data, msgs[i].buf, msgs[i].len);
}
// 每个msg结束发STOP,除非要求NO_STOP
if (!(msgs[i].flags & I2C_M_NO_STOP)) {
gpio_i2c_stop(data);
}
}
return i; // 成功处理的msg数量
}
调试这种驱动时,一定要在gpio_i2c_send_byte()里加printk打印每个字节,不然时序错了都不知道死在哪一步。曾经因为ACK检测逻辑写反,读数据总是0xFF,折腾了一整天。
硬件控制器的寄存器操作
真实硬件控制器驱动主要就是配置寄存器。以某款SoC的I2C控制器为例:
c
static int hw_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct hw_i2c_dev *dev = adap->algo_data;
unsigned long timeout;
// 1. 使能控制器时钟(很多SoC不默认开时钟)
clk_prepare_enable(dev->clk);
// 2. 配置时钟分频,这里容易算错
u32 div = clk_get_rate(dev->clk) / (dev->bus_freq * 2) - 1;
writel(div, dev->base + I2C_CLK_DIV_REG);
// 3. 清中断状态,不然可能一上来就进中断
writel(0xFF, dev->base + I2C_INT_CLEAR_REG);
// 4. 遍历处理所有消息
for (int i = 0; i < num; i++) {
// 填目标地址
writel(msgs[i].addr, dev->base + I2C_TARGET_ADDR_REG);
// 配置传输方向和数据长度
u32 ctrl = msgs[i].len & 0xFF;
if (msgs[i].flags & I2C_M_RD)
ctrl |= I2C_CTRL_READ_BIT;
writel(ctrl, dev->base + I2C_CTRL_REG);
// 触发传输
writel(I2C_CMD_START, dev->base + I2C_CMD_REG);
// 等中断或轮询状态寄存器
timeout = jiffies + msecs_to_jiffies(1000);
while (!(readl(dev->base + I2C_STATUS_REG) & I2C_XFER_DONE)) {
if (time_after(jiffies, timeout)) {
dev_err(dev->dev, "i2c transfer timeout\n");
return -ETIMEDOUT;
}
cpu_relax();
}
// 读数据寄存器(如果是读操作)
if (msgs[i].flags & I2C_M_RD) {
for (int j = 0; j < msgs[i].len; j++) {
msgs[i].buf[j] = readl(dev->base + I2C_DATA_REG + j);
}
}
}
clk_disable_unprepare(dev->clk);
return num;
}
寄存器版本最头疼的是中断处理和DMA配置。建议先用轮询调通,再加中断优化。有个坑要注意:有些控制器要求先配置DMA再使能I2C,顺序反了数据传不出。
适配器注册的完整流程
c
static const struct i2c_algorithm my_algo = {
.master_xfer = my_i2c_xfer,
.functionality = my_i2c_func,
};
static int my_i2c_probe(struct platform_device *pdev)
{
struct i2c_adapter *adap;
struct my_priv *priv;
// 1. 分配私有数据结构
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
// 2. 映射寄存器、申请GPIO、配置时钟等
priv->base = devm_platform_ioremap_resource(pdev, 0);
priv->clk = devm_clk_get(&pdev->dev, NULL);
// 3. 填充适配器结构
adap = &priv->adapter;
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; // 声明支持的设备类
adap->algo = &my_algo;
adap->algo_data = priv; // 这里赋值,xfer函数才能拿到私有数据
adap->dev.parent = &pdev->dev;
adap->dev.of_node = pdev->dev.of_node;
snprintf(adap->name, sizeof(adap->name), "my-i2c-%s",
dev_name(&pdev->dev));
// 4. 设置超时和重试次数(默认值可能不适合你的设备)
adap->timeout = HZ; // 1秒超时
adap->retries = 3; // 失败重试3次
// 5. 注册到内核
i2c_add_adapter(adap); // 或者i2c_add_numbered_adapter()
// 6. 如果是平台设备,最好也添加到设备树兼容列表
of_i2c_register_devices(adap);
platform_set_drvdata(pdev, priv);
return 0;
}
注册失败最常见的原因是adap->owner没设对,模块卸载时内核会抱怨。还有adap->class字段,如果你明确知道这个I2C总线只接某种设备(比如只有温度传感器),就设上对应的CLASS,能避免不必要的设备探测。
调试技巧与经验
-
先看
/sys/bus/i2c/devices/i2c-*/name,确认你的适配器注册成功,名字对不对。 -
用
i2cdetect -l列出所有适配器 ,再用i2cdetect -y <编号>扫描设备地址。扫不出来先查硬件,再查驱动。 -
内核配置打开
CONFIG_I2C_DEBUG_CORE,能看到详细的I2C传输日志。不过输出量很大,最好用dmesg -w实时看。 -
GPIO模拟I2C时,用逻辑分析仪抓波形最直接。注意SCL/SDA的边沿时间,很多设备对上升沿时间有要求。
-
硬件I2C控制器驱动,先确保时钟配置正确。我遇到过因为父时钟分频比不对,实际I2C频率只有预期一半的情况。
-
10位地址设备 ,除了声明
I2C_FUNC_10BIT_ADDR,还要注意地址传递方式:msg->addr直接存10位地址,内核会自动处理两次地址发送。 -
适配器注销要在remove里做 ,但更推荐用
devm_i2c_add_adapter(),让设备模型自动管理生命周期。
最后说点个人体会:I2C驱动调试,三分在软件,七分在硬件。波形抓对了,问题就解决了一大半。写驱动时多想想"硬件这时候在干嘛",比盲目改代码有效得多。还有,别完全相信数据手册的时序图------有些芯片的I2C实现比较"个性",稍微不符合标准但能工作,这时候该妥协就得妥协,加几个udelay()能解决的事,别死磕。