【Linux驱动开发】第20天:I2C 驱动框架与读写 API 详解

一、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 的情况
  1. 标准寄存器访问:设备支持标准 SMBus 协议
  2. 代码简洁性:希望代码更简洁、易读
  3. 小数据量:每次传输 ≤ 32 字节
  4. 常见外设:传感器、EEPROM、RTC 等标准组件
2.3.2 选择 i2c_transfer 的情况
  1. 大数据传输:超过 32 字节
  2. 复杂时序:需要 repeated start
  3. 非标准协议:设备使用自定义通信协议
  4. 多个消息:需要发送/接收多个独立消息
  5. 高性能需求:需要精确控制传输时序
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 = &reg;
    
    /* 消息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 调试建议

  1. 启用内核调试

    bash 复制代码
    # 启用 I2C 调试
    echo 1 > /sys/module/i2c_core/parameters/debug
    
    # 查看 I2C 适配器
    cat /sys/class/i2c-adapter/i2c-0/name
    
    # 扫描 I2C 总线
    i2cdetect -y 1
  2. 日志输出

    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);
  3. 错误处理

    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 性能优化

  1. 批量读写优化

    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 进行连续传输 */
  2. 缓存常用配置

    c 复制代码
    struct 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 兼容性考虑

  1. 适配器功能检查

    c 复制代码
    static 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;
    }
  2. 向后兼容

    c 复制代码
    /* 提供不同内核版本的兼容性 */
    #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 0, 0)
    /* 旧内核 API */
    #else
    /* 新内核 API */
    #endif