Linux I2C设备分析1(基于Linux6.6)---总体框架
前言
在 Linux 中,I2C(Inter-Integrated Circuit)是一种常用的串行通信协议,用于在低速设备(如传感器、时钟芯片、EEPROM等)与主机(如微控制器、嵌入式系统)之间传输数据。I2C 总线采用主从架构,允许多个设备通过两个信号线(SDA:数据线,SCL:时钟线)进行通信。I2C 设备驱动模型是 Linux 内核中用于管理 I2C 设备的基础框架。
1. I2C 总线和 I2C 设备
I2C 协议通过两条线(SDA 和 SCL)提供双向的数据传输。I2C 总线上的设备可以有多个,其中一个是主设备,其他是从设备。每个 I2C 从设备都有唯一的地址。
在 Linux 内核中:
- I2C 总线控制器驱动负责处理与 I2C 总线本身相关的操作(如启动、停止、时钟信号控制等)。
- I2C 设备驱动负责与连接到 I2C 总线的特定设备进行通信。
2. I2C 设备驱动模型的主要组件
I2C 设备驱动模型的主要组件包括:
- I2C 总线驱动(I2C Bus Driver)
- I2C 设备结构(i2c_device_id)
- I2C 设备驱动结构(i2c_driver)
一、I2C控制器-CPU-I2C设备间的关系
在分析的LINUX设备驱动模型以及PLATFORM驱动模型,均只涉及总线-
设备-驱动,而i2c驱动模型则多了i2c适配器抽象,为什么会多个i2c适配器呢,它和i2c设备、i2c驱动、i2c总线的关系是怎样的呢,下面通过如下逻辑图进行说明。
如下图所示,CPU通过片间总线与I2C控制器通信,而各I2C设备则通过I2C总线与I2C控制器铜线。具体说明如下:
- CPU与I2C控制器之间主要是通过片间总线进行通信(如APB总线),但针对驱动工程师而言,对CPU与I2C控制器之间的片间总线是无感知的,驱动工程师只需要通过映射至CPU地址空间的寄存器地址,通过对寄存器进行读写即可与I2C控制器通信;
- I2C控制器与I2C设备之间则通过I2C总线进行通信,而I2C控制器作为主模式时,则实现用于产生时序、启动或停止传输等功能。而我们可以为该I2C控制器编写相应的驱动,实现时序产生、启动或停止传输时序,建立数据通信渠道,实现CPU与具体的I2C设备间的数据通信。
- 而针对具体的I2C设备,由于其实现的功能不同(rtc设备、风扇、温度传感器等),因此若要驱动具体的I2C设备,则需要针对具体的I2C设备编写相应的驱动程序。
针对上面CPU-I2C控制器-I2C设备,我们也可以进行相应的抽象,如I2C控制器及其驱动、I2C
设备及其设备、I2C设备依附于具体的I2C控制器(需要借助具体I2C控制器的方法,才可以与挂载其上的I2C设备通信)、I2C控制器提供产生I2C通信相关的时序等。
以上抽象出的设备与驱动,而在LINUX的I2C驱动模型中,与上面的抽象基本一致:
- 在LINUX的I2C驱动模型中,其抽象了I2C adapter对应I2C controller,同时需要为每一个I2C控制器编写驱动程序
- LINUX定义了I2C client对应I2C设备;
- LINUX定义了I2C 的i2c_algorithm,该方法主要由具体的I2C adapter来实现产生I2C控制器与I2C设备间通信的机制(实现时序产生、启动或停止传输时序,建立数据通信渠道)。
- LINUX定义了I2C driver,作为对应I2C设备的驱动方法。
由以上的分析,我们可以看到,LINUX I2C模块通过抽象出I2C adapter、I2C client、I2C driver、i2c_algorithm,实现了I2C总线通信,而I2C模块也是对LINUX设备驱动模型的继承,自然也定义了总线类型的变量(bus_type类型的变量)。如下就是这几个结构体变量的逻辑关联图:

