最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

全志公司对I2C的讲解

IT圈 admin 16浏览 0评论

2024年11月4日发(作者:可悦欣)

1. 几个基本概念

1.1. 设备模型

由 总线(bus_type) + 设备(device) + 驱动(device_driver) 组成,在该模型下,所有的

设备通过总线连接起来,即使有些设备没有连接到一根物理总线上,linux为其设置了一个内部

的、虚拟的platform总线,用以维持总线、驱动、设备的关系。

因此,对于实现一个linux下的设备驱动,可以划分为两大步:

1、设备注册;

2、驱动注册。

当然,其中还有一些细节问题:

1、驱动的probe函数

2、驱动和设备是怎么进行绑定的。

1.2. i2c设备驱动的几个数据结构

i2c_adapter:

每一个i2c_adapter对应一个物理上的i2c控制器,在i2c总线驱动probe函数中动态创建。通过

i2c_add_adapter注册到i2c_core。

i2c_algorithm:

i2c_algorithm中的关键函数master_xfer(),以i2c_msg为单位产生i2c访问需要的信号。不同

的平台所对应的master_xfer()是不同的,需要根据所用平台的硬件特性实现自己的xxx_xfer()方

法以填充i2c_algorithm的master_xfer指针;在A31上即是sun6i_i2c_algorithm函数。

i2c_client:

代表一个挂载到i2c总线上的i2c从设备,包含该设备所需要的数据:

该i2c从设备所依附的i2c控制器 struct i2c_adapter *adapter

该i2c从设备的驱动程序struct i2c_driver *driver

该i2c从设备的访问地址addr, name

该i2c从设备的名称name。

2. i2c总线驱动

2.1. 功能划分

从硬件功能上可划分为:i2c控制器和i2c外设(从设备)。每个i2c控制器总线上都可以挂载多

个i2c外设。Linux中对i2c控制器和外设分开管理:通过 i2c-sun6i.c 文件完成了i2c控制器

的设备注册和驱动注册;通过i2c-core.c为具体的i2c外设提供了统一的设备注册接口和驱动

注册接口,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器)。

2.2. i2c-sun6i.c

该文件是与具体硬件平台相关的,对应于A3x系列芯片。该文件实际上是i2c总线驱动的实现,

本质上就是向内核注册i2c总线设备、注册总线驱动、实现总线传输的时序控制算法。i2c控制

器被注册为Platform设备,如下:

if (twi_used_mask & TWI0_USED_MASK)

platform_device_register(&sun6i_twi0_device);

if (twi_used_mask & TWI1_USED_MASK)

platform_device_register(&sun6i_twi1_device);

if (twi_used_mask & TWI2_USED_MASK)

platform_device_register(&sun6i_twi2_device);

if (twi_used_mask & TWI3_USED_MASK)

platform_device_register(&sun6i_twi3_device);

if (twi_used_mask)

return platform_driver_register(&sun6i_i2c_driver);

需要注意的是:设备与驱动的对应关系是多对一的;即如果设备类型是一样的,会共用同一套驱

动,因此上面代码只是注册了一次驱动platform_driver_register(&sun6i_i2c_driver)。

设备注册:

将i2c控制器设备注册为platform设备,为每一个控制器定义一个struct platform_device数

据结构,并且把.name都设置为"sun6i-i2c"(后面会通过名字进行匹配驱动的),然后是调用

platform_device_register()将设备注册到platform bus上。

设备注册完成后其直观的表现就是在文件系统下出现:/sys/bus/platform/devices/sun6i-i2c.0

通过platform_device_register()进行的注册过程,说到底就是对struct platform_device这个数

据结构的更改,逐步完成.、.、.的赋值,然后将.加入到

platform_bus->kobj的链表上。

驱动注册:

步骤和设备注册的步骤类似,也是为驱动定义了一个数据结构:

struct platform_driver sun6i_i2c_driver;

因为一个驱动是可以对应多个设备的,而在系统里的3个控制器基本上是一致的(区别就是寄存

器的地址不一样),所以上面注册的3个设备共享的是同一套驱动。

初始化.probe和.remove函数,然后调用platform_driver_register进行驱动注册。主要函数调用流

程:

platform_driver_register --> driver_register --> bus_add_driver --> driver_attac

