收藏 分销(赏)

linux内核I2C总线驱动实现.doc

上传人:pc****0 文档编号:9010427 上传时间:2025-03-11 格式:DOC 页数:14 大小:150.50KB
下载 相关 举报
linux内核I2C总线驱动实现.doc_第1页
第1页 / 共14页
linux内核I2C总线驱动实现.doc_第2页
第2页 / 共14页
点击查看更多>>
资源描述
linux内核I2C总线驱动实现 谈到在linux系统下编写I2C驱动,目前主要有两种方式,一种是把I2C设备当作一个普通的字符设备来处理,另一种是利用linux I2C驱动体系结构来完成。下面比较下这两种驱动。 第一种方法的好处(对应第二种方法的劣势)有:         ●    思路比较直接,不需要花时间去了解linux内核中复杂的I2C子系统的操作方法。 第一种方法问题(对应第二种方法的好处)有:         ●    要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器操作;         ●    要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可移植性差;         ●    对内核的资源无法直接使用。因为内核提供的所有I2C设备器及设备驱动都是基于I2C子系统的格式。I2C适配器的操作简单还好,如果遇到复杂的I2C适配器(如:基于PCI的I2C适配器),工作量就会大很多。 本文针对的对象是熟悉I2C协议,并且想使用linux内核子系统的开发人员。网络和一些书籍上有介绍I2C子系统的源码结构。但发现很多开发人员看了这些文章后,还是不清楚自己究竟该做些什么。究其原因还是没弄清楚I2C子系统为我们做了些什么,以及我们怎样利用I2C子系统。本文首先要解决是如何利用现有内核支持的I2C适配器,完成对I2C设备的操作,然后再过度到适配器代码的编写。本文主要从解决问题的角度去写,不会涉及特别详细的代码跟踪。 I2C的通信肯定至少要有2个芯片完成,所以它的驱动是由2大部分组成:主芯片的i2c的驱动、从芯片的i2c的驱动。   注:万一选的都不支持怎么办?(只能2个芯片的驱动都得实现了,不过过程差不多) 1 分析linux内核中I2C驱动框架 1 .1主芯片的I2C的驱动 首先要查看linux内核是否支持主芯片中i2c驱动器,如果支持就配置一下就ok了,否则要编写主控芯片的i2c驱动器。 编写方法: (1)要有i2c总线驱动(首先要查查内核i2c文件是否支持这种总线驱动,一般都有支持,如果没有只好自己写了) (2)i2c设备驱动(主控芯片的地址等等信息) 这个过程都是差不多的,以后在分析。一般的主控芯片的i2c控制器linux内核基本上支持的很好,如:2410的i2c驱动器的支持。 1.2从芯片的I2C的驱动 下面主要分析从芯片的I2C驱动(也有2种方式,第一个是利用内核提供的i2c-dev.c来构建,另一个是自己写) 主要分析第一种方式: 利用系统给我们提供的i2c-dev.c来实现一个i2c适配器的设备文件。然后通过在应用层操作i2c适配器来控制i2c设备。 i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read()、write()和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的i2c设备的存储空间或寄存器,并控制I2C设备的工作方式。 需要特别注意的是:i2c-dev.c的read()、write()方法都只适合于如下方式的数据格式(可查看内核相关源码) 图1 单开始信号时序 所以不具有太强的通用性,如下面这种情况就不适用(通常出现在读目标时)。 图2 多开始信号时序 但是read和write方法适用性有限,只适用用于适配器支持i2c算法的情况,如: static const struct i2c_algorithm s3c24xx_i2c_algorithm = {             .master_xfer = s3c24xx_i2c_xfer,             .functionality = s3c24xx_i2c_func,         }; 而不适合适配器只支持smbus算法的情况,如:         static const struct i2c_algorithm smbus_algorithm = {             .smbus_xfer = i801_access,             .functionality = i801_func,         }; 基于以上原因,一般都不会使用i2c-dev.c的read()、write()方法。最常用的是ioctl()方法。ioctl()方法可以实现上面所有的情况(两种数据格式、以及I2C算法和smbus算法)。  针对i2c的算法,需要熟悉struct i2c_rdwr_ioctl_data 、struct i2c_msg。使用的命令是I2C_RDWR。         struct i2c_rdwr_ioctl_data      {              struct i2c_msg __user *msgs; /* pointers to i2c_msgs */               __u32 nmsgs; /* number of i2c_msgs */           };         struct i2c_msg {             _ _u16 addr; /* slave address */             _ _u16 flags; /* 标志(读、写) */              _ _u16 len; /* msg length */             _ _u8 *buf; /* pointer to msg data */         }; 针对smbus算法,需要熟悉struct i2c_smbus_ioctl_data。使用的命令是I2C_SMBUS。对于smbus算法,不需要考虑“多开始信号时序”问题。         struct i2c_smbus_ioctl_data {             __u8 read_write; //读、写             __u8 command; //命令             __u32 size; //数据长度标识             union i2c_smbus_data __user *data; //数据         }; 下面以一个实例讲解操作的具体过程。通过S3C2410操作AT24C02 e2prom。实现在AT24C02中任意位置的读、写功能。 首先在内核中已经包含了对s3c2410 中的i2c控制器(总线驱动)驱动的支持。提供了i2c算法(非smbus类型的,所以后面的ioctl的命令是I2C_RDWR)         static const struct i2c_algorithm s3c24xx_i2c_algorithm = {             .master_xfer = s3c24xx_i2c_xfer,             .functionality = s3c24xx_i2c_func,         };   另外一方面需要确定为了实现对AT24C02 e2prom的操作,需要确定从机芯片的地址及读写访问时序。 ●      AT24C02地址的确定 原理图上将A2、A1、A0都接地了,所以地址是0x50。 ●        AT24C02任意地址字节写的时序 可见此时序符合前面提到的“单开始信号时序” ●        AT24C02任意地址字节读的时序 可见此时序符合前面提到的“多开始信号时序” 下面开始具体代码的分析(代码在2.6.22内核上测试通过):         /*i2c_test.c         * hongtao_liu <lht@>         */         #include <stdio.h>         #include <linux/types.h>         #include <stdlib.h>         #include <fcntl.h>         #include <unistd.h>         #include <sys/types.h>         #include <sys/ioctl.h>         #include <errno.h>         #define I2C_RETRIES 0x0701         #define I2C_TIMEOUT 0x0702         #define I2C_RDWR 0x0707          /*********定义struct i2c_rdwr_ioctl_data和struct i2c_msg,要和内核一致*******/      #define I2C_M_TEN 0x0010         #define I2C_M_RD 0x0001   struct i2c_msg         {                 unsigned short addr;                 unsigned short flags;                 unsigned short len;                 unsigned char *buf;         };   struct i2c_rdwr_ioctl_data         {                 struct i2c_msg *msgs;                 int nmsgs;         /* nmsgs这个数量决定了有多少开始信号,对于“单开始时序”,取1*/         };    /***********主程序***********/         int main()         {                 int fd,ret;                 struct i2c_rdwr_ioctl_data e2prom_data;                 fd=open("/dev/i2c-0",O_RDWR);      /* 为什么是i2c-0呢??那就要到内核里看啦,等会再说。 open底层调用了i2c_get_adapter(int id)函数,这个函数很重要,他可以识别占用了哪个i2c总线使用地0个i2c控制器 /dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c(这里说啦上面的为什么) 的方式,就没有,也不需要这个节,i2c_driver结构体中有attach_adapter方法:里面用device_create(i2c_dev_class, &adap->dev,MKDEV(I2C_MAJOR, adap->nr), NULL,"i2c-%d",adap->nr);I2C_MAJOR=89,即i2c-dev.c针对每个i2c适配器生成一个主设备号位89的设备文件,次设备要自己定义。  /dev/i2c-0是在注册i2c-dev.c后产生的,代表一个可操作的适配器。如果不使用i2c-dev.c 的方式,就没有,也不需要这个节点。          */                 if(fd<0)                 {                         perror("open error");                 }                 e2prom_data.nmsgs=2;         /*         *因为操作时序中,最多是用到2个开始信号(字节读操作中),所以此将         *e2prom_data.nmsgs配置为2         */                 e2prom_data.msgs=(struct i2c_msg*)malloc(e2prom_data.nmsgs*sizeof(struct i2c_msg));                 if(!e2prom_data.msgs)                 {                         perror("malloc error");                         exit(1);                 }                 ioctl(fd,I2C_TIMEOUT,1);/*超时时间*/                 ioctl(fd,I2C_RETRIES,2);/*重复次数*/                 /***write data to e2prom**/ /**/                 e2prom_data.nmsgs=1;                 (e2prom_data.msgs[0]).len=2; //1个 e2prom 写入目标的地址和1个数据                 (e2prom_data.msgs[0]).addr=0x50;//e2prom 设备地址                 (e2prom_data.msgs[0]).flags=0; //write                 (e2prom_data.msgs[0]).buf=(unsigned char*)malloc(2);                 (e2prom_data.msgs[0]).buf[0]=0x10;// e2prom 写入目标的地址                 (e2prom_data.msgs[0]).buf[1]=0x58;//the data to write              ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);                 if(ret<0)                 {                         perror("ioctl error1");                 }                 sleep(1);         /******read data from e2prom*******/                 e2prom_data.nmsgs=2;                 (e2prom_data.msgs[0]).len=1; //e2prom 目标数据的地址                 (e2prom_data.msgs[0]).addr=0x50; // e2prom 设备地址                 (e2prom_data.msgs[0]).flags=0;//write                 (e2prom_data.msgs[0]).buf[0]=0x10;//e2prom数据地址                 (e2prom_data.msgs[1]).len=1;//读出的数据                 (e2prom_data.msgs[1]).addr=0x50;// e2prom 设备地址                 (e2prom_data.msgs[1]).flags=I2C_M_RD;//read                 (e2prom_data.msgs[1]).buf=(unsigned char*)malloc(1);//存放返回值的地址。                 (e2prom_data.msgs[1]).buf[0]=0;//初始化读缓冲             ret=ioctl(fd,I2C_RDWR,(unsigned long)&e2prom_data);                 if(ret<0)                 {                         perror("ioctl error2");                 }                 printf("buff[0]=%x/n",(e2prom_data.msgs[1]).buf[0]); /***打印读出的值,没错的话,就应该是前面写的0x58了***/                  close(fd);       i2c_put_adapter(client->adapter);//释放i2c总线 return 0;          } 以上讲述了一种比较常用的利用i2c-dev.c操作i2c设备的方法,这种方法可以说是在应用层完成了对具体i2c设备的驱动工作。 计划下一篇总结以下几点: (1)在内核里写i2c设备驱动的两种方式: ●    Probe方式(new style),如:                 static struct i2c_driver pca953x_driver = {                         .driver = {                                 .name = "pca953x",                         },                         .probe = pca953x_probe,                         .remove = pca953x_remove,                         .id_table = pca953x_id,                 }; ●    Adapter方式(LEGACY),如:                 static struct i2c_driver pcf8575_driver = {                         .driver = {                                 .owner = THIS_MODULE,                                 .name = "pcf8575",                         },                         .attach_adapter = pcf8575_attach_adapter,                         .detach_client = pcf8575_detach_client,                 }; (2)适配器驱动编写方法 (3)分享一些项目中遇到的问题         希望大家多提意见,多多交流。 2 I2C驱动编写 下面具体分析如何写第一部分: 2.1 主芯片的I2C驱动编写 主控芯片的i2c驱动分为2个步骤: 2.1.1写总线驱动 选了个主控芯片,比如:S3C8900(自己瞎选的芯片),在driver/i2c/busses/i2c-s3c2410.c中没有找到这个芯片的I2C支持(总线驱动支持)。(倒霉了,没有选好芯片,也可能是最新型号的,linux内核没跟上)。 在此之前先分析i2c-s3c2410.c中完成的工作(总线驱动): l 设计对应于i2c_adapter_xxx_init()模板的s3c8900的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。 l 设计对应于i2c_adapter_xxx_xfer()模板的 s3c8900适配器的通信方法函数,针对 s3c24xx、64xx、s5pc1XX、s5p64xx处理器functionality()函数s3c24xx_i2c-func()只需简单的返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能 话说没找到总线驱动支持,(这倒霉孩子)那就得编写个类似的i2c-s3c8900.c的总线驱动支持,嘿嘿,照着上面的功能写吧,反正是总线驱动。 Ø I2C适配器驱动加载与卸载  1. 初始化i2c适配器所使用的硬件资源,如申请I/O地址、中断号等; 2. 通过i2c_add_adapter添加i2c_adapter数据结构,当然这个数据结构的成员已经被xxx适配器的相应的函数指针所初始化; 3. i2c总线卸载模块与装载相反,是否i2c适配器使用的硬件资源,通过i2c_del_adapter删除i2c_adapter的数据结构。         模板如下:                 static int __init i2c_adapter_xxx_init(void)                 {                    xxx_adapter_hw_init();//初始化硬件资源                     i2c_add_adapter(&xxx_adapter);                 }                     static void __init i2c_adapter_xxx_exit(void)                         {         xxx_adapter_hw_free();//释放硬件资源                             i2c_del_adapter(&xxx_adapter);                             }             具体CPU具体分析,有的用platform做的,可以参考6410的做法 2.1.2 I2C总线的通信方法 我们需要为特定的i2c适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。functionality函数很简单,用于返回algorithm所支持的通信协议,如:I2C_FUCN-_I2C, I2C_FUNC_10BIT_ADDR,I2C_FUNC_SMBUS_READ_BYTE,I2C_FUNC_SMBUS_write_byte等 master_xfer函数在i2c适配器上完成传递给他的i2c_msg数组中的每个i2c消息。              模板如下:             static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)                 {                     ......                     for(i = 0; i<num ; i++){                         i2c_adapter_xxx_start(); //产生开始位                         //如果是读消息                         if(msg[i]->falgs &I2C_M_RD){                         i2c_adapter_xxx_setaddr((msg->addr<<1)|1); //发送从设备读地址                          i2c_adapter_xxx_wait_ack();//获取从设备的ack信息                          i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);//读取msg[i]->len长的数据到msg[i]->buf里                             }    else{    //是写消息                               i2c_adapter_xxx_setaddr((msg->addr<<1)|1); //发送从设备写地址                              i2c_adapter_xxx_wait_ack();//获取从设备的ack信息                              i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);//读取msg[i]->len长的数据到msg[i]->buf里                                 }                                     }                         i2c_adapter_xxx_stop(); //产生停止位      }   好啦,完成了装载和卸载,又完成了通信方法这两个重要的东东,那么总线驱动结构已经完成啦,累死了!   2.2 写设备驱动 四部曲: Ø 构建i2c_driver Ø 注册i2c_driver Ø 构建i2c_client ( 第一种方法:注册字符设备驱动、第二种方法:通过板文件的i2c_board_info填充,然后注册) Ø 注销i2c_driver 具体如下: 2.2.1构建i2c_driver static struct i2c_driver pca953x_driver = {                 .driver = {                                     .name= "pca953x", //名称                                 },                 .id= ID_PCA9555,//id号                 .attach_adapter= pca953x_attach_adapter, //调用适配器连接设备                 .detach_client= pca953x_detach_client,//让设备脱离适配器         }; 2.2.2注册i2c_driver   static int __init pca953x_init(void)         {                 return i2c_add_driver(&pca953x_driver);         }         module_init(pca953x_init);   执行i2c_add_driver(&pca953x_driver)后,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接我们的i2c设备。此过程是通过调用i2c_driver中的attach_adapter方法完成的。具体实现形式如下:   static int pca953x_attach_adapter(struct i2c_adapter *adapter)         {                 return i2c_probe(adapter, &addr_data, pca953x_detect);                 /*                 adapter:适配器                 addr_data:地址信息                 pca953x_detect:探测到设备后调用的函数                 */         }   地址信息addr_data是由下面代码指定的。         /* Addresses to scan */         static unsigned short normal_i2c[] = {0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END};         I2C_CLIENT_INSMOD;   注意:normal_i2c里的地址必须是你i2c芯片的地址。否则将无法正确探测到设备。而I2C_ CLIENT_INSMOD是一个宏,它会利用normal_i2c构建addr_data。 2.2.3构建i2c_client,并注册字符设备驱动 i2c_probe在探测到目标设备后,后调用pca953x_detect,并把当时的探测地址address作为参数传入。 static int pca953x_detect(struct i2c_adapter *adapter, int address, int kind)         {                 struct i2c_client *new_client;                 struct pca953x_chip *chip; //设备结构体                 int err = 0,result;                 dev_t pca953x_dev=MKDEV(pca953x_major,0);//构建设备号,根据具体情况设定,这里我只考虑了normal_i2c中只有一个地址匹配的情况。主次设备号来源                 if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_WORD_DATA))//判定适配器能力                 goto exit;                 if (!(chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL))) {                         err = -ENOMEM;                         goto exit;                 }                 /****构建i2c-client****/                 chip->client=kzalloc(sizeof(struct i2c_client),GFP_KERNEL);                 new_client = chip->client;                 i2c_set_clientdata(new_client, chip);                 new_client->addr = address;                 new_client->adapter = adapter;                 new_client->driver = &pca953x_driver;                 new_client->flags = 0;                 strlcpy(new_client->name, "pca953x", I2C_NAME_SIZE);                 if ((err = i2c_attach_client(new_client)))//注册i2c_client                 goto exit_kfree;                 if (err)                 goto exit_detach;                 if(pca953x_major)                 {                         result=register_chrdev_region(pca953x_dev,1,"pca953x");                 }                 else{                         result=alloc_chrdev_region(&pca953x_dev,0,1,"pca953x");                         pca953x_major=MAJOR(pca953x_dev);                 }                 if (result < 0) {                         printk(KERN_NOTICE "Unable to get pca953x region, error %d/n", result);                         return result;                 }                 pca953x_setup_cdev(chip,0); //注册字符设备,此处不详解                 return 0;                 exit_detach:                 i2c_detach_client(new_client);         exit_kfree:                 kfree(chip);         exit:                 return err;         } i2c_check_functionality用来判定设配器的能力,这一点非常重要。你也可以直接查看对应设配器的能力,如 static const struct i2c_algorithm smbus_algorithm = {                 .smbus_xfer= i801_access,                 .functionality= i801_func,         };         static u32 i801_func(struct i2c_adapter *adapter)         {                         return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |                                I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |                                I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK|(isich4 ? I2C_FUNC_SMBUS_HWPEC
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服