Linux I2C子系统

Linux I2C子系统架构

Linux I2C子系统采用三层架构:

第一层:用户空间应用程序

  • i2c-tools工具集
  • 用户自定义应用程序
  • 通过/dev/i2c-X设备文件访问

第二层:I2C核心层 (i2c-core)

  • 注册/注销适配器和设备
  • 提供统一的API接口
  • 设备模型管理
  • 总线管理
  • 向上提供接口(用户空间)
    • 通过 i2c-dev 驱动提供 /dev/i2c-X 设备文件
    • 用户程序可以通过 ioctl()read()write() 访问I2C设备
  • 向下提供接口(驱动层)
    • 给设备驱动 :提供 i2c_client 结构,设备驱动通过它访问设备
    • 给适配器驱动 :提供适配器注册接口(i2c_add_adapter等)

第三层:驱动层

  • I2C适配器驱动 (i2c-adapter) :实现硬件操作
    • master_xfer:实现I2C传输的核心函数
    • 注册适配器到内核
  • I2C设备驱动 (i2c-driver) :实现设备功能
    • probe/remove:设备探测和移除
    • 读写数据:通过i2c_client与设备通信

架构图和数据流向

硬件层 第三层 第三层 第二层 第一层:用户空间应用程序 向上提供接口
(/dev/i2c-X, ioctl, read, write) 向上提供接口
(/dev/i2c-X, ioctl, read, write) 向上提供接口
(/dev/i2c-X, ioctl, read, write) 向下提供接口
(i2c_client) 向下提供接口
(适配器注册接口) 通过核心层 I2C总线/设备 I2C适配器驱动
(i2c-adapter) master_xfer 硬件操作 I2C设备驱动
(i2c-driver) probe/remove 设备功能实现 I2C核心层
(i2c-core) 设备模型管理 总线管理 创建和管理 i2c_client 创建和管理 i2c_adapter i2c-tools 用户自定义程序 通过 /dev/i2c-X 访问

关键说明:

  • 核心层向上(用户空间)提供接口 :通过 i2c-dev 驱动创建 /dev/i2c-X 设备文件,用户程序可以直接访问I2C设备
  • 核心层向下(驱动层)提供接口
    • 给设备驱动:提供 i2c_client 结构
    • 给适配器驱动:提供适配器注册和管理接口
  • 数据流向
    • 用户空间 → 核心层(通过 /dev/i2c-X)→ 适配器驱动 → 硬件
    • 设备驱动 → 核心层(通过 i2c_client)→ 适配器驱动 → 硬件

核心数据结构

1. struct i2c_adapter

代表I2C适配器(控制器),负责实际的硬件操作。

2. struct i2c_client

代表I2C设备(从设备),包含设备地址等信息。i2c_client 是I2C设备在内核中的表示,每个连接到I2C总线的设备都有一个对应的 i2c_client 结构。

所属层次: i2c_client 属于 I2C核心层(第二层),由核心层创建和管理,作为核心层提供给设备驱动层的接口。

结构体定义:

c 复制代码
struct i2c_client {
    unsigned short flags;        // 标志位(I2C_CLIENT_TEN表示10位地址,I2C_CLIENT_PEC表示SMBus PEC)
    unsigned short addr;          // 设备地址(7位地址存储在低7位)
    char name[I2C_NAME_SIZE];    // 设备名称(通常是芯片名称)
    struct i2c_adapter *adapter;  // 指向所属的I2C适配器
    struct device dev;            // 设备结构(内嵌,用于设备模型)
    int irq;                      // 设备中断号(如果有)
    struct list_head detected;    // 链表节点
};

关键字段说明:

  • addr:设备的I2C地址(7位地址模式,地址存储在低7位)
  • adapter:指向该设备所在的I2C适配器(总线)
  • dev:内嵌的设备结构,用于Linux设备模型管理
  • flags:标志位,常用值:
    • I2C_CLIENT_TEN:使用10位地址模式
    • I2C_CLIENT_PEC:使用SMBus PEC(包错误检查)
  • irq:设备中断号,如果设备支持中断

重要说明:

  • 所属层次i2c_client 属于 I2C核心层(第二层) ,由核心层的 i2c_new_device() 等函数创建
  • 创建方式i2c_client 通常由内核自动创建(通过设备树或静态声明)
  • 使用方式 :设备驱动在 probe() 函数中会收到 i2c_client 指针,作为核心层提供的接口
  • 作用i2c_client 是核心层和设备驱动层之间的桥梁,设备驱动通过它访问设备地址、适配器等信息
  • 私有数据 :可以使用 i2c_set_clientdata()i2c_get_clientdata() 存储/获取驱动私有数据
