基于i2c子系统的驱动分析

本文深入分析了Linux内核i2c子系统的结构,包括i2c总线核心、适配器驱动和设备注册。重点讨论了新旧内核环境下i2c适配器和设备的注册差异,并概述了驱动编写流程,强调了设备树对驱动匹配的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于i2c子系统的驱动分析

和i2c有关的代码都在源码drivers/i2c目录下。内核提供了两种i2c的实现方法:

  1. 第一种叫i2c_dev,对应drivers/i2c/i2c-dev.c,这种方法仅仅封装了soc的i2c控制器操作,并向应用层提供操作接口。其本质是为应用层提供了一个库,驱动功能由应用层实现,不是主流的做法
  2. 第二种是驱动层实现所有驱动功能,是比较主流的做法

第二种可以认为是正统的i2c驱动,其本质是:工程师任意选用input子系统、misc框架、普通字符驱动等方式实现i2c驱动,i2c子系统的意义仅仅是为硬件操作提供接口(库)

1.i2c子系统的结构

如图
这里写图片描述
可以看出,i2c子系统基本机制和platform很类似,都是设备和驱动两者匹配来工作。i2c驱动只需调用核心层提供的接口(相当于核心层提供了库),即可方便地操作i2c

2.i2c总线核心分析

i2c总线核心提供了设备驱动和设备(client)的注册、注销方法, 还提供了一组不依赖于硬件平台的接口函数,I2C 总线驱动和设备驱动之间依赖于 I2C 核心作为纽带

3.i2c适配器(adapter)驱动分析

所谓的i2c适配器驱动,就是soc内部的i2c控制器的驱动,由原厂移植内核时提供,一般位于driver/i2c/busses内。而i2c适配器设备的注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论

老内核下的i2c适配器

我们这里用的是i2c-s3c2410.c,该驱动兼容三星大部分的soc,包括210。该驱动由platform总线实现,该驱动probe函数中主要做了:

  • 填充了一个i2c_adapter结构体,并调用接口注册之,i2c_adapter 对应于SOC上的一个适配器
  • 从platform_data(自留地)接收硬件信息,做必要的处理(为寄存器申请虚拟地址映射、申请中断等)
  • 通过操作寄存器,对soc内的i2c适配器做初始化,比如把i2c速率设置为默认的100k。这一套设置基本通吃大部分器件,一般情况不用改动的

新内核下的i2c适配器

在新内核下,i2c适配器的驱动倒是没有变化,而i2c适配器设备体的注册,却采用了设备树的方式

  • 下面是imx6qdl.dtsi中对i2c1适配器设备的定义和注册,里面定义了很多参数,一般来说我们是根本不用去修改这个节点的。假设我们要修改其中的参数(比如频率),只需在项目的dts中引用该节点,并重写即可
i2c1: i2c@021a0000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
    reg = <0x021a0000 0x4000>;
    interrupts = <0 36 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6QDL_CLK_I2C1>;
    status = "disabled";
};

4.i2c设备(client)注册分析

所谓的i2c设备(client),就是挂在i2c上的外设(比如各种传感器),这个需要我们自己注册,在3.x后的kernel中采用了设备树节点的方式,故这里需要分类讨论

老内核下的i2c设备(client)

对于老版本的内核,首先应该进入mach-xxx.c完成i2c设备(client)的注册。如何注册?这方面i2c和platform有较大不同,主要是soc上有多个i2c,所以是分开注册的

  • 在mach-xxx.c中的xxx_machine_init函数中,发现由i2c_register_board_info来注册三个i2c上各自的设备。以i2c_devs0为例,i2c_devs0是一个数组,里面是i2c0上所有的设备
    i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));
    i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));
    i2c_register_board_info(2, i2c_devs2, ARRAY_SIZE(i2c_devs2));
  • 查看i2c_devs0的定义,我们发现该数组内部都是i2c_board_info结构体,如果要添加设备到i2c0,只需在该数组中使用I2C_BOARD_INFO这个宏即可,第一个参数是名字,第二个参数是设备在i2c上的地址,此宏的本质就是填充一个struct i2c_board_info,这一步作用是把wm8580以i2c设备的身份被注册,并且绑定i2c0这个适配器
