一、I2C 驱动框架
1.1 I2C 驱动核心组件
1.1.1 i2c_driver 结构体
i2c_driver 是 I2C 驱动的核心数据结构,定义如下:
c
struct i2c_driver {
unsigned int class;
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
void (*remove)(struct i2c_client *client);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*suspend)(struct device *dev, pm_message_t mesg);
int (*resume)(struct device *dev);
int (*shutdown)(struct i2c_client *client);
};
关键字段说明:
probe: 设备探测函数,匹配成功后调用remove: 设备移除函数,驱动卸载时调用driver: 设备驱动结构,包含名称、所有者、设备树匹配表等id_table: 传统匹配表(非设备树)of_match_table: 设备树匹配表(现代系统推荐)
1.2 设备匹配机制
1.2.1 设备树匹配(推荐方式)
c
static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "vendor,my-i2c-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
设备树节点示例:
dts
&i2c1 {
my_device@50 {
compatible = "vendor,my-i2c-device";
reg = <0x50>;
interrupt-parent = <&gpio>;
interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
vdd-supply = <&vdd_3v3>;
};
};
1.2.2 传统 ID 匹配
c
static const struct i2c_device_id my_i2c_id_table[] = {
{ "my_i2c_device", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id_table);
1.3 Probe 和 Remove 回调
1.3.1 Probe 函数核心任务
c
static int my_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 1. 检查适配器功能 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
return -ENODEV;
}
/* 2. 分配设备私有数据结构 */
struct my_i2c_data *data = devm_kzalloc(&client->dev,
sizeof(*data), GFP_KERNEL);
/* 3. 关联 client 与私有数据 */
i2c_set_clientdata(client, data);
/* 4. 初始化硬件(读取设备ID、配置寄存器等) */
u8 device_id;
i2c_smbus_read_byte_data(client, REG_DEVICE_ID);
/* 5. 注册字符设备、sysfs节点、申请中断等(可选) */
return 0;
}
1.3.2 Remove 函数
c
static void my_i2c_remove(struct i2c_client *client)
{
/* 释放资源 */
struct my_i2c_data *data = i2c_get_clientdata(client);
/* 注意:devm_* 分配的资源会自动释放,无需手动释放 */
}
1.4 完整 I2C 驱动模板(Linux 5.15)
c
/*
* my_i2c_device.c - Linux 5.15 标准 I2C 设备驱动模板
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#define DRIVER_NAME "my_i2c_device"
#define DEVICE_ID_REG 0x00
#define CONFIG_REG 0x01
struct my_i2c_data {
struct i2c_client *client;
struct mutex lock;
u8 config;
};
/* 设备树匹配表 */
static const struct of_device_id my_i2c_of_match[] = {
{ .compatible = "vendor,my-i2c-device" },
{ }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);
/* 传统匹配表 */
static const struct i2c_device_id my_i2c_id_table[] = {
{ "my_i2c_device", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id_table);
/* 寄存器读写函数 */
static int read_reg(struct i2c_client *client, u8 reg, u8 *val)
{
int ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "读寄存器 0x%02x 失败: %d\n", reg, ret);
return ret;
}
*val = (u8)ret;
return 0;
}
static int write_reg(struct i2c_client *client, u8 reg, u8 val)
{
int ret = i2c_smbus_write_byte_data(client, reg, val);
if (ret < 0) {
dev_err(&client->dev, "写寄存器 0x%02x 失败: %d\n", reg, ret);
}
return ret;
}
/* Probe 函数 */
static int my_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct my_i2c_data *data;
u8 device_id;
int ret;
/* 检查适配器功能 */
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA)) {
dev_err(&client->dev, "适配器不支持 SMBus 操作\n");
return -ENODEV;
}
/* 分配设备数据结构 */
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
/* 验证设备通信 */
ret = read_reg(client, DEVICE_ID_REG, &device_id);
if (ret)
return ret;
dev_info(&client->dev, "设备 ID: 0x%02x\n", device_id);
/* 初始化设备 */
ret = write_reg(client, CONFIG_REG, 0x80);
if (ret)
return ret;
return 0;
}
static void my_i2c_remove(struct i2c_client *client)
{
dev_info(&client->dev, "设备移除\n");
}
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(my_i2c_of_match),
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
.id_table = my_i2c_id_table,
};
module_i2c_driver(my_i2c_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Linux 5.15 I2C 设备驱动模板");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");
二、I2C 读写 API 详解
2.1 I2C 与 SMBus 协议关系
2.1.1 协议对比
| 特性 | I2C | SMBus |
|---|---|---|
| 时钟频率 | 标准模式:100 kHz 快速模式:400 kHz 高速模式:3.4 MHz | 固定 10-100 kHz |
| 超时机制 | 无强制超时 | 35ms 超时(强制) |
| 电气特性 | 相对灵活 | 更严格(上拉电阻、电容等) |
| 数据包大小 | 无限制 | 最大 32 字节 |
| 用途 | 通用芯片间通信 | 系统管理、电源管理 |
2.1.2 兼容性说明
- SMBus 设备可连接到 I2C 总线:SMBus 是 I2C 的子集
- I2C 设备不一定兼容 SMBus:特别是时序要求严格的设备
- 驱动开发建议:优先使用 SMBus API,必要时回退到通用 I2C API
2.2 常用 I2C/SMBus API 对比
2.2.1 API 功能分类
1. SMBus 字节操作(最常用)
c
/* 单字节读写 */
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
u8 command, u8 value);
/* 字读写(16位) */
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(const struct i2c_client *client,
u8 command, u16 value);
2. SMBus 块操作(最大32字节)
c
s32 i2c_smbus_read_block_data(const struct i2c_client *client,
u8 command, u8 *values);
s32 i2c_smbus_write_block_data(const struct i2c_client *client,
u8 command, u8 length, const u8 *values);
3. 通用 I2C 传输
c
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
4. 简化接口(不推荐用于寄存器访问)
c
int i2c_master_send(const struct i2c_client *client,
const char *buf, int count);
int i2c_master_recv(const struct i2c_client *client,
char *buf, int count);
2.2.2 API 详细对比表
| API 函数 | 协议 | 数据传输方向 | 最大长度 | 返回值 | 使用场景 |
|---|---|---|---|---|---|
i2c_smbus_read_byte_data() |
SMBus | 读 | 1字节 | 成功:读取值(0-255) 失败:负数 | 读取8位寄存器 |
i2c_smbus_write_byte_data() |
SMBus | 写 | 1字节 | 成功:0 失败:负数 | 写入8位寄存器 |
i2c_smbus_read_word_data() |
SMBus | 读 | 2字节 | 成功:读取值(0-65535) 失败:负数 | 读取16位寄存器 |
i2c_smbus_write_word_data() |
SMBus | 写 | 2字节 | 成功:0 失败:负数 | 写入16位寄存器 |
i2c_smbus_read_block_data() |
SMBus | 读 | 32字节 | 成功:读取字节数 失败:负数 | 读取配置块 |
i2c_smbus_write_block_data() |
SMBus | 写 | 32字节 | 成功:0 失败:负数 | 写入配置块 |
i2c_transfer() |
I2C | 双向 | 无限制 | 成功:传输消息数 失败:负数 | 复杂时序、大数据 |
i2c_master_send() |
I2C | 写 | 无限制 | 成功:发送字节数 失败:负数 | 简单设备(无寄存器) |
i2c_master_recv() |
I2C | 读 | 无限制 | 成功:接收字节数 失败:负数 | 简单设备(无寄存器) |
2.3 API 选型原则
2.3.1 选择 SMBus API 的情况
- 标准寄存器访问:设备支持标准 SMBus 协议
- 代码简洁性:希望代码更简洁、易读
- 小数据量:每次传输 ≤ 32 字节
- 常见外设:传感器、EEPROM、RTC 等标准组件
2.3.2 选择 i2c_transfer 的情况
- 大数据传输:超过 32 字节
- 复杂时序:需要 repeated start
- 非标准协议:设备使用自定义通信协议
- 多个消息:需要发送/接收多个独立消息
- 高性能需求:需要精确控制传输时序
2.3.3 功能检查代码
c
/* 在 probe 函数中进行适配器功能检查 */
static int my_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 检查基本 SMBus 功能 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE))
dev_warn(&client->dev, "Adapter 不支持 SMBus 字节操作\n");
/* 检查 SMBus 字节数据功能(最常用) */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
dev_warn(&client->dev, "Adapter 不支持 SMBus 字节数据操作\n");
/* 检查 I2C 功能 */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
dev_warn(&client->dev, "Adapter 不支持标准 I2C\n");
/* 根据功能选择合适的 API */
if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
/* 使用 SMBus API */
dev_info(&client->dev, "使用 SMBus API\n");
} else if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
/* 使用 I2C 传输 API */
dev_info(&client->dev, "使用 I2C 传输 API\n");
} else {
dev_err(&client->dev, "适配器不支持所需功能\n");
return -ENODEV;
}
return 0;
}
2.4 寄存器读写示例代码
2.4.1 单字节寄存器读写
方法1:SMBus API(推荐)
c
/* 读取单字节寄存器 */
static int smbus_read_reg(struct i2c_client *client, u8 reg, u8 *value)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "读取寄存器 0x%02x 失败: %d\n", reg, ret);
return ret;
}
*value = (u8)ret;
return 0;
}
/* 写入单字节寄存器 */
static int smbus_write_reg(struct i2c_client *client, u8 reg, u8 value)
{
int ret;
ret = i2c_smbus_write_byte_data(client, reg, value);
if (ret < 0) {
dev_err(&client->dev, "写入寄存器 0x%02x 失败: %d\n", reg, ret);
}
return ret;
}
方法2:通用 i2c_transfer
c
/* 使用 i2c_transfer 读取单字节寄存器 */
static int i2c_read_reg(struct i2c_client *client, u8 reg, u8 *value)
{
struct i2c_msg msgs[2];
int ret;
/* 消息1:发送寄存器地址 */
msgs[0].addr = client->addr;
msgs[0].flags = 0; /* 写操作 */
msgs[0].len = 1;
msgs[0].buf = ®
/* 消息2:读取寄存器值 */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; /* 读操作 */
msgs[1].len = 1;
msgs[1].buf = value;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs)) {
dev_err(&client->dev, "I2C 读取寄存器 0x%02x 失败: %d\n", reg, ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
/* 使用 i2c_transfer 写入单字节寄存器 */
static int i2c_write_reg(struct i2c_client *client, u8 reg, u8 value)
{
struct i2c_msg msg;
u8 buf[2];
int ret;
buf[0] = reg;
buf[1] = value;
msg.addr = client->addr;
msg.flags = 0; /* 写操作 */
msg.len = 2;
msg.buf = buf;
ret = i2c_transfer(client->adapter, &msg, 1);
if (ret != 1) {
dev_err(&client->dev, "I2C 写入寄存器 0x%02x 失败: %d\n", reg, ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
2.4.2 多字节寄存器读写
16位寄存器读写(注意字节序)
c
/* 读取16位寄存器(小端) */
static int read_reg_16le(struct i2c_client *client, u8 reg, u16 *value)
{
int ret;
ret = i2c_smbus_read_word_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "读取16位寄存器 0x%02x 失败: %d\n", reg, ret);
return ret;
}
*value = (u16)ret;
return 0;
}
/* 读取16位寄存器(大端) */
static int read_reg_16be(struct i2c_client *client, u8 reg, u16 *value)
{
int ret;
ret = i2c_smbus_read_word_swapped(client, reg);
if (ret < 0) {
dev_err(&client->dev, "读取16位寄存器 0x%02x 失败: %d\n", reg, ret);
return ret;
}
*value = (u16)ret;
return 0;
}
块数据读写(≤32字节)
c
/* 读取块数据(SMBus,最大32字节) */
static int read_reg_block(struct i2c_client *client, u8 reg,
u8 *buffer, u8 length)
{
int ret;
if (length > I2C_SMBUS_BLOCK_MAX) {
dev_err(&client->dev, "块长度 %d 超过 SMBus 限制 %d\n",
length, I2C_SMBUS_BLOCK_MAX);
return -EINVAL;
}
ret = i2c_smbus_read_block_data(client, reg, buffer);
if (ret < 0) {
dev_err(&client->dev, "读取块寄存器 0x%02x 失败: %d\n", reg, ret);
return ret;
}
/* 返回实际读取的字节数 */
return ret;
}
/* 写入块数据(SMBus,最大32字节) */
static int write_reg_block(struct i2c_client *client, u8 reg,
const u8 *buffer, u8 length)
{
int ret;
if (length > I2C_SMBUS_BLOCK_MAX) {
dev_err(&client->dev, "块长度 %d 超过 SMBus 限制 %d\n",
length, I2C_SMBUS_BLOCK_MAX);
return -EINVAL;
}
ret = i2c_smbus_write_block_data(client, reg, length, buffer);
if (ret < 0) {
dev_err(&client->dev, "写入块寄存器 0x%02x 失败: %d\n", reg, ret);
}
return ret;
}
连续读取多个寄存器(通用方法,支持任意长度)
c
/* 连续读取多个寄存器(支持超过32字节) */
static int read_regs_sequential(struct i2c_client *client, u8 start_reg,
u8 *buffer, int count)
{
struct i2c_msg msgs[2];
int ret;
if (count <= 0) {
return -EINVAL;
}
/* 消息1:发送起始寄存器地址 */
msgs[0].addr = client->addr;
msgs[0].flags = 0; /* 写操作 */
msgs[0].len = 1;
msgs[0].buf = &start_reg;
/* 消息2:读取连续数据 */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD; /* 读操作 */
msgs[1].len = count;
msgs[1].buf = buffer;
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs)) {
dev_err(&client->dev, "连续读取寄存器失败: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
return 0;
}
2.4.3 复合操作示例
读取-修改-写入操作
c
/* 读取-修改-写入寄存器位 */
static int update_reg_bit(struct i2c_client *client, u8 reg,
u8 mask, u8 value)
{
u8 current_val, new_val;
int ret;
/* 1. 读取当前值 */
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
return ret;
}
current_val = (u8)ret;
/* 2. 修改特定位 */
new_val = (current_val & ~mask) | (value & mask);
/* 3. 如果值改变,则写入 */
if (new_val != current_val) {
ret = i2c_smbus_write_byte_data(client, reg, new_val);
if (ret < 0) {
return ret;
}
}
return 0;
}
/* 批量配置寄存器 */
static int configure_device(struct i2c_client *client,
const struct reg_config *configs, int num_configs)
{
int i, ret;
for (i = 0; i < num_configs; i++) {
ret = i2c_smbus_write_byte_data(client,
configs[i].reg,
configs[i].value);
if (ret < 0) {
dev_err(&client->dev,
"配置寄存器 0x%02x 失败: %d\n",
configs[i].reg, ret);
return ret;
}
}
return 0;
}
三、最佳实践与调试技巧
3.1 调试建议
-
启用内核调试:
bash# 启用 I2C 调试 echo 1 > /sys/module/i2c_core/parameters/debug # 查看 I2C 适配器 cat /sys/class/i2c-adapter/i2c-0/name # 扫描 I2C 总线 i2cdetect -y 1 -
日志输出:
c/* 使用适当的日志级别 */ dev_dbg(&client->dev, "读取寄存器 0x%02x = 0x%02x\n", reg, value); dev_info(&client->dev, "设备探测成功\n"); dev_warn(&client->dev, "配置值超出范围\n"); dev_err(&client->dev, "通信失败: %d\n", ret); -
错误处理:
c/* 检查返回值并恢复 */ ret = i2c_smbus_write_byte_data(client, reg, value); if (ret < 0) { /* 尝试恢复通信 */ ret = i2c_smbus_read_byte_data(client, DEVICE_ID_REG); if (ret < 0) { /* 通信完全失败,可能需要重置 */ dev_err(&client->dev, "设备无响应,尝试重置\n"); gpio_set_value(reset_gpio, 0); msleep(10); gpio_set_value(reset_gpio, 1); msleep(100); } }
3.2 性能优化
-
批量读写优化:
c/* 避免多次单独读写,使用批量操作 */ for (i = 0; i < 10; i++) { /* 不推荐:每次读写都有起始/停止条件 */ i2c_smbus_write_byte_data(client, reg + i, data[i]); } /* 推荐:使用块操作 */ i2c_smbus_write_block_data(client, reg, 10, data); /* 或使用 i2c_transfer 进行连续传输 */ -
缓存常用配置:
cstruct my_i2c_device { struct i2c_client *client; u8 config_cache[16]; /* 缓存常用寄存器 */ bool cache_valid; }; /* 读取时先检查缓存 */ static int cached_read_reg(struct my_i2c_device *dev, u8 reg, u8 *value) { if (dev->cache_valid && reg < ARRAY_SIZE(dev->config_cache)) { *value = dev->config_cache[reg]; return 0; } /* 缓存无效,从硬件读取 */ return i2c_smbus_read_byte_data(dev->client, reg); }
3.3 兼容性考虑
-
适配器功能检查:
cstatic bool check_adapter_capability(struct i2c_adapter *adap) { /* 检查 SMBus 功能 */ if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK)) dev_dbg(&adap->dev, "支持 SMBus Quick\n"); if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) dev_dbg(&adap->dev, "支持 SMBus Byte\n"); if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE_DATA)) dev_dbg(&adap->dev, "支持 SMBus Byte Data\n"); if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_WORD_DATA)) dev_dbg(&adap->dev, "支持 SMBus Word Data\n"); if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_BLOCK_DATA)) dev_dbg(&adap->dev, "支持 SMBus Block Data\n"); if (i2c_check_functionality(adap, I2C_FUNC_I2C)) dev_dbg(&adap->dev, "支持标准 I2C\n"); return true; } -
向后兼容:
c/* 提供不同内核版本的兼容性 */ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0) /* 旧内核 API */ #else /* 新内核 API */ #endif