3. struct i2c_driver

I2C设备驱动,实现设备的probe、remove等操作。

4. struct i2c_msg

I2C消息结构,用于数据传输。

I2C适配器驱动

什么是I2C适配器?

I2C适配器(I2C Adapter)是Linux内核中代表I2C控制器的抽象。每个I2C适配器对应一个物理的I2C总线控制器,负责:

  • 实现硬件相关的I2C传输操作
  • 管理I2C总线的物理层通信
  • 提供统一的接口给上层使用

适配器驱动是I2C子系统中最底层的驱动,直接操作硬件寄存器,实现I2C协议的物理层和链路层。

核心数据结构详解

struct i2c_adapter
c 复制代码
struct i2c_adapter {
    struct module *owner;           // 模块所有者
    const struct i2c_algorithm *algo; // I2C算法(传输函数)
    void *algo_data;                 // 算法私有数据
    struct device dev;                // 适配器自己的设备结构(内嵌)
    int nr;                          // 适配器编号(-1表示自动分配)
    char name[48];                   // 适配器名称
    // ... 其他字段
};

关键字段说明:

  • dev:适配器自己的设备结构(内嵌在 i2c_adapter 中),用于设备模型管理
  • dev.parent:指向父设备的指针,通常是提供I2C控制器的硬件设备(如 platform_device、pci_device)
  • name:适配器名称,会出现在/sys/class/i2c-adapter/i2c-X/name
  • algo:指向i2c_algorithm结构,包含实际的传输函数
  • nr:适配器编号,设置为-1让内核自动分配,或指定具体编号
struct i2c_algorithm
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核心函数,实现I2C传输,必须实现
  • smbus_xfer:实现SMBus传输,可选(不实现返回-ENOSYS)
  • functionality:返回适配器支持的功能标志,必须实现
struct i2c_msg
c 复制代码
struct i2c_msg {
    __u16 addr;      // 设备地址(7位或10位)
    __u16 flags;     // 标志位
    __u16 len;       // 消息长度
    __u8 *buf;       // 数据缓冲区
};

常用flags:

  • I2C_M_RD:读操作标志
  • I2C_M_TEN:10位地址模式
  • I2C_M_STOP:发送停止信号
  • I2C_M_NOSTART:不发送起始信号

master_xfer函数详解

master_xfer是适配器驱动中最重要的函数,它负责实现实际的I2C传输。当上层调用i2c_transfer()时,最终会调用到适配器的master_xfer函数。

函数原型
c 复制代码
int (*master_xfer)(struct i2c_adapter *adap, 
                   struct i2c_msg *msgs, 
                   int num);

参数说明:

  • adap:I2C适配器指针
  • msgs:I2C消息数组,可能包含多个消息(组合传输)
  • num:消息数量

返回值:

  • 成功:返回成功传输的消息数量(通常等于num)
  • 失败:返回负数错误码
实现流程

在实际硬件驱动中,master_xfer函数需要实现以下步骤:

  1. 遍历消息数组 :处理每个i2c_msg消息
  2. 发送起始信号(START):拉低SDA,然后拉低SCL
  3. 发送设备地址
    • 7位地址模式:发送7位地址 + R/W位(0=写,1=读)
    • 10位地址模式:发送特殊序列
  4. 等待ACK:检查设备是否应答
  5. 传输数据
    • 写操作:逐个字节发送数据,每字节后等待ACK
    • 读操作:逐个字节接收数据,每字节后发送ACK/NACK
  6. 发送停止信号(STOP):拉高SCL,然后拉高SDA
组合传输示例

多个消息可以组合成一个传输序列,例如:

c 复制代码
// 消息1:写寄存器地址
msgs[0].addr = 0x50;
msgs[0].flags = 0;  // 写操作
msgs[0].len = 1;
msgs[0].buf = &reg_addr;

// 消息2:读数据
msgs[1].addr = 0x50;
msgs[1].flags = I2C_M_RD;  // 读操作
msgs[1].len = 16;
msgs[1].buf = data_buf;

这两个消息会组合成一个传输:先写寄存器地址,然后读数据(中间不发送停止信号)。

functionality函数详解

functionality函数返回适配器支持的功能标志,内核会根据这些标志决定使用哪种传输方式。

