【linux kernel 3.18】I2C总线驱动注册、注销和使用流程

【linux kernel 3.18】I2C总线驱动注册、注销和使用流程

一、前言

前言主要阐述了编写该技术文档的目的,使用的平台环境,以及看懂该文档所需要掌握的基本知识,方便学习者按图索骥,填充相关的知识体系。

linux驱动开发的过程中,我们每天会接触到大量的代码,会遇到不同的总线和设备,有时会忘记某些设备驱动的注册、使用和注销的流程,或者由于没有系统化地掌握、注册注销流程的一些细节,导致代码编译不通过,通过了却无法调试成功,使得工程师本应该专注于核心代码的实现和优化,却花费了大量的时间去检测框架上的错误。因此,本技术文档的第一个目的,就是将驱动设备的注册、注销过程流程化,加快编写效率,也方便工程师查漏。

但是,为了突出流程化的这个特点,该文档应当具有的一个特点就是,即使是一个尚未接触过linux驱动(但有一定编程基础)的人,看到该文档也应该能够大致明白整个流程,从而迅速上手,实现某些驱动功能,完成某些较为简单的工作任务。因此,该文档也应当具有一定的机制原理介绍。

本文就是在技术性和流程化的两端中取平衡。

内核版本Linux 3.18linux不同版本的驱动框架差异较大,尤其是linux kernel 3.x版本引入了设备树,与2.x版本有着巨大的差别,因此本文中所讲的代码、机制,不一定适用于所有版本,要根据工作环境来灵活选择是否采用本文中的内核机制)

预备知识C语言(熟练)、linux总线驱动设备框架(熟悉)、linux字符设备或杂项设备的创建和使用(熟悉)设备树文件编写知识(熟悉)、i2c总线通信协议(了解)

*程度水平

了解:阅读过相关的技术资料,知道其中的技术原理,但并没有经过实践。

熟悉:阅读过相关的工程代码或者开放源代码,知道该项知识是如何应用到实际的工程中的。

熟练:能够熟练地应用该项知识完成一定的工程任务。

精通:能够详细地阐述该项知识的技术实现细节,能够分析该项知识在实际应用中客观存在的优点和缺点,并能够横向对比相似的技术,比较它们的优劣之处。

 

*英文名限定

为了区分代码中的英文和普通的英文,方便读者阅读理解,在此将正文中出现的英文进行了划分,划分如下:

英文正文:英文名首部不加任何解释

英文函数名:英文名后添加小括号,如device_init()。为了节省行文篇幅,在正文中就不写返回类型和函数参数了

英文变量/结构体:英文名首部添加”.”,.bus_type

所有的函数名和英文变量/结构体都是该函数/变量在内核代码中的实际名称,没有缩写或者简写,可以直接搜索内核工程找到相应的定义

 

 

二、I2C驱动注册过程

1.of_device_id结构体,用于匹配设备树

例:

static struct of_device_id  xxx_of_match[]={

{.compatible=”device_provider,xxx_device”,},

{},

};

.compatible中的内容即为设备树下挂载在i2c总线下的设备的属性compatible,当调用i2c_driver_register()时,系统就会调用.i2c_bus_tpye下的match()将设备与驱动配对。

若该驱动能够对应多个设备,则可以继续往.xxx_of_match里添加设备匹配信息。

 

 

2.创建i2c_driver结构体

例:

static struct i2c_driver xxxx_i2c_driver =

{

.id_table   = &xxxx_i2c_id_table,

.probe  = xxxx_i2c_probe,

.remove = xxxx_i2c_remove,

.driver =

{

.name           = xxxx_DEV_NAME,

.of_match_table = &xxxx_i2c_match_table,

},

};

以上是一个较为简单的声明方式,该结构已经可以完成i2c设备驱动的匹配和使用了。下面介绍以上的每一个成员。

.id_table是必不可少的,因为在i2c_device_probe()的调用过程中如果没有这个.id_table ,则probe函数会返回一个失败值,从而导致probe失败(与platform总线不同,i2c总线下的驱动必须要有.id_table,因为.i2c_bus_type下的i2c_device_probe()中有对id_table的检测,如果.id_table为空,则i2c总线i2c_device_probe()失败,根本不会进入到驱动的xxx_i2c_probe()。不要问为什么platform driver注册时不需要这个东西,因为linux内核代码就是这样写的,platform bus在调用platform_drv_probe()的时候完全不需要检测id_table)。在调用.i2c_bus_type下的match()时,系统会通过.i2c_client中的.name来寻找与.i2c_driver里的.id_table列表中相同的名称来进行匹配,从而match成功。但实际上,可以看到,在i2c_driver里还有一个driver,里面的.of_match_table成员才是匹配i2c设备的关键,它具有匹配的优先权,外层的.id_table 只是一个辅助驱动与设备匹配的结构而已(在设备树节点匹配失败的时候,才会使用.id_table去匹配设备)。

.probe 指向自己创建的驱动probe函数,该函数在总线将驱动与设备匹配成功时调用,在这里面可以配置一些驱动初始化相关的工作。函数原型如下:

int (*probe)(struct i2c_client *, const struct i2c_device_id *);

