day11

回顾:
1.linux内核platform机制
  platform机制实现linux内核分离思想
  linux内核分离思想就是将硬件和软件分离,软件一旦写好无需改动,只需维护硬件相关即可
  让驱动代码的可移植性变得非常好
  具体参见platform.bmp
 
*******************************************************   
2.linux内核I2C驱动编程
  2.1.回顾I2C总线的特性
      面试题:谈谈对I2C总线的理解
      UART:波特率115200,8o1
            数据0x59
            画出操作时序图
      I2C:CPU向I2C总线发送START,发送设备地址0x21,对设备进行写
            画出这部分的具体操作时序图
   
  2.2.linux内核I2C驱动包含两部分内容
          I2C总线驱动
                  操作的硬件对象仅仅是I2C控制器
                  根据用户需求最终操作SCL和SDA两根信号线
                  此驱动由各个芯片厂家完成实现,驱动开发者
                  只需配置linux内核选择为*即可!
                  以S5P6818处理器为例,对应的I2C总线驱动:
                  cd /opt/kernel
                  make menuconfig
                      Device Drivers->
                              I2C supports->
                                  I2C Hardware Bus support  --->
                                            <*> Slsiap I2C  //S5P6818 I2C控制器的驱动
                                                                          //也就是I2C总线驱动的支持
                  问:对应的I2C总线驱动的源码是什么呢?
                  答:Kconfig->配置项->Makefile->源码
                  
          I2C设备驱动:
                  操作的硬件对象就是I2C外设本身
                  I2C设备驱动就是发起I2C外设本身所需的时序
                  当然这种时序最终由I2C控制器来完成!
                  驱动开发者重点关注I2C设备驱动的开发!
   
   2.3.linux内核I2C驱动框架
          I2C总线驱动和I2C设备驱动如何配合
          以CPU获取MMA8653三轴加速度传感器的ID为例
          (CPU通过I2C总线读取MMA8653片内寄存器0x0D的ID(0x5A))
          应用层:驱动工程师
                      struct mma8653 {
                          unsigned char addr; //片内寄存器地址
                          unsigned char data; //片内寄存器数据
                      };            
                      struct mma8653 mma; //分配用户缓冲区
                      mma.addr = 0x0D; //指定要访问的片内寄存器地址
                      mma.data = ?                     
                      ioctl(fd, MMA8653_READ, &mma);//发起读取MMA8653动作
                      printf("ID=%#x\n", mma.data); //mma.data=0x5A
          -------------------------------------------------
          I2C设备驱动层:驱动工程师
                      long mma8653_ioctl(file, cmd, arg) {
                          //1.分配内核缓冲区
                          struct mma8653 kmma;
                          
                          //2.拷贝用户数据到内核缓冲区
                          copy_from_user(&kmma,  
                                      (struct mma8653*)arg, sizeof(kmma));
                          //此时:kmma.addr=0x0D;kmma.data=?
                          
                          //3.I2C设备驱动发起硬件操作时序要求
                              此要求最终由I2C总线驱动来完成
                              I2C总线驱动操作I2C控制器发起
                              I2C设备驱动要求的时序
                              I2C设备驱动只需调用内核提供的SMBUS
                              接口函数即可完成相关的请求:
                              kmma.data = i2c_smbus_*(...,0x0D);  
                                //kmma.data = 0x5A
                         
                        //4.拷贝内核缓冲区的数据到用户缓冲区
                        copy_to_user((strut mma8653*)arg,
                                                    &kmma, sizeof(kmma));
                          return 0;
                      }
       ----------------------------------------------
       SMBUS接口层:内核提供
                                   作为I2C设备驱动和I2C总线驱动的桥梁
                                   此接口都是给I2C设备驱动层使用
                                   0x0D //由I2C设备驱动提供
       ----------------------------------------------
       I2C总线驱动层:芯片厂家
                                 根据I2C设备驱动的要求最终发起操作
                                 I2C控制器,发起最终的I2C硬件时序
                                 0x0D //由SMBUS接口提供
       ST->0x1D<<1|0->[ACK]->0x0D->[ACK]->RST->0x1D<<1|1->[ACK]->[0x5A]->NACK->SP
       最后I2C总线驱动将获取的0x5A数据返回给SMBUS接口
       SMBUS接口将0x5A再返回给I2C设备驱动
       I2C设备驱动最后将0x5A返回到用户               
             ----------------------------------------------
             硬件层:硬件工程师
             I2C控制器<------>MMA8653硬件外设
              
             画出一个调用操作流程图:smbus.bmp
        
        2.4.问:如何编写一个I2C设备驱动程序呢?
            答:同样利用内核的分离思想,但是不是platform机制
                同样也是设备-总线-驱动编程模型
                platform机制本身也是采用设备-总线-驱动编程模型
            实现原理:
            1.首先内核已经帮你定义好了一个虚拟总线叫i2c_bus_type
              在这个总线上维护着两个链表:dev链表和drv链表
            2.dev链表上每一个节点描述的I2C外设的纯硬件信息
              对应的数据结构为struct i2c_client,每当向dev
              链表添加一个I2C外设的硬件信息节点时,只需用此
              数据结构定义初始化一个对象即可,然后向dev链表
              添加,一旦添加完毕,内核会帮你遍历drv链表,从
              drv链表上取出每一个软件节点跟这个要注册的硬件
              节点进行匹配,内核通过调用总线提供的match函数进行比较
              比较i2c_client的name和i2c_driver的id_table的name
              如果匹配成功,硬件找到了对应的软件,内核会调用i2c_driver
              的probe函数,并且把匹配成功的硬件节点的首地址传递给probe函数
              最终完成硬件和软件的再次结合
            3.drv链表上每一个节点描述的I2C外设的纯软件信息
              对应的数据结构为struct i2c_driver,每当向drv
              链表添加一个I2C外设的软件信息节点时,只需用此
              数据结构定义初始化一个对象即可,然后向drv链表
              添加,一旦添加完毕,内核会帮你遍历dev链表,从
              dev链表上取出每一个硬件节点跟这个要注册的软件
              节点进行匹配,内核通过调用总线提供的match函数进行比较
              比较i2c_client的name和i2c_driver的id_table的name
              如果匹配成功,软件找到了对应的硬件,内核会调用i2c_driver
              的probe函数,并且把匹配成功的硬件节点的首地址传递给probe函数
              最终完成硬件和软件的再次结合  
               
           4.驱动开发者要实现一个I2C设备驱动,只需关注以下两个数据结构:
             struct i2c_client
             struct i2c_driver
              
         2.5.struct i2c_client
                  struct i2c_client {
                              unsigned short addr;
                              char name[I2C_NAME_SIZE];
                              struct device dev;
                              int irq;
                              ...
                  };
                  功能:用于描述I2C外设的纯硬件信息
                  成员:
                  addr:I2C外设的设备地址,用于找外设,必须初始化!
                  name:用于匹配,必须初始化!
                  dev:重点关注其中的void *platform_data字段
                      此字段将来用于装载自定义的用于描述I2C
                      外设的硬件信息
                  irq:如果I2C外设和CPU之间有中断,此字段保存
                      对应的中断号
                    
             切记:linux内核对i2c_client的操作和platform_device
             所有区别,驱动开发者不用去自己定义初始化和注册一个
             struct i2c_client硬件节点对象,定义初始化和注册
             过程统一由内核来帮你完成  
             问:内核定义初始化注册i2c_client硬件节点对象
                 尤其初始化过程,内核怎么知道要初始化具体
                 哪些硬件信息(addr/name/platform_data)
             答:驱动开发者只需利用以下数据结构将要初始化的
                 信息注册到内核中即可,也就是告诉内核将来要初始化的
                 具体信息,将来内核根据你提供的信息来去初始化
                 struct i2c_client硬件节点对象:
                  
                 struct i2c_board_info {
                         char        type[I2C_NAME_SIZE];
                                unsigned short    addr;
                                void        *platform_data;
                                int        irq;
                         ...
                 };
                 功能:驱动开发者利用此数据结构将I2C外设的
                       硬件信息告诉给linux内核,将来内核根据
                       提供的I2C外设的硬件信息帮你定义初始化
                       和注册一个i2c_client硬件节点对象到dev链表
                 成员:
                 type:指定硬件节点的名称,此字段将来会自动的
                              赋值给i2c_client的name,将来用于匹配
                              所以必须初始化!
                 addr:指定I2C外设的设备地址,此字段将来会自动
                       赋值给i2c_client的addr,将来用于找外设
                       所以必须初始化!
                 platform_data:用于装载自定义的硬件信息,将来
                                            会自动赋值给i2c_client.dev.platform_data               
                 irq:如果CPU和外设需要中断,此字段用来指定中断号
                     将来会赋值给i2c_client.irq
                  
                 配套函数:
                 i2c_register_board_info(int busnum,
                                        struct i2c_board_info const *info,  
                                                                     unsigned len)  
                         函数功能:注册I2C外设硬件信息到内核,将来
                         linux内核会帮你定义初始化和注册i2c_client
                         硬件节点到dev链表,内核初始化i2c_client
                         所需的内容都是根据i2c_board_info来进行提供
                          
                         参数:
                         busnum:I2C外设所在的CPU的I2C总线编号
                                         必须根据原理图获取
                                         例如:X6818开发板上的mma8653所对应的
                                         I2C总线编号为2(第三路I2C总线)
                         info:传递要注册的I2C硬件信息
                         len:用i2c_board_info描述的硬件信息的个数
                                 
                         切记切记切记:struct i2c_board_info的定义
                         初始化和注册不能采用insmod/rmmod进行,必须将
                         代码和uImage写到一起,一般要写到开发板对应的
                         平台文件中(内核源码/arch/arm/plat-s5p6818/x6818/device.c)!    
                      