h

需要注意的是driver_attach,这个函数遍历了总线上(platform_bus_type)的所有设备,寻找与

驱动匹配的设备,并把满足条件的设备结构体上的驱动指针指向驱动,从而完成了驱动和设备的

匹配(__driver_attach函数完成)。

如果匹配到设备,这时就需要执行platform_bus_type的probe函数,最终会调用设备驱动的probe

函数(sun6i_i2c_probe)。

2.2.1 sun6i_i2c_probe

在sun6i_i2c_probe函数中完成了大量的工作,包括硬件初始化、中断注册、为每个i2c控制器创

建i2c_adapter等。

1268 pdata = pdev->rm_data;

1269 if (pdata == NULL) {

1270 return -ENODEV;

1271 }

1272

1273 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

1274 irq = platform_get_irq(pdev, 0);

1275 if (res == NULL || irq < 0) {

1276 return -ENODEV;

1277 }

1278

1279 if (!request_mem_region(res->start, resource_size(res), res->name)) {

1280 return -ENOMEM;

1281 }

• 首先得到当前设备的私有数据指针,并将其保留在pdata;进而通过

platform_get_resource得到该设备占用的内存资源,并申请:request_mem_region。

同时将irq资源也保留下来。

1288

1289 strlcpy(i2c->, "sun6i-i2c", sizeof(i2c->));

1290 i2c-> = THIS_MODULE;

1291 i2c-> = pdata->bus_num;

1292 i2c->s = 3;

1293 i2c->t = 5*HZ;

1294 i2c-> = I2C_CLASS_HWMON | I2C_CLASS_SPD;

1295 i2c->bus_freq = pdata->frequency;

1296 i2c->irq = irq;

1297 i2c->bus_num = pdata->bus_num;

1298 i2c->status = I2C_XFER_IDLE;

1299 i2c->suspended = 0;

1300 spin_lock_init(&i2c->lock);

1301 init_waitqueue_head(&i2c->wait);

初始化i2c_adapter,并初始化一个工作队列 init_waitqueue_head。

通过ioremap申请IO资源;

通过request_irq申请irq资源,中断的处理服务函数是:sun6i_i2c_handler;

sun6i_i2c_hw_init,对i2c控制进行硬件初始化;

i2c-> = &sun6i_i2c_algorithm,初始化控制器的总线传输算法,设备驱

动调用;

将初始化好的i2c_adapter注册到i2c_core:i2c_add_numbered_adapter。

至此,probe函数完成。

2.2.2 sun6i_i2c_core_process

i2c控制器的中断服务程序sun6i_i2c_handler调用了sun6i_i2c_core_process,i2c总线的实际传输

控制也是在该函数里完成的。

主要流程:

1. 读取i2c控制器当前状态,twi_query_irq_status,保留在state中;

2. 根据state的值进行分支跳转,控制i2c的工作状态;

3. 传输完成,调用sun6i_i2c_xfer_complete,唤醒工作队列。

2.2.3 sun6i_i2c_xfer

每一个i2c控制器设备,在驱动绑定后,都会创建一个i2c_adapter,用以描述该控制器,i2c_adapter

的建立与初始化是在驱动probe的时候建立的。每一个i2c_adapter包含了一个i2c_algorithm结

构体的指针,i2c_algorithm是用来对外提供操作i2c控制器的函数接口的,主要是master_xfer

函数,对应于i2c-sun6i.c,实际就是:

static int sun6i_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

该函数的功能是通知i2c_adapter需要对外设进行数据交换,需要交换的信息通过

struct i2c_msg *msgs传入。sun6i_i2c_xfer实际上是调用了sun6i_i2c_do_xfer进行传输。

因为i2c总线读写速率有限,sun6i_i2c_do_xfer启动i2c传输后,通过wait_event_timeout进入休

眠,直到中断唤醒或者超时;中断唤醒是由sun6i_i2c_xfer_complete完成的。

3. i2c设备驱动

3.1. 驱动注册

i2c从设备的驱动注册,使用的是i2c-core.c提供的接口:i2c_register_driver;其调用如下:

i2c_register_driver --> driver_register --> bus_add_driver;

对bus_add_driver进行分析:

• 关于device_driver数据结构的 struct driver_private *p

