#导入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驱动架构全面分析的主要内容,如果未能解决你的问题,请参考以下文章

#导入Word文档图片# Linux下FrameBuffe(LCD)驱动编写

#导入Word文档图片# Linux下音频编程

#导入Word文档图片# Linux下信号处理

#导入Word文档图片# Linux下目录编程

#导入Word文档图片# Linux下线程编程

#导入Word文档图片# Linux下文件目录权限操作

(c)2006-2024 SYSTEM All Rights Reserved IT常识