设备驱动开发实验教程(13)_Linux的I2C驱动框架分析

        I2C总线仅仅使用SCL、SDA这两根信号线就实现了设备之间的数据交互,极大地简化了硬件资源和PCB板布线空间的占用。因此,I2C总线被非常广泛地应用在EEPROM、实时钟、小型LCD等设备与CPU的接口中。

        Linux系统定义了I2C驱动体系结构,在Linux系统中,I2C驱动由3部分组成,即I2C核心、I2C总线驱动和I2C设备驱动。这3部分相互协作,形成了非常通用、可适应性很强的I2C框架。

一、基本概念

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

                      

 

总线

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

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

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

 

总线设备

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

 

总线驱动

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

 

 

二、I2C传输协议

        I2C传输协议可以说是再正常不过了,协议这东西,就需要遵守一些规则,I2C也是一样,这块儿以后单独介绍。

 

 

三、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;

}

四、总结

 

        相信大家学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部分,只有你有个代码,跟进去看看就知道了。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值