1.基本概念
总线设备驱动模型,是Linux 内核的一个基础,基本理论可以说按照大企业的分工原则,每个人只要负责自己的事情,向其他部门给出标准的接口调用,后勤部就负责后勤工作,厨房有可能跟后勤部产生工作上的沟通,不能一个厨师炒菜就去找后勤部的某个人员拿一根大白菜,而是由厨房统一申请,由后勤部门去采购再给回厨房,写代码很多时候跟生活中相识,需要遵守一定的规则,如果喜欢打擦边球,绕过规则的程序员,也很像那些走机动车道的开电动车的人们,有时候都能达到目的,但是存在车祸的风险。
总线
Linux 内核里面的总线很多,总线的工作主要是管理设备和驱动的,可以是驱动和设备耦合的媒婆,没有总线,设备找不到驱动,驱动也找不到设备,所以如果一个设备,首先要确定它是属于什么总线的,就是一个单身狗,他是想找哪个地方的妹子,需要向总线注册,告诉总线我的name是什么,对于驱动也是一样,这个驱动也要告诉总线,我是什么类型的驱动,实现了哪些接口,我的name是什么?
设备在注册的时候,向总线的设备链表添加一个设备,然后通过name,后面还有一个table_id,来查找这个总线上有没有已经实现了这个设备的驱动,如果有了,就执行这个驱动的probe函数。
驱动在注册的时候也是一样,通过name 和 table_id 匹配来查找设备,找到了就执行驱动对应的probe函数来做一系列事情。一个驱动是可以对应多个设备的,但是一个设备只能对应一个驱动。
总线设备
设备对应描述的是一个硬件,原来老的Linux 内核使用板级文件来描述设备,新的Linux 内核使用dts来描述设备,实际上是一个东西,都是用来描述设备,dts更能显示面向对象思想,也更能题先程序员的能力,比如一个I2C设备,需要描述I2C地址,I2C gpio端口,设备挂载在哪路I2C总线上等等。
总线驱动
一个驱动程序,总是要有依赖的,既然是对应的总线设备,就需要对应的总线驱动来驱动它,让硬件设备能够正常工作起来, 驱动也就是操作设备的方式和操作设备的流程还有接口。
2.I2C传输协议
3.Linux下I2C驱动程序的体系结构
Linux下的i2c框架,分为了3个子模块
1、I2C核心
I2C核心主要是i2c-core.c,里面涉及的adapter都是和i2c核心进行耦合的,设备和驱动不需要关心adapter部分。
2、I2C总线驱动
I2C总线驱动是对I2C硬件体系结构中适配器(i2c-adapter)端的实现,这部分主要是产生I2C协议的波形,操作硬件完成开始信号,数据传输,停止信号,设备应答检测等等,还是看上面那个经典的图片,adapter就是往下走的,所以就是操作到平台cpu的部分了。
3、I2C设备驱动
I2C设备驱动(i2c-client)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
重要的文件说明
\kernel\drivers\i2c\i2c-core.c
这个文件实现了 I2C 核心的功能以及/proc/bus/i2c*接口。同时对I2C底层的收发函数进行封装。会调用i2c_transfer ,里面实现了adap->algo->master_xfer(adap, msgs, num)
kernel\drivers\i2c\i2c-dev.c
该函数注册了一个设备文件的功能,也就是注册了一个字符设备驱动程序,可以通过/dev/i2c-0(i2c-0, i2c-1,…, i2c-10,…)找到具体的I2C适配器,这个I2C设备的主设备号为89,次设备号0~255。通过访问这个接口,可以通过open()、 write()、 read()、 ioctl()和 close()等来访问这个设备。
kernel\drivers\i2c\busses\i2c-rk30.c
跟平台相关的i2c-adapter,通过这些接口,达到控制cpu的I2C控制器寄存器,然后可以在i2c总线上产生i2c信号,驱动i2c设备。
比较重要的结构体和调用过程
i2c_driver
对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。
i2c_client
对应于真实的物理设备,每个 I2C 设备都需要一个 i2c_client 来描述。i2c_client 一般被包含在 I2C 字符设备的私有信息结构体中。
i2c_adpater
用来匹配i2c_driver与i2c_client。即 i2c_client 依附于 i2c_adpater。由于一个适配器上可以连接多个 I2C 设备, 所以一个 i2c_adpater 也可以被多个 i2c_client 依附, i2c_adpater 中包括依附于它的 i2c_client 的链表 。
i2c_algorithm
i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。
i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体
我们在驱动里面调用
这个i2c_transfer继续往下看
会跑到i2c_core.c里面的__i2c_transfer,里面会调用一个指针adap->algo->master_xfer
这个是一个指针,那我们就要找到这个指针的初始化位置
这个位置在 i2c_rk29.c里面
所以这个才是最终的产生I2C波形的位置。
贴出最终的代码
static int rk29_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int start, int stop)
{ struct rk29_i2c_data *i2c = (struct rk29_i2c_data *)adap->algo_data; int ret = 0; if(msg->len == 0) { ret = -EINVAL; i2c_err(i2c->dev, "<error>msg->len = %d\n", msg->len); goto exit; } if(msg->flags & I2C_M_NEED_DELAY) i2c->udelay = msg->udelay; else i2c->udelay = 0; if((ret = rk29_send_address(i2c, msg, start))!= 0) { rk29_set_nak(i2c); i2c_err(i2c->dev, "<error>rk29_send_address timeout\n"); goto exit; } if(msg->flags & I2C_M_RD) { if(msg->flags & I2C_M_REG8_DIRECT) { struct i2c_msg msg1 = *msg; struct i2c_msg msg2 = *msg; msg1.len = 1; msg2.len = msg->len - 1; msg2.buf = msg->buf + 1; if((ret = rk29_i2c_send_msg(i2c, &msg1)) != 0) i2c_err(i2c->dev, "<error>rk29_i2c_send_msg timeout\n"); if((ret = rk29_i2c_recv_msg(i2c, &msg2)) != 0) { i2c_err(i2c->dev, "<error>rk29_i2c_recv_msg timeout\n"); goto exit; } } else if((ret = rk29_i2c_recv_msg(i2c, msg)) != 0) { i2c_err(i2c->dev, "<error>rk29_i2c_recv_msg timeout\n"); goto exit; } } else { if((ret = rk29_i2c_send_msg(i2c, msg)) != 0) { rk29_set_nak(i2c); i2c_err(i2c->dev, "<error>rk29_i2c_send_msg timeout\n"); goto exit; } } exit: if(stop || ret < 0) { rk29_i2c_stop(i2c); } return ret; }
4.总结
相信大家学Linux 之前都做过单片机吧,不管什么总线协议,最终都是要发信号出去的,不发信号出去的总线协议框架是没有任何意义的,所以我们在这里吹牛这么多,最终还是要回到代码里面去fucking the code的。不要我说了一大堆,你还是没有知道怎么看代码,那也是没有任何作用的。
也就是device与driver同时向i2c总线上注册。当注册在总线上时,可以通过id_table进行匹配,匹配上之后会调用driver的probe函数。对于一般的I2C设备,可以在probe函数中注册一个字符设备驱动,从而应用层可以通过open函数打开/dev/i2c-0等设备节点对I2C设备进行读写操作。
I2C只是驱动的一部分,我们需要把I2C糅合到Input子系统里面,糅合到摄像头V4L2里面等等,这些都是需要去看代码了解里面的脉络的,我很多时候总是担心自己太水讲了大家也不懂,然后就把图贴上来,i2c到最后还是通过writel readl等函数读取IO部分,只有你有个代码,跟进去看看就知道了。
好了,就说这么多,使劲评论。
扫码或长按关注
回复「 加群 」进入技术群聊