linux-2.6.22源码分析\linux-2.6.22\drivers\i2c\chips\eeprom.c为例,分析i2c设备驱动程序的原理

1.从驱动的入口函数开始分析
  eeprom_init
    >i2c_add_driver(&eeprom_driver)
      >i2c_register_driver(THIS_MODULE, driver)
        >is_newstyle_driver(driver) //这是一处宏定义:#define is_newstyle_driver(d) ((d)->probe || (d)->remove)
        /*用于判断是否是新风格的驱动,新风格的驱动i2c_driver成员的probe与remove函数指针都为空*/
        接下来填充i2c_driver的owner与bus成员
        >driver_register(&driver->driver)
          >bus_add_driver(&driver->driver)也就是(struct device_driver * drv)//添加成功返回0
        >list_add_tail(&driver->list,&drivers)//把该i2c_driver添加到链表drivers全局变量中
        >list_for_each_entry(adapter, &adapters, list) //遍历adapters适配器链表的所有成员
         {driver->attach_adapter(adapter);}        //调用i2c_driver->attach_adapter
   
    在eeprom.c中定义了一个全局变量:
  static struct i2c_driver eeprom_driver = {
   .driver = {
    .name = "eeprom",
   },
   .id  = I2C_DRIVERID_EEPROM,
   .attach_adapter = eeprom_attach_adapter,
   .detach_client = eeprom_detach_client,
  };
  
2.i2c设备的探测过程分析
   在1中遍历调用了适配器链表中的每一个成员的attach_adapter成员函数,来完成i2c设备的探测
   driver->attach_adapter(adapter)
   等效于
   eeprom_attach_adapter
     >i2c_probe(adapter, &addr_data, eeprom_detect)
     //当探测到i2c设备后会调用第三个参数所指的函数:也就是函数eeprom_detect。
     /*在i2c.h头文件中定义了addr_data全局变量
   *static struct i2c_client_address_data addr_data = {   \
    * .normal_i2c = normal_i2c,     \
    * .probe  = probe,     \
    * .ignore  = ignore,     \
    * .forces  = forces,     \
    *}
      */
      函数原型为:
      i2c_probe(struct i2c_adapter *adapter,
       struct i2c_client_address_data *address_data,
       int (*found_proc) (struct i2c_adapter *, int, int))
        >判断是否强制识别 if(address_data->forces),如果设置了i2c_client_address_data的
        成员.forces则强制识别,采取相应的措施。
        >i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)//检查是否支持SMBUS_QUICK方式
        >for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2){
         判断if(address_data->probe[i] || address_data->probe[i] == ANY_I2C_BUS)成立则:
           >i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);
             >i2c_check_addr(adapter, addr)
               >__i2c_check_addr(adapter, addr)//检查地址是否可用
             >i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL)
             >如果定义了adapter->algo->smbus_xfer则调用:
             adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data)
            
                >found_proc(adapter, addr, kind)//也就是调用i2c_probe函数的第三个参数
                 (该参数为一个函数)
               
             >否则调用:
             i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);
        }
  由以上的程序分析可知:
  在编写i2c设备驱动时需要手动 设置i2c_client_address_data结构体的成员变量。使驱动能探测到设备
3.i2c_probe的第三个参数(函数)分析
  在事例代码中是eeprom_detect,下面分析这个函数都是做了什么工作。
  如果通过第2项的程序调用后,说明i2c设备(i2c_client)已经找到能配合工作的适配器(adapter),接下来
  就会调用用户自定义的一个函数,该函数为 eeprom_driver结构体成员函数指针:attach_adapter中调用
  的i2c_probe(adapter, &addr_data, xxxx)的第三个参数xxxx
  xxxx <==>eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
  >struct i2c_client *new_client //在这里填充一个和欲驱动的设备相应的i2c_client结构体
   或者在该函数中分配一个与设备相关的结构体
   data = kzalloc(sizeof(struct eeprom_data), GFP_KERNEL)
  >i2c_set_clientdata(new_client, data);//将指针new_client->dev->driver_data(=data)指向data
 
  >填充i2c_client结构体成员:
  new_client->addr = address;
  new_client->adapter = adapter;
  new_client->driver = &eeprom_driver;
  new_client->flags = 0; 
  /* Fill in the remaining client fields */
  strlcpy(new_client->name, "eeprom", I2C_NAME_SIZE);
  
 >i2c_attach_client(new_client) //告诉i2c协议层已经生成了一个i2c_client
   >struct i2c_adapter *adapter = client->adapter;//取出client中的适配器adapter
   >list_add_tail(&client->list,&adapter->clients);//把client加入到适配器adapter的链表尾
   >现对client的成员进行进一步地相应设置
   >device_register(&client->dev)
      >device_initialize(dev);
      >device_add(dev)
  >sysfs_create_bin_file(&new_client->dev.kobj, &eeprom_attr)//创建sysfs

