概述
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子系统框架所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复