linux内核I2C总线驱动实现.doc
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 内核 I2C 总线 驱动 实现
- 资源描述:
-
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展开阅读全文
咨信网温馨提示:1、咨信平台为文档C2C交易模式,即用户上传的文档直接被用户下载,收益归上传人(含作者)所有;本站仅是提供信息存储空间和展示预览,仅对用户上传内容的表现方式做保护处理,对上载内容不做任何修改或编辑。所展示的作品文档包括内容和图片全部来源于网络用户和作者上传投稿,我们不确定上传用户享有完全著作权,根据《信息网络传播权保护条例》,如果侵犯了您的版权、权益或隐私,请联系我们,核实后会尽快下架及时删除,并可随时和客服了解处理情况,尊重保护知识产权我们共同努力。
2、文档的总页数、文档格式和文档大小以系统显示为准(内容中显示的页数不一定正确),网站客服只以系统显示的页数、文件格式、文档大小作为仲裁依据,个别因单元格分列造成显示页码不一将协商解决,平台无法对文档的真实性、完整性、权威性、准确性、专业性及其观点立场做任何保证或承诺,下载前须认真查看,确认无误后再购买,务必慎重购买;若有违法违纪将进行移交司法处理,若涉侵权平台将进行基本处罚并下架。
3、本站所有内容均由用户上传,付费前请自行鉴别,如您付费,意味着您已接受本站规则且自行承担风险,本站不进行额外附加服务,虚拟产品一经售出概不退款(未进行购买下载可退充值款),文档一经付费(服务费)、不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。
4、如你看到网页展示的文档有www.zixin.com.cn水印,是因预览和防盗链等技术需要对页面进行转换压缩成图而已,我们并不对上传的文档进行任何编辑或修改,文档下载后都不会有水印标识(原文档上传前个别存留的除外),下载后原文更清晰;试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓;PPT和DOC文档可被视为“模板”,允许上传人保留章节、目录结构的情况下删减部份的内容;PDF文档不管是原文档转换或图片扫描而得,本站不作要求视为允许,下载前可先查看【教您几个在下载文档中可以更好的避免被坑】。
5、本文档所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用;网站提供的党政主题相关内容(国旗、国徽、党徽--等)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
6、文档遇到问题,请及时联系平台进行协调解决,联系【微信客服】、【QQ客服】,若有其他问题请点击或扫码反馈【服务填表】;文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“【版权申诉】”,意见反馈和侵权处理邮箱:1219186828@qq.com;也可以拔打客服电话:0574-28810668;投诉电话:18658249818。




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



实名认证













自信AI助手
















微信客服
客服QQ
发送邮件
意见反馈



链接地址:https://www.zixin.com.cn/doc/9010427.html