至此i2c设备驱动程序分析完毕,那么,如何写一个i2c设备驱动呢?比如at24c08

a.在入口函数中调用i2c_add_driver(struct i2c_driver *driver)
  所以需要定义一个全局变量 i2c_driver ,并对一些必要的成员进行初始化
b.编写 i2c_driver成员函数: attach_adapter 与 detach_client
 int (*attach_adapter)(struct i2c_adapter *);
    >i2c_probe
     所以需要提供 i2c_client_address_data 结构体,并提供一个探测函数:gq_at24cxx_detect
 int (*detach_adapter)(struct i2c_adapter *);
c.定义struct i2c_client *new_client,并填充
  i2c_attach_client(at24cxx_client);
d.至此,其实该设备的i2c驱动框架已经完成了,下来要做的就是提供一个字符设备或块设备驱动的接口
e.读写i2c设备时的接口函数设计:
  读过程:
  copy_from_user(&dev_addr,buf,1);//拷贝来数据地址
  需要构造两个msg消息结构,第一个用来写欲读取的数据地址,第二个才是用来读取数据值
  /* 数据传输:源地址,目的地址,数据长度 */
 /* 先把要读的数据地址写入 */
 msg[0].addr  = at24cxx_client->addr;//distination
 msg[0].len   = 1;                    //length 地址长度=1byte
 msg[0].buf   = &dev_addr;            //source
 msg[0].flags = 0;                    //write flag
 /* 再把数据读出 */
 msg[1].addr  = at24cxx_client->addr;//source
 msg[1].len   = 1;                    //length 数据长度=1byte
 msg[1].buf   = &read_data;           //distination
 msg[1].flags = I2C_M_RD;             //read flag
 构造完成后,调用下面的这个函数,它会调用i2c-core等提供的适配器算法的相关函数来 发送/接收 数据
 i2c_transfer(at24cxx_client->adapter, msg, 2);
 copy_to_user(buf,&read_data,1);
 把读到的数据拷贝给应用程序
 
 写过程:
 copy_from_user(dev_addr,buf,2);//把写的地址和数据拷贝过来
 构造一个msg消息,用于发送地址和数据
 /* 数据传输:源地址,目的地址,数据长度 */
 msg[0].addr  = at24cxx_client->addr;//distination
 msg[0].len   = 2;                    //length 地址+数据 长度=2byte
 msg[0].buf   = dev_addr;            //source
 msg[0].flags = 0;                    //write flag
 ret = i2c_transfer(at24cxx_client->adapter, msg, 1);//同读过程
f.卸载
 在gq_at24cxx_detach_client 中完成卸载所需要的操作,释放内存,反注册等。

源码如下:

/*
 * 在字符驱动中的读写操作:
 * 全部页的读写操作
 * 读: copy_from_user(write_buf,buf,2)  write_buf数组的0元素为该页中的地址,1元素为页数
 * 写: copy_from_user(dev_addr,buf,3)   dev_addr数组的0元素为页数,1元素为该页中的地址,2元素为写入的数据
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/types.h>

static int gq_at24cxx_detect(struct i2c_adapter *adapter, int addrss, int kind);

static unsigned short normal_gq[] = {I2C_CLIENT_END};
static unsigned short ignore_gq[] = {ANY_I2C_BUS,0x50,I2C_CLIENT_END};
static struct i2c_client_address_data gq_address_data = {
 .normal_i2c = normal_gq,
 .probe      = ignore_gq,
 .ignore     = ignore_gq,
 //.forces     = ,
};
static struct i2c_client *at24cxx_client;

static struct class *cls;
static struct class_device *cls_dev;
static int major;

static ssize_t at24cxx_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
#if 0
 /*现在驱动程序只能读一页的数据 256 byte*/
 struct i2c_msg msg[2];
 unsigned char dev_addr;
 unsigned char read_data;
 int ret;
 copy_from_user(&dev_addr,buf,1);

