1.linux设备驱动到底复杂在什么地方?
假设soc芯片有两个i2c adapter:i2c_adapter1,i2c_adapter1;然后外部有三个i2c接口的设备i2c_device1,i2c_device2,i2c_device3。
现在要求在裸机下写出他们的驱动函数。那么肯定要写出6个不同的驱动函数:
设想一共有m个i2c adapter和n个外设i2c device,那么将需要m*n个驱动。并且这m*n个驱动程序必要会有很大部分重复的代码,而且不利于驱动程序的移植。
如果采用adapter和device分离的思想来写这样的驱动会是怎样呢?
图1
这样分离之后,只需要m+n个驱动,而且Adapter和Device的几乎没有耦合性,增加一个Adapter或者device并不会影响其余的驱动。
这就是分离思想带来的好处。除此之外,linux虽然是C写的,但是大量使用了面向对象的变成方法(可以理解为分层的思想),仅仅分离细想和分层思想的引入,就大大增加了linux设备驱动的复杂度。
2.I2C总线汇总概览
1)三根通信线:SCL、SDA、GND
2)同步、串行、电平、低速、近距离
3)总线式结构,支持多个设备挂接在同一条总线上
4)主从式结构,通信双方必须一个为主(master)一个为从(slave),主设备掌握每次通信的主动权,从设备按照主设备的节奏被动响应。每个从设备在总线中有唯一的地址(slave address),主设备通过从地址找到自己要通信的从设备(本质是广播)。
5)I2C主要用途就是主SoC和外围设备之间的通信,最大优势是可以在总线上扩展多个外围设备的支持。常见的各种物联网传感器芯片(如gsensor、温度、湿度、光强度、酸碱度、烟雾浓度、压力等)均使用I2C接口和主SoC进行连接。
3.linux内核的I2C驱动框架总览
下图就是i2c驱动框架示意图,类似中断子系统, i2c子系统中也使用一个对象来描述一个物理实体,设备对象与驱动分离,驱动结合设备对象对硬件设备的描述才可以驱动一个具体的物理设备,体现了分离的设计思想,实现了代码的复用,比如:
事实上,对于任何一种总线,内核都有一个bus_type类型的对象与之对应,但platform_bus_type并没有对应的实际的物理总线,这也就是platform总线也叫虚拟总线的原因。
除了分离,i2c子系统也体现的软件分层的设计思想,整个i2c子系统由3层构成:设备驱动层--i2c核心--控制器驱动
2)I2C驱动架构主要由I2C核心、I2C总线驱动以及I2C设备驱动三个部分组成!
3)I2C核心主要提供了以下几个功能:I2C总线驱动和设备驱动的注册及注销函数,I2C lgorithm的上层通信代码实现,探测设备、检测设备地址的上层代码实现。
4)I2C总线驱动负责实现I2C适配器数据结构(i2c_adapter)、I2C适配器的algorithm数据结构(i2c_algorithm)以及控制适配器产生通信信号的函数。可以为我们控制I2C产生开始位、停止位、读写周期以及从设备的读写、产生ACK等。
5)I2C设备驱动负责实现i2c_driver和i2c_client两个数据结构。i2c_driver结构对应一套具体的驱动方法,例如:probe、remove、suspend等,需要自己申明。i2c_client数据结构由内核根据具体的设备注册信息自动生成,设备驱动根据硬件具体情况填充。
6)源码中I2C相关的驱动均位于:drivers/i2c目录下。
Algos---à包含了I2C总线适配器的算法实现,被I2C总线驱动用于和I2C总线对话!
Busses--à包含I2C总线的驱动,都是一些特定的驱动
i2c-boardinfo.c--à包含一些板级信息。
i2c-core.c-à实现了I2C的核心功能以及/proc/bus/i2c*接口
i2c-dev.c--à通用驱动
linux系统提供2种I2C驱动实现方法:第一种叫i2c-dev,对应drivers/i2c/i2c-dev.c,这种方法只是封装了主机(I2C master,一般是SoC中内置的I2C控制器)的I2C基本操作,并且向应用层提供相应的操作接口,应用层代码需要自己去实现对slave的控制和操作,所以这种I2C驱动相当于只是提供给应用层可以访问slave硬件设备的接口,本身并未对硬件做任何操作,应用需要实现对硬件的操作,因此写应用的人必须对硬件非常了解,其实相当于传统的驱动中干的活儿丢给应用去做了,所以这种I2C驱动又叫做“应用层驱动”,这种方式并不主流,它的优势是把差异化都放在应用中,这样在设备比较难缠(尤其是slave是非标准I2C时)时不用动驱动,而只需要修改应用就可以实现对各种设备的驱动。这种驱动在驱动层很简单(就是i2c-dev.c)我们就不分析了。
7)第二种I2C驱动是所有的代码都放在驱动层实现,直接向应用层提供最终结果。应用层甚至不需要知道这里面有I2C存在,譬如电容式触摸屏驱动,直接向应用层提供/dev/input/event1的操作接口,应用层编程的人根本不知道event1中涉及到了I2C。这种是我们后续分析的重点。
4.I2C子系统的4个关键结构体
1)struct i2c_adapter I2C适配器
实现了一组通过一个i2c控制器发送消息的所有信息,包括时序,地址等等,即封装了i2c控制器的"控制信息"。它被i2c主机驱动创建,通过clien域和i2c_client和i2c_driver相连,这样设备端驱动就可以通过其中的方法以及i2c物理控制器来和一个i2c总线的物理设备进行交互。
2)struct i2c_algorithm I2C算法
描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调。
【i2c_adapter与i2c_algorithm之间的关系】
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体
I2c_msg描述一个在设备端和主机端之间进行流动的数据, 在设备驱动中打包并通过i2c_transfer()发送。相当于skbuf之于网络设备,urb之于USB设备。
3)struct i2c_client I2C(从机)设备信息
描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。
【i2c_adapter和i2c_client】
i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。
从图1图2中都可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为下面的硬件设备提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。
4)struct i2c_driver I2C(从机)设备驱动
描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连。
【i2c_driver和i2c_client】
i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client(),i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述,i2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.
核心方法
5.关键文件
1)i2c-core.c
2)busses目录
3)algos目录
6.i2c-core.c初步分析
1)smbus代码略过
2)模块加载和卸载:bus_register(&i2c_bus_type);
7.I2C总线的匹配机制
1)match函数
匹配方式有两种,一种是name匹配,一种是id_table匹配。
我们打开相应的i2c-s3c2410.c文件,看到:
那我们与谁匹配呢?即client.name,看起来在device_driver中:
2)probe函数
总结:I2C总线上有2条分支:i2c_client链和i2c_driver链,当任何一个driver或者client去注册时,I2C总线都会调用match函数去对client.name和driver.id_table.name进行循环匹配。如果driver.id_table中所有的id都匹配不上则说明client并没有找到一个对应的driver,没了;如果匹配上了则标明client和driver是适用的,那么I2C总线会调用自身的probe函数,自身的probe函数又会调用driver中提供的probe函数,driver中的probe函数会对设备进行硬件初始化和后续工作。
8.核心层开放给其他部分的注册接口
1)i2c_add_adapter/i2c_add_numbered_adapter 注册adapter的,最终调用的是device_register
2)i2c_add_driver 注册driver的,最终调用的是driver_register
3)i2c_new_device 注册client的,最终调用的是device_register
9.adapter模块的注册
1)平台总线方式注册
2)找到driver和device,并且确认其配对过程
3)probe函数
10.probe函数分析
1)填充一个i2c_adapter结构体,并且调用接口去注册之
2)从platform_device接收硬件信息,做必要的处理(request_mem_region & ioremap、request_irq等)
3)对硬件做初始化(直接操作210内部I2C控制器的寄存器)
12.i2c_algorithm---时序
1)i2c->adap.algo = &s3c24xx_i2c_algorithm;
2)functionality
3)s3c24xx_i2c_doxfer
13.i2c_driver的注册
1)以gslX680的驱动为例
2)将驱动添加到内核SI项目中
3)i2c_driver的基本分析:name和probe
14.i2c_client从哪里来
1)直接来源:i2c_register_board_info
smdkc110_machine_init
i2c_register_board_info
struct i2c_board_info {
char type[I2C_NAME_SIZE]; // 设备名
unsigned short flags; // 属性
unsigned short addr; // 设备从地址
void *platform_data; // 设备私有数据
struct dev_archdata *archdata;
#ifdef CONFIG_OF
struct device_node *of_node;
#endif
int irq; // 设备使用的IRQ号,对应CPU的EINT
};
2)实现原理分析
内核维护一个链表 __i2c_board_list,这个链表上链接的是I2C总线上挂接的所有硬件设备的信息结构体。也就是说这个链表维护的是一个struct i2c_board_info结构体链表。
真正的需要的struct i2c_client在别的地方由__i2c_board_list链表中的各个节点内容来另外构建生成。
函数调用层次:
i2c_add_adapter/i2c_add_numbered_adapter
i2c_register_adapter
i2c_scan_static_board_info
i2c_new_device
device_register
总结:I2C总线的i2c_client的提供是内核通过i2c_add_adapter/i2c_add_numbered_adapter接口调用时自动生成的,生成的原料是mach-x210.c中的i2c_register_board_info(1, i2c_devs1, ARRAY_SIZE(i2c_devs1));然后调用device_register进行设备的注册。
15.将触摸屏的代码加到内核
第一步:将厂商给的源码放到\\192.168.1.3\share\kernel\kernel\drivers\input\touchscreen中去
第二步:定义一个宏名:CONFIG_TOUCHSCREEN_GSLX680,将其添加到makefile文件中
第三步:修改client源码,添加宏
第四步:将其添加到内核中,在make menuconfig中显示,修改Kconfig
第五步:make menuconfig,勾选Y,然后保存,之后进行编译
第六步:编译之后,烧录,触摸屏幕进行查看结果