static struct i2c_board_info i2c_devs0[] __initdata = {
    {
        I2C_BOARD_INFO("wm8580", 0x1b),
<think>我们正在讨论sysfs和I2C子系统在EEPROM驱动开发中的区别。首先需要明确:sysfs是内核提供的虚拟文件系统,用于导出内核对象信息;而I2C子系统是Linux内核中专门管理I2C总线和设备的框架。两者在驱动开发中扮演不同角色,但可以协同工作。 用户的问题核心在于:开发EEPROM驱动时,使用sysfs接口和基于I2C子系统的标准方法有何区别。下面从几个关键维度展开分析: 1. **功能定位差异**: - **sysfs**:提供用户空间与内核的通用交互接口。对于EEPROM,可通过sysfs文件读写其内容(如创建`/sys/class/eeprom/eeprom0/data`文件)。 - **I2C子系统**:提供I2C设备的核心驱动框架(如`i2c_driver`结构、适配器注册、数据传输API)。它处理硬件层面的I2C通信细节(如起始条件、ACK/NACK信号)。 2. **驱动开发流程对比**: - **基于sysfs的EEPROM驱动**(简化场景): - 创建sysfs属性文件(如`data`),在`read()`/`write()`函数中直接调用`i2c_transfer()`读写EEPROM。 - 需自行管理设备节点创建(如`class_create()` + `device_create()`)。 - 示例代码片段: ```c static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { struct i2c_client *client = to_i2c_client(kobj_to_dev(kobj)); i2c_master_send(client, &off, 1); // 发送读取地址 i2c_master_recv(client, buf, count); // 读取数据 return count; } static BIN_ATTR(data, 0644, eeprom_read, eeprom_write, EEPROM_SIZE); ``` - **基于I2C子系统的标准EEPROM驱动**: - 实现`i2c_driver`结构(含`.probe`、`.remove`等),通过`i2c_add_driver()`注册。 - 利用I2C核心提供的`struct i2c_device_id`匹配设备。 - 内核已提供通用EEPROM驱动框架(`drivers/misc/eeprom`),开发者只需填充设备特定参数(如地址、容量)。 - 示例流程: ```c static struct i2c_driver eeprom_driver = { .driver = { .name = "my_eeprom" }, .probe = eeprom_probe, // 在此函数中创建sysfs节点 .id_table = eeprom_ids, }; module_i2c_driver(eeprom_driver); ``` 3. **用户空间交互方式**: - **sysfs驱动**:用户通过读写`/sys/class/eeprom/eeprom0/data`直接操作EEPROM。 - **I2C标准驱动**:通常通过标准字符设备(如`/dev/eeprom0`)或sysfs属性(由I2C核心自动生成部分文件)交互。 4. **优势与适用场景**: - **sysfs直接开发**: - *优点*:快速实现简单需求,无需深入I2C子系统细节。 - *缺点*:重复造轮子,缺乏硬件抽象(如未处理I2C总线竞争)。 - **I2C子系统开发**: - *优点*:复用内核现有框架(如自动设备发现、电源管理),支持多总线适配器,符合Linux设备模型。 - *缺点*:学习曲线较陡,需理解`i2c_adapter`、`i2c_client`等概念。 5. **典型工作流差异**(以EEPROM写入为例): - **sysfs驱动**: ```bash echo "test" > /sys/class/eeprom/eeprom0/data ``` 驱动直接调用`i2c_transfer`发送数据。 - **I2C子系统驱动**: - 用户可能通过字符设备接口: ```c write(fd, "test", 4); // 触发驱动中的i2c_master_send() ``` - 或通过I2C核心生成的sysfs文件(如`/sys/bus/i2c/devices/0-0050/eeprom`)。 **总结**: sysfs为EEPROM驱动提供用户空间接口,而I2C子系统提供硬件通信基础设施。实际开发中,**推荐基于I2C子系统实现驱动**,因其提供标准化、可维护的框架,同时仍可通过sysfs暴露设备属性。两者并非互斥——一个完整的EEPROM驱动通常同时使用I2C子系统处理硬件通信,并通过sysfs或字符设备提供用户访问接口[^1]。 ### §§相关问题§§ 1. 如何通过sysfs接口直接控制I2C设备而不使用内核驱动? 2. Linux内核I2C子系统的设备匹配机制是怎样的? 3. 在EEPROM驱动中,sysfs属性文件与字符设备文件哪种访问方式更高效?</think>### sysfs与I2C子系统在EEPROM驱动开发中的区别 在Linux设备驱动开发中,sysfs和I2C子系统在EEPROM驱动实现中扮演不同角色,主要区别如下: #### 1. **功能定位不同** - **sysfs**: - 提供用户空间与内核的**通用交互接口** - 用于暴露EEPROM的**属性信息**(如容量、版本号)和**操作接口**(读/写) - 通过文件节点(如`/sys/class/eeprom/eeprom0/data`)提供访问 - *本质是信息展示和控制通道* - **I2C子系统**: - 处理**底层硬件通信协议** - 管理I2C总线适配器(adapter)和设备(client)的**物理连接** - 实现I2C协议的**数据传输机制**(起始位、地址确认、数据包传输等) - *本质是硬件驱动框架* #### 2. **开发流程差异** **基于sysfs的EEPROM驱动开发流程**: ```c // 1. 创建sysfs属性 static ssize_t eeprom_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, char *buf, loff_t off, size_t count) { // 通过I2C核心API读取数据 i2c_master_send(client, &off, 1); // 发送读取地址 i2c_master_recv(client, buf, count); // 读取数据 return count; } // 2. 注册sysfs接口 static BIN_ATTR(eeprom_data, 0644, eeprom_read, eeprom_write, EEPROM_SIZE); // 3. 在probe函数中创建节点 static int eeprom_probe(struct i2c_client *client) { sysfs_create_bin_file(&client->dev.kobj, &bin_attr_eeprom_data); } ``` **基于I2C子系统的核心开发流程**: ```c // 1. 实现i2c_driver结构体 static struct i2c_driver eeprom_driver = { .driver = { .name = "at24c02" }, .probe = eeprom_probe, .id_table = eeprom_ids, // 设备ID匹配表 }; // 2. 注册I2C驱动 module_i2c_driver(eeprom_driver); // 3. 在probe中初始化设备 static int eeprom_probe(struct i2c_client *client) { // 初始化硬件寄存器 i2c_smbus_write_byte_data(client, REG_CONFIG, 0x01); } ``` #### 3. **数据流对比** ```mermaid graph TD A[用户空间] -->|read/write| B(sysfs文件节点) B --> C[驱动属性回调函数] C --> D[I2C核心层] D -->|i2c_transfer| E[I2C适配器驱动] E --> F[硬件EEPROM芯片] ``` #### 4. **关键区别点** | 特性 | sysfs | I2C子系统 | |---------------------|---------------------------|-------------------------| | **主要作用** | 用户空间接口 | 硬件通信协议栈 | | **依赖关系** | 依赖I2C子系统实现数据传输 | 独立硬件抽象层 | | **设备发现** | 不处理设备匹配 | 通过id_table匹配设备[^1] | | **典型操作** | 属性读写 | 总线注册/数据传输 | | **用户空间访问** | 直接文件操作 | 通常通过sysfs间接访问 | #### 5. **协同工作场景** 在实际EEPROM驱动中,两者通常结合使用: 1. I2C子系统处理硬件注册和设备匹配 ```c // 设备ID表 static const struct i2c_device_id eeprom_ids[] = { { "at24c02", 0 }, { } }; ``` 2. probe函数中初始化硬件 3. 通过sysfs暴露用户接口 ```bash # 用户空间读取EEPROM $ hexdump -C /sys/bus/i2c/devices/0-0050/eeprom ``` #### 6. **典型应用差异** - **纯sysfs驱动**:适合简单设备,快速实现用户空间访问 - **I2C子系统驱动**:必需选项,处理所有硬件相关操作 - **组合使用**:工业级驱动的标准模式(I2C处理通信,sysfs提供接口) > 📌 **关键结论**: > sysfs是用户空间交互接口,I2C子系统是硬件通信基础。开发EEPROM驱动时,**必须基于I2C子系统实现硬件通信**,而sysfs用于构建用户访问层。两者是互补关系而非替代关系[^1]。 ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值