Linux i2c-gpio 驱动 v6.x 深度解析:从设备树到 /dev/i2c-x 的 5 个关键步骤
Linux i2c-gpio 驱动 v6.x 深度解析:从设备树到 /dev/i2c-x 的 5 个关键步骤
在嵌入式Linux开发中,当硬件I2C控制器资源不足时,GPIO模拟I2C成为了一种经济高效的解决方案。本文将深入剖析Linux内核中i2c-gpio驱动的实现机制,特别针对v6.x内核版本,从设备树配置到用户空间设备节点创建的完整流程。
1. 设备树配置与属性解析
i2c-gpio驱动的起点始于设备树(Device Tree)的配置。一个典型的i2c-gpio节点示例如下:
i2c_gpio: i2c-gpio {
compatible = "i2c-gpio";
gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>, /* SDA */
<&gpio1 28 GPIO_ACTIVE_HIGH>; /* SCL */
i2c-gpio,delay-us = <5>; /* ~100 kHz */
#address-cells = <1>;
#size-cells = <0>;
};
关键属性解析:
| 属性 | 作用 | 默认值 | 备注 |
|---|---|---|---|
gpios |
指定SDA和SCL使用的GPIO | 无 | 必须按SDA、SCL顺序排列 |
i2c-gpio,delay-us |
位间隔时间(μs) | 5 | 决定I2C时钟频率 |
i2c-gpio,sda-open-drain |
SDA开漏模式 | 自动配置 | 仅在外部已配置开漏时使用 |
i2c-gpio,scl-open-drain |
SCL开漏模式 | 自动配置 | 同上 |
i2c-gpio,scl-output-only |
SCL仅输出 | false | 特殊场景使用 |
注意:
i2c-gpio,sda-open-drain和i2c-gpio,scl-open-drain属性在新版内核中已被标记为deprecated,推荐直接在gpios属性中使用GPIO_OPEN_DRAIN标志。
驱动在probe阶段通过 of_i2c_gpio_get_props() 函数解析这些属性,转换为 i2c_gpio_platform_data 结构体:
static void of_i2c_gpio_get_props(struct device_node *np,
struct i2c_gpio_platform_data *pdata)
{
u32 reg;
of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay);
if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", ®))
pdata->timeout = msecs_to_jiffies(reg);
pdata->sda_is_open_drain = of_property_read_bool(np, "i2c-gpio,sda-open-drain");
pdata->scl_is_open_drain = of_property_read_bool(np, "i2c-gpio,scl-open-drain");
pdata->scl_is_output_only = of_property_read_bool(np, "i2c-gpio,scl-output-only");
}
2. GPIO初始化和算法接口准备
获取设备树配置后,驱动需要完成GPIO的初始化和算法接口的准备:
-
GPIO获取与配置 :
- 通过
i2c_gpio_get_desc()获取SDA和SCL GPIO描述符 - 根据是否指定open-drain属性,决定GPIO配置模式:
if (pdata->sda_is_open_drain) gflags = GPIOD_OUT_HIGH; else gflags = GPIOD_OUT_HIGH_OPEN_DRAIN; priv->sda = i2c_gpio_get_desc(dev, "sda", 0, gflags);
- 通过
-
算法接口准备 :
- 填充
i2c_algo_bit_data结构体,提供GPIO操作函数:bit_data->setsda = i2c_gpio_setsda_val; bit_data->setscl = i2c_gpio_setscl_val; if (!pdata->scl_is_output_only) bit_data->getscl = i2c_gpio_getscl; bit_data->getsda = i2c_gpio_getsda; bit_data->udelay = pdata->udelay; bit_data->timeout = pdata->timeout;
- 填充
-
时钟频率计算 :
- 根据
delay-us属性计算实际传输速率 - 标准模式(100kHz)通常对应
delay-us = 5 - 快速模式(400kHz)需要
delay-us = 1
- 根据
GPIO状态操作函数实现示例 :
static void i2c_gpio_setsda_val(void *data, int state)
{
struct i2c_gpio_private_data *priv = data;
gpiod_set_value(priv->sda, state);
}
static int i2c_gpio_getsda(void *data)
{
struct i2c_gpio_private_data *priv = data;
return gpiod_get_value(priv->sda);
}
3. I2C适配器注册流程
准备好算法接口后,驱动需要向I2C核心层注册适配器:
-
适配器结构体初始化 :
adap->owner = THIS_MODULE; adap->algo_data = bit_data; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->dev.parent = dev; adap->dev.of_node = np; -
总线编号分配 :
- 通过
pdev->id获取初始编号 - 最终编号由
i2c_add_numbered_adapter()根据aliases节点确定 - 设备树aliases示例:
aliases { i2c3 = &i2c_gpio; }
- 通过
-
适配器注册 :
- 调用
i2c_bit_add_numbered_bus()完成注册 - 内部通过
__i2c_bit_add_bus()实现核心注册逻辑
- 调用
注册过程关键代码路径 :
ret = i2c_bit_add_numbered_bus(adap);
→ __i2c_bit_add_bus(adap, i2c_add_numbered_adapter)
→ adap->algo = &i2c_bit_algo;
→ add_adapter(adap) // i2c_add_numbered_adapter
→ __i2c_add_numbered_adapter
→ i2c_register_adapter
成功注册后,内核会为该适配器创建 /dev/i2c-x 设备节点,用户空间程序可通过标准I2C接口访问。
4. 位操作算法实现
i2c-algo-bit.c提供了GPIO模拟I2C的核心位操作算法,主要包括:
1. 主传输函数 bit_xfer()
- 处理I2C消息的起始条件
- 实现字节级别的发送/接收
- 处理ACK/NACK响应
- 生成停止条件
2. 关键时序操作
static void i2c_start(struct i2c_algo_bit_data *adap)
{
/* START条件: SDA在SCL高电平时拉低 */
adap->setsda(adap->data, 1);
adap->setscl(adap->data, 1);
ndelay(adap->udelay);
adap->setsda(adap->data, 0);
ndelay(adap->udelay);
adap->setscl(adap->data, 0);
ndelay(adap->udelay);
}
3. 字节传输流程
- SCL拉低,准备发送位
- 设置SDA电平(发送阶段)或读取SDA(接收阶段)
- SCL拉高,保持稳定时钟周期
- SCL拉低,完成位传输
- 重复8次完成一个字节传输
4. 时钟同步机制
- 通过
udelay保证时序符合I2C规范 - 超时处理通过
timeout参数实现
5. 用户空间访问与调试接口
注册成功后,用户可通过多种方式访问GPIO模拟的I2C总线:
1. 标准设备节点
/dev/i2c-x:提供基本的I2C设备访问- 使用
i2c-tools进行测试:# 扫描总线上的设备 i2cdetect -y 4 # 读取设备寄存器 i2cget -y 4 0x50 0x00
2. 调试文件系统接口 新版内核为i2c-gpio提供了debugfs接口,可用于底层调试:
debugfs_create_file("incomplete_address_phase", 0200,
priv->adap.debugfs, priv, &fops_incomplete_addr_phase);
3. 故障注入测试 驱动内置了多种故障注入测试功能:
- 模拟仲裁丢失
- 不完整地址相位
- 异常终止条件
性能优化建议:
- 根据实际需求调整
delay-us参数 - 对于高速传输,考虑降低GPIO操作延迟
- 避免在中断上下文中执行长距离传输
通过这五个关键步骤,Linux内核实现了完整的GPIO模拟I2C功能。相比硬件I2C控制器,这种方案虽然性能较低,但在资源受限的场景下提供了灵活可靠的解决方案。实际开发中,建议通过示波器验证信号质量,确保时序符合I2C规范。
更多推荐



所有评论(0)