ac3165 linux驱动_Linux的 i2c 驱动框架分析

本文深入介绍了Linux内核中的总线设备驱动模型,特别是针对I2C总线的工作原理。讲解了I2C驱动程序的体系结构,包括I2C核心、总线驱动和设备驱动的职责。还详细阐述了I2C传输协议的规则,并以rk29_xfer_msg函数为例展示了如何在代码层面实现I2C波形的生成。最后强调了理解并阅读代码的重要性。

1.基本概念

总线设备驱动模型,是Linux 内核的一个基础,基本理论可以说按照大企业的分工原则,每个人只要负责自己的事情,向其他部门给出标准的接口调用,后勤部就负责后勤工作,厨房有可能跟后勤部产生工作上的沟通,不能一个厨师炒菜就去找后勤部的某个人员拿一根大白菜,而是由厨房统一申请,由后勤部门去采购再给回厨房,写代码很多时候跟生活中相识,需要遵守一定的规则,如果喜欢打擦边球,绕过规则的程序员,也很像那些走机动车道的开电动车的人们,有时候都能达到目的,但是存在车祸的风险。

设备驱动模型,推荐看这篇文章 《Linux设备驱动模型》

84d9da7b178913d5a0c23632b0bba79d.png

39ca4fa29afc5a51931360416c6c7fd0.png

总线

Linux 内核里面的总线很多,总线的工作主要是管理设备和驱动的,可以是驱动和设备耦合的媒婆,没有总线,设备找不到驱动,驱动也找不到设备,所以如果一个设备,首先要确定它是属于什么总线的,就是一个单身狗,他是想找哪个地方的妹子,需要向总线注册,告诉总线我的name是什么,对于驱动也是一样,这个驱动也要告诉总线,我是什么类型的驱动,实现了哪些接口,我的name是什么?

设备在注册的时候,向总线的设备链表添加一个设备,然后通过name,后面还有一个table_id,来查找这个总线上有没有已经实现了这个设备的驱动,如果有了,就执行这个驱动的probe函数。

驱动在注册的时候也是一样,通过name 和 table_id 匹配来查找设备,找到了就执行驱动对应的probe函数来做一系列事情。一个驱动是可以对应多个设备的,但是一个设备只能对应一个驱动。

总线设备

设备对应描述的是一个硬件,原来老的Linux 内核使用板级文件来描述设备,新的Linux 内核使用dts来描述设备,实际上是一个东西,都是用来描述设备,dts更能显示面向对象思想,也更能题先程序员的能力,比如一个I2C设备,需要描述I2C地址,I2C gpio端口,设备挂载在哪路I2C总线上等等。

总线驱动

一个驱动程序,总是要有依赖的,既然是对应的总线设备,就需要对应的总线驱动来驱动它,让硬件设备能够正常工作起来, 驱动也就是操作设备的方式和操作设备的流程还有接口。

2.I2C传输协议

I2C传输协议可以说是再正常不过了,协议这东西,就需要遵守一些规则,I2C也是一样,不说多,我之前写的I2C协议文章大家可以看看《I2C总线协议》

3.Linux下I2C驱动程序的体系结构


Linux下的i2c框架,分为了3个子模块

e3967b66188ecab041d8a44b1edfb2e8.png

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设备。

0322aec7c9cc013a24379857a024ed4b.png

比较重要的结构体和调用过程

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也非常关键,调用驱动中的发送接收函数需要填充该结构体

我们在驱动里面调用

1b56ecdf8f4201928bb64f24085ecfdf.png

这个i2c_transfer继续往下看

06049f959b8d8809721a64f7b8f45387.png

会跑到i2c_core.c里面的__i2c_transfer,里面会调用一个指针adap->algo->master_xfer

这个是一个指针,那我们就要找到这个指针的初始化位置

这个位置在 i2c_rk29.c里面

5a868a1f84509bd6b3821f72d422a6e8.png

7db2e4613db259579145a4733473becb.png

所以这个才是最终的产生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, "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, "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, "rk29_i2c_send_msg timeout\n");      if((ret = rk29_i2c_recv_msg(i2c, &msg2)) != 0)      {        i2c_err(i2c->dev, "rk29_i2c_recv_msg timeout\n");        goto exit;      }          }    else if((ret = rk29_i2c_recv_msg(i2c, msg)) != 0)    {      i2c_err(i2c->dev, "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, "rk29_i2c_send_msg timeout\n");      goto exit;    }  }  exit:    if(stop || ret < 0)  {    rk29_i2c_stop(i2c);        }  return ret;}

4.总结

相信大家学Linux 之前都做过单片机吧,不管什么总线协议,最终都是要发信号出去的,不发信号出去的总线协议框架是没有任何意义的,所以我们在这里吹牛这么多,最终还是要回到代码里面去fucking the code的。不要我说了一大堆,你还是没有知道怎么看代码,那也是没有任何作用的。

80ec48dc18b7095eac03da06177611c6.png

也就是device与driver同时向i2c总线上注册。当注册在总线上时,可以通过id_table进行匹配,匹配上之后会调用driver的probe函数。对于一般的I2C设备,可以在probe函数中注册一个字符设备驱动,从而应用层可以通过open函数打开/dev/i2c-0等设备节点对I2C设备进行读写操作。

I2C只是驱动的一部分,我们需要把I2C糅合到Input子系统里面,糅合到摄像头V4L2里面等等,这些都是需要去看代码了解里面的脉络的,我很多时候总是担心自己太水讲了大家也不懂,然后就把图贴上来,i2c到最后还是通过writel readl等函数读取IO部分,只有你有个代码,跟进去看看就知道了。

好了,就说这么多,使劲评论。

0ded0139f51ef6ad4e541ae521bc0e65.png

b46db28efbea4e16dd77fa772dedb3ac.png

扫码或长按关注

回复「 加群 」进入技术群聊

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值