案例:编写MMA8653三轴加速度传感器的I2C设备驱动
      首先搞定MMA8653的硬件信息的注册过程
实施步骤:
上位机执行:
1.首先去除官方的MMA8653的三轴加速度传感器驱动
  cd /opt/kernel
  make menuconfig
       Deivce Drivers->
               Hardware Monitoring support->
                   //按N键去除
                       <*>Freescale MMA865X 3-Axis Accelerometer  
  保存退出
  make uImage
  cp arch/arm/boot/uImage /tftpboot
   
  重启下位机,进入uboot执行:
  tftp 48000000 uImage
  bootm 48000000 //此时的内核不再有MMA8653的官方驱动
 
2.向内核添加MMA8653的硬件信息
  cd /opt/kernel
  vim arch/arm/plat-s5p6818/x6818/device.c 在文件的最开头添加如下代码:
     //定义初始化MMA8653外设的硬件信息对象
     static struct i2c_board_info mma8653[] = {
       {    
           .type = "mma8653",//用于匹配
                           //会赋值给i2c_client.name
           .addr = 0x1D //用于找外设
                       //会赋值给i2c_client.addr
       }
     };
      
     然后函数nxp_board_devs_register,在函数最开头添加:
     i2c_register_board_info(2, mma8653, ARRAY_SIZE(mma8653));
     //心里念叨:只要将来这条语句执行,内核就会帮我
     //定义初始化和注册一个i2c_client硬件节点对象到dev
     //链表,开始各种遍历,匹配,调用,传递参数
     //i2c_client对象中的所有成员都是我这注册的i2c_board_info
       来提供的(i2c_client.name=type,i2c_client.addr,
       i2c_client.dev.platform_data=platform_data,
       i2c_client.irq = irq)
   保存退出
   make uImage
   cp arch/arm/boot/uImage /tftpboot/
    
   重启下位机,uboot执行:
   tftp 48000000 uImage
   bootm 48000000 //此时uImage就会有MMA8653的硬件节点对象i2c_client
    
