#导入Word文档图片# Linux下I2C驱动架构全面分析
Posted DS小龙哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#导入Word文档图片# Linux下I2C驱动架构全面分析相关的知识,希望对你有一定的参考价值。
- 物理接线I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
- I2C总线特征I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这个地址来确定与哪个器件进行通信,在通常的应用中,
我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。I2C总线上可挂接的设备数量受总线的最大电容400pF限制,如果所挂接的是相同型号的器件,则还受器件地位的限制。I2C总线数据传输速率在标准模式下可达 100kbit/s,快速模式下可达 400kbit/s,高速模式下可达3.4Mbit/s。一般通过 I2C总线接口可编程时钟来实现传输速率的调整。I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传。 - I2C总线协议1.I2C总线协议基本时序信号空闲状态:SCL和SDA都保持着高电平。起始条件:总线在空闲状态时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他2C器件无法访问总线。
① 停止条件:当SCL为高而SDA由低到高的跳变,表示产生一个停止条件。
② 应答信号: 每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为低,则 表示一个应答信号。
③ 非应答信号:每个字节传输完成后的下一个时钟信号,在SCL高电平期间,SDA为高,则表示一个应答信号。
注意:起始和结束信号总是由主设备产生。
17.1 Linux下的驱动思路
在linux系统下编写I2C驱动,目前主要有两种方法:
1)把I2C设备当作一个普通的字符设备来处理;
2)利用linux下I2C驱动体系结构(子系统)来完成。
下面比较下这两种方法:
第一种方法:
优点:思路比较直接,不需要花很多时间去了解linux中复杂的I2C子系统的操作方法。
缺点:
要求工程师不仅要对I2C设备的操作熟悉,而且要熟悉I2C的适配器(I2C控制器)操作。
要求工程师对I2C的设备器及I2C的设备操作方法都比较熟悉,最重要的是写出的程序可移植性差。
对内核的资源无法直接使用,因为内核提供的所有I2C设备器以及设备驱动都是基于I2C子系统的格式。
第一种方法的优点就是第二种方法的缺点.
第一种方法的缺点就是第二种方法的优点。
17.2 I2C架构概述
上图完整的描述了linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂。
I2C子系统由上到下分成3层:
层名 | 描述 |
I2C设备驱动层 | 真正实现具体设备的时序的代码。使用核心层提供API接口写,有特定编写框架。 |
I2C核心层 | 提供了I2C总线驱动和设备驱动的注册、注销方法、I2C通信方法(”algorithm”),与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。 提供了设备驱动层和适配器驱动层需要API接口,以及实现收发数据管理功能。起到一个连接上下两的作用。 |
I2C适配器驱动层 | 对I2C硬件体系结构中适配器端的实现。 适配器可由CPU控制,甚至可以直接集成在CPU内部。 通俗说就是直接操作硬件上IIC控制的的驱动代码。真正的实现IIC数据收发。 |
上面三层,设备驱动层需要自己写,核心层不变,由内核提供,I2C适配器驱动层一般由芯片厂商提供。
17.3 Linux下I2C体系文件构架
在Linux内核源代码中的driver目录下包含一个i2c目录。
文件 | 功能描述 |
i2c-core.c | 这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。 |
i2c-dev.c | 实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。 I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。 |
busses文件夹 | 这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c。 |
algos文件夹 | 实现了一些I2C总线适配器的algorithm。 |
[root@WBYQ /]# ls /dev/i2c-* -l crw-rw---- 1 root root 89, 0 Jul 12 01:30 /dev/i2c-0 crw-rw---- 1 root root 89, 1 Jul 12 01:30 /dev/i2c-1 crw-rw---- 1 root root 89, 2 Jul 12 01:30 /dev/i2c-2 crw-rw---- 1 root root 89, 3 Jul 12 01:30 /dev/i2c-3 crw-rw---- 1 root root 89, 7 Jul 12 01:30 /dev/i2c-7 crw-rw---- 1 root root 89, 8 Jul 12 01:30 /dev/i2c-8 [root@WBYQ /]# |
核心层:i2c-core.c i2c-boardinfo.c i2c-smbus.c i2c-mux.c
I2C适配器驱动层:
busses文件夹下的一个C文件对应于一个物理的I2C适配器驱动程序。
比如:EXYNOS4412 的I2C 适配器驱动i2c-s3c2410.c。
现在这里的两层我们都实现不了,linux系统和芯片厂家提供。
我们主要实现设备驱动层,下面主要讲解它了。
17.4 设备驱动层(重点)
17.4.0 设备驱动层结构
由设备层代码 + 驱动层代码构成,可以类比平台设备驱动模型。
17.4.1 驱动层核心结构
该核心结构在I2c.h(include\\Linux )下。内核使用 struct i2c_driver 结构描述一个I2C设备驱动,这个结构必须实现的是:probe,remove。
struct i2c_driver unsigned int class; /* Notifies the driver that a new bus has appeared or is about to be * removed. You should avoid using this, it will be removed in a 老接口,可能会消失,建议不要使用. */ int (*attach_adapter)(struct i2c_adapter *) __deprecated; int (*detach_adapter)(struct i2c_adapter *) __deprecated; /* 新的接口,用来替代上面两个接口,必须实现,功能类型平台模型的probe,remove*/ int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); /* driver model interfaces that dont relate to enumeration */ void (*shutdown)(struct i2c_client *); int (*suspend)(struct i2c_client *, pm_message_t mesg); int (*resume)(struct i2c_client *); /* Alert callback, for example for the SMBus alert protocol. * The format and meaning of the data value depends on the protocol. * For the SMBus alert protocol, there is a single bit of data passed * as the alert responses low bit ("event flag"). */ void (*alert)(struct i2c_client *, unsigned int data); /* a ioctl like command that can be used to perform specific functions * with the device. */ int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver; const struct i2c_device_id *id_table; /* Device detection callback for automatic device creation */ int (*detect)(struct i2c_client *, struct i2c_board_info *); const unsigned short *address_list; struct list_head clients; ; |
17.4.2 设备层核心结构
内核使用 struct i2c_client 来描述一个设备信息。
比如,器件地址,标志(器件地址位数:10位,7位等),所依赖的总线等。
内核定义的结构:
struct i2c_client ; 标志,比如说设备地址10位还是7位 unsigned short addr; 低7位为芯片地址 char name[I2C_NAME_SIZE];设备名称,随便,但是要和驱动层id_table相同 struct i2c_adapter *adapter;依附的 i2c_adapter,它表示一个IIC控制器 依附的i2c_driver 设备结构体 设备所使用的中断号 链表头 ; |
最小的情况下实现flags,addr,name,adapter
17.5 API函数
17.5.1 注册iic驱动
int i2c_add_driver(struct i2c_driver *driver) |
注册I2C设备驱动,driver 是已经填充好的struct i2c_driver结构指针
一般写在模块的初始化代码。
17.5.2 注销iic驱动
void i2c_del_driver(struct i2c_driver *driver) |
注销I2C设备驱动,
一般写在模块的出口处。
17.5.3 标准的发送数据函数
以下关于内核I2C核心层提供的标准发收函数:
int i2c_master_send(struct i2c_client *client, const char *buf , 发送函数 |
功能:发送数据给真正的硬件设备。
参数:
:指针I2C设备结构的指针。
:发送的数据指针
发送的字符数量
返回发送的字节数,失败返回-1。
注意:此函数只是实现标准IIC的写协议,不代表具体器件写协议。
如:要写数据给AT24C02 ,从内部地址10开始写,应该怎么写。
方法1:把内部地址当数据写在第一个缓冲中,后面是真正的数据。
buf[0]=subaddr; //内部地址
buf[1]=? //数据
……
buf[9]=?;
i2c_master_send(client,buf ,10) ;
方法2:先单独发器件地址,再发送要写在内部地址的数据。
subaddr=subaddr; //内部地址
i2c_master_send(client,&subaddr ,1) ;
?; //数据
buf[1]=?
……
buf[9]=?;
i2c_master_send(client,buf ,10) ;
17.5.4 标准的读取数据函数
int i2c_master_recv(struct i2c_client *client, char *buf ,int count) |
功能:从硬件中读取数据
参数:
:指针I2C设备结构的指针。
:存放数据指针
要读的字节数量
注意:此函数只是实现标准IIC的读协议,不代表具体器件读协议。
比如,对24c02进行读操作,先使用 i2c_master_send发送内部地址,然后调用 i2c_master_recv 函数读数据。
17.5.5 收发一体函数
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) |
功能:这个函数是I2C传输函数,收发一体的函数。
参数:
adap:指针I2C适配器结构的指针,这个指针是使用 i2c_client 中的 adapter 指针。
:存放数据指针
要传输的 struct i2c_msg 数量。
内核使用struct i2c_msg 结构来描述一则消息,包含了目标地址,操作方式(读写),数据存放位置或源位置。
struct i2c_msg __u16 addr;/* 这条消息是发送给谁*/ __u16 flags; /* 消息额外的标志,可选择的值有以下宏*/ #define I2C_M_TEN0x0010/* 表示目标器件地址是10位的 */ #define I2C_M_RD0x0001/* 在从设备中读取数据 */ /* 没有专门定义一个写的标志,默认是写,*/ //以下标志使用不到 #define I2C_M_NOSTART0x4000/* if I2C_FUNC_NOSTART */ #define I2C_M_REV_DIR_ADDR0x2000/* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK0x1000/* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK0x0800/* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN0x0400/* length will be first received byte */ __u16 len;/* 传输的数据字节数量*/ __u8 *buf;/* 接收/发送缓冲区*/ ; |
一则消息不是传递一个字节,可以传递多个字节,最大65536字节。
比如:要写数据给AT24C02 ,从内部地址10开始写,写2个,应该怎么写。
方法1:把内部地址当数据写在第一个缓冲中,后面是真正的数据。
char subaddr = 10; //内部地址 char data[100]=1,2,3;//等待发送的数据 struct i2c_msg msgs[2]= [0]= .addr = EEPROM_DEVICE_ADDR,//EEPROM_DEVICE_ADDR器件地址 .flags= 0 , //默认是写 .len = 1, .buf =&subaddr , [1]= 器件地址 .flags= 0 , .len = 2, .buf = data , ; i2c_transfer(client->adapter,msgs,2); |
注意:24c02连续进行页写操作不能跨页写,这个要用户自己保证。
17.5.6 注册IIC适配器
static int i2c_register_adapter(struct i2c_adapter *adap) //注册IIC适配器,该函数在下面两个函数里已经调用 int i2c_add_adapter(struct i2c_adapter *adapter) //声明并注册i2c适配器,使用动态总线编号 int i2c_add_numbered_adapter(struct i2c_adapter *adap) //声明并注册i2c适配器,使用静态总线编号 |
- 适配器数据结构:
struct i2c_adapter struct module *owner; unsigned int class;允许探测的类 */ 以上是关于#导入Word文档图片# Linux下I2C驱动架构全面分析的主要内容,如果未能解决你的问题,请参考以下文章 |