当前位置:   article > 正文

IMX6ULL I2C框架_i2c imx

i2c imx

本节来看I2C驱动框架,题目叫IMX6ULL I2C框架,不是imx特有的i2c驱动框架,只是借着IMX6ULL的平台来分析linux内核中的I2C驱动框架,

在Linux内核中,把I2C分为I2c总线、设备、驱动,实现了分层的概念,那么,在内核中,I2C总线抽象为一个结构体叫做I2c_adapter,一个i2c_adapter就是平台上一个真实的物理I2C

i2c_adapter

来看一下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适配器要进行三个步骤

  1. 申请一个i2c_adapter结构体
  2. 设置i2c_adapter结构体,主要是struct i2c_algorithm成员的设置
  3. 注册i2c_adapter结构体

这样一来,和平台相关的I2C通信方法就注册好了,接下来就是具体的I2C设备来使用这些方法,具体的I2C设备也叫作I2C框架中的设备层

上面注册的i2c_adapter要怎么来使用呢?下面来看一个具体的例子

I2C实例

IMX6ULL平台使用的i2c器件ap3216c,它是在物理的i2c1总线下面,所以在设备树中添加节点时要把它添加到i2c1节点下面

  1. i2c1 {
  2. clock-frequency = <100000>;
  3. pinctrl-names = "default";
  4. pinctrl-0 = <&pinctrl_i2c1>;
  5. status = "okay";
  6. ap3216c@1e {
  7. compatible = "alientek,ap3216c";
  8. reg = <0x1e>;
  9. };
  10. };

针对设备树提供的驱动函数

  1. /* 设备树匹配列表 */
  2. static const struct of_device_id ap3216c_of_match[] = {
  3. { .compatible = "alientek,ap3216c" },
  4. { /* Sentinel */ }
  5. };
  6. /* i2c 驱动结构体 */
  7. static struct i2c_driver ap3216c_driver = {
  8. .probe = ap3216c_probe,
  9. .remove = ap3216c_remove,
  10. .driver = {
  11. .owner = THIS_MODULE,
  12. .name = "ap3216c",
  13. .of_match_table = ap3216c_of_match,
  14. },
  15. .id_table = ap3216c_id,
  16. };
  17. static int __init ap3216c_init(void)
  18. {
  19. int ret = 0;
  20. ret = i2c_add_driver(&ap3216c_driver);
  21. return ret;
  22. }
  23. static void __exit ap3216c_exit(void)
  24. {
  25. i2c_del_driver(&ap3216c_driver);
  26. }
  27. /* module_i2c_driver(ap3216c_driver) */
  28. module_init(ap3216c_init);
  29. module_exit(ap3216c_exit);
  30. MODULE_LICENSE("GPL");
  31. 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的注册过程,总结一下

内核要做的事情

  1. 申请i2c_adapter结构体
  2. 构造i2c_adapter结构体中的数据传输函数,填充i2c_adapter结构体
  3. 注册i2c_adapter结构体

内核主要做的事情是针对具体的单板提供i2c控制器的操作函数,以为它不知道我们要用i2c总线发送什么数据,但是它只知道怎么样来操作这个i2c总线,

所以,它为用户提供数据传输函数,至于传输什么数据它是不知道的.

 

接下来就是程序员要去做的事情,i2c总线的操作函数有了,就要使用这些函数操作具体的i2c设备了,这里也有一个重要的结构体i2c_driver

  1. 申请i2c_driver结构体
  2. 填充i2c_driver结构体,这里使用的是设备树,所以,着重的是它的匹配列表of_match_table
  3. 注册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设备,具体的根据程序员的需求去操作,到这里就结束了

 

内核中的驱动大多是这个流程,它会抽象出一套公共的部分,然后不同的部分留出来,由程序员去填充,实现了只需要修改设备,而不是每次都去重新定义那些公共代码,看内核驱动就会感叹代码的艺术

 

 

 

 

 

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/406114
推荐阅读
相关标签
  

闽ICP备14008679号