/* 数据传输:源地址,目的地址,数据长度 */
 /* 先把要读的数据地址写入 */
 msg[0].addr  = at24cxx_client->addr;//distination
 msg[0].len   = 1;                    //length 地址长度=1byte
 msg[0].buf   = &dev_addr;            //source
 msg[0].flags = 0;                    //write flag
 /* 再把数据读出 */
 msg[1].addr  = at24cxx_client->addr;//source
 msg[1].len   = 1;                    //length 数据长度=1byte
 msg[1].buf   = &read_data;           //distination
 msg[1].flags = I2C_M_RD;             //read flag
 
 ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
 if(ret==2){
  copy_to_user(buf,&read_data,1);
  return 1;
 }
 else
  return -1;
#endif
#if 1
 /*使驱动程序能读e2prom全部的页*/
 struct i2c_msg msg[2];
 unsigned char write_buf[2];/*数组的0元素为该页中的地址,1元素为页数*/
 unsigned char read_buf;
 int ret;
 if(size!=2){
  printk("error param:write size is 2\n");
  return -1;
 } 
 copy_from_user(write_buf,buf,2);

/* 数据传输:源地址,目的地址,数据长度 */
 /* 先把要读的数据地址写入 */
 msg[0].addr  = at24cxx_client->addr + write_buf[1]%4;//distination
 msg[0].len   = 1;                    //length 地址长度=1byte
 msg[0].buf   = &write_buf[0];            //source
 msg[0].flags = 0;                    //write flag
 /* 再把数据读出 */
 msg[1].addr  = at24cxx_client->addr + write_buf[1]%4;//source
 msg[1].len   = 1;                    //length 数据长度=1byte
 msg[1].buf   = &read_buf;           //distination
 msg[1].flags = I2C_M_RD;             //read flag
 
 ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
 if(ret==2){
  copy_to_user(buf,&read_buf,1);
  return 1;
 }
 else
  return -1;
#endif
}
static ssize_t at24cxx_write(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
#if 0
 /*现在驱动程序只能写一页的数据 256 byte*/
 struct i2c_msg msg[1];
 unsigned char dev_addr[2];/*第一个元素是数据地址,第二个元素是数据值*/
 int ret;
 copy_from_user(dev_addr,buf,2);
 /* 数据传输:源地址,目的地址,数据长度 */
 msg[0].addr  = at24cxx_client->addr;//distination
 msg[0].len   = 2;                    //length 地址+数据 长度=2byte
 msg[0].buf   = dev_addr;            //source
 msg[0].flags = 0;                    //write flag

// printk("i2c dev write\n");
 ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
 if(ret==1){
  return 2;
 }
 else
  return -1;
#endif
#if 1
 /*使驱动程序能写e2prom全部的页*/
 struct i2c_msg msg[1];
 unsigned char dev_addr[3];/*第一二个元素是数据地址并认为数组的0为高位1为低位,第三个元素是数据值*/
 /*dev_addr数组的0元素为页数,1元素为该页中的地址,2元素为写入的数据*/
 int ret;
 if(size!=3){
  printk("error param:write size is 3\n");
  return -1;
 }
 copy_from_user(dev_addr,buf,3);

/* 数据传输:源地址,目的地址,数据长度 */
 msg[0].addr  = at24cxx_client->addr+dev_addr[0]%4;//distination
 msg[0].len   = 2;                //length 地址+数据 长度=2byte
 msg[0].buf   = &dev_addr[1];     /*source第一个元素是数据地址,第二个元素是数据值*/
 msg[0].flags = 0;                //write flag

ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
 if(ret==1){
  
  return 2;
 }
 else{
  printk("error:i2c_transfer failed!\n");
  return -1;
 }
#endif
}

static struct file_operations at24cxx_fops = {
 .read  = at24cxx_read,
 .write = at24cxx_write,
 .owner = THIS_MODULE,
};
static int gq_at24cxx_attach_adapter(struct i2c_adapter *adapter)
{
 printk("attach_adapter\n");
 return i2c_probe(adapter, &gq_address_data, gq_at24cxx_detect);
}
static int gq_at24cxx_detach_client(struct i2c_adapter *adapt)
{
 printk("detach_client\n");
 
 class_device_destroy(cls,MKDEV(major,0));
 class_destroy(cls);
 unregister_chrdev(major,"at24cxx");

/* detach the client */
 i2c_detach_client(at24cxx_client);
 kfree(at24cxx_client);
 return 0;
}

