I2C子系统与驱动开发:从协议到实战

一、顶级架构一句话总结

i2c_adapter(控制器) ← I2C核心层 → i2c_client(设备) ← i2c_driver(驱动)

I2C子系统采用分层架构,将控制器驱动与设备驱动分离,实现高度复用。


二、I2C子系统架构

架构层次

复制代码
┌─────────────────────────────────────────────────────────┐
│                   I2C子系统架构                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   用户空间    /dev/i2c-X 或 sysfs                       │
│       ↓                                                 │
│   I2C设备驱动  i2c_driver (传感器、EEPROM等)             │
│       ↓                                                 │
│   I2C核心层   i2c-core.c (统一抽象层)                   │
│       ↓                                                 │
│   I2C控制器   i2c_adapter (SoC厂商驱动)                 │
│       ↓                                                 │
│   硬件层      I2C寄存器操作                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

核心数据结构

c 复制代码
// I2C适配器(控制器)
struct i2c_adapter {
    struct module *owner;
    unsigned int class;
    const struct i2c_algorithm *algo;  // 传输算法
    void *algo_data;
    struct device dev;
    int nr;                            // 总线编号
    char name[48];
};

// I2C算法
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);
};

// I2C客户端(设备)
struct i2c_client {
    unsigned short flags;        // 标志
    unsigned short addr;         // 设备地址
    char name[I2C_NAME_SIZE];    // 设备名称
    struct i2c_adapter *adapter; // 所属适配器
    struct device dev;           // 内嵌device
    int irq;                     // 中断号
};

// I2C驱动
struct i2c_driver {
    unsigned int class;
    int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
    int (*remove)(struct i2c_client *client);
    struct device_driver driver;
    const struct i2c_device_id *id_table;
    const struct of_device_id *of_match_table;
};

三、I2C消息传输

i2c_msg结构

c 复制代码
struct i2c_msg {
    __u16 addr;      // 设备地址
    __u16 flags;     // 消息标志
    __u16 len;       // 数据长度
    __u8 *buf;       // 数据缓冲区
};

// 常用标志
#define I2C_M_RD          0x0001  // 读操作
#define I2C_M_TEN         0x0010  // 10位地址
#define I2C_M_STOP        0x8000  // 发送STOP
#define I2C_M_NOSTART     0x4000  // 不发送START

传输函数

c 复制代码
// 原始传输函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);

// SMBus传输
s32 i2c_smbus_read_byte(const struct i2c_client *client);
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);
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);
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);
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);

四、设备树配置

I2C设备树节点

dts 复制代码
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;  // 400KHz

    // 温度传感器
    temp_sensor: lm75@48 {
        compatible = "national,lm75";
        reg = <0x48>;  // I2C地址
    };

    // EEPROM
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <8>;
    };

    // 触摸屏
    touchscreen@38 {
        compatible = "focaltech,ft5x06";
        reg = <0x38>;
        interrupt-parent = <&gpio>;
        interrupts = <17 IRQ_TYPE_EDGE_FALLING>;
        reset-gpios = <&gpio 16 GPIO_ACTIVE_LOW>;
    };
};

五、I2C设备驱动示例

EEPROM驱动

c 复制代码
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>

#define EEPROM_SIZE 256

struct eeprom_data {
    struct i2c_client *client;
    struct mutex lock;
    u8 *buffer;
};

// 读取EEPROM
static int eeprom_read(struct eeprom_data *data, u8 offset, u8 *buf, int len)
{
    struct i2c_client *client = data->client;
    struct i2c_msg msgs[2];
    int ret;

    mutex_lock(&data->lock);

    // 第一条消息:发送要读取的地址
    msgs[0].addr = client->addr;
    msgs[0].flags = 0;
    msgs[0].len = 1;
    msgs[0].buf = &offset;

    // 第二条消息:读取数据
    msgs[1].addr = client->addr;
    msgs[1].flags = I2C_M_RD;
    msgs[1].len = len;
    msgs[1].buf = buf;

    ret = i2c_transfer(client->adapter, msgs, 2);

    mutex_unlock(&data->lock);

    return ret == 2 ? len : ret;
}

