Linux驱动之i2c用户态调用
- 格式:docx
- 大小:390.92 KB
- 文档页数:9
1、I2C概述I2C是philips公司提供的外设总线,I2C有两条数据线,一条是串行数据线SDA、一条是时钟线SCL,使用SDA和SCL实现了数据的交换,便于布线。
I2C总线方便用在EEPROM、实时钟、小型LCD等与CPU外部的接口上。
2、Linux下的驱动思路Linux系统下编写I2c驱动主要有两种方法:一种是把I2C当做普通字符设备来使用;另一种利用Linux下驱动的体系结构来实现。
第一种方法:优点:思路比较直接,不用花费大量时间去了解Linux系统下I2C体系结构缺点:不仅对I2C设备操作要了解,还有了解I2C的适配器操作不仅对I2C设备器和设备操作需要了解,编写的驱动移植性差,内核提供的I2C设备器都没有用上。
第二种方法:第一种的优点就是第二种的缺点,第一种的缺点就是第二种的优点。
3、I2C框架概述Linux的I2C体系结构分为3部分:1)I2C核心I2C核心提供了I2C总线驱动和设备驱动的注册和注销的方法,I2C 通信方法(algorithm)上层,与具体适配器无关的代码,检测设备上层的代码等。
2)I2C总线驱动I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可以直接受CPU来控制。
3)I2C设备驱动I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备端挂在受CPU控制的适配器上,通过I2C适配器与CPU交换数据。
Linux下的I2C体系结构:1)Linux下的I2C体系结构4、I2C设备驱动编写方法首先让我们明白适配器驱动的作用是让我们能够通过它发出标准的I2C时序,在linux内核源代码中driver/I2C/buss包含一些适配器的驱动,例如s3c2410的驱动I2C-s3c2410.c,适配器被加载到内核中,接下的任务就是实现设备驱动的编写。
编写设备驱动的方法主要分为两种方法:第一种:利用设备提供的I2C-dev.c来实现I2C适配器设备文件,然后通过上层应用程序来操作I2C设备器来控制I2C设备。
Linux I2C驱动完全分析(二)先说一下,本文中有个疑惑,一直没有搞懂,写在这里,望高人指点一二,不胜感激!#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */这里I2C_FUNC_PROTOCOL_MANGLING 是什么意思?为什么定义这些东东?看了注释也不太理解。
求解释!3. I2C总线驱动代码分析s3c2440的总线驱动代码在i2c-s3c2410.c中。
照例先从init看起。
static int __init i2c_adap_s3c_init(void){return platform_driver_register(&s3c24xx_i2c_driver);}在init中只是调用了平台驱动注册函数注册了一个i2c的平台驱动s3c24xx_i2c_driver。
这个驱动是一个platform_driver的结构体变量。
注意这里不是i2c_driver结构体,因为i2c_driver是对设备的驱动,而这里对控制器的驱动要使用platform_driverstatic struct platform_driver s3c24xx_i2c_driver = {.probe = s3c24xx_i2c_probe,.remove = s3c24xx_i2c_remove,.suspend_late = s3c24xx_i2c_suspend_late,.resume = s3c24xx_i2c_resume,.id_table = s3c24xx_driver_ids,.driver = {.owner = THIS_MODULE,.name = "s3c-i2c",},};同样的,重要的函数还是那几个:probe,remove,suspend_late,resume。
linux下S3C2440–I2C驱动学习之四“i2c我们进入I2C驱动的最后一个小节,在这个小节里,我们主要探讨 i2c_algorithm 数据结构和i2c-core.c 的一些主要函数及其作用。
i2c_algorithm 结构体struct i2c_algorithm {int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);u32 (*functionality) (struct i2c_adapter *);};一个i2c 适配器上的i2c 总线通信方法由其驱动程序提供的i2c_algorithm 数据结构描述,由algo 指针指向。
i2c_algorithm 数据结构即为i2c_adapter 数据结构与具体i2c 适配器的总线通信方法的中间层,正是这个中间层使得上层的i2c 框架代码与与具体i2c 适配器的总线通信方法无关,从而实现了i2c 框架的可移植性和重用性。
当安装具体i2c 适配器的驱动程序时由相应驱动程序实现具体的i2c_algorithm 数据结构,其中的函数指针指向操作具体i2c 适配器的代码。
master_xfer/smbus_xfer 指针指向i2c 适配器驱动程序模块实现的i2c 通信协议或者smbus 通信协议。
在用户进程通过i2c-dev 提供的/dev/i2c/%d 设备节点访问i2c 设备时,最终是通过调用master_xfer 或者smbus_xfer 指向的方法完成的。
i2c-core.ci2c.h 和i2c-core.c 为i2c 框架的主体,提供了核心数据结构的定义、i2c 适配器驱动和设备驱动的注册、注销管理,i2c 通信方法上层的、与具体适配器无关的代码、检测设备地址的上层代码等;i2c-dev.c 用于创建i2c 适配器的/dev/i2c/%d 设备节点,提供i2c 设备访问方法等。
嵌入式Linux的I2C设备驱动程序的操作系统分析
0 引言
由于I2C总线的通用性,Linux作为一款优秀的嵌入式操作系统,也必须
要对其要有很好的支持。
在Linux内核源码中对I2C总线的驱动是基于总线设备驱动模型的,其驱动程序用到了特殊的几个数据结构,对I2C总线协议进行了更抽象更通用的定义,极大的增加了设备驱动的可移植性。
要编写出自己的I2C 设备驱动程序,必须对这种内核I2C总线驱动的架构有深刻的理解。
1 I2C总线的硬件构成
I2C 总线协议只有两条总线线路,一条是串行数据线(SDA),一条是串行时钟线(SCL)。
SDA 负责数据的传输,SCL 负责数据传输的时钟同步。
I2C 设备通过这两条总线连接到处理器的I2C总线控制器上,不同设备之间通过7 位地址来区别,而且数据的传输是双向的,方向的确定由1位二进制数确定,地址位加方向位是操作I2C 设备的惟一标示,I2C 设备与CPU 的连接如图1所示。
I2C 总线上有3 种类型的信号,分别是:开始信号,结束信号和应答信号。
这些信号都是由SDA和SCL上的电平变化来表示的。
开始信号(S):当SCL为高电平时,SDA由高电平向低电平跳变,表示开始传输数据。
结束信号(P):当SCL为高电平时,SDAY由低电平向高电平跳变,表示结束传输数据。
相应信号(ACK):从机接收到8位数据后,在第9个时钟周期,拉低SDA。
LinuxI2C驱动--⽤户态驱动简单⽰例1. Linux内核⽀持I2C通⽤设备驱动(⽤户态驱动:由应⽤层实现对硬件的控制可以称之为⽤户态驱动),实现⽂件位于drivers/i2c/i2c-dev.c,设备⽂件为/dev/i2c-02. I2C通⽤设备驱动以字符设备注册进内核的static const struct file_operations i2cdev_fops = {.owner = THIS_MODULE,.llseek = no_llseek,.read = i2cdev_read,.write = i2cdev_write,.unlocked_ioctl = i2cdev_ioctl,.open = i2cdev_open,.release = i2cdev_release,};res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);3. 对设备⽂件进⾏读写时,可以调⽤read、write或者ioctl等⽅法,他们都是通过调⽤函数i2c_transfer来实现对I2C设备的操作的int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num){int ret;/* REVISIT the fault reporting model here is weak:** - When we get an error after receiving N bytes from a slave,* there is no way to report "N".** - When we get a NAK after transmitting N bytes to a slave,* there is no way to report "N" ... or to let the master* continue executing the rest of this combined message, if* that's the appropriate response.** - When for example "num" is two and we successfully complete* the first message but get an error part way through the* second, it's unclear whether that should be reported as* one (discarding status on the second message) or errno* (discarding status on the first one).*/if (adap->algo->master_xfer) {#ifdef DEBUGfor (ret = 0; ret < num; ret++) {dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, ""len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)'R' : 'W', msgs[ret].addr, msgs[ret].len,(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");}#endifif (in_atomic() || irqs_disabled()) {ret = mutex_trylock(&adap->bus_lock);if (!ret)/* I2C activity is ongoing. */return -EAGAIN;} else {mutex_lock_nested(&adap->bus_lock, adap->level);}ret = adap->algo->master_xfer(adap,msgs,num);mutex_unlock(&adap->bus_lock);return ret;} else {dev_dbg(&adap->dev, "I2C level transfers not supported\n");return -EOPNOTSUPP;}}4. i2c_transfer通过代码可以看出,i2c_transfer 通过调⽤相应的 adapter 的 master_xfer ⽅法实现的,⽽ master_xfer 主要是根据 struct i2c_msg 类型的msgs来进⾏处理的。
linux i2c驱动1. i2c-dev interfaceI2C dev-interface通常,i2c设备由某个内核驱动控制。
但是在用户空间,也可以访问某个I2C设备:你需要加载i2c-dev模块。
每个被注册的i2c适配器(控制器)会获得一个数字号,从0开始。
你可以检查/sys/class/i2c-dev,来查看适配器对应哪个数字号。
你也可以通过命令"i2cdetect -l"获取你的当前系统的所有I2c适配器的列表。
i2cdetct是i2c-tool包中的一个工具。
i2c设备文件是字符设备,主设备号是89,次设备号的分配如上所述。
设备文件名通常被规定为"i2c-%d"(i2c-0, i2c-1, ...,i2c-10, ...)i2c设备文件是字符设备,主设备号是89,次设备号的分配如上所述。
设备文件名通常被规定为"i2c-%d"(i2c-0, i2c-1, ...,i2c-10, ...).所有256个次设备号都保留给i2c使用。
C example=========假定你要在你的C应用程序中访问i2c适配器。
第一件事情就是包含头文件"#include<linux/i2c-dev.h>"。
注意,存在两个"i2c-dev.h"文件: 一个属于Linux kernel,用于内核驱动中;一个由i2c-tools发布,用于用户程序。
显然,这里需要使用第二个i2c-dev.h文件。
现在,你需要确定访问哪个适配器。
你需要通过查看/sys/class/i2c-dev/或者运行"i2cdetect -l"确定。
适配器号时常是动态分配的,你无法预先假定某个值。
因为它们甚至会在系统重启后变为不同的值。
下一步,打开设备文件,如下:int file;int adapter_nr = 2; /*probably dynamically determined */char filename[20];snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);file = open(filename, O_RDWR);if (file < 0) {/* ERROR HANDLING; you can check errno to see what went wrong */ exit(1);}当你打开设备后,你必须明确说明你相与哪个设备地址进行通信:int addr = 0x40;if (ioctl(file, I2C_SLAVE, addr) < 0) {/* ERROR HANDLING; you can check errno to see what went wrong */ exit(1);}Well, 你现在准备好与I2C设备通信了。
linux 应用访问i2c设备原理Linux是一个开放源代码的操作系统内核,支持许多硬件设备的驱动程序。
其中一种常用的硬件接口是I2C(Inter-Integrated Circuit)总线。
I2C总线允许多个设备通过共享同一组线路进行通信,这些设备可以是传感器、存储器、转换器等外围设备。
Linux提供了一种访问I2C设备的机制,通过此机制,应用程序可以与I2C设备进行通信。
在本文中,将介绍Linux应用程序访问I2C设备的原理及步骤。
1.硬件连接:首先,需要将I2C设备连接到主机上。
I2C总线由两根线路组成,即SDA(Serial Data Line)和SCL(Serial Clock Line)。
SDA用于传输数据,SCL用于提供时钟信号。
I2C设备通常具有一个I2C地址,用于在总线上识别不同的设备。
2. Linux内核驱动:Linux内核提供了I2C总线的驱动程序,用于与I2C设备进行通信。
通常,这些驱动程序已经包含在内核中,不需要单独安装。
但是,可能需要通过编译内核时打开相应的配置选项来启用I2C支持。
3. I2C设备驱动:每个I2C设备都需要一个设备驱动程序,用于与应用程序进行交互。
这些驱动程序通常由设备制造商提供,也有一些常见的驱动程序包含在Linux内核中。
设备驱动程序将设备上的读写操作映射到I2C总线上的读写操作。
4.用户空间库:为了更方便地编写应用程序,Linux提供了一些用户空间库,用于访问I2C设备。
其中最常用的库是libi2c-dev,它提供了一组API函数,可以通过文件描述符进行I2C通信。
此库通常已经安装在Linux系统中,可以通过编译时链接到应用程序中。
5.应用程序访问:应用程序需要通过打开I2C总线来访问I2C设备。
首先,需要获取I2C总线的文件描述符,可以通过调用open()函数并传递文件路径来实现。
常见的I2C总线路径为"/dev/i2c-x",其中"x"是总线号。
Linux IIC驱动在嵌入式系统中,IIC设备是非常常见的。
下面就讨论下在LINUX如何实现IIC设备的驱动。
因为IIC的主机适配驱动程序一般芯片厂家在BSP里面都会提供,这里就不详细说明了,下面主要介绍下客户驱动程序如何实现。
首先,我们需要在驱动的初始化函数中注册IIC。
我们需要调用i2c_add_driver 。
这个函数的原形是int i2c_add_driver(struct i2c_driver * IIC),在驱动的exit函数里面需要调用i2c_del_driver释放资源。
下面是注册和释放的例子:static const struct i2c_device_id tsc2003_id[] = {{ “ts_2003”, 0 },{ }};static struct i2c_driver tsc2003_driver = {.probe = tsc2003_probe,.remove = tsc2003_remove,.id_table = tsc2003_id,.driver = {.name = “ts_2003”,},};static int __init tsc2003_init(void){int res;res = i2c_add_driver(&tsc2003_driver);if(res){return res;}return 0;}void __exit tsc2003_exit(void){i2c_del_driver(&tsc2003_driver);}在tsc2003_init这个函数里面调用了i2c_add_driver注册IIC。
下面介绍下i2c_driver这个结构:这个结构里面定义了.probe = tsc2003_probe,那么系统在检测到IIC核心定义了.name指定的设备名。
那么系统会调用tsc2003_probe,并且将struct i2c_client的指针作为参数传递进来。
28.Linux-IIC驱动(详解)上⼀节我们学习了:IIC接⼝下的24C02 驱动分析:接下来本节, 学习Linux下如何利⽤linux下I2C驱动体系结构来操作24C021. I2C体系结构分析1.1⾸先进⼊linux内核的driver/i2c⽬录下,如下图所⽰:其中重要的⽂件介绍如下:1)algos⽂件夹(algorithms)⾥⾯保存I2C的通信⽅⾯的算法2)busses⽂件夹⾥⾯保存I2C总线驱动相关的⽂件,⽐如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。
3)chips⽂件夹⾥⾯保存I2C设备驱动相关的⽂件,如下图所⽰,⽐如m41t00,就是RTC实时钟4) i2c-core.c这个⽂件实现了I2C核⼼的功能(I2C总线的初始化、注册和适配器添加和注销等相关⼯作)以及/proc/bus/i2c*接⼝。
5) i2c-dev.c提供了通⽤的read()、 write()和ioctl()等接⼝,实现了I2C适配器设备⽂件的功能,其中I2C设备的主设备号都为89,次设备号为0~255。
应⽤层可以借⽤这些接⼝访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的⼯作⽅式显然,它和前⼏次驱动类似, I2C也分为总线驱动和设备驱动,总线就是协议相关的,它知道如何收发数据,但不知道数据含义,设备驱动却知道数据含义1.2 I2C驱动架构,如下图所⽰:如上图所⽰,每⼀条I2C对应⼀个adapter适配器,在kernel中, adapter适配器是通过struct adapter结构体定义,主要是通过i2c core层将i2c设备与i2c adapter关联起来.在kernel中提供了两个adapter注册接⼝,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每⼀条I2C 总线对应⼀个编号,下⽂中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件⽆关,只是软件上便于区分⽽已.对于i2c_add_adapter()⽽⾔,它使⽤的是动态总线号,即由系统给其分析⼀个总线号,⽽i2c_add_numbered_adapter()则是⾃⼰指定总线号,如果这个总线号⾮法或者是被占⽤,就会注册失败.2.接下来便来分析I2C总线驱动参考 drivers/i2c/busses/i2c-s3c2410.c先进⼊init⼊⼝函数,如下图所⽰:在init函数中,注册了⼀个“s3c2440-i2c”的platform_driver平台驱动,我们来看看probe函数做了些什么3.进⼊s3c24xx_i2c_probe函数struct i2c_adapter adap;static int s3c24xx_i2c_probe(struct platform_device *pdev){ struct s3c24xx_i2c *i2c = &s3c24xx_i2c;... .../*获取,使能I2C时钟*/i2c->clk = clk_get(&pdev->dev, "i2c"); //获取i2c时钟clk_enable(i2c->clk); //使能i2c时钟... ..../*获取资源*/res = platform_get_resource(pdev, IORESOURCE_MEM, 0);i2c->regs = ioremap(res->start, (res->end-res->start)+1);... ..../*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/ i2c->adap.algo_data = i2c; //i2c_adapter适配器指向s3c24xx_i2c;i2c->adap.dev.parent = &pdev->dev; /* initialise the i2c controller *//*初始化2440的I2C相关的寄存器*/ret = s3c24xx_i2c_init(i2c);if (ret != 0)goto err_iomap;... .../*注册中断服务函数*/ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,pdev->name, i2c);... .../*注册i2c_adapter适配器结构体*/ret = i2c_add_adapter(&i2c->adap);... ...}其中i2c_adapter结构体是放在s3c24xx_i2c->adap下,如下图所⽰:4.接下来我们进⼊i2c_add_adapter()函数看看,到底如何注册的int i2c_add_adapter(struct i2c_adapter *adapter){int id, res = 0;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0) //调⽤idr_pre_get()为i2c_adapter预留内存空间return -ENOMEM;mutex_lock(&core_lists);/* "above" here means "above or equal to", sigh */res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);//调⽤idr_get_new_above()将结构插⼊i2c_adapter_idr中,并将插⼊的位置赋给id,以后可以通过id在i2c_adapter_idr中找到相应的i2c_adapter结构体 mutex_unlock(&core_lists);if (res < 0) {if (res == -EAGAIN)goto retry;return res;}adapter->nr = id;return i2c_register_adapter(adapter); //调⽤i2c_register_adapter()函数进⼀步来注册.}其中i2c_register_adapter()函数代码如下所⽰:static int i2c_register_adapter(struct i2c_adapter *adap){struct list_head *item; //链表头,⽤来存放i2c_driver结构体的表头struct i2c_driver *driver; //i2c_driver,⽤来描述⼀个IIC设备驱动list_add_tail(&adap->list, &adapters); //添加到内核的adapter链表中... ...list_for_each(item,&drivers) { //for循环,从drivers链表⾥找到i2c_driver结构体的表头driver = list_entry(item, struct i2c_driver, list); //通过list_head表头,找到i2c_driver结构体if (driver->attach_adapter)/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap); //调⽤i2c_driver的attach_adapter函数来看看,这个新注册的设配器是否⽀持i2c_driver}}在i2c_register_adapter()函数⾥主要执⾏以下⼏步:①将adapter放⼊i2c_bus_type的adapter链表②将所有的i2c设备调出来,执⾏i2c_driver设备的attach_adapter函数来匹配其中, i2c_driver结构体会在后⾯讲述到⽽i2c_adapter适配器结构体的成员结构,如下所⽰:struct i2c_adapter {struct module *owner; //所属模块unsigned int id; //algorithm的类型,定义于i2c-id.h,unsigned int class;const struct i2c_algorithm *algo; //总线通信⽅法结构体指针void *algo_data; //algorithm数据struct rt_mutex bus_lock; //控制并发访问的⾃旋锁int timeout;int retries; //重试次数struct device dev; //适配器设备int nr; //存放在i2c_adapter_idr⾥的位置号char name[48]; //适配器名称struct completion dev_released; //⽤于同步struct list_head userspace_clients; //client链表头};i2c_adapter表⽰物理上的⼀个i2C设备(适配器), 在i2c-s3c2410.c中,是存放在s3c24xx_i2c结构体下的(struct i2c_adapter adap)成员中5.其中s3c24xx_i2c的结构体成员如下所⽰static const struct i2c_algorithm s3c24xx_i2c_algorithm = {.master_xfer = s3c24xx_i2c_xfer, //主机传输.functionality = s3c24xx_i2c_func,};static struct s3c24xx_i2c s3c24xx_i2c = {.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),.tx_setup = 50, //⽤来延时,等待SCL被释放.adap = { // i2c_adapter适配器结构体.name = "s3c2410-i2c",.owner = THIS_MODULE,.algo = &s3c24xx_i2c_algorithm, //存放i2c_algorithm算法结构体.retries = 2, //重试次数.class = I2C_CLASS_HWMON,},};显然这⾥是直接设置了i2c_adapter结构体,所以在s3c24xx_i2c_probe ()函数中没有分配i2c_adapter适配器结构体,其中, i2c_adapter结构体的名称等于"s3c2410-i2c",它的通信⽅式等于s3c24xx_i2c_algorithm,重试次数等于2PS:如果缺少i2c_algorithm的i2c_adapter什么也做不了,就只是个I2C设备,⽽没有通信⽅式s3c24xx_i2c_algorithm中的关键函数master_xfer()就是⽤于产⽣i2c访问周期需要的start stop ack等信号⽐如,在s3c24xx_i2c_algorithm中的关键函数master_xfer()⾥,调⽤了:s3c24xx_i2c_xfer -> s3c24xx_i2c_doxfer()->s3c24xx_i2c_message_start()来启动传输message信息, 其中s3c24xx_i2c_message_start()函数代码如下:static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg){unsigned int addr = (msg->addr & 0x7f) << 1; //IIC从设备地址的最低位为读写标志位... ...stat = 0;stat |= S3C2410_IICSTAT_TXRXEN; //设置标志位启动IIC收发使能if (msg->flags & I2C_M_RD) { //判断是读,还是写stat |= S3C2410_IICSTAT_MASTER_RX;addr |= 1; //设置从IIC设备地址为读标志} elsestat |= S3C2410_IICSTAT_MASTER_TX;s3c24xx_i2c_enable_ack(i2c); //使能ACK信号 iiccon = readl(i2c->regs + S3C2410_IICCON); //读出IICCON寄存器writel(stat, i2c->regs + S3C2410_IICSTAT); //写⼊IICSTAT寄存器,使能IIC的读或写标志dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);writeb(addr, i2c->regs + S3C2410_IICDS); //将IIC从设备地址写⼊IICDS寄存器/* delay here to ensure the data byte has gotten onto the bus* before the transaction is started */ndelay(i2c->tx_setup); //延时,等待SCL被释放,下⾯便可以发送起始信号+IIC设备地址值dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);writel(iiccon, i2c->regs + S3C2410_IICCON);stat |= S3C2410_IICSTAT_START;writel(stat, i2c->regs + S3C2410_IICSTAT); //设置IICSTAT寄存器的bit5=1,开始发送起始信号+IIC从设备地址值,并回应ACK}通过上⾯的代码和注释,发现主要是写⼊IIC从设备地址,然后发送起始信号+IIC从设备地址值,并回应ACK显然IIC总线驱动i2c-s3c2410.c,主要设置适配器adapter,⾥⾯帮我们做好了IIC通信的架构,就是不知道发什么内容我们进⼊driver/i2c/chips中,看看eeprom设备驱动是如何写的参考: driver/i2c/chips/eeprom.c6.还是⾸先来看它的init⼊⼝函数:其中struct i2c_driver eeprom_driver的成员如下:static struct i2c_driver eeprom_driver = {.driver = {.name = "eeprom", //名称},.id = I2C_DRIVERID_EEPROM, //IIC设备标识ID.attach_adapter = eeprom_attach_adapter, //⽤来与总线驱动的适配器匹配,匹配成功添加到适配器adapter中.detach_client = eeprom_detach_client, //与总线驱动的适配器解绑,分离这个IIC从设备};如下图所⽰, eeprom_driver结构体的ID成员在i2c-id.h中,⾥⾯还定义了⼤部分常⽤I2C设备驱动的设备ID显然,在init函数中通过i2c_add_driver()注册i2c_driver结构体,然后通过i2c_driver ->attach_adapter来匹配内核中的各个总线驱动的适配器, 发送这个设备地址,若有ACK响应,表⽰匹配成功7.接下来,我们进⼊i2c_add_driver()来看看是不是这样的int i2c_add_driver(struct module *owner, struct i2c_driver *driver){driver->driver.owner = owner;driver->driver.bus = &i2c_bus_type; //将i2c_driver放在i2c_bus_type链表中res = driver_register(&driver->driver); //注册⼀个i2c_driver... ...if (driver->attach_adapter) {struct i2c_adapter *adapter; //定义⼀个i2c_adapter适配器list_for_each_entry(adapter, &adapters, list) //for循环提取出adapters链表中所有的i2c_adapter适配器,放⼊到adapter结构体中 {driver->attach_adapter(adapter); //来匹配取出来的i2c_adapter适配器}}... ...return0;}在i2c_add_driver ()函数⾥主要执⾏以下⼏步:①放⼊到i2c_bus_type链表②取出adapters链表中所有的i2c_adapter,然后执⾏i2c_driver->attach_adapter()所以i2c_adapter适配器和i2c_driver设备驱动注册框架如下所⽰:这⾥调⽤了i2c_driver ->attach_adapter(adapter),我们看看⾥⾯是不是通过发送IIC设备地址,等待ACK响应来匹配的8.以struct i2c_driver eeprom_driver 为例,进⼊i2c_driver ->eeprom_attach_adapter()函数如下图所⽰,⾥⾯调⽤了i2c_probe(adapter, &addr_data, eeprom_detect)函数上图的第1个参数就是i2c_adapter适配器,第2个参数addr_data变量,⾥⾯存放了IIC设备地址的信息,第3个参数eeprom_detect就是具体的设备探测回调函数i2c_probe()函数,会通过adapter适配器发送IIC设备地址addr_data,如果收到ACK信号,就调⽤eeprom_detect()回调函数来注册i2c_client结构体,该结构体对应真实的物理从设备,⽽i2c_driver对应的是设备驱动,也就是说,只有当适配器⽀持这个设备驱动,才会注册i2c_client从设备,后⾯会讲这个回调函数如何注册i2c_client⽽在i2c_driver ->detach_client()中,则注销i2c_client结构体其中addr_data变量是struct i2c_client_address_data结构体,它的成员如下所⽰:struct i2c_client_address_data {unsigned short *normal_i2c; //存放正常的设备⾼7位地址数据unsigned short *probe; //存放不受*ignore影响的⾼7位设备地址数据unsigned short *ignore; //存放*ignore的⾼7位设备地址数据unsigned short **forces; //forces表⽰适配器匹配不了该设备,也要将其放⼊适配器中};当上⾯结构体的数组成员以I2C_CLIENT_END结尾,则表⽰地址已结束,⽐如at24c02设备为例,看这个结构体如何定义的:#define AT24C02_ADDR (0xA0>>1) //AT24C02地址static unsigned short ignore[] = { I2C_CLIENT_END };static unsigned short normal_addr[] = { AT24C02_ADDR, I2C_CLIENT_END };static unsigned short force_addr[] = {ANY_I2C_BUS, AT24C02_ADDR ,2C_CLIENT_END};static unsigned short * forces[] = {force_addr, NULL}; //ANY_I2C_BUS:表⽰⽀持所有适配器总线,若填指定的适配器总线ID,则表⽰该设备只⽀持指定的那个适配器static struct i2c_client_address_data addr_data = {.normal_i2c = normal_addr, //存放at24c02地址.probe = ignore, //表⽰⽆地址.ignore = ignore, //表⽰⽆地址. forces = forces, //存放强制的at24c02地址,表⽰强制⽀持};⼀般⽽⾔,都不会设置.forces成员,这⾥只是打个⽐⽅8.1接下来继续进⼊i2c_probe()函数继续分析,如下所⽰:int i2c_probe(struct i2c_adapter *adapter,struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int)){... ...err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);}⾥⾯调⽤了i2c_probe_address()函数,从名称上来看,显然它就是⽤来发送起始信号+设备地址,来探测IIC设备地址⽤的8.2进⼊i2c_probe_address()函数:static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int)){/*判断设备地址是否有效,addr⾥存放的是设备地址前7位,⽐如AT24C02=0xA0,那么addr=0x50*/if (addr < 0x03 || addr > 0x77) {dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr); //打印地址⽆效,并退出return -EINVAL;}/*查找链表中其它IIC设备的设备地址,若这个设备地址已经被使⽤,则return*/if (i2c_check_addr(adapter, addr))return0;if (kind < 0) {if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0) //进⼊I2C传输函数 return0;... ...}8.3 其中i2c_smbus_xfer()传输函数如下:s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){s32 res;flags &= I2C_M_TEN | I2C_CLIENT_PEC;if (adapter->algo->smbus_xfer) { //如果adapter适配器有smbus_xfer这个函数mutex_lock(&adapter->bus_lock); //加互斥锁res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data); //调⽤adapter适配器⾥的传输函数mutex_unlock(&adapter->bus_lock); //解互斥锁} else//否则使⽤默认函数传输设备地址res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);return res;}看了上⾯代码后,显然我们的s3c2410-i2c适配器没有algo->smbus_xfer函数,⽽是使⽤i2c_smbus_xfer_emulated()函数,如下图所⽰:PS:通常适配器都是不⽀持的,使⽤默认的i2c_smbus_xfer_emulated()函数8.4 接下来看i2c_smbus_xfer_emulated()函数如何传输的:static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size, union i2c_smbus_data * data) {unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3]; //属于 msg[0]的buf成员unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2]; //属于 msg[1]的buf成员int num = read_write == I2C_SMBUS_READ?2:1; //如果为读命令,就等于2,表⽰要执⾏两次数据传输struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 }, { addr, flags | I2C_M_RD, 0, msgbuf1 }}; //定义两个i2c_msg结构体,msgbuf0[0] = command; //IIC设备地址最低位为读写命令... ...if (i2c_transfer(adapter, msg, num) < 0)return -1;/*设置i2c_msg结构体成员*/if (read_write == I2C_SMBUS_READ)switch(size) {... ...case I2C_SMBUS_BYTE_DATA: //如果是读字节if (read_write == I2C_SMBUS_READ)msg[1].len = 1;else {msg[0].len = 2;msgbuf0[1] = data->byte;}break;... ...}... ...if (i2c_transfer(adapter, msg, num) < 0) //将 i2c_msg结构体的内容发送给I2C设备return -1;... ...}其中i2c_msg结构体的结构,如下所⽰:struct i2c_msg {__u16 addr; //I2C从机的设备地址__u16 flags; //当flags=0表⽰写, flags= I2C_M_RD表⽰读__u16 len; //传输的数据长度,等于buf数组⾥的字节数__u8 *buf; //存放数据的数组};上⾯代码中之所以读操作需要两个i2c_msg,写操作需要⼀个i2c_msg,是因为读IIC设备是两个流程在上⼀节IIC接⼝下的24C02 驱动分析:⾥就已经分析到了,只要发送⼀个S起始信号则就是⼀个i2c_msg,如下两个读写操作图所⽰:⽽在i2c_transfer()函数中,最终⼜是调⽤了之前分析的i2c_adapter->algo->master_xfer()发送函数,如下图所⽰:其中i2c_transfer()的参数*adap表⽰通过哪个适配器传输出去,msgs表⽰I2C消息,num表⽰msgs的数⽬内核每发送⼀个Msg都会先发出S开始信号和设备地址.直到所有Msg传输完毕,最后发出P停⽌信号。
linux自带i2c工具使用总线被全球超过50个公司的1000+个ICs所使用,已然是一个世界标.另外,I2C总线与多种不同的控制总线是兼容的,比如SMBus(系统管理),PMBus(电源管理总线),IPMI(智能平台管理总线),DDC(显示数据通)以及ATCA(高级电信架构).如果没记错的话,linux中的I2C框架是完SMBus的.要全面了解I2C,可以从《I2C-bus specification and user manual》看起.I2C最初是由Philips提出的,那么这文档就是由NXP维护的.我们接下来要了解I2C总线是如何工作的,在一个具体应用中如何设计.I2C的数据传输,握手以及总线仲裁机制都需要了解.I2C总线在每种操作模式下的时序和电气特性都需要了解,这里还是从嵌入式软件工程师的角度着重了解时序特性.安装工具:apt-get install i2c-toolsI2C总线的特性:在消费电子,通信以及工业电子中,看起来不相关的设计中有很多类似的地方.例如,基本上每个系统都包括:1.一些智能控制,通常是单芯片的微控制器2.通用的电路:LCD和LED驱动器,远程IO口,RAM,EEPROM,RTC 或者DAC\ADC.3.面向应用的电路:比如收音机和视频系统的数字调谐和信号处理电路,温度传感器和智能卡.为了让这些共同之处对于系统设计者和设备厂商都有用,也为了最大化硬件的有效性和电路的简单性,Philips半导体开发了一个简单的双向2线的总线,实现了IC之间的有效控制.这个总线就被称为I2C总线.所有兼容I2C总线的设备都包含一个片上的接口,允许它们直接通过I2C 总线进行通信.这个设计解决了在设计数字控制电路时带来的很多接口问题.下面是I2C总线的一些特性:1.只需要两根线,一个串行数据线(SDA)额一个串行时钟线(SCL).2.可以通过软件方式和一个唯一的地址来寻找到每一个连接到总线的设备,简单的主从关系一直存在.主机可以作为主机发送器或者主机接收器.3.它是真正的多主机总线,如果有两个或多个主机初始化数据传输,可以通过冲突检测和仲裁来防止数据被破坏.4.串行的8位双向数据传输在标准模式下达到100kb/s,快速模式下是400kb/s,超速模式下是3.4Mb/s.5.串行的8位单向数据在快速模式下可以达到5Mb/s.6.片上的滤波器可以保护数据完整性.7.总线上连接的最大IC数量由总线最大的电容所限制.系统设计者的好处:由于I2C总线是个是一个标准的总线,不需要额外的接口.所以在系统升级或者修改时,可以简单的换IC.厂商的好处:符合I2C总线的IC不仅帮助了设计者,同样给设备厂商很多好处,因为:1.I2C只有两根线,所以IC的pin脚会少,PCB的面积会小,成本会降.2.完整的I2C总线协议不需要地址译码器或其他逻辑电路.这只是一些好处.另外,符合I2C总线的IC会增加系统设计的灵活性.IC很容易升级,比如需要一个更大的ROM,只需要选择一个有更大ROM的微控制器就可以了.IC设计者的好处:微控制器的设计者经常要考虑输出pin脚.I2C协议允许在没有单独寻址信号和芯片使能信号的条件下各种外设的连接.另外,带I2C接口的微控制器在市场上更受欢迎,因为有很多外设可以选.通过i2cdetect -l指令可以查看TX1上的I2C总线,从返回的结果来看TX1含有七个I2C总线。
linux驱动学习i2c驱动架构davinc dm368 i2c驱动分析但是Linux的i2c驱动体系结构却有相当的复杂度,不管是叫linux i2c驱动还是单片机i2c驱动,其根本还是操作soc芯片内部的i2c模块(也叫i2c adapter)(读写i2c相关的寄存器)来产生start、stop还有ack信号而已。
linux设备驱动到底复杂在什么地方?假设soc芯片dm368有两个i2c adapter(368内部真正只有一个i2c模块):i2c_adapter1,i2c_adapter1;然后外部有三个i2c接口的设备i2c_device1,i2c_device2,i2c_device3。
现在要求在裸机下写出他们的驱动函数。
那么肯定要写出6个不同的驱动函数:i2c_adapter1_ReadWrite_i2c_device1();i2c_adapter1_ReadWrite_i2c_device2()i2c_adapter1_ReadWrite_i2c_device3()i2c_adapter2_ReadWrite_i2c_device1()i2c_adapter2_ReadWrite_i2c_device2()i2c_adapter2_ReadWrite_i2c_device3()设想一共有m个i2c adapter和n个外设i2c device,那么将需要m*n个驱动。
并且这m*n个驱动程序必要会有很大部分重复的代码,而且不利于驱动程序的移植。
如果采用adapter和device分离的思想来写这样的驱动会是怎样呢?图1这样分离之后,只需要m+n个驱动,而且Adapter和Device的几乎没有耦合性,增加一个Adapter或者device并不会影响其余的驱动。
这就是分离思想带来的好处。
除此之外,linux虽然是C写的,但是大量使用了面向对象的变成方法(可以理解为分层的思想),仅仅分离细想和分层思想的引入,就大大增加了linux设备驱动的复杂度。
Linux下I2C设备驱动开发和实现2008-05-16嵌入式在线收藏|打印I2C总线具有结构简单使用方便的特点。
本文描述了Linux下I2C驱动的结构,并在此基础上给出了I2C设备驱动和应用的实现。
1引言I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。
I2C总线最主要的优点是其简单性和有效性。
由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。
I2C总线最初为音频和视频设备开发,现已应用于各种服务与管理场合,来实现配置或掌握组件的功能状态,如电源、系统风扇、系统温度等参数,增加了系统的安全性,方便了管理。
2I2C总线概述I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,每个器件都有一个惟一的地址识别。
I2C规程运用主/从双向通讯。
器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。
主器件和从器件都可以工作于接收和发送状态。
总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。
SDA 线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA 状态的改变被用来表示起始和停止条件。
I2C总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。
CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。
若未收到应答信号,由判断为受控单元出现故障。
学会嵌入式Linux下I2C的接口调试1). 简介I2C是嵌入式设备最为常用的接口之一,常用于如下面这些应用场景,因此本文就基于嵌入式Linux演示在User Space进行I2C设备调试。
- Digital to Analog converter- EEPROM- Real Time Clock- Touch screen LCD- Audio codec本文所演示的平台来自于Toradex Apalis iMX6Q ARM嵌入式平台,这是一个基于NXP iMX6Q ARM处理器,支持四核心Cortex-A9。
2. 准备a).Apalis iMX6Q ARM核心版配合Apalis Ixora载板,连接调试串口UART1到开发主机方便调试,同时配置好Ubuntu开发主机开发环境,具体操作方法可以参考这里。
b).Apalis iMX6Q系统使用Toradex Linux Release V2.6.1,下载和更新方法请参考这里。
3). I2C总线user space操作命令测试a). Apalis iMX6Q核心版默认定义提供了三个I2C总线可供外部使用(i2c-2为核心板内部电源管理使用),如下所示,其中i2c-1为通用I2C接口;i2c-0为DDC接口,用于连接HDMI DDC/EDID接口,不能用做通用I2C接口;而i2c-3通常用于连接camera接口使用,不过也可以用做通用I2C接口。
b). 本文演示示例则通过读写Apalis Ixora载板连接在i2c-1总线上面的2Kb EEPROMc). User Space下通过I2C tools直接操作i2c-1总线进行访问EEPROM./ 查看Apalis iMX6Q的所有I2C总线---------------------------------root@apalis-imx6:~# ls -l /dev/i2c-*。
LinuxI2C驱动整理(以RK3399Pro+Kernel4.4为例)⼀. Linux I2C驱动架构Linux内核⾥,I2C驱动框架可以分为两层,adapter驱动和deivce驱动。
Adapter驱动也可以理解为I2C总线驱动,指的是SOC⾥的I2C控制器驱动。
⼀个SOC可能包含多个I2C控制器,⽽每个控制器的使⽤⽅式是相同的(寄存器参数、收发数据的⽅法等),因此多个控制器可以共⽤⼀套adapter驱动;Deivce驱动,对应的是SOC外围的I2C设备,不同类型I2C设备需要开发不同的设备驱动,同⼀类型的I2C设备可以使⽤⼀种驱动,但是每⼀个I2C设备都由⼀个唯⼀的client来描述。
⼆. Adapter配置DTSI⽂件(kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi)描述了RK3399Pro所有的I2C控制器信息:i2c0: i2c@ff3c0000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff3c00000x00x1000>;clocks = <&pmucru SCLK_I2C0_PMU>, <&pmucru PCLK_I2C0_PMU>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 57 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c0_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c1: i2c@ff110000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1100000x00x1000>;clocks = <&cru SCLK_I2C1>, <&cru PCLK_I2C1>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c1_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c2: i2c@ff120000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1200000x00x1000>;clocks = <&cru SCLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 35 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c2_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c3: i2c@ff130000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1300000x00x1000>;clocks = <&cru SCLK_I2C3>, <&cru PCLK_I2C3>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c3_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c5: i2c@ff140000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1400000x00x1000>;clocks = <&cru SCLK_I2C5>, <&cru PCLK_I2C5>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 38 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c5_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c6: i2c@ff150000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1500000x00x1000>;clocks = <&cru SCLK_I2C6>, <&cru PCLK_I2C6>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c6_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c7: i2c@ff160000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff1600000x00x1000>;clocks = <&cru SCLK_I2C7>, <&cru PCLK_I2C7>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c7_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c4: i2c@ff3d0000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff3d00000x00x1000>;clocks = <&pmucru SCLK_I2C4_PMU>, <&pmucru PCLK_I2C4_PMU>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 56 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c4_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};i2c8: i2c@ff3e0000 {compatible = "rockchip,rk3399-i2c";reg = <0x00xff3e00000x00x1000>;clocks = <&pmucru SCLK_I2C8_PMU>, <&pmucru PCLK_I2C8_PMU>;clock-names = "i2c", "pclk";interrupts = <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH 0>;pinctrl-names = "default";pinctrl-0 = <&i2c8_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";};可以看出,该SOC共有9个I2C控制器,分别为I2C0~I2C8, 每个控制器对应了不同的寄存器基地址(例如I2C0对应0xff3c0000),它们的compatible匹配属性都是"rockchip,rk3399-i2c",也就是对应了同⼀个adapter驱动。
一、概述I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.二、用户态实现设备驱动在Linux内核代码文件i2c-dev.c中实现了I2C适配器设备文件的功能,针对每个适配器生成一个主设备号为89的设备节点(次设备号为0-255),I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用户空间的应用层就可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
i2c适配器的设备节点是/dev/i2c-x,其中x是数字。
由于适配器编号是动态分配的(和注册次序有关),所以想了解哪一个适配器对应什么编号,可以查看/sys/class/i2c-dev/目录下的文件内容。
三、用户态调用3.1、i2c-dev用户空间操作i2c,需要包含以下头文件。
打开适配器对应的设备节点i2c-dev为打开的线程建立一个i2c_client,但是这个i2c_client并不加到i2c_adapter的client链表当中。
他是一个虚拟的临时client,当用户打开设备节点时,它自动产生,当用户关闭设备节点时,它自动被释放。
3.2、ioctl()查看include/linux/i2c-dev.h文件,可以看到i2c支持的IOCTL命令1.#define I2C_RETRIES0x0701/*设置收不到ACK时的重试次数*/2.#define I2C_TIMEOUT0x0702/*设置超时时限的jiffies*/3.#define I2C_SLAVE0x0703/*设置从机地址*/4.#define I2C_SLAVE_FORCE0x0706/*强制设置从机地址*/5.#define I2C_TENBIT0x0704/*选择地址位长:=0for7bit,!=0for10bit*/6.#define I2C_FUNCS0x0705/*获取适配器支持的功能*/7.#define I2C_RDWR0x0707/*Combin ed R/W transfer(one STOP only)*/8.#define I2C_PEC0x0708/* !=0to use PEC with SMBus*/9.#define I2C_SMBUS0x0720/*SMBus transfer*/例如:1、设置重试次数:ioctl(fd, I2C_RETRIES,m);设置适配器收不到ACK时重试的次数为m。
默认的重试次数为12、设置超时ioctl(fd, I2C_TIMEOUT,m);设置SMBus的超时时间为m,单位为jiffies。
3、设置从机地址ioctl(fd, I2C_SLAVE,addr);ioctl(fd, I2C_SLAVE_FORCE, addr);在调用read()和write()函数之前必须设置从机地址。
这两行都可以设置从机的地址,区别是第二行无论内核中是否已有驱动在使用这个地址都会成功,第一行则只在该地址空闲的情况下成功。
由于i2c-dev 创建的i2c_client不加入i2c_adapter的client列表,所以不能防止其它线程使用同一地址,也不能防止驱动模块占用同一地址。
4、设置地址模式ioctl(file,I2C_TENBIT,select)如果select不等于0选择10bit地址模式,如果等于0选择7bit模式,默认7位模式。
3.3数据包i2c发送或者接收一次数据都以数据包( struct i2c_msg )封装addr是设备从地址。
flags是通信标志,发送数据为0,接收数据为I2C_M_RD。
len是数据长度buf是传输数据3.4、接受数据设备驱动中我们通常调用/driver/i2c/i2c-core.c 定义的接口i2c_master_recv 来接收一次数据。
通过i2c_transfer调用数据包。
int i2c_master_recv(struct i2c_client *client, char *buf ,int count){struct i2c_adapter *adap=client->adapter; // 获取adapter信息struct i2c_msg msg; // 定义一个临时的数据包int ret;msg.addr = client->addr; // 将从机地址写入数据包msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包msg.flags |= I2C_M_RD; // 将此次通信的标志并入数据包msg.len = count; // 将此次接收的数据字节数写入数据包msg.buf = buf;ret = i2c_transfer(adap, &msg, 1); // 调用平台接口接收数据/* If everything went ok (i.e. 1 msg transmitted), return #bytestransmitted, else error code. */return (ret == 1) ? count : ret; // 如果接收成功就返回字节数}EXPORT_SYMBOL(i2c_master_recv);参考驱动i2c_master_recv()函数封装属于自己用户态的接受函数。
用户态是通过ioctl(handle->fd, I2C_RDWR, &data)函数与i2c从设备进行数据交互。
主要有2个步骤:首先是写入需要读取的寄存器的地址,然后从寄存器中读取数据。
需要2个数据包。
如下:3.5、发送数据设备驱动中我们通常调用/driver/i2c/i2c-core.c 定义的接口i2c_master_send来发送一次数据。
通过i2c_transfer调用数据包int i2c_master_send(struct i2c_client *client,const char *buf ,int count){int ret;struct i2c_adapter *adap=client->adapter; // 获取adapter信息struct i2c_msg msg; // 定义一个临时的数据包msg.addr = client->addr; // 将从机地址写入数据包msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包msg.len = count; // 将此次发送的数据字节数写入数据包msg.buf = (char *)buf; // 将发送数据写入数据包ret = i2c_transfer(adap, &msg, 1); // 调用平台接口发送数据/* If everything went ok (i.e. 1 msg transmitted), return #bytestransmitted, else error code. */return (ret == 1) ? count : ret; // 如果发送成功就返回字节数}EXPORT_SYMBOL(i2c_master_send);参考驱动i2c_master_send()函数封装属于自己用户态的接受函数。
用户态是通过ioctl(handle->fd, I2C_RDWR, &data)函数与i2c从设备进行数据交互。
每次要写入两个字节数据主要包括写入的寄存器地址和要写入的数据。
只需发送一次数据包。
如下:3.6、使用案例1.#include<stdio.h>2.#include<linux/i2c.h>3.#include<linux/i2c-dev.h>4.#include<fcntl.h>5.#include<stdio.h>6.#include<unistd.h>7.#include<sys/ioctl.h>8.#include<string.h>9.#include<stdlib.h>10.11.#define I2C_FILE_NAME "/dev/i2c-1"12.#define I2C_ADDR 0x4013.14.int fd;15.16.int i2c_open()17.{18.fd = open(I2C_FILE_NAME, O_RDWR);19.if(fd < 0){20.perror("Unable to open i2c control file");21.return 1;22.}23.}24.25.int i2c_write(int fd, unsigned char dev_addr, unsigned char reg_addr,unsigned char val)26.{27.int ret;28.unsigned char buf[2];29.struct i2c_rdwr_ioctl_data data;30.struct i2c_msg messages;31.32.buf[0] = reg_addr;33.buf[1] = val;34.messages.addr = dev_addr; //device address35.messages.flags = 0; //write36.messages.len = 2;37.messages.buf = buf; //data address38.39.data.msgs = &messages;40.data.nmsgs = 1;41.if(ioctl(fd, I2C_RDWR, &data) < 0){42.printf("write ioctl err\n");43.return 1;44.}leep(1000);46.47.return 1;48.}49.50.int i2c_read(int fd, unsigned char addr, unsigned char reg, unsigned char *val)51.{52.int ret;53.struct i2c_rdwr_ioctl_data data;54.struct i2c_msg messages[2];55.56.messages[0].addr = addr; //device address57.messages[0].flags = 0; //write58.messages[0].len = sizeof(reg);59.messages[0].buf = ® //data address60.61.messages[1].addr = addr; //device address62.messages[1].flags = I2C_M_RD; //read63.messages[1].len = sizeof(val);64.messages[1].buf = val;65.66.data.msgs = messages;67.data.nmsgs = 2;68.if(ioctl(fd, I2C_RDWR, &data) < 0){69.printf("read ioctl err\n");70.return 1;71.}72.73.return 0;74.}75.76.int main()77.{78.int i;79.unsigned char buf[4];80.unsigned char val[] = {0x04, 0x05, 0x06, 0x07};81.82.i2c_open();83.84.85.for(i =0; i< 4; i++)86.i2c_write(fd, I2C_ADDR, i, val[i]);87.88.89.memset(buf, 0x00, sizeof(buf));90.for(i = 0; i < sizeof(buf); i++){91.if(i2c_read(fd, I2C_ADDR, i, &buf[i])){92.printf("Unable to get register!\n");93.}94.}95.96.for(i=0; i< 4;i++)97.printf("buf[%d]=%d\n",i, buf[i]);98.99.}。