常用功能标志:

  • I2C_FUNC_I2C:支持标准I2C协议
  • I2C_FUNC_SMBUS_QUICK:支持SMBus快速命令
  • I2C_FUNC_SMBUS_BYTE:支持SMBus字节操作
  • I2C_FUNC_SMBUS_BYTE_DATA:支持SMBus字节数据操作
  • I2C_FUNC_SMBUS_WORD_DATA:支持SMBus字数据操作
  • I2C_FUNC_SMBUS_BLOCK_DATA:支持SMBus块数据操作
  • I2C_FUNC_10BIT_ADDR:支持10位地址模式

适配器注册流程

  1. 分配适配器结构 :使用kzalloc分配i2c_adapter结构
  2. 初始化适配器字段
    • 设置适配器名称
    • 设置ownerTHIS_MODULE
    • 设置algo指向算法结构
    • 设置nr为-1(自动分配)或指定编号
  3. 保存私有数据 :使用i2c_set_adapdata()保存驱动私有数据
  4. 注册适配器 :调用i2c_add_adapter()注册到内核
  5. 获取总线编号 :注册成功后,adapter.nr会被设置为实际的总线编号

实际硬件实现要点

在实际的硬件驱动中,需要关注以下要点:

  1. 硬件初始化

    • 配置I2C控制器时钟
    • 设置I2C总线速度(100kHz/400kHz等)
    • 配置GPIO引脚为I2C功能
    • 使能I2C控制器
  2. 中断处理

    • 许多I2C控制器支持中断模式
    • 需要在中断处理函数中处理传输完成、错误等事件
  3. 超时处理

    • I2C传输可能因为设备无响应而超时
    • 需要设置合理的超时时间
  4. 错误处理

    • 处理NACK(设备无应答)
    • 处理总线错误
    • 处理仲裁丢失(多主模式)
  5. 并发控制

    • I2C总线是共享资源,需要互斥访问
    • 内核已经提供了互斥机制,但需要注意不要在持有锁时睡眠

关键函数

  • i2c_add_adapter():注册I2C适配器到内核
  • i2c_del_adapter():注销I2C适配器
  • i2c_set_adapdata():设置适配器私有数据
  • i2c_get_adapdata():获取适配器私有数据
  • i2c_transfer():执行I2C传输(由核心层调用,最终调用master_xfer)

示例代码解析

参考i2c-adapter-demo.c中的实现:

  1. master_xfer实现:遍历消息数组,根据flags判断读写操作
  2. functionality实现:返回支持的功能标志
  3. 模块初始化:分配结构、初始化字段、注册适配器
  4. 模块退出:注销适配器、释放资源

注意 :示例代码中的master_xfer只是模拟实现,实际硬件驱动需要操作硬件寄存器来实现真正的I2C传输。

I2C设备驱动

设备驱动负责:

  • 实现设备特定的功能
  • 响应设备的probe和remove
  • 通过i2c_client与设备通信

i2c_client详解

i2c_client 是I2C设备驱动中最重要的数据结构,它代表一个I2C从设备。设备驱动通过 i2c_client 来访问和控制I2C设备。

层次归属: i2c_client 属于 I2C核心层(第二层),由核心层创建和管理。

接口提供关系:

  • 核心层向上(用户空间)提供接口:通过 i2c-dev 驱动提供 /dev/i2c-X 设备文件
  • 核心层向下(驱动层)提供接口:i2c_client 是核心层提供给设备驱动层的接口

设备驱动通过 i2c_client 与核心层交互,核心层再通过 i2c_adapter 与适配器驱动交互,最终完成硬件操作。

i2c_client的创建方式

i2c_client 通常由内核自动创建,有以下几种方式:

  1. 设备树方式(推荐)

    • 在设备树中声明I2C设备节点
    • 内核在匹配到驱动后自动创建 i2c_client
    • 示例设备树节点:
    dts 复制代码
    &i2c0 {
        eeprom@50 {
            compatible = "demo,i2c-device";
            reg = <0x50>;
        };
    };
  2. 静态声明方式

    • 在板级代码中使用 i2c_board_info 静态声明
    • 调用 i2c_new_device() 创建 i2c_client
  3. 用户空间创建

    • 通过 /sys/class/i2c-dev/i2c-X/device/new_device 接口创建
i2c_client在驱动中的使用

设备驱动的 probe() 函数会收到 i2c_client 指针:

