赞
踩
本节来看I2C驱动框架,题目叫IMX6ULL I2C框架,不是imx特有的i2c驱动框架,只是借着IMX6ULL的平台来分析linux内核中的I2C驱动框架,
在Linux内核中,把I2C分为I2c总线、设备、驱动,实现了分层的概念,那么,在内核中,I2C总线抽象为一个结构体叫做I2c_adapter,一个i2c_adapter就是平台上一个真实的物理I2C
来看一下I2C框架中最重要的i2c_adapter结构体的内容
其中重要的成员是struct i2c_algorithm *algo,这个结构体中包含了数据传输的一些函数,这个函数都是和具体的单板相关的,比如我们使用IMX6ULL这个平台,那么恩智浦官方就会针对他们的平台提供这些
i2c的传输函数,然后注册到内核中,来看一个具体的例子
在官方的设备树中找到i2c的设备节点,根据compatible属性查找对应的驱动程序
注册平台设备驱动
其中的匹配列表和设备树中对应
平台设备驱动重点就是probe函数
probe函数开始的时候申请了一个结构体imx_i2c_struct,这个结构体是nxp自己定义的结构体,但是它里边肯定包含linux内核要求的结构体i2c_adapter
果然如此的,再接着往下看,然后就是去获取i2c控制器的基地址,往下就是重点了,可以看到设置imx_i2c_struct结构体中的i2c_adapter成员了
重点就是设置adapter.algo = &i2c_imx_algo; i2c_imx_algo结构体就是算法结构体,其中包含i2c控制器的数据传输的函数
master_xfer函数就是用于数据的传输的,点进去i2c_imx_xfer中可以看到,就已经去设置imx6ull平台的i2c控制寄存器了,来完成数据的传输,这里就不再去查看,
回到probe函数继续往下分析,下面就是时钟和中断的一些配置,最后是注册i2c_adapter结构体
总结:
到这里可以看出,对于内核中的I2C适配器要进行三个步骤
这样一来,和平台相关的I2C通信方法就注册好了,接下来就是具体的I2C设备来使用这些方法,具体的I2C设备也叫作I2C框架中的设备层
上面注册的i2c_adapter要怎么来使用呢?下面来看一个具体的例子
IMX6ULL平台使用的i2c器件ap3216c,它是在物理的i2c1总线下面,所以在设备树中添加节点时要把它添加到i2c1节点下面
- i2c1 {
- clock-frequency = <100000>;
- pinctrl-names = "default";
- pinctrl-0 = <&pinctrl_i2c1>;
- status = "okay";
-
- ap3216c@1e {
- compatible = "alientek,ap3216c";
- reg = <0x1e>;
- };
- };
针对设备树提供的驱动函数
- /* 设备树匹配列表 */
- static const struct of_device_id ap3216c_of_match[] = {
- { .compatible = "alientek,ap3216c" },
- { /* Sentinel */ }
- };
-
- /* i2c 驱动结构体 */
- static struct i2c_driver ap3216c_driver = {
- .probe = ap3216c_probe,
- .remove = ap3216c_remove,
- .driver = {
- .owner = THIS_MODULE,
- .name = "ap3216c",
- .of_match_table = ap3216c_of_match,
- },
- .id_table = ap3216c_id,
- };
-
- static int __init ap3216c_init(void)
- {
-
- int ret = 0;
-
- ret = i2c_add_driver(&ap3216c_driver);
- return ret;
- }
-
- static void __exit ap3216c_exit(void)
- {
- i2c_del_driver(&ap3216c_driver);
- }
-
- /* module_i2c_driver(ap3216c_driver) */
-
- module_init(ap3216c_init);
- module_exit(ap3216c_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("zuozhongkai");
申请并设置一个i2c_driver结构体,然后在入口函数中通过i2c_add_driver去注册这个结构体,注册i2c_driver结构体的部分比较复杂,因为它会根据设备树中的i2c节点的地址和之前已经注册的i2c_adapter去给设备发生输出,
根据设备的应答从而判断是否真实存在此设备,这个过程这里就不再列出来了,
但是我们需要知道的是,设备匹配成功之后会进入i2c_driver->probe函数,来看一下probe函数的参数
static int ap3216c_probe(struct i2c_client *client,const struct i2c_device_id *id)
第一个参数i2c_client,一个i2c_client就表示这个设备的一个代理,
i2c_client中有一个成员是i2c_adapter,到这里就会明白,在probe函数中我们拿到了i2c_client结构体,其中包含i2c_adapter结构体,这个i2c_adapter结构体就是具体的i2c设备挂载的i2c总线的抽象
前面所说的,i2c_adapter中有最重要的输出传输函数,那么,在probe函数中就可以访问i2c设备了,
不管在什么时候,只要我们拿到i2c_client结构体,就可以访问i2c设备
简单过了一下i2c的注册过程,总结一下
内核要做的事情
内核主要做的事情是针对具体的单板提供i2c控制器的操作函数,以为它不知道我们要用i2c总线发送什么数据,但是它只知道怎么样来操作这个i2c总线,
所以,它为用户提供数据传输函数,至于传输什么数据它是不知道的.
接下来就是程序员要去做的事情,i2c总线的操作函数有了,就要使用这些函数操作具体的i2c设备了,这里也有一个重要的结构体i2c_driver
这个i2c_driver的注册过程会很复杂,因为要去匹配设备,
会获取设备树中匹配的i2c节点的设备地址,使用内核提供的i2c总线的操作函数,针对此i2c设备地址发送一个开始信号,然后检测i2c设备有没有回应,
如果有i2c设备的ack信号,说明i2c设备存在,那么这个I2c_adapter+i2c 地址这个组合就是没有问题的
然后,会创建一个i2c_client结构体,i2c_client结构体中包含i2c_adapter,以及i2c设备地址,并把这个i2c_client结构体作为参数返回调用i2c_driver中的probe函数
在i2c_driver结构体的probe函数中就可以访问想要操作的i2c设备,具体的根据程序员的需求去操作,到这里就结束了
内核中的驱动大多是这个流程,它会抽象出一套公共的部分,然后不同的部分留出来,由程序员去填充,实现了只需要修改设备,而不是每次都去重新定义那些公共代码,看内核驱动就会感叹代码的艺术
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。