二、I2C模型相关的结构体分析
既然i2c模型也是对linux 设备-总线-驱动模型的继承,因此i2c模块相关的结构体中一定存在bus_type、driver、device-driver这三种类型的变量,否则其将无法使用linux设备-总线-驱动模型。只要是linux系统中使用设备-总线-驱动模型的子模块,基本上其结构体都要包含bus_type、driver、device_driver这三种类型的变量。在i2c中定义了bus_type类型的总线变量,命名为i2c_bus_type。而i2c_client结构体是对一个i2c硬件设备的逻辑抽象,其中包含了device类型的成员变量;i2c_adapter结构体是对一个i2c控制器的逻辑抽象,其中也包含了device类型的成员变量;i2c_driver结构体是对i2c硬件设备的驱动的逻辑抽象,其中包含了device_driver类型的成员变量。分析下i2c_client、i2c_adapter、i2c_driver这三个结构体变量。
2.1、i2c_client结构体
该结构体是i2c硬件设备的逻辑抽象,包括了该i2c设备的地址类型、i2c设备地址、其所依附的adapter指针、其所绑定的i2c驱动、device类型的成员变量、链接至i2c驱动的链接头等信息,其通过adapter、driver指针连接了i2c适配器与驱动。
include/linux/i2c.h
struct i2c_client {
/*主要用于设置i2c client的地址类型(10bit还是7bit)等信息*/
unsigned short flags; /* div., see below */
#define I2C_CLIENT_PEC 0x04 /* Use Packet Error Checking */
#define I2C_CLIENT_TEN 0x10 /* we have a ten bit chip address */
/* Must equal I2C_M_TEN below */
#define I2C_CLIENT_SLAVE 0x20 /* we are the slave */
#define I2C_CLIENT_HOST_NOTIFY 0x40 /* We want to use I2C host notify */
#define I2C_CLIENT_WAKE 0x80 /* for board_info; true iff can wake */
#define I2C_CLIENT_SCCB 0x9000 /* Use Omnivision SCCB protocol */
/* Must match I2C_M_STOP|IGNORE_NAK */
/*标注该i2c设备的地址,7bit或9bit()*/
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
/*该i2c client所依附的i2c适配器*/
struct i2c_adapter *adapter; /* the adapter we sit on */
/*该i2c设备所绑定的驱动*/
struct i2c_driver *driver; /* and our access routines */
/*对应的device类型的变量,用于使用linux设备驱动模型*/
struct device dev; /* the device structure */
/*中断号*/
int irq; /* irq issued by device */
/*用于将该设备链接至其绑定的驱动对应的clients链表上*/
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
void *devres_group_id; /* ID of probe devres group */
};
2.2、i2c_adapter结构体
该结构体的定义如下,主要包括了该adapter提供的方法,该adapter支持的类型(class变量主要用于自动探测尚未注册到i2c总线上的i2c client,需要adapter和i2c driver有共同的类型,且i2c driver支持detect方法时才会自动探测)
include/linux/i2c.h
struct i2c_adapter {
struct module *owner;
/*用于表示该adapter支持自动detect i2c设备的类型(包括spd、hwmon等),若该
adapter不支持通过detect接口探测尚未注册至i2c bus上的i2c设备,则可不填*/
unsigned int class; /* classes to allow probing for */
/*该i2c适配器支持的方法,该变量说明了该适配器支持的方法(i2c/smbus,支持访问的方式等等)*/
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
const struct i2c_lock_operations *lock_ops;
struct rt_mutex bus_lock;
struct rt_mutex mux_lock;
int timeout; /* in jiffies */
int retries;
/*该adapter对应的device*/
struct device dev; /* the adapter device */
/*adapter的id与名称等*/
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
struct irq_domain *host_notify_domain;
struct regulator *bus_regulator;
};
2.3、i2c_driver结构体
该结构体的定义如下,其中class的含义与i2c_adapter中的含义相同,而detect函数指针就是上面所说对未注册进i2c总线的I2c设备进行自动探测,并注册进i2c总线中;而probe、remove等接口则主要是对设备驱动模型中device_driver中对应函数指针的实例;而attach_adapter接口则是在adapter注册进i2c总线时,对adapter下所有未注册的设备进行探测与匹配注册的操作,目前该接口已不推荐使用;而id_table主要用于说明该驱动支持哪些i2c client;address_list主要是配置detect函数指针使用,当探测尚未注册的i2c 设备时,仅对该变量中定义的i2c设备地址进行探测操作。
include/linux/i2c.h
struct i2c_driver {
/*该变量主要指示该driver支持的类型,包括hwmon、内存相关的spd等,主要用于detect某一个adapter下的
已挂载的硬件设备(且该硬件设备对应的逻辑设备未注册到i2c总线上)。*/
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
/*该接口主要用于新注册adapter时,通过遍历i2c总线上所有已注册的驱动程序,针对每一个驱动均调用
attach_adapter接口,识别该adapter上挂载的i2c设备,并未识别到的设备创建其对应的i2c_client*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
/*设备驱动模型的probe接口,在driver_register/device_register时,对于已注册的device和driver,
若匹配则调用driver_probe_device进行探测操作,最终即调用该驱动的probe接口*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
/*设备驱动模型的probe接口,在driver_unregister/device_unregister时,对于已绑定的device-driver,
调用__device_release_driver进行移除操作,最终会调用该去的的remove接口*/
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
/*电源管理相关的接口*/
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
/*设备驱动模型中的device-driver类型变量,为了继承设备驱动模型,包含该变量,即可
使用设备驱动模型中的相应接口,实现驱动注册、驱动与设备的探测与移除等等内容*/
struct device_driver driver;
/*该驱动支持的设备列表,该变量主要用于设备与驱动的匹配*/
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
/*该接口的作用是若已注册到i2c总线上的adapter上挂载的物理设备,并没有创建对应的逻辑对象,则调用
该接口,并根据该驱动的address_list上支持的地址类型,进行搜索该adapter上挂载的物理设
备(且没有创建对应的逻辑对象)*/
int (*detect)(struct i2c_client *, struct i2c_board_info *);
/*表明该驱动所支持的i2c设备的地址列表*/
const unsigned short *address_list;
struct list_head clients;
u32 flags;
};
三、i2c模块各结构体间的关联
在分析具体的实现前,先分析i2c模块的结构体间的关联,当对结构体间的关联熟悉后,再分析实现就简单很多。
主要从以下几个方面进行说明:
I2c各结构体之间设备驱动模型的关联、i2c adapter与i2c client的关联、i2c adapter与i2c client、i2c driver的关联。
3.1、I2c各结构体之间设备驱动模型的关联
本模块主要用于说明i2c adapter、 i2c driver、i2c client、i2c_bus是如何通过设备-总线-驱动模型间的结构体进行关联的。
- I2c driver通过device_driver类型的成员变量,完成与i2c_bus的关联,主要通过i2c bus的driver_kset、klist_drivers这两个变量实现不同的关联;
- I2c client/adapter通过device类型变量,完成与i2c bus的关联,主要通过klist_devices变量实现关联。特别的是i2c client通过device类型变量完成与i2c_driver的device_driver类型的成员变量的关联,通过knode_driver以及device_driver的klist_drivers的关联。
而device_driver、device、bus_type的关联以及驱动与设备绑定及探测,是通过设备驱动模型的接口device_register、driver_register实现的。
3.2、I2c client与i2c adapter之间的关联
上面已经说明了i2c client、i2c adapter、i2c driver之间通过设备-驱动模型的结构体进行关联,此处仅说明i2c client、i2c adapter这两个结构体本身成员的关联,如下图所示:
I2c adapter对应的device变量成员,是所有依附在该adapter上的i2c client的父设备,它们之间通过klist完成了关联(图中黑色箭头流向为此种关联);
I2c client通过其i2c_adapter类型的指针,完成对其所依附i2c adapter的指定。

