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函数需要实现以下步骤:
- 遍历消息数组 :处理每个
i2c_msg消息 - 发送起始信号(START):拉低SDA,然后拉低SCL
- 发送设备地址 :
- 7位地址模式:发送7位地址 + R/W位(0=写,1=读)
- 10位地址模式:发送特殊序列
- 等待ACK:检查设备是否应答
- 传输数据 :
- 写操作:逐个字节发送数据,每字节后等待ACK
- 读操作:逐个字节接收数据,每字节后发送ACK/NACK
- 发送停止信号(STOP):拉高SCL,然后拉高SDA
组合传输示例
多个消息可以组合成一个传输序列,例如:
c
// 消息1:写寄存器地址
msgs[0].addr = 0x50;
msgs[0].flags = 0; // 写操作
msgs[0].len = 1;
msgs[0].buf = ®_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位地址模式
适配器注册流程
- 分配适配器结构 :使用
kzalloc分配i2c_adapter结构 - 初始化适配器字段 :
- 设置适配器名称
- 设置
owner为THIS_MODULE - 设置
algo指向算法结构 - 设置
nr为-1(自动分配)或指定编号
- 保存私有数据 :使用
i2c_set_adapdata()保存驱动私有数据 - 注册适配器 :调用
i2c_add_adapter()注册到内核 - 获取总线编号 :注册成功后,
adapter.nr会被设置为实际的总线编号
实际硬件实现要点
在实际的硬件驱动中,需要关注以下要点:
-
硬件初始化:
- 配置I2C控制器时钟
- 设置I2C总线速度(100kHz/400kHz等)
- 配置GPIO引脚为I2C功能
- 使能I2C控制器
-
中断处理:
- 许多I2C控制器支持中断模式
- 需要在中断处理函数中处理传输完成、错误等事件
-
超时处理:
- I2C传输可能因为设备无响应而超时
- 需要设置合理的超时时间
-
错误处理:
- 处理NACK(设备无应答)
- 处理总线错误
- 处理仲裁丢失(多主模式)
-
并发控制:
- I2C总线是共享资源,需要互斥访问
- 内核已经提供了互斥机制,但需要注意不要在持有锁时睡眠
关键函数
i2c_add_adapter():注册I2C适配器到内核i2c_del_adapter():注销I2C适配器i2c_set_adapdata():设置适配器私有数据i2c_get_adapdata():获取适配器私有数据i2c_transfer():执行I2C传输(由核心层调用,最终调用master_xfer)
示例代码解析
参考i2c-adapter-demo.c中的实现:
- master_xfer实现:遍历消息数组,根据flags判断读写操作
- functionality实现:返回支持的功能标志
- 模块初始化:分配结构、初始化字段、注册适配器
- 模块退出:注销适配器、释放资源
注意 :示例代码中的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 通常由内核自动创建,有以下几种方式:
-
设备树方式(推荐):
- 在设备树中声明I2C设备节点
- 内核在匹配到驱动后自动创建
i2c_client - 示例设备树节点:
dts&i2c0 { eeprom@50 { compatible = "demo,i2c-device"; reg = <0x50>; }; }; -
静态声明方式:
- 在板级代码中使用
i2c_board_info静态声明 - 调用
i2c_new_device()创建i2c_client
- 在板级代码中使用
-
用户空间创建:
- 通过
/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 进行数据传输,有以下几种方式:
- 使用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);
- 使用通用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 = ®_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);
- 使用便捷函数:
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():获取客户端私有数据