Linux下I2C驱动介绍
- 格式:doc
- 大小:167.00 KB
- 文档页数:7
Linux下I2C驱动架构全面分析I2C 概述I2C是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线。
因此,I2C总线被非常广泛地应用在EEPROM,实时钟,小型LCD等设备与CPU的接口中。
linux下的驱动思路在linux系统下编写I2C驱动,目前主要有两种方法,一种是把I2C设备当作一个普通的字符设备来处理,另一种是利用linux下I2C驱动体系结构来完成。
下面比较下这两种方法:第一种方法:优点:思路比较直接,不需要花很多时间去了解linux中复杂的I2C子系统的操作方法。
缺点:要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器(I2C控制器)操作。
要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可以移植性差。
对内核的资源无法直接使用,因为内核提供的所有I2C设备器以及设备驱动都是基于I2C 子系统的格式。
第一种方法的优点就是第二种方法的缺点,第一种方法的缺点就是第二种方法的优点。
I2C架构概述Linux的I2C体系结构分为3个组成部分:I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。
I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xferf()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter 的algo指针。
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。
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设备原理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"是总线号。
I2C设备驱动介绍I2C(Inter-Integrated Circuit)是一种串行通信协议,用于连接并使多个外部设备与主控制器进行通信。
在嵌入式系统中,I2C设备驱动起着至关重要的作用,负责将操作系统与I2C总线上的设备进行通信,促进数据的传输和交互。
1.初始化:驱动程序需要初始化I2C控制器,包括设置时钟频率、地址范围等。
2.设备注册:设备驱动需要在操作系统中注册I2C设备,以便操作系统能够识别和管理设备。
3.读写操作:驱动程序需要实现读写设备寄存器的功能,包括发送开始和停止信号、以及发送、接收数据等。
4.错误处理:驱动程序需要处理I2C通信过程中可能出现的错误,例如传输失败、设备无响应等情况。
5.中断处理:驱动程序需要支持I2C设备的中断机制,以便及时处理设备的状态变化或数据传输完成的中断信号。
6.电源管理:驱动程序需要支持设备的电源管理功能,包括设备的唤醒、睡眠等操作。
7.设备控制:驱动程序需要实现设备特定的控制功能,例如设置传感器的采样率、配置设备的工作模式等。
8. 虚拟文件系统接口:在Linux系统中,驱动程序通常通过虚拟文件系统接口(如/dev)与用户空间进行交互,提供读写设备寄存器的功能。
1.确定设备:首先,开发者应该确定需要驱动的I2C设备。
这可能包括传感器、EEPROM、显示器等。
2.确定硬件连接:确定I2C设备与主控制器之间的硬件连接和电气特性。
这包括设备的I2C地址、I2C总线上的物理接口等。
3.编写驱动程序:在操作系统中,开发者可以根据设备的文档或芯片厂商提供的驱动程序框架,编写自己的I2C设备驱动程序。
驱动程序需要实现上述提到的功能,并且根据设备的特点进行相应的适配和优化。
4.编译和测试:完成驱动程序的编写后,需要将其编译成与操作系统内核匹配的模块或静态链接库。
然后,通过加载驱动模块或重新编译内核来使驱动程序生效。
最后,进行测试,确保驱动程序在各种场景下的正常运行。
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的指针作为参数传递进来。
手把手教你写Linux I2C设备驱动Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。
Linux I2C驱动涉及的知识点还是挺多的,主要分为Linux I2C的总线驱动(I2C BUS Driver)和设备驱动(I2C Clients Driver),本文主要关注如何快速地完成一个具体的I2C 设备驱动(I2C Clients Driver)。
关于Linux I2C驱动的整体架构、核心原理等可以在网上搜索其他相关文章学习。
本文主要参考了Linux内核源码目录下的 ./Documentation/i2c/writing-clients 文档。
以手头的一款视频采集芯片TVP5158为驱动目标,编写Linux I2C设备驱动。
1. i2c_driver结构体对象每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象,该结构体包含了I2C 设备探测和注销的一些基本方法和信息,示例如下:1.static struct i2c_driver tvp5158_i2c_driver = {2. .driver = {3. .name = "tvp5158_i2c_driver",4. },5. .attach_adapter = &tvp5158_attach_adapter,6. .detach_client = &tvp5158_detach_client,7. .command = NULL,8.};其中,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detac h_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数,后面将详细讲述。
i2c_register_driver函数详解在嵌入式软件开发中,I2C(Inter-Integrated Circuit)总线是一种常用的串行通信接口,用于在微控制器和外部设备之间传输数据。
i2c_register_driver函数是Linux内核中一个重要的函数,用于注册I2C 驱动程序。
本文将详细解析i2c_register_driver函数的功能、参数和应用。
一、i2c_register_driver函数概述i2c_register_driver函数是在Linux内核中注册一个I2C驱动程序的函数。
它的作用是将驱动程序与对应的I2C适配器绑定,使得操作系统能够正确地识别和管理该驱动程序。
在驱动程序注册后,当相应的I2C设备连接到系统时,驱动程序将会自动加载并为该设备提供服务。
二、i2c_register_driver函数参数i2c_register_driver函数包含一个结构体参数,该结构体用于指定驱动程序的相关信息和功能。
1. struct i2c_driverstruct i2c_driver是一个定义I2C驱动程序的结构体,包含了以下重要的成员:- .driver:指向内核的struct device_driver结构体,用于描述驱动程序的信息,如名称、文件操作方法等。
- .probe:指向I2C设备探测函数的指针,用于在设备连接时进行初始化和配置。
- .remove:指向I2C设备移除函数的指针,用于在设备断开连接时进行清理和释放资源。
- .id_table:指向I2C设备ID表的指针,用于匹配设备和驱动程序。
2. I2C设备探测函数(probe函数)I2C设备探测函数是I2C驱动程序的核心功能之一,在I2C设备连接到系统时被调用。
该函数的作用是检测和初始化I2C设备,并将设备与驱动程序进行绑定。
在probe函数中,可以执行一系列必要的操作,如配置寄存器、分配内存、注册字符设备等。
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停⽌信号。
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驱动。
RK Linux下I2C总线驱动读写原理RK Linux,一个开源的、强大的、可定制的操作系统,广泛应用于各种嵌入式系统。
I2C总线是一种常用的通信协议,常用于连接低速外围设备,如传感器、EEPROM等。
在RK Linux下,I2C总线驱动的读写原理是什么呢?首先,我们来了解下I2C总线的基本概念。
I2C总线是一种双线串行通信总线,由数据线SDA和时钟线SCL组成。
通过这两根线,多个设备可以在同一总线上进行通信。
每个设备都有一个唯一的地址,主机可以通过发送设备的地址来选择与之通信的设备。
在RK Linux下,I2C总线驱动的读写操作主要依赖于内核提供的API。
这些API 包括i2c_read()、i2c_write()等,它们提供了与I2C设备通信的接口。
那么,这些API是如何实现读写操作的呢?在内核中,I2C驱动程序负责管理I2C总线上所有的设备和它们的通信。
当需要从设备读取数据时,驱动程序首先会向设备发送读请求。
设备接收到请求后,会将数据写入SDA线。
驱动程序会持续监听SDA线,一旦接收到数据,就会将其保存并通知应用程序。
同样地,当需要向设备写入数据时,驱动程序会向设备发送写请求。
设备接收到请求后,会准备好接收数据。
驱动程序会将数据写入SDA线,设备接收到数据后,会将数据保存到内部寄存器中。
需要注意的是,I2C总线的读写操作都是通过驱动程序来完成的。
应用程序只需要调用内核提供的API即可与I2C设备进行通信。
这样设计的好处是应用程序可以专注于自己的业务逻辑,而不需要关心底层的通信细节。
同时,这也使得应用程序与具体的I2C设备无关,具有更好的可移植性和扩展性。
最近因为工作需要涉及到了I2C总线。
虽然我过去用过I2c,但看了 Linux kernel 后才发现,一个 layer 能被做到这样完善。
1.Linux的I2C驱动架Linux中I2C总线的驱动分为两个部分,总线驱动(BUS)和设备驱动(DEVICE)。
其中总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。
但是总线驱动本身并不会进行任何的通讯,它只是存在在那里,等待设备驱动调用其函数。
设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。
通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。
1.1. 总线驱动在系统开机时,首先装载的是I2C总线驱动。
一个总线驱动用于支持一条特定的I2C总线的读写。
一个总线驱动通常需要两个模块,一个struct i2c_adapter和一个struct i2c_algorithm来描述:static struct i2c_adapter pb1550_board_adapter ={name: "pb1550 adapter",id: I2C_HW_AU1550_PSC,algo: NULL,algo_data: &pb1550_i2c_info,inc_use: pb1550_inc_use,dec_use: pb1550_dec_use,client_register: pb1550_reg,client_unregister: pb1550_unreg,client_count: 0,};这个样例挂接了一个叫做“pb1550 adapter”的驱动。
但这个模块并未提供读写函数,具体的读写方法由第二个模块,struct i2c_algorithm提供。
static struct i2c_algorithm au1550_algo ={.name = "Au1550 algorithm",.id = I2C_ALGO_AU1550,.master_xfer = au1550_xfer,.functionality = au1550_func,};i2c_adap->algo = &au1550_algo;这个样例给上述总线驱动增加了读写“算法”。
LinuxI2C驱动框架⼀、I2C总线概述I2C是由Philips公司开发的⼀种简单的、双向同步串⾏总线,它只需要两条线即可在连接于总线上的器件之间传送信息,其硬件连接框图如下所⽰:SCL:串⾏时钟线,数据传输过程中⽤于同步的时钟信号,低电平时允许SDA线上数据改变。
SDA:串⾏数据线,在时钟信号作⽤下,数据按位在数据线上进⾏传输。
I2C总线上的设备之间通信都要遵从I2C总线协议,I2C总线由起始信号、停⽌信号、应答信号、⾮应答信号组成。
起始信号:当时钟线SCL为⾼期间,数据线SDA由⾼到低的跳变。
停⽌信号:当时钟线SCL为⾼期间,数据线SDA由低到⾼的跳变。
应答信号(ACK):应答位为低电平时,规定为有效应答位,表⽰接收器已经成功接收到该字节。
⾮应答信号(NACK):应答位为⾼电平时,规定为⾮应答位,⼀般表⽰接收器接收该字节没有成功。
挂接在同⼀条I2C总线上的设备都要⾃⼰的物理地址,I2C主机控制器在和设备通信前需要先发送设备的地址,设备接收到总线上传过来的地址,看是否是⾃⼰的地址,如果是产⽣后续的应答。
主机控制器和设备通信⼀般是由⼀个起始信号开始和⼀个停⽌信号结束,地址信息⼀般是7bit,发送地址的最后⼀位代表数据传输的⽅向,1表⽰是读,0表⽰写操作,其发送时序⼀般如下所⽰:主机发送数据主机读取数据前⾯对I2C总线的⼀些基本概念和I2C协议的做了简单的介绍,下⾯开始来分析Linux内核的I2C驱动框架,看看内核中如何实现对I2C设备的⽀持。
⼆、Linux内核I2C驱动1、⼏个重要对象内核中的I2C驱动框架使⽤了总线设备驱动模型,在分析内核I2C驱动之前,先讨论这⼏个重要的数据结构。
1.1、I2C总线I2C总线是⼀条虚拟的bus总线(同platform总线⼀样,位于/sys/bus⽬录),其在drivers\i2c\i2c-core.c实现,具体内容如下:struct bus_type i2c_bus_type = {.name = "i2c",.dev_attrs = i2c_dev_attrs,.match = i2c_device_match,.uevent = i2c_device_uevent,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,.suspend = i2c_device_suspend,.resume = i2c_device_resume,};这个i2c总线结构管理着I2C设备与I2C驱动的匹配、删除等操作。
Linux i2c驱动详细分析.首先在S3C2440平台的初始化函数中,主要是将开发平台的设备注册进了系统,也就是将device注册到了platform虚拟的总线上,并进行了一些初始化的工作,这里我们只关注I2C的部分。
static void __init smdk2440_machine_init(void){s3c24xx_fb_set_platdata(&smdk2440_fb_info);s3c_i2c0_set_platdata(NULL);platform_add_devices(smdk2440_devices,ARRAY_SIZE(smdk2440_devices));smdk_machine_init();}s3c_i2c0_set_platdata()函数将S3C2440上的I2C控制器进行了一些初始化,但是并没有写入硬件寄存器,仅仅是保存在了s3c2410_platform_i2c结构体中。
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd){struct s3c2410_platform_i2c *npd;if (!pd)pd = &default_i2c_data0;npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c),GFP_KERNEL);if (!npd)printk(KERN_ERR "%s: no memory for platform data\n",__func__);else if (!npd->cfg_gpio)npd->cfg_gpio = s3c_i2c0_cfg_gpio;/* s3c_i2c0_cfg_gpio为 配置I2C控制器GPIO函数指针 */ s3c_device_i2c0.dev.platform_data = npd;/*最后将struct device 中的platform_data指针直指向了初始化后的 s3c2410_platform_i2c结构体 */}函数s3c_i2c0_cfg_gpio()很简单,实际上就是配置GPIO为I2C的工作模式void s3c_i2c0_cfg_gpio(struct platform_device *dev){s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL); }s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)函数实际上就是把初始化数据段中的default_i2c_data0结构体复制过来,然后对GPIO进行配置的函数指针进行了初始化。
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设备。
第二种:为I2C设备独立编写一个设备驱动注意:第二种方法不能用设备提供的I2C-dev.c5、I2C系统下的文件架构在linux下driver下面有个I2C目录,在I2C目录下包含以下文件和文件夹1)I2C-core.c 这个文件实现I2C核心功能以及/proc/bus/I2C*接口2)I2C-dev.c 实现I2C适配器设备文件的功能,每个I2C适配器被分配一个设备,通过适配器访问设备的时候,主设备号是89,此设备号是0-255. I2C-dev.c并没有针对特定设备而设计,只提供了read() write()和ioctl()等接口,应用层可以通过这些接口访问挂在适配器上的I2C设备存储空间和寄存器,并控制I2C设备的工作方式。
3)Chips 这个文件下面包含特定的I2C设备驱动。
4)Busses 这个文件包含一些I2C总线驱动。
5)Algos文件夹下实现了I2C总线适配器的algorithm6、重要结构体1)在内核中的I2C.h这个头文件中对I2C_driver;I2C_client;I2C_adapter和I2C_algorithm 这个四个结构体进行了定义。
理解这4个结构体的作用十分关键。
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;char name[48]; //适配器名称struct completion dev_released; //用于同步struct list_head userspace_clients; //client链表头};I2c_algorithm结构体struct i2c_algorithm {int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);//I2C传输函数指针int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, unioni2c_smbus_data *data);//smbus传输函数指针u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能};SMbus大部分基于I2C总线规范,SMbus不需要增加额外引脚。
与I2C总线相比,SMbus 增加了一些新的功能特性,在访问时序也有一定的差异。
i2c_driver结构体struct i2c_driver {unsigned int class;int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);void (*shutdown)(struct i2c_client *);int (*suspend)(struct i2c_client *, pm_message_t mesg);int (*resume)(struct i2c_client *);void (*alert)(struct i2c_client *, unsigned int data);int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表struct device_driver driver;const struct i2c_device_id *id_table;//该驱动所支持的设备ID表int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;};i2c_client结构体struct i2c_client {unsigned short flags;//标志unsigned short addr; //低7位为芯片地址char name[I2C_NAME_SIZE];//设备名称struct i2c_adapter *adapter;//依附的i2c_adapterstruct i2c_driver *driver;//依附的i2c_driverstruct device dev;//设备结构体int irq;//设备所使用的结构体struct list_head detected;//链表头};2)各结构体的作用和他们之间的关系i.I2C_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。
一个i2c适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。
缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位。
i2c_msg也很重要,代码清单如下:struct i2c_msg {__u16 addr;//设备地址__u16 flags;//标志__u16 len;//消息长度__u8 *buf;//消息数据};ii.i2c_driver与i2c_client i2c_driver对应一套驱动方法,其主要成员函数是probe(),remove(),suspend(),resume()等,另外id_table是该驱动所支持的I2C设备的ID表。
i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。
i2c_driver与i2c_client的关系是一对多,一个i2c_driver上可以支持多个同等类型的i2c_client。
i2c_client信息通常在BSP的板文件中通过i2c_board_info填充。
一般在arch/arm目录下的板文件中。
在I2C总线驱动i2c_bus_type的match()函数i2c_device_match()中,会调用i2c_match_id()函数匹配板文件中的ID和i2c_driver所支持的ID表。
iii.i2c_adpater与i2c_client i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附与i2c_adpater.由于一个适配器上可以连接多个I2C设备,所以就一个i2c_adpter也可以被多个i2c_client依附,i2c_adpter中包括依附与它的i2c_client的链表。
3) 编写驱动需要完成的工作编写具体的I2C驱动时,工程师需要处理的主要工作如下:i.提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生。
ii.提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针。
iii.实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针。