2.6.struct i2c_driver
        struct i2c_driver {
                    int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
                    int (*remove)(struct i2c_client *client);
                    const struct i2c_device_id *id_table;
                    ...
        };
        说明:描述I2C外设的软件信息
        成员:
        probe:硬件节点和软件节点匹配成功,内核调用
                    形参client指针指向匹配成功的I2C外设的硬件节点
                    //心里念叨:此client指针里面的内容都是i2c_board_info提供
                    client->addr //获取I2C外设的设备地址
                    client->irq  //获取I2C外设的中断号
                    client->dev.platform_data //获取自定义的硬件信息
        remove:卸载软件节点,内核调用此函数
                形参client指针指向匹配成功的I2C外设的硬件节点
                    //心里念叨:此client指针里面的内容都是i2c_board_info提供
                    client->addr //获取I2C外设的设备地址
                    client->irq  //获取I2C外设的中断号
                    client->dev.platform_data //获取自定义的硬件信息
        id_table:重点关注其中的name字段,此字段将来用于匹配
                         struct i2c_device_id {
                                     char name[I2C_NAME_SIZE]; //用于匹配
                                    unsigned long driver_data //用于给probe函数传递参数
                         };
        
        配套函数:
        i2c_add_driver(&软件节点对象)
        向内核drv链表注册添加I2C外设软件节点对象
        将来内核会帮你遍历,匹配,调用probe函数,传递参数
        
        i2c_del_driver(&软件节点对象)    
        从内核drv链表删除软件节点对象
        内核会帮你调用remove函数
                      
案例:编写MMA8653三轴加速度传感器的I2C设备驱动
      然后搞定MMA8653的软件信息的注册过程
      参考代码:ftp://DRV/day11/mma8653.rar/2.0
      认真阅读分析此驱动程序
      mkdir /opt/drivers/day11/
      cp 2.0 /opt/drivers/day11/
      cd /opt/drivers/day11/2.0
      make
      cp mma8653_drv.ko /opt/rootfs/home/drivers
       
      下位机执行:
      cd /home/drivers
      insmod mma8653_drv.ko //查看probe函数是否被调用
      rmmod mma8653_drv //查看remove函数是否被调用
           
案例:最终完成MMA8653三轴加速度传感器的I2C设备驱动                                           
     参考代码:ftp://DRV/day11/mma8653.rar/3.0
      认真阅读分析此驱动程序
      mkdir /opt/drivers/day11/
      cp 3.0 /opt/drivers/day11/
      cd /opt/drivers/day11/3.0
      make
      cp mma8653_drv.ko /opt/rootfs/home/drivers
      arm...gcc -o mma8653_test mma8653_test.c
       
      下位机执行:
      cd /home/drivers
      insmod mma8653_drv.ko  
      ./mma8653_test                         
 
注意:
    //SMBUS接口函数的使用步骤:
    // 1.打开SMBUS接口函数的说明使用文档,在内核源码的Documentation\i2c\smbus-protocol
    // 打开此文件
    // 2.再打开MMA8653的芯片手册,找到对应的读时序图
    // 3.根据读时序图在文档smbus-protocol中找到对应的实现函数
    // 4.找到对应的函数以后,在sourceinsight中找到这个函数的定义
    //获取到函数的参数和返回值
    //注意:smbus接口函数中的client指针一定要传递匹配成功的硬件节点对象指针   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值