设备驱动模型是通过kobject对设备驱动进行层次管理的,因此device_driver应该包含

kobject成员,linux是将kobject包含在struct driver_private中,再在device_driver中

包含struct driver_private;我们可以理解driver_private是device_driver的私有数据,

由内核进行操作。

struct driver_private 是在驱动注册的开始,动态申请,并初始化的。

• klist_init(&priv->klist_devices, NULL, NULL);

初始化设备链表,每一个与该驱动匹配的device都会添加到该链表下。

• priv-> = bus->p->drivers_kset;

指定该驱动所属的kset;

• kobject_init_and_add

初始化kobject,并将kobject添加到其对应的kset集合中(即bus->p->drivers_kset)。

该函数最终是调用kobject_add_internal将kobject添加到对应的kset中;需要主要的是,

如果kobject的parent如果为NULL,在此会将其parent设置为所属kset集合的kobject:

parent = kobject_get(&kobj->kset->kobj);

接下来是为kobject创建文件夹:create_dir(kobj);从而能从/sys/目录下显示。

• driver_attach,将驱动和设备进行绑定

将遍历总线上的设备链表,查找可以匹配的设备,并绑定。

driver_attach --> bus_for_each_dev(drv->bus, NULL, drv, __driver_att

ach);

将函数指针__driver_attach传入bus_for_each_dev,将每个查找得到的device进行驱动

匹配。

bus_for_each_dev:

遍历总线上的所有设备,因为总线上的设备都是bus->p->klist_devices链表上的一个节

点,因此该函数其实就是对链表的遍历,具体可以参考klist。

__driver_attach(源码位置drivers/base/dd.c):

进行设备和驱动匹配,如果匹配成功,尝试进行绑定。

1. 首先进行匹配确认:driver_match_device(drv, dev);

调用关系: --> drv->bus->match --> i2c_device_match

--> of_driver_match_device

i2c_match_id

可以看出,最终有两种方式进行驱动匹配查询:

方法一:通过of_driver_match_device对比of_device_id;

方法二:通过i2c_match_id对比id_table;

方法二实际上就是对比

i2c_driver->id_table->name 和client->name是否一致。

2. 如果匹配确认,进行驱动与设备绑定:driver_probe_device;

调用关系: driver_probe_device --> really_probe

--> dev->bus->probe

driver_bound

在really_probe中,首先将设备的驱动指针指向该驱动:dev->driver = drv。

对应于i2c_bus_type,dev->bus->probe 即是:i2c_device_probe,最终调用驱动的probe

函数。

最后是driver_bound,将驱动与设备进行绑定:

其实就是调用klist_add_tail:将设备节点添加到驱动的klist_devices;

• 调用klist_add_tail,将被注册的驱动添加到总线的klist_drivers上;

klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

• module_add_driver(drv->owner, drv)

在sysfs创建drivers目录

3.2. 设备注册

方式一:i2c设备动态发现注册

在i2c_register_driver的最后:

INIT_LIST_HEAD(&driver->clients);

/* Walk the adapters that are already present */

i2c_for_each_dev(driver, __process_new_driver);

观察i2c_for_each_dev:

int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))

{

int res;

mutex_lock(&core_lock);

res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);

mutex_unlock(&core_lock);

return res;

}

其实就是遍历i2c总线上的klist_devices链表,对得到的每一个device,执行

__process_new_driver。

跟踪 __process_new_driver --> i2c_do_add_adapter --> i2c_detect

i2c_detect实现了i2c设备发现:在注册驱动后,通过i2c_detect检测是否有适合的设备连

接在总线上。i2c_detect实现如下:

在每一个adapter上遍历驱动给出的地址列表(address_list),由i2c_detect_address函数

完成;最终会调用driver->detect(即设备驱动提供的设备发现函数);

如果发现满足条件的设备,执行i2c_new_device,为设备建立i2c_client;并且将设备

添加到i2c_bus_type->p->klist_devices链表上(device_register),通过bus_add_device

函数完成,最后调用bus_probe_device,尝试绑定驱动。

• 将client添加到驱动的设备链表上:

list_add_tail(&client->detected, &driver->clients)

方式二:i2c设备之静态注册

