我是靠谱客的博主 心灵美季节,最近开发中收集的这篇文章主要介绍Linux内核IIC子系统框架,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

IIC子系统框架

 

可以看到iic子系统分为三层。

1、设备驱动层:这一层是我们重点关注的,含有IIC设备层(相当于平台设备驱动模型的设备)i2c_client,IIC设备驱动层(相当于相当于平台设备驱动模型的驱动)i2c_driver。i2c_client里含有需要通信的从机的所有信息,配置i2c_client需要使用到第三层的适配器驱动层。设备驱动层给应用层提供操作底层硬件的接口。

2、核心层:具有承上启下作用,为设备驱动层和适配器驱动层提供编程的接口,将设备驱动层的i2c_driver和i2c_client相匹配,把需要通信的从机挂载到主机上。作为驱动开发者无需关注此层。

3、适配器驱动层:内核使用i2c_adapter结构体描述一个适配器驱动,从而实现我们需要通信的IIC控制器驱动程序还有IIC协议标准时序。这一层是由芯片厂商提供。

IIC子系统分层可以使得具体的设备驱动和硬件无关,移植起来方便,多个设备操作同一个总线时无资源冲突。

首先来看设备驱动层的iic_driver。

 

我们重点关注其中的四个成员即可。

struct i2c_driver{

Unsigned int class;  //设备的分类,传感器

Int (*probe)(struct i2c_client*,const struct i2c_device_id *);  // 和i2c_device匹配时会调用此函数

Int (*remove)(struct i2c_client *);  /和i2c_device解除匹配关系时会调用此函数

struct device_driver driver;   //下面会列举

const struct i2c_device_id *id_table;  //类似于平台设备驱动模型的platform_device_id,其下的name是和client的name相匹配的

}

I2c_driver下的device_driver下的name成员是必须实现的,但是这个name成员不是用来和设备层做匹配的,没有实际意义。

 I2c_driver下的i2c_device_id:

 

struct i2c_device_id {

   char name[I2C_NAME_SIZE]; //和client的name相匹配

   kernel_ulong_t driver_data;//驱动程序的专用数据,可以在probe函数中通过i2c_device_id指针使用此数据

};

因此编写i2c_driver驱动代码步骤大概如下:

1、写一个probe探测函数,和client匹配时会调用此函数。此函数有两个参数,struct i2c_client *clt和const struct i2c_device_id *id,第一个参数是设备端的核心结构体,存储了设备的属性信息。第二个参数是驱动端定义的i2c_device_id数组,可以通过这个指针来访问driver_data(驱动程序专用数据)。

2、写一个remove移除函数。和client解除匹配时会调用此函数。

3、定义并初始化一个i2c_device_id数组,类似于平台设备驱动模型的platform_device_id,存储需要和设备端匹配的名字和数据。

4、定义并初始化一个i2c_driver驱动端的核心结构体。初始化里面的必要成员。

5、在模块初始化加载函数中调用注册i2c_driver核心结构体函数。

6、在模块初始化卸载函数中调用注销i2c_driver核心结构体函数。

接下来看设备驱动层的i2c_client。首先说明的是设备端的这个i2c_client核心结构体不像平台设备驱动模型一样,不是我们自己初始化实现的,是调用特定的api函数来初始化的。

要了解i2c_client,首先简单了解一下适配器驱动层的核心结构体i2c_adapter,是用来表示一个iic适配器(控制器)的。

 

struct i2c_adapter{

struct module *owner; //一般为THIS_MODULE

unsigned int class; //表示这个iic控制器支持哪些类型的设备。例如硬件监控类、存储类的模组、数字显示通道

const struct i2c_algorithm *algo;   //i2c总线发送和接收数据的方法,是需要我们重点关注的,下面会列出

int nr;    //此i2c总线的编号(如i2c0则为0,i2c1编号则为1),和硬件物理上的编号相同

}

I2c_adapter下的i2c_algorithm结构体如下:

master_xfer函数指针指向的是标准IIC收发函数,第一个参数是具体的iic适配器(控制器)结构体,第二个参数是struct i2c_msg类型的数组,其大小就是由第三个参数num指定的。I2c_msg下面会列出

smbus_xfer函数指针指向的是SMBUS收发函数,SMBUS是一种二线制串行总线,为IIC协议的子集,类似于IIC。如果此函数没有指向,则中间的核心层会使用上述的master_xfer来模拟。

Functionality函数指针指向的是自定义的函数功能,通过返回值来确定algorithm所支持的通信协议,比如I2C_FUNC_I2C,I2C_FUNC_10BIT_ADDR等。

 

I2c_msg就是存储收发数据的结构体

Struct i2c_msg{

   __u16 addr;   //存储从机的地址
       __u16 flags;  //标志。如I2C_M_TEN(十位地址标志),I2C_M_RD(接收数据标志)

   __u16 len;  //数据的长度

   __u8 *buf;  //数据指针

}

一般情况下,这个i2c_adapter和其下的i2c_algorithm是内核已经实现好的,内核实现好的适配器(控制器)地址。我们只需去查看原理图,确定从机挂载在哪个IIC总线上,从而获取相应的适配器(控制器)地址,拿到适配器(控制器)地址就可以初始化接下来的i2c_client核心结构体。

在初始化i2c_client之前,需要初始化i2c_board_info结构体。

 

struct i2c_board_info {

   char type[I2C_NAME_SIZE]; //初始化此数组后,调用对应的i2c_client初始化函数时,就会将此名字赋值给i2c_client结构体下的name值

   unsigned short flags; //同样的也会赋值给i2c_client结构体下的flags值,决定设备的器件地址addr是7位的还是10位的

   unsigned short addr; //存放需要通信的从机地址,同样的也会赋值给i2c_client结构体下的addr值

   int    irq; //存放中断号

};

这里补充说明,i2c_board_info下的platform_data会赋予到i2c_client核心结构体下的struct device dev下的platform_data,所以可以通过i2c_client指针提取dev后得到设备端的数据。使用dev_get_platdata()函数就可以得到dev下的platform_data,参数就是i2c_client下的struct device dev。

 

 

因此,编写i2c_client设备端代码步骤大概如下:

1、定义一个i2c_client和i2c_adapter的全局变量指针,存放核心结构体和适配器(控制器)的地址。

2、在模块初始化加载函数中,定义并初始化一个i2c_board_info结构体,把相应的需要通信的从机信息存进去,以便于后面初始化i2c_client。如果有中断则需要获取相应中断编号存在其下的irq中。通过i2c_get_adapter()获取需要通信的从机所挂在在的IIC总线,用全局变量指针i2c_adapter获取返回值。通过i2c_new_device()注册i2c_client核心结构体对象。

3、在模块的卸载函数中通过i2c_unregister_device()注销i2c_client核心结构体对象。

最后

以上就是心灵美季节为你收集整理的Linux内核IIC子系统框架的全部内容,希望文章能够帮你解决Linux内核IIC子系统框架所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(79)

评论列表共有 0 条评论

立即
投稿
返回
顶部