static struct i2c_driver gq_at24cxx_driver = {
 .driver ={
  .name = "at24cxx",
 },
 .id =  -1,
 .attach_adapter = gq_at24cxx_attach_adapter,
 .detach_client  = gq_at24cxx_detach_client,
};

static int gq_at24cxx_detect(struct i2c_adapter *adapter, int addrss, int kind)
{
 printk("detect the at24c08 device 0x%2x\n",addrss);
 at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
 if(at24cxx_client==NULL)
 {
  printk("alloc client failed\n");
  return -1;
 }

/* 设置i2c_client */
 at24cxx_client->adapter = adapter;
 at24cxx_client->addr    = addrss;
 at24cxx_client->driver  = &gq_at24cxx_driver;
 at24cxx_client->flags   = 0;
 strcpy(at24cxx_client->name, "at24cxx");

/* 通知协议层已经生成一个client,并attach */
 i2c_attach_client(at24cxx_client);

/* 下来就提供字符设备接口 */
 major = register_chrdev(0, "at24cxx", &at24cxx_fops);
 cls = class_create(THIS_MODULE, "gq_at24c08");
 cls_dev = class_device_create(cls, NULL, MKDEV(major,0), NULL, "at24cxx");
 return 0;
}

static int gq_at24cxx_init(void)
{
 i2c_add_driver(&gq_at24cxx_driver);
 return 0;
}

static void gq_at24cxx_exit(void)
{
 i2c_del_driver(&gq_at24cxx_driver);
}

module_init(gq_at24cxx_init);
module_exit(gq_at24cxx_exit);
MODULE_LICENSE("GPL");

测试:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,char **argv)
{
#if 0
 /*测试读写at24c08第0页*/
 int fd;
 unsigned char buf[2];
 unsigned char read_addr;
 int i;
 fd = open("/dev/at24cxx",O_RDWR);
 if(fd<0)
 {
  printf("open error\n");
  return -1;
 }
 
 for(i=0;i<256;i++){
  buf[0] = i;
  buf[1] = 255-i;
  if(2==write(fd,buf,2))
  { 
   printf("write %d : data:0x%2x \n",i,buf[1]);
  }
 }
 
 for(i=0;i<256;i++){
  read_addr = i;
  if(1==read(fd,&read_addr,1))
  { 
   printf("read %d : data:0x%2x \n",i,read_addr);
  }
 } 
#endif
#if 1
 /*测试读写at24c08的全部页*/
 int fd;
 unsigned char buf[3]={3,0xfe,0x01};
 unsigned short read_addr=1000;
 int i;
 fd = open("/dev/at24cxx",O_RDWR);
 if(fd<0)
 {
  printf("open error\n");
  return -1;
 }
 for(i=0;i<1024;i++){
  if(i%4==0)
   printf("\n");
  buf[0]=i/256;
  buf[1]=i%256;
  buf[2]=i%256;
  if(2==write(fd,buf,3))
  {
   printf("write %d : data 0x%2x__",i,buf[2]);
  }

}
 
 for(i=0;i<1024;i++){  
  read_addr = i;
  if(1==read(fd,&read_addr,2))
  { 
   if(i%4==0)
    printf("\n");
   printf("read %d : data:0x%2x__",i,read_addr&0xff);
  }
 }
 close(fd);
#endif
 return 0;
}