Linux 3.3 提供了静态定义的方式来注册设备,接口原型:

linux-3.3/drivers/i2c/i2c-boardinfo.c

int __init

i2c_register_board_info(int busnum,

struct i2c_board_info const *info, unsigned len)

核心内容:

申请struct i2c_devinfo,用以描述一个i2c外设;

list_add_tail(&devinfo->list, &__i2c_board_list),将devinfo加入链表

__i2c_board_list,以供后续查找;

扫描__i2c_board_list,创建client

i2c_register_board_info只是把设备描述符加入到了__i2c_board_list,并没有创建

client,当调用i2c_register_adapter注册adapter时,会扫描__i2c_board_list,创建client;具

体调用:

i2c_register_adapter

--> i2c_scan_static_board_info

--> i2c_new_device

--> device_register

在 i2c_new_device完成了client创建,以及设备注册device_register。

PS:

由上面的注册流程可知,i2c_register_board_info应该在i2c_register_adapter之前完成,

否则__i2c_board_list中的节点不会被扫描到。

总结:

• 由上述分析可知,i2c设备驱动是通过i2c_register_driver注册的,i2c设备是通过

i2c_new_device注册的,在最后,这两个函数都尝试进行驱动和设备绑定

(driver_attach和bus_probe_device);因此不管是先注册驱动还是先注册设备,最

后都能够将合适的驱动和设备进行绑定。

• 有两种方式进行设备注册:

1、通过i2c_register_board_info,在系统启动之初静态地进行i2c设备注册(axp

电源驱动就是这样做的);

2、实现i2c设备驱动的detect函数,在驱动加载的时候动态检测创建设备,aw平台

的触摸屏驱动就是通过这种方式。

• Linux是通过在驱动数据结构中内嵌kobject、kset,完成了设备驱动的层次管理的,理

解kobject、kset对理解设备驱动模型很重要。

4. i2c驱动架构图

1、i2c_add_adapter

2、i2c_new_device/i2c_register_board_info

3、i2c_add_driver

4、调用i2c bus中注册的match函数进行匹配

5、调用platform bus中注册的match函数进行匹配

6、i2cdev_attach_adapter

2024年11月4日发(作者:可悦欣)

1. 几个基本概念

1.1. 设备模型

由 总线(bus_type) + 设备(device) + 驱动(device_driver) 组成,在该模型下,所有的

设备通过总线连接起来,即使有些设备没有连接到一根物理总线上,linux为其设置了一个内部

的、虚拟的platform总线,用以维持总线、驱动、设备的关系。

因此,对于实现一个linux下的设备驱动,可以划分为两大步:

1、设备注册;

2、驱动注册。

当然,其中还有一些细节问题:

1、驱动的probe函数

2、驱动和设备是怎么进行绑定的。

1.2. i2c设备驱动的几个数据结构

i2c_adapter:

每一个i2c_adapter对应一个物理上的i2c控制器,在i2c总线驱动probe函数中动态创建。通过

i2c_add_adapter注册到i2c_core。

i2c_algorithm:

i2c_algorithm中的关键函数master_xfer(),以i2c_msg为单位产生i2c访问需要的信号。不同

的平台所对应的master_xfer()是不同的,需要根据所用平台的硬件特性实现自己的xxx_xfer()方

法以填充i2c_algorithm的master_xfer指针;在A31上即是sun6i_i2c_algorithm函数。

i2c_client:

代表一个挂载到i2c总线上的i2c从设备,包含该设备所需要的数据:

该i2c从设备所依附的i2c控制器 struct i2c_adapter *adapter

该i2c从设备的驱动程序struct i2c_driver *driver

该i2c从设备的访问地址addr, name

该i2c从设备的名称name。

2. i2c总线驱动

2.1. 功能划分

从硬件功能上可划分为:i2c控制器和i2c外设(从设备)。每个i2c控制器总线上都可以挂载多

个i2c外设。Linux中对i2c控制器和外设分开管理:通过 i2c-sun6i.c 文件完成了i2c控制器

的设备注册和驱动注册;通过i2c-core.c为具体的i2c外设提供了统一的设备注册接口和驱动

注册接口,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器)。

2.2. i2c-sun6i.c