// 写入EEPROM
static int eeprom_write(struct eeprom_data *data, u8 offset, const u8 *buf, int len)
{
    struct i2c_client *client = data->client;
    u8 *write_buf;
    int ret;

    write_buf = kmalloc(len + 1, GFP_KERNEL);
    if (!write_buf)
        return -ENOMEM;

    mutex_lock(&data->lock);

    write_buf[0] = offset;
    memcpy(&write_buf[1], buf, len);

    ret = i2c_master_send(client, write_buf, len + 1);

    mutex_unlock(&data->lock);
    kfree(write_buf);

    return ret > 0 ? len : ret;
}

static int eeprom_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct eeprom_data *data;

    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);

    dev_info(&client->dev, "EEPROM probed at 0x%02x\n", client->addr);
    return 0;
}

static int eeprom_remove(struct i2c_client *client)
{
    dev_info(&client->dev, "EEPROM removed\n");
    return 0;
}

static const struct i2c_device_id eeprom_id[] = {
    { "eeprom_24c02", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, eeprom_id);

static const struct of_device_id eeprom_of_match[] = {
    { .compatible = "atmel,24c02", },
    { }
};
MODULE_DEVICE_TABLE(of, eeprom_of_match);

static struct i2c_driver eeprom_driver = {
    .probe = eeprom_probe,
    .remove = eeprom_remove,
    .id_table = eeprom_id,
    .driver = {
        .name = "eeprom_24c02",
        .of_match_table = eeprom_of_match,
    },
};
module_i2c_driver(eeprom_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Driver Developer");

六、SMBus操作示例

温度传感器驱动

c 复制代码
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>

#define LM75_TEMP_REG   0x00
#define LM75_CONF_REG   0x01
#define LM75_THYST_REG  0x02
#define LM75_TOS_REG    0x03

struct lm75_data {
    struct i2c_client *client;
    struct mutex lock;
};

// 读取温度(SMBus方式)
static int lm75_read_temp(struct lm75_data *data)
{
    struct i2c_client *client = data->client;
    s32 raw_temp;
    int temp;

    mutex_lock(&data->lock);

    // 使用SMBus读取16位数据
    raw_temp = i2c_smbus_read_word_swapped(client, LM75_TEMP_REG);

    mutex_unlock(&data->lock);

    if (raw_temp < 0)
        return raw_temp;

    // 转换为摄氏度(0.5度精度)
    temp = (raw_temp >> 5) * 125;

    return temp;
}

// 设置过温阈值
static int lm75_set_threshold(struct lm75_data *data, int temp)
{
    struct i2c_client *client = data->client;
    u16 value;
    int ret;

    // 转换为寄存器格式
    value = (temp / 500) << 7;

    mutex_lock(&data->lock);
    ret = i2c_smbus_write_word_swapped(client, LM75_TOS_REG, value);
    mutex_unlock(&data->lock);

    return ret;
}

static int lm75_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct lm75_data *data;

    if (!i2c_check_functionality(client->adapter,
                                  I2C_FUNC_SMBUS_BYTE_DATA |
                                  I2C_FUNC_SMBUS_WORD_DATA))
        return -EIO;

    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);

    dev_info(&client->dev, "LM75 temperature sensor probed\n");
    return 0;
}