c 复制代码
static int i2c_demo_probe(struct i2c_client *client,
                          const struct i2c_device_id *id)
{
    /* 获取设备地址 */
    u8 addr = client->addr;
    
    /* 获取适配器指针 */
    struct i2c_adapter *adap = client->adapter;
    
    /* 获取设备结构 */
    struct device *dev = &client->dev;
    
    /* 保存驱动私有数据 */
    struct i2c_demo_device *demo_dev = kzalloc(...);
    i2c_set_clientdata(client, demo_dev);
    
    return 0;
}
通过i2c_client进行数据传输

设备驱动可以通过 i2c_client 进行数据传输,有以下几种方式:

  1. 使用SMBus函数(推荐,简单)
c 复制代码
/* 读取一个字节数据 */
u8 val = i2c_smbus_read_byte_data(client, reg_addr);

/* 写入一个字节数据 */
i2c_smbus_write_byte_data(client, reg_addr, val);

/* 读取一个字(2字节)数据 */
u16 val = i2c_smbus_read_word_data(client, reg_addr);

/* 写入一个字数据 */
i2c_smbus_write_word_data(client, reg_addr, val);
  1. 使用通用I2C传输函数(灵活,支持任意长度)
c 复制代码
struct i2c_msg msgs[2];
u8 reg_addr = 0x00;
u8 data[16];

/* 消息1:写寄存器地址 */
msgs[0].addr = client->addr;
msgs[0].flags = 0;  /* 写操作 */
msgs[0].len = 1;
msgs[0].buf = &reg_addr;

/* 消息2:读数据 */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;  /* 读操作 */
msgs[1].len = 16;
msgs[1].buf = data;

/* 执行传输 */
i2c_transfer(client->adapter, msgs, 2);
  1. 使用便捷函数
c 复制代码
/* 发送数据 */
i2c_master_send(client, buf, count);

/* 接收数据 */
i2c_master_recv(client, buf, count);
i2c_client的私有数据管理

每个 i2c_client 都有一个私有数据指针,用于存储驱动特定的数据:

c 复制代码
/* 设置私有数据 */
void i2c_set_clientdata(struct i2c_client *client, void *data);

/* 获取私有数据 */
void *i2c_get_clientdata(const struct i2c_client *client);

使用示例:

c 复制代码
/* 在probe中保存私有数据 */
static int i2c_demo_probe(struct i2c_client *client, ...)
{
    struct i2c_demo_device *demo_dev = kzalloc(...);
    i2c_set_clientdata(client, demo_dev);
    return 0;
}

/* 在remove中获取并释放私有数据 */
static int i2c_demo_remove(struct i2c_client *client)
{
    struct i2c_demo_device *demo_dev = i2c_get_clientdata(client);
    kfree(demo_dev);
    return 0;
}

关键函数

设备驱动注册相关
  • i2c_add_driver():注册I2C设备驱动(推荐使用)
  • i2c_register_driver():注册I2C设备驱动(旧接口)
  • i2c_del_driver():注销I2C设备驱动
  • i2c_unregister_driver():注销I2C设备驱动(旧接口)
数据传输相关
  • i2c_transfer():执行I2C传输(最底层,支持任意消息组合)
  • i2c_master_send():发送数据到设备
  • i2c_master_recv():从设备接收数据
  • i2c_smbus_read_byte_data():SMBus读取字节数据
  • i2c_smbus_write_byte_data():SMBus写入字节数据
  • i2c_smbus_read_word_data():SMBus读取字数据
  • i2c_smbus_write_word_data():SMBus写入字数据
客户端数据管理
  • i2c_set_clientdata():设置客户端私有数据
  • i2c_get_clientdata():获取客户端私有数据
相关推荐
百***06011 小时前
Linux下PostgreSQL-12.0安装部署详细步骤
linux·运维·postgresql
c++逐梦人1 小时前
Linux下编写进度条小程序
linux·运维·小程序
求知若渴,虚心若愚。1 小时前
traefik 启用并指定根证书*.cer
linux·运维·服务器
Claudedy2 小时前
Linux 网络代理指南:解决下载慢、访问受限的开发痛点
linux·运维·网络·代理·proxy代理
zhaqonianzhu2 小时前
【保姆级】无外网 Linux 服务器用 VSCode 通义灵码:SSH 代理配置全流程
linux·服务器·vscode
Murphy_lx2 小时前
C++ 条件变量
linux·开发语言·c++
LCG元3 小时前
Linux 信号(Signals)机制详解:如何优雅地关闭你的进程?
linux
梁正雄4 小时前
linux服务-Nginx+Tomcat+Redis之Session 共享
linux·nginx·tomcat
dyxal4 小时前
linux系统上 WPS Office新增字体
linux·运维·wps