该文件是与具体硬件平台相关的,对应于A3x系列芯片。该文件实际上是i2c总线驱动的实现,

本质上就是向内核注册i2c总线设备、注册总线驱动、实现总线传输的时序控制算法。i2c控制

器被注册为Platform设备,如下:

if (twi_used_mask & TWI0_USED_MASK)

platform_device_register(&sun6i_twi0_device);

if (twi_used_mask & TWI1_USED_MASK)

platform_device_register(&sun6i_twi1_device);

if (twi_used_mask & TWI2_USED_MASK)

platform_device_register(&sun6i_twi2_device);

if (twi_used_mask & TWI3_USED_MASK)

platform_device_register(&sun6i_twi3_device);

if (twi_used_mask)

return platform_driver_register(&sun6i_i2c_driver);

需要注意的是:设备与驱动的对应关系是多对一的;即如果设备类型是一样的,会共用同一套驱

动,因此上面代码只是注册了一次驱动platform_driver_register(&sun6i_i2c_driver)。

设备注册:

将i2c控制器设备注册为platform设备,为每一个控制器定义一个struct platform_device数

据结构,并且把.name都设置为"sun6i-i2c"(后面会通过名字进行匹配驱动的),然后是调用

platform_device_register()将设备注册到platform bus上。

设备注册完成后其直观的表现就是在文件系统下出现:/sys/bus/platform/devices/sun6i-i2c.0

通过platform_device_register()进行的注册过程,说到底就是对struct platform_device这个数

据结构的更改,逐步完成.、.、.的赋值,然后将.加入到

platform_bus->kobj的链表上。

驱动注册:

步骤和设备注册的步骤类似,也是为驱动定义了一个数据结构:

struct platform_driver sun6i_i2c_driver;

因为一个驱动是可以对应多个设备的,而在系统里的3个控制器基本上是一致的(区别就是寄存

器的地址不一样),所以上面注册的3个设备共享的是同一套驱动。

初始化.probe和.remove函数,然后调用platform_driver_register进行驱动注册。主要函数调用流

程:

platform_driver_register --> driver_register --> bus_add_driver --> driver_attac

h

需要注意的是driver_attach,这个函数遍历了总线上(platform_bus_type)的所有设备,寻找与

驱动匹配的设备,并把满足条件的设备结构体上的驱动指针指向驱动,从而完成了驱动和设备的

匹配(__driver_attach函数完成)。

如果匹配到设备,这时就需要执行platform_bus_type的probe函数,最终会调用设备驱动的probe

函数(sun6i_i2c_probe)。

2.2.1 sun6i_i2c_probe

在sun6i_i2c_probe函数中完成了大量的工作,包括硬件初始化、中断注册、为每个i2c控制器创

建i2c_adapter等。

1268 pdata = pdev->rm_data;

1269 if (pdata == NULL) {

1270 return -ENODEV;

1271 }

1272

1273 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

1274 irq = platform_get_irq(pdev, 0);

1275 if (res == NULL || irq < 0) {

1276 return -ENODEV;

1277 }

1278

1279 if (!request_mem_region(res->start, resource_size(res), res->name)) {

1280 return -ENOMEM;

1281 }

• 首先得到当前设备的私有数据指针,并将其保留在pdata;进而通过

platform_get_resource得到该设备占用的内存资源,并申请:request_mem_region。

同时将irq资源也保留下来。

1288

1289 strlcpy(i2c->, "sun6i-i2c", sizeof(i2c->));

1290 i2c-> = THIS_MODULE;

1291 i2c-> = pdata->bus_num;

1292 i2c->s = 3;

1293 i2c->t = 5*HZ;

1294 i2c-> = I2C_CLASS_HWMON | I2C_CLASS_SPD;

1295 i2c->bus_freq = pdata->frequency;

1296 i2c->irq = irq;

1297 i2c->bus_num = pdata->bus_num;

1298 i2c->status = I2C_XFER_IDLE;

1299 i2c->suspended = 0;

1300 spin_lock_init(&i2c->lock);

1301 init_waitqueue_head(&i2c->wait);

初始化i2c_adapter,并初始化一个工作队列 init_waitqueue_head。

通过ioremap申请IO资源;

通过request_irq申请irq资源,中断的处理服务函数是:sun6i_i2c_handler;