linux设备驱动之 i2c设备驱动 at24c08驱动程序分析相关推荐

  1. Linux设备驱动篇——[I2C设备驱动-1]

    Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...

  2. linux IIC子系统分析(九)——实例分析通过设备节点访问I2c设备

    在< linux IIC子系统分析(四)--I2c bus初始化> 中我们创建了I2C 总线驱动,I2C adapter device 和adapter drivers也在这时创建 在&l ...

  3. linux kernel 2.6 i2c设备驱动程序框架介绍,linux设备驱动程序-i2c(2)-adapter和设备树的解析...

    linux设备驱动程序-i2c(2)-adapter和设备树的解析 (注: 基于beagle bone green开发板,linux4.14内核版本) 而在linux设备驱动程序--串行通信驱动框架分 ...

  4. 基于linux4.4程序,linux驱动之i2c总线驱动调用分析【基于linux4.4】

    平台:RK3399 使用设备树描述板级资源: 框架: linux i2c框架同样采用分层.分离的模式设计:从上到下分为  app调用层.i2c core层.驱动层:驱动层又分为 cpu平台 i2c控制 ...

  5. 【linux iic子系统】i2c设备的添加方法(四)

    文章目录 前言 一.静态注册 二.动态注册 三.用户空间注册 四.i2c驱动扫描注册 前言 I2C设备的4种添加方法: 1)静态注册 2)动态注册 3)用户空间注册 4)i2c驱动扫描注册 一.静态注 ...

  6. tiny4412 设备树之i2c设备(二)

    开发板:tiny4412(1611) 内核:linux4.4 编译器: arm-none-linux-gnueabi-gcc (gcc version 4.8.3 20140320) mma7660连 ...

  7. 设备树解析 i2c设备模型

    目录 1.基础概念 1.总线 2.手机启动流程 1.MTK启动流程 2.高通启动流程的差别 3.设备树解析 1.设备树相关 2.设备树解析 4. i2c 设备初始化流程 1.基础概念 1.总线 总线是 ...

  8. v4l2驱动框架_【干货分享】Xilinx Linux V4L2视频管道(Video Pipeline)驱动程序分析...

    作者:付汉杰,hankf@xilinx.com, 文章转载自:赛灵思中文社区论坛 概述 Xilinx提供了完整的V4L2的驱动程序,Xilinx V4L2 driver.处于最顶层的驱动程序是V4L2 ...

  9. 驱动开发(二)——最简单的驱动程序分析

    文章目录 系统调用与驱动程序的流程 最简单的驱动程序 源码 Makefile 驱动测试 应用程序 测试 系统调用与驱动程序的流程 如下图 通过分析源码,我们可以得出,从我们的测试程序到调用我们的驱动程 ...

  10. asoc linux设备驱动_Linux驱动分析之I2C设备

    内核:4.20 芯片:HYM8563 RTC 下面的代码分析主要都在注释中,会按照驱动中函数的执行顺序分析. (1) 加载和卸载函数 static const struct i2c_device_id ...

最新文章

  1. C/C++ 全局变量和局部变量在内存里的区别?堆和栈
  2. 华为荣耀手机指令代码大全_双十二,华为/荣耀手机推荐选购指南,全系列横评推荐,那一款华为/荣耀手机最值得够买...
  3. OCS2007R2部署之二准备AD及扩展
  4. 怎么将oracle的sql文件转换成mysql的sql文件
  5. lambda python_Python | Lambda和filter()与示例
  6. tcp协议seq和ack
  7. $router和$route的区别
  8. 常见SQL Server 2000漏洞及其相关利用2
  9. Web前端是什么?大牛推荐的高效学习路线,减少2倍时间
  10. 如果一年只能两次旅游,你会选择什么季节去什么地方?
  11. JQuery杂项方法
  12. 使用pycharm编写python爬虫时出现乱码的解决方案UnicodeEncodeError: ‘gbk‘ codec can‘t encode character ‘\U0001f601‘ in
  13. Qt 5.9 mysql 驱动加载失败解决办法
  14. 数据结构之队列和栈的应用
  15. C++20 span
  16. php项目怎么分工,路德维希·冯·贝塔郎菲 Ludwig von Bertalanffy
  17. 下载腾讯视频为mp4格式
  18. Git使用 从入门到入土 收藏吃灰系列 (九) git reset 移除暂存区与版本回退
  19. [零基础学JAVA]Java SE实战开发-37.MIS信息管理系统实战开发[文件保存](1)
  20. 学习笔记 c++ (简单的消息队列)

热门文章

  1. 一篇文章让小白了解什么是软件测试
  2. Win10安装程序修复计算机,directx修复工具win10最新版
  3. banner设圆角_Banner设计技巧!
  4. 在plc中用c语言实现电梯控制程序,基于三菱FX2N PLC的两部电梯控制系统设计(附梯形图程序)...
  5. 计算机控制电梯报告总结,电梯控制实验报告电梯控制实验报告.doc
  6. 51单片机通过WIFI模块ESP8266控制四路继电器
  7. 计算机毕业设计jspm平面设计类众包威客网网站mjmBBmysql程序
  8. 三宝小精灵机器人_三宝小精灵机器人
  9. 建造者2全部岛屿_勇者斗恶龙建造者2全流程攻略介绍 通关流程攻略分享 蒙佐拉岛-游侠网...
  10. 三、FreeNas实现SMB共享、FTP搭建实验报告