static const struct i2c_device_id lm75_id[] = {
    { "lm75", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, lm75_id);

static const struct of_device_id lm75_of_match[] = {
    { .compatible = "national,lm75", },
    { }
};
MODULE_DEVICE_TABLE(of, lm75_of_match);

static struct i2c_driver lm75_driver = {
    .probe = lm75_probe,
    .id_table = lm75_id,
    .driver = {
        .name = "lm75",
        .of_match_table = lm75_of_match,
    },
};
module_i2c_driver(lm75_driver);

MODULE_LICENSE("GPL");

七、I2C控制器驱动

简化的控制器驱动框架

c 复制代码
static int my_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    struct my_i2c_dev *dev = i2c_get_adapdata(adap);
    int i, ret;

    for (i = 0; i < num; i++) {
        // 发送START
        my_i2c_start(dev);

        // 发送地址
        ret = my_i2c_send_addr(dev, msgs[i].addr, msgs[i].flags & I2C_M_RD);
        if (ret < 0)
            break;

        // 传输数据
        if (msgs[i].flags & I2C_M_RD)
            ret = my_i2c_read(dev, msgs[i].buf, msgs[i].len);
        else
            ret = my_i2c_write(dev, msgs[i].buf, msgs[i].len);

        if (ret < 0)
            break;
    }

    // 发送STOP
    my_i2c_stop(dev);

    return i;
}

static u32 my_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm my_i2c_algo = {
    .master_xfer = my_i2c_xfer,
    .functionality = my_i2c_func,
};

static int my_i2c_probe(struct platform_device *pdev)
{
    struct my_i2c_dev *dev;
    struct i2c_adapter *adap;
    int ret;

    dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

    // 初始化硬件...

    adap = &dev->adapter;
    adap->owner = THIS_MODULE;
    adap->algo = &my_i2c_algo;
    adap->dev.parent = &pdev->dev;
    snprintf(adap->name, sizeof(adap->name), "my_i2c");

    i2c_set_adapdata(adap, dev);

    ret = i2c_add_adapter(adap);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, dev);
    return 0;
}

static int my_i2c_remove(struct platform_device *pdev)
{
    struct my_i2c_dev *dev = platform_get_drvdata(pdev);
    i2c_del_adapter(&dev->adapter);
    return 0;
}

八、调试技巧

查看I2C总线

bash 复制代码
# 查看I2C总线
ls /dev/i2c-*

# 查看I2C设备
i2cdetect -l
i2cdetect -y 1

# 读取寄存器
i2cget -y 1 0x48 0x00

# 写入寄存器
i2cset -y 1 0x48 0x00 0x55

# dump寄存器
i2cdump -y 1 0x48

内核调试

bash 复制代码
# 查看I2C设备
ls /sys/bus/i2c/devices/

# 查看设备信息
cat /sys/bus/i2c/devices/1-0048/name

# 查看内核日志
dmesg | grep -i i2c

九、终极总结

I2C子系统 = 分层架构的典范

  • 核心概念:adapter(控制器)、client(设备)、driver(驱动)
  • 传输方式:i2c_transfer(原始)、SMBus(简化)
  • 设备树:通过reg属性指定I2C地址
  • 调试工具:i2cdetect、i2cget、i2cset

掌握I2C子系统,就掌握了总线类驱动的开发模式!

相关推荐
xixixi777777 小时前
从5G标准到6G前沿:Polar码的技术演进与未来之路
开发语言·人工智能·5g·大模型·php·通信·polar码
Crazy CodeCrafter7 小时前
服装实体店现在还适合转电商吗?
大数据·运维·人工智能·经验分享·自动化·开源软件
唐山柳林7 小时前
基于 AI 边缘计算终端的水文精准测报体系建设
人工智能·边缘计算
kobesdu7 小时前
「ROS2实战-2」集成大语言模型:ollama_ros_chat 本地智能对话功能包部署和使用解析
人工智能·语言模型·自然语言处理·机器人·ros
Mark White7 小时前
深入理解 Linux 打印体系:CUPS、驱动、ULD 与 Docker 容器化
linux·运维·docker
信也科技布道师7 小时前
把7个页面变成1段对话:AI如何重构借款流程
前端·人工智能·重构·架构·交互·用户体验
xianluohuanxiang7 小时前
2026年深度:高精度气象+新能源,从风速误差到收益偏差,行业赋能正在重构电站盈利模型
大数据·开发语言·人工智能·机器学习
大数据在线7 小时前
浙江省中医院钮罗涌:重构“数字经络”,为中医现代化“搭桥修路”
人工智能·数字化转型·超融合·浙江省中医院·fuisoncube