sun6i_i2c_hw_init,对i2c控制进行硬件初始化;

i2c-> = &sun6i_i2c_algorithm,初始化控制器的总线传输算法,设备驱

动调用;

将初始化好的i2c_adapter注册到i2c_core:i2c_add_numbered_adapter。

至此,probe函数完成。

2.2.2 sun6i_i2c_core_process

i2c控制器的中断服务程序sun6i_i2c_handler调用了sun6i_i2c_core_process,i2c总线的实际传输

控制也是在该函数里完成的。

主要流程:

1. 读取i2c控制器当前状态,twi_query_irq_status,保留在state中;

2. 根据state的值进行分支跳转,控制i2c的工作状态;

3. 传输完成,调用sun6i_i2c_xfer_complete,唤醒工作队列。

2.2.3 sun6i_i2c_xfer

每一个i2c控制器设备,在驱动绑定后,都会创建一个i2c_adapter,用以描述该控制器,i2c_adapter

的建立与初始化是在驱动probe的时候建立的。每一个i2c_adapter包含了一个i2c_algorithm结

构体的指针,i2c_algorithm是用来对外提供操作i2c控制器的函数接口的,主要是master_xfer

函数,对应于i2c-sun6i.c,实际就是:

static int sun6i_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)

该函数的功能是通知i2c_adapter需要对外设进行数据交换,需要交换的信息通过

struct i2c_msg *msgs传入。sun6i_i2c_xfer实际上是调用了sun6i_i2c_do_xfer进行传输。

因为i2c总线读写速率有限,sun6i_i2c_do_xfer启动i2c传输后,通过wait_event_timeout进入休

眠,直到中断唤醒或者超时;中断唤醒是由sun6i_i2c_xfer_complete完成的。

3. i2c设备驱动

3.1. 驱动注册

i2c从设备的驱动注册,使用的是i2c-core.c提供的接口:i2c_register_driver;其调用如下:

i2c_register_driver --> driver_register --> bus_add_driver;

对bus_add_driver进行分析:

• 关于device_driver数据结构的 struct driver_private *p

设备驱动模型是通过kobject对设备驱动进行层次管理的,因此device_driver应该包含

kobject成员,linux是将kobject包含在struct driver_private中,再在device_driver中

包含struct driver_private;我们可以理解driver_private是device_driver的私有数据,

由内核进行操作。

struct driver_private 是在驱动注册的开始,动态申请,并初始化的。

• klist_init(&priv->klist_devices, NULL, NULL);

初始化设备链表,每一个与该驱动匹配的device都会添加到该链表下。

• priv-> = bus->p->drivers_kset;

指定该驱动所属的kset;

• kobject_init_and_add

初始化kobject,并将kobject添加到其对应的kset集合中(即bus->p->drivers_kset)。

该函数最终是调用kobject_add_internal将kobject添加到对应的kset中;需要主要的是,

如果kobject的parent如果为NULL,在此会将其parent设置为所属kset集合的kobject:

parent = kobject_get(&kobj->kset->kobj);

接下来是为kobject创建文件夹:create_dir(kobj);从而能从/sys/目录下显示。

• driver_attach,将驱动和设备进行绑定

将遍历总线上的设备链表,查找可以匹配的设备,并绑定。

driver_attach --> bus_for_each_dev(drv->bus, NULL, drv, __driver_att

ach);

将函数指针__driver_attach传入bus_for_each_dev,将每个查找得到的device进行驱动

匹配。

bus_for_each_dev:

遍历总线上的所有设备,因为总线上的设备都是bus->p->klist_devices链表上的一个节

点,因此该函数其实就是对链表的遍历,具体可以参考klist。

__driver_attach(源码位置drivers/base/dd.c):

进行设备和驱动匹配,如果匹配成功,尝试进行绑定。

1. 首先进行匹配确认:driver_match_device(drv, dev);

调用关系: --> drv->bus->match --> i2c_device_match

--> of_driver_match_device

i2c_match_id

可以看出,最终有两种方式进行驱动匹配查询:

方法一:通过of_driver_match_device对比of_device_id;

方法二:通过i2c_match_id对比id_table;

方法二实际上就是对比

i2c_driver->id_table->name 和client->name是否一致。