3.3、I2c adapter、i2c client、i2c driver之间的关联
此处主要说明这三个结构体体间的关联,它们三个通过i2c client为桥梁实现了链接,其中i2c client、 i2c adapter之间的关联上面已经说明。
而i2c client与i2c driver之间,若i2c client是由i2c driver的detect接口探测到并添加到i2c总线上的,则它们之间通过链表链接在一起;若不是通过i2c driver的detect接口探测到的,则它们之间无须通过i2c driver的clients链表链接(这也是下图中使用虚线链接的原因,而通过detect探测的i2c设备一般是spd eeprom、hwmon类型的设备)。

3.3.1、Linux I2C 模块总体框架
Linux I2C 模块提供了一种标准的方式来在 I2C 总线上与多个设备进行通信。它是一个层次化的框架,主要包括以下几个关键组件:
-
I2C 总线(I2C Adapter):
- 每个 I2C 总线都由一个 I2C 总线适配器(
i2c_adapter) 驱动来管理。适配器负责执行 I2C 事务,并与物理硬件(例如 I2C 控制器)进行通信。
- 每个 I2C 总线都由一个 I2C 总线适配器(
-
I2C 设备(I2C Client):
- 每个连接到 I2C 总线的设备都有一个
i2c_client结构体,表示该设备在总线上。设备通过该结构体与驱动进行交互,并执行数据读写操作。
- 每个连接到 I2C 总线的设备都有一个
-
I2C 驱动(I2C Driver):
i2c_driver结构体定义了设备驱动的核心功能,负责匹配设备、处理设备的探测(probe)和移除(remove)操作。- 驱动与 I2C 设备通过设备 ID(
i2c_device_id)进行匹配,确定哪个设备将由该驱动管理。
-
设备与驱动的匹配:
i2c_device_id结构体列出了设备的名称及其他标识信息,帮助内核识别哪个设备匹配哪一个驱动。
3.3.2、主要结构体及其关联关系
1. i2c_adapter(I2C 总线适配器)
-
功能:管理和操作 I2C 总线,提供数据传输的接口。
-
关键字段:
master_xfer:定义如何执行 I2C 传输操作的函数指针。dev:继承自device结构体,表示该总线适配器作为设备存在。
-
与
i2c_client关系:- 每个
i2c_client(I2C 设备)都属于一个i2c_adapter(I2C 总线适配器)。通过i2c_client->adapter可以访问到相应的总线适配器。
- 每个
2. i2c_client(I2C 设备)
-
功能:表示一个 I2C 设备实例,包含设备在 I2C 总线上的地址和与设备驱动交互所需的基本信息。
-
关键字段:
adapter:指向i2c_adapter,即设备所连接的总线适配器。dev:继承自device,表示该设备的基本信息。addr:设备的 I2C 地址。
-
与
i2c_driver关系:i2c_driver通过匹配表id_table找到对应的i2c_client。匹配成功后,probe函数会被调用来初始化设备。
3. i2c_driver(I2C 设备驱动)
-
功能:描述一个 I2C 设备驱动,处理设备的探测、初始化、数据传输以及设备的移除等操作。
-
关键字段:
probe:设备探测函数,内核会在匹配成功时调用此函数进行设备初始化。remove:设备移除函数,在设备被移除时调用,释放资源。id_table:设备与驱动之间的匹配表,存储i2c_device_id数组,帮助内核确定设备和驱动的对应关系。
-
与
i2c_device_id关系:i2c_driver通过id_table数组中的设备 ID 匹配表,确定哪个设备属于当前驱动。
4. i2c_device_id(I2C 设备标识符)
-
功能:标识一个特定的 I2C 设备。通常包含设备的名称和一些特定标识数据。
-
关键字段:
name:设备的名称,通常与设备硬件的名称一致。driver_data:与设备驱动相关的数据,通常用于特定驱动程序的标识符。
-
与
i2c_driver关系:i2c_driver使用id_table来匹配设备名(name)与设备驱动。
3.3.3、I2C 设备驱动的工作流程
-
设备驱动注册:
- 使用
module_i2c_driver()宏注册 I2C 驱动,内核通过i2c_driver结构体找到匹配的设备并执行probe。
- 使用
-
设备探测(
probe):- 当一个 I2C 设备被检测到并与驱动匹配时,内核调用
probe函数进行初始化。 - 驱动会在
probe函数中初始化设备,配置设备硬件,申请资源等。
- 当一个 I2C 设备被检测到并与驱动匹配时,内核调用
-
数据传输:
- 在设备驱动的运行过程中,可以通过 I2C 接口函数(如
i2c_master_send()和i2c_master_recv())来执行数据传输。
- 在设备驱动的运行过程中,可以通过 I2C 接口函数(如
-
设备移除(
remove):- 当设备被移除时,内核调用
remove函数释放资源,关闭设备。
- 当设备被移除时,内核调用
2487

被折叠的 条评论
为什么被折叠?