其中的client就是i2c设备实体,是通过.of_match_table匹配设备与驱动后传下来的i2c设备信息,之后的i2c通信都要通过该client完成,因此建议创建一个全局的i2c_client结构体指针来指向通过probe传下来的client地址,以方便后续编程时给i2c_master_send和i2c_master_recv传递参数(i2c通信全靠这两个函数)。probe传下来的client指针对应的内存空间在client设备注册时已经被创建(client创建过程的分析要占用大量的篇幅,如果想彻底明白client创建的过程,可以在内核工程中搜索of_i2c_register_devices()这个函数,细致分析该函数就能明白内核是如何通过设备树来创建client的),并在系统运行的过程中永久地存在,除非注销设备,但注销设备也会导致match()失败(关于match和probe的顺序关系,在后续的步骤中会解释),根本不会走到probe这一步,因此不用担心会指向一个可能被动态删除的内存空间。

.remove指向自己创建的驱动remove函数,在i2c_del_driver()时被调用,可以在其中配置驱动卸载时的工作。

.driver就是linux最原始的driver结构体,所有总线设备的驱动结构体如i2c_driver都是内嵌了该driver的结构体。关于driver结构体的完整作用,在总线、设备、驱动模型的知识点中应当会有解析,可以在网络上去搜索,在此不铺开介绍。但在这个结构体里面有一个.of_match_table成员,由于它和设备匹配息息相关,在此必须阐述一下,顺便帮助阅读者理清驱动设备匹配的过程。

在i2c驱动注册时会使用i2c_add_driver()来进行注册,这个函数在检测driver的一些参数后若没有错误,会调用bus_add_driver()来进行驱动的添加,在该函数中会检测设备链表是否有误,若检测无误则会调用driver_attach(),在该函数中会遍历i2c总线下的设备链表,在每一个节点会调用__driver_attach()函数来检测注册的驱动是否和当前节点的设备匹配,并完成probe工作。在__driver_attach()中首先会调用driver_match_device(),搜寻当前设备节点是否和注册的驱动匹配,在driver_match_device()中会调用驱动所挂靠的总线下的match()函数,完成匹配的检测,在此以.i2c_bus_type下的i2c_device_match()为例,在i2c_device_match()中(请打开工程搜索相应的函数),我们看到,驱动与设备的匹配优先通过设备树方法(即使用.of_match_table这个成员),匹配失败时再通过ACPI方法,若再次失败最后才是用之前讲过的id_table来进行匹配(之所以这样做是为了兼容之前版本的linux驱动)。当匹配成功后,__driver_attach()会调用总线的probe()函数,一般情况下总线的probe()最终会进入驱动的xxx_probe(),在i2c驱动就是.i2c_driver中的.probe指向的函数,完成probe工作。

以上,完成i2c_driver的创建。

 

3.调用i2c_add_driver()注册驱动

该函数实际上是一个宏定义,如下:

#define i2c_add_driver(driver) \

i2c_register_driver(THIS_MODULE, driver)

实际上是调用了i2c_register_driver()这个函数,函数原型如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

成功则返回0,否则返回非0值。

可见i2c_add_driver()要传入的参数是i2c_driver*类型。

如:

i2c_add_driver(&xxx_i2c_driver);

虽然该函数的返回值一般都会是成功值,但为了严谨地判断i2c驱动是否创建成功,建议还是对该返回值进行判断,可以在module初始化的时候检测一下。

 

以上,完成对i2c驱动的注册。由于默认读者理解了设备树创建设备节点的基本过程,和i2c总线协议过程,因此不再此阐述i2c设备的创建过程。

 

三、I2C驱动使用过程

之前已经分析过,在i2c驱动的注册过程中会调用.i2c_driver下的probe函数,在调用该函数时内核会自动向该probe传递一个client结构体指针,指向匹配该驱动的i2c从设备.i2c_client的结构体地址。

在此,我们建议先在全局定义.i2c_client结构体指针,如

i2c_client *gobal_xxx_i2c_client=NULL;

然后,在驱动的probe函数中将该指针指向自动传下来的client指针所对应的地址。如:

gobal_xxx_i2c_client=client;

此后,就可以利用该指针进行i2c通信了。

i2c通信需要调用i2c_master_send()和i2c_master_recv()来完成i2c主设备(通常是板上的CPU)和i2c从设备(通常是板上的外围设备)的信息交互。

i2c_master_send()的原型如下:

int i2c_master_send(const struct i2c_client *client, const char *buf, int count);

该函数实现的功能是主设备向从设备发送数据。第一个参数是i2c_client指针,第二个参数是要发送给从设备的数据首地址,该buf中存放的信息以字节为单位,第三个参数是要发送的字节数。若成功则返回发送的字节数,失败则返回失败值。

 i2c_master_recv()的原型如下:

int i2c_master_recv(const struct i2c_client *client, char *buf, int count);

该函数实现的功能是主设备接收从设备所发送的数据。第一个参数是i2c_client指针,第二个参数是要接收该数据的内核空间首地址,该buf中存放的信息以字节为单位,第三个参数是要接收的字节数。若成功则返回接收的字节数,失败则返回失败值。

通过以上两个函数,就可以完成i2c通信的过程了。

在此,建议在i2c驱动注册的同时也注册一个字符设备或者杂项设备,此后可以在字符设备或者杂项设备的write()、read()、ioctl()中通过传入之前所创建的全局i2c_client指针调用以上两个函数,这样就可以很方便地给上层提供一个用户接口。

 

四、I2C驱动注销过程

调用i2c_del_driver(),注销i2c驱动。

原型如下:

void i2c_del_driver(struct i2c_driver *driver);

传入对应的驱动指针即可。

一般在module注销的时候调用。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值