2. 如果匹配确认,进行驱动与设备绑定:driver_probe_device;

调用关系: driver_probe_device --> really_probe

--> dev->bus->probe

driver_bound

在really_probe中,首先将设备的驱动指针指向该驱动:dev->driver = drv。

对应于i2c_bus_type,dev->bus->probe 即是:i2c_device_probe,最终调用驱动的probe

函数。

最后是driver_bound,将驱动与设备进行绑定:

其实就是调用klist_add_tail:将设备节点添加到驱动的klist_devices;

• 调用klist_add_tail,将被注册的驱动添加到总线的klist_drivers上;

klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

• module_add_driver(drv->owner, drv)

在sysfs创建drivers目录

3.2. 设备注册

方式一:i2c设备动态发现注册

在i2c_register_driver的最后:

INIT_LIST_HEAD(&driver->clients);

/* Walk the adapters that are already present */

i2c_for_each_dev(driver, __process_new_driver);

观察i2c_for_each_dev:

int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))

{

int res;

mutex_lock(&core_lock);

res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);

mutex_unlock(&core_lock);

return res;

}

其实就是遍历i2c总线上的klist_devices链表,对得到的每一个device,执行

__process_new_driver。

跟踪 __process_new_driver --> i2c_do_add_adapter --> i2c_detect

i2c_detect实现了i2c设备发现:在注册驱动后,通过i2c_detect检测是否有适合的设备连

接在总线上。i2c_detect实现如下:

在每一个adapter上遍历驱动给出的地址列表(address_list),由i2c_detect_address函数

完成;最终会调用driver->detect(即设备驱动提供的设备发现函数);

如果发现满足条件的设备,执行i2c_new_device,为设备建立i2c_client;并且将设备

添加到i2c_bus_type->p->klist_devices链表上(device_register),通过bus_add_device

函数完成,最后调用bus_probe_device,尝试绑定驱动。

• 将client添加到驱动的设备链表上:

list_add_tail(&client->detected, &driver->clients)

方式二:i2c设备之静态注册

Linux 3.3 提供了静态定义的方式来注册设备,接口原型:

linux-3.3/drivers/i2c/i2c-boardinfo.c

int __init

i2c_register_board_info(int busnum,

struct i2c_board_info const *info, unsigned len)

核心内容:

申请struct i2c_devinfo,用以描述一个i2c外设;

list_add_tail(&devinfo->list, &__i2c_board_list),将devinfo加入链表

__i2c_board_list,以供后续查找;

扫描__i2c_board_list,创建client

i2c_register_board_info只是把设备描述符加入到了__i2c_board_list,并没有创建

client,当调用i2c_register_adapter注册adapter时,会扫描__i2c_board_list,创建client;具

体调用:

i2c_register_adapter

--> i2c_scan_static_board_info

--> i2c_new_device

--> device_register

在 i2c_new_device完成了client创建,以及设备注册device_register。

PS:

由上面的注册流程可知,i2c_register_board_info应该在i2c_register_adapter之前完成,

否则__i2c_board_list中的节点不会被扫描到。

总结:

• 由上述分析可知,i2c设备驱动是通过i2c_register_driver注册的,i2c设备是通过

i2c_new_device注册的,在最后,这两个函数都尝试进行驱动和设备绑定

(driver_attach和bus_probe_device);因此不管是先注册驱动还是先注册设备,最

后都能够将合适的驱动和设备进行绑定。

• 有两种方式进行设备注册:

1、通过i2c_register_board_info,在系统启动之初静态地进行i2c设备注册(axp

电源驱动就是这样做的);

2、实现i2c设备驱动的detect函数,在驱动加载的时候动态检测创建设备,aw平台

的触摸屏驱动就是通过这种方式。

• Linux是通过在驱动数据结构中内嵌kobject、kset,完成了设备驱动的层次管理的,理

解kobject、kset对理解设备驱动模型很重要。

4. i2c驱动架构图

1、i2c_add_adapter

2、i2c_new_device/i2c_register_board_info

3、i2c_add_driver

4、调用i2c bus中注册的match函数进行匹配

5、调用platform bus中注册的match函数进行匹配

6、i2cdev_attach_adapter

发布评论

评论列表 (0)

  1. 暂无评论