概述
文章目录
- 一、linux为什么要整个IIC驱动框架?
- 二、linux的IIC驱动为什么要分层?
- 三、linux的IIC体系结构
- 四、Linux IIC体系文件
- 五、核心数据结构
一、linux为什么要整个IIC驱动框架?
学过单片机的都知道,写一个IIC设备的驱动还是比较容易的。对于软件IIC来说,写几个模拟产生信号的函数和读写函数就好了;对于硬件IIC来说,配置好IIC控制器的寄存器,然后写几个读写函数就好了。那么linux为什么要整个IIC驱动框架呢?
这里举个例子,假如一个cpu有2个IIC控制器1、2,那么我们将IIC设备挂在IIC控制器1上时候,要写一个IIC设备驱动程序,当我们将IIC设备挂在IIC控制器2上时候,有要写一个IIC设备驱动程序。这就很烦了,明明两个驱动程序大部分相同,只有配置寄存器的部分存在差异,却要整两个驱动,怎么办呢?用宏定义区分不同IIC控制器?当然可以这样做,但如果cpu的IIC控制器很多呢?更进一步,如果IIC控制器上挂了很多个相同的IIC设备呢?傻眼了吧,所以linux整了个IIC驱动架构!
二、linux的IIC驱动为什么要分层?
同样举一个例子,我们将两个不同的IIC设备挂在同一个IIC控制器下,然后给这两个设备编写驱动程序,就会发现一个问题,它们配置寄存器的操作时相同的(取两器件最小时间要求的最小值)!!!所以这部分代码是耦合的,我们都知道linux是要求代码高内聚低耦合的,所以这样可不行,因此linux将IIC驱动分层了。
举个例子,将上面两个驱动相同部分代码提取出来,写成一个IIC控制器的驱动,由两个IIC设备的驱动间接控制IIC控制器的驱动来完成操作IIC设备!这样就有三个驱动程序了,在linux中将IIC控制器的驱动程序称为总线驱动(包含两部分,硬件相关、硬件无关)
,将IIC设备的驱动程序称为设备驱动(包含两部分,IIC设备匹配相关、IIC设备操作方法集)
!
三、linux的IIC体系结构
Linux的IIC体系结构分为3个组成部分,分别是IIC核心、IIC总线驱动和IIC设备驱动!
(1)IIC核心
提供了IIC总线驱动与IIC设备驱动的注册、注销方法和与具体IIC控制器无关的代码,该部分用来管理IIC总线驱动与IIC设备驱动。
(2)IIC总线驱动
IIC总线驱动是适配器(IIC控制器)的驱动程序,包含了适配器(IIC控制器)的数据描述结构i2c_adapter、通信方法数据结构i2c_algorithm、控制适配器产生通信时序的函数。
(3)IIC设备驱动
是具体IIC设备的驱动,因为不同的IIC设备可能有不同的读写时序要求,比如at24cxx读写需要发送16位地址,而tmp75只需要8位地址即可。IIC设备驱动通过i2c_algorithm通信方法驱动适配器驱动程序去访问具体的IIC设备。
体系结构如下图所示:
架构层次分类
第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。图中的硬件实现控制层。
第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xfer()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。图中的访问抽象层。
第三层
:实现i2c设备驱动中的i2c_driver接口,用具体的i2c device设备的probe()、remove()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的匹配挂接。图中的driver驱动层。
第四层
:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。也是图中的driver驱动层。
第一层和第二层属于i2c总线驱动(bus),第三第四属于i2c设备驱动(device driver)。
在linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为linux内核几乎集成所有总线bus,如usb、pci、i2c等等。并且总线bus中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c-2440平台i2c总线bus为/drivers/i2c/buses/i2c-s3c2410.c
第三第四层与特定device相干的就需要驱动工程师来实现了。
四、Linux IIC体系文件
在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通信方法。
五、核心数据结构
i2c_bus_type:
I2C总线对应着/bus下的一条总线,这个i2c总线结构体管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动的probe函数。
注意:i2c_device_match会管理I2C设备和I2C总线匹配规则,这将和如何编写I2C驱动程序息息相关
i2c_adapter:
描述一个适配器(IIC控制器),用于向IIC核心注册。设备数据结构i2c_client中的一个对象指向i2c_adapter,这样就可以通过其中的方法(i2c_adapter内部的i2c_algorithm对象)以及i2c物理控制器来和一个i2c总线的物理设备进行交互。
i2c_algorithm:
描述一个适配器的通信方法,用于产生 I2C 访问周期需要的信号,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer() 回调。在构建i2c_adapter数据结构时要填充这个数据结构,否则i2c_adapter什么也做不了。
i2c_client:
描述一个挂接在硬件i2c总线上的设备的设备信息,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver相连。一般通过i2c_new_device()创建。
i2c_driver:
描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象相连。
i2c_msg:
描述一个在设备端和主机端之间进行流动的数据,在设备驱动中打包并通过i2c_transfer()发送。
i2c_bus_type结构体
struct bus_type i2c_bus_type = {
.name = "i2c", //总线的名称
.match = i2c_device_match, /* iic设备与驱动注册时候会调用这个函数进行匹配操作,里面有匹配规则,目前是通过IIC设备i2c_client结构体中名字与驱动i2c_driver结构体中id_table匹配,注意:并不会匹配驱动名字! */
.probe = i2c_device_probe, //匹配成功后会调用总线的probe函数,在里面进一步调用iic驱动的probe函数(也就是自己写的)
.remove = i2c_device_remove, //与上面一样
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops, //电源管理
};
i2c_adapter结构体
这个结构体用来描述适配器(IIC控制器),它的algo成员包含通信方法。
struct i2c_adapter {
struct module *owner;//所有者
unsigned int class; /*适配器支持的从设备类型 */
const struct i2c_algorithm *algo; /* 适配器的通信方法,也就是上面的i2c_algorithm */
void *algo_data; //algo私有数据
int timeout; /* 超时时间 */
int retries; //重传次数,在通信方法中使用
struct device dev; /* 表明这是一个设备,挂载在I2C总线上 */
int nr; //适配器(总线)的编号
char name[48]; //适配器名称
struct list_head userspace_clients; //IIC设备的client依附链表头,只有在用户层echo name addr > /sys/bus/i2c/devices/i2c-x/new_device 时候创建的client才会依附在此链表
…//省略部分无关成员
};
i2c_algorithm结构体
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); //I2C传输函数指针,i2c_transfer函数的底层调用
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data); //smbus传输函数指针,SMBUS协议发送函数,i2c_smbus_xxx函数的底层调用
u32 (*functionality) (struct i2c_adapter *); /* 这个IIC适配器支持什么样的功能,比如支持SMBUS字节发送或读取操作,标志位为I2C_FUNC_SMBUS_BYTE_DATA */
};
这个结构体是 i2c_adapter 结构体的 algo 成员,因此在构建 i2c_adapter 数据结构时要填充这个数据结构,否则 i2c_adapter 什么也做不了。
i2c_client结构体
struct i2c_client {
unsigned short flags; //I2C_CLIENT_TEN表示设备使用10bit从地址,I2C_CLIENT_PEC表示设备使用SMBus检错
unsigned short addr; /*IIC设备基地址,7bit。这里说一下为什么是7位,因为最后1位0表示写,1表示读,通过对这个7bit地址移位处理即可。addr<<1 & 0x0即写,addr<<1 | 0x01即读*/
char name[I2C_NAME_SIZE]; //iic设备名字,用于匹配iic驱动
struct i2c_adapter *adapter; /*iic设备是挂在哪个适配器(总线)上 */
struct device dev; /* 表明这是一个设备,挂在I2C总线上 */
int irq; /* 中断号,用于申请中断,一般是在自己实现的iic驱动的probe函数中使用*/
struct list_head detected; //是i2c_adapter结构体或i2c_driver结构体中链表的节点
};
struct i2c_board_info {
char type[I2C_NAME_SIZE]; //设备名,最长20个字符,最终安装到client的name上
unsigned short flags; //最终安装到client.flags
unsigned short addr; //设备从地址slave address,最终安装到client.addr上
void *platform_data; //设备数据,最终存储到i2c_client.dev.platform_data上
struct dev_archdata *archdata;
struct device_node *of_node; //OpenFirmware设备节点指针
struct acpi_dev_node acpi_node;
int irq; //设备采用的中断号,最终存储到i2c_client.irq上
};
可以看到,i2c_board_info 基本是与 i2c_client 对应的。
i2c_driver结构体
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated; //老的匹配函数
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //当前匹配成功后执行函数,一般是申请资源,注册字符驱动
int (*remove)(struct i2c_client *); //当前移除设备或驱动时执行的移除函数,一般是释放资源
int (*probe_new)(struct i2c_client *); //未来匹配成功后的执行函数
void (*shutdown)(struct i2c_client *); //关闭设备
void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; //表明这是一个驱动,驱动模型用来挂在I2C总线上
const struct i2c_device_id *id_table; //设备匹配列表,非常重要,IIC设备的名字与这个列表匹配
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list; //该驱动所支持的所有(次设备)的地址数组,用于注册驱动时自动去匹配
struct list_head clients; //用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
bool disable_i2c_core_irq_mapping;
};
i2c_msg 结构体
struct i2c_msg {
__u16 addr; /* IIC设备的基地址,7位 */
__u16 flags; //操作标志位
#define I2C_M_RD 0x0001 /* 设置了这个标志位表示本次通信i2c控制器是处于接收方,否则就是发送方 */
#define I2C_M_TEN 0x0010 /* 设置了这个标志位表示从设备的地址是10bit */
#define I2C_M_DMA_SAFE 0x0200 /* the buffer of this message is DMA safe */
/* makes only sense in kernelspace */
/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
#define I2C_M_NO_RD_ACK 0x0800 /* 设置这个标志位表示在读操作中主机不用ACK */
#define I2C_M_IGNORE_NAK 0x1000 /* 设置这个标志意味当前i2c_msg忽略I2C器件的ack和nack信号 */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
__u16 len; /* 读写数据的长度 */
__u8 *buf; /* 装有数据的缓冲区 */
};
参考链接
最后
以上就是无奈往事为你收集整理的【linux iic子系统】linux下i2c框架(二)一、linux为什么要整个IIC驱动框架?二、linux的IIC驱动为什么要分层?三、linux的IIC体系结构四、Linux IIC体系文件五、核心数据结构的全部内容,希望文章能够帮你解决【linux iic子系统】linux下i2c框架(二)一、linux为什么要整个IIC驱动框架?二、linux的IIC驱动为什么要分层?三、linux的IIC体系结构四、Linux IIC体系文件五、核心数据结构所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复