• Linux学习第37天:Linux I2C 驱动实验:哥俩好


    Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


            世界上的很多事物都是成双成对出现的。也包括在驱动开发的过程中,比如I2C中其实就是数据线和时钟线的相互配合才能完成的。

            I2C常用于连接各种外设、传感器等器件。本节笔记学习如何在Linux下开发I2C接口器件驱动。重点是学习Linux下的I2C驱动框架。以正点原子I.MX6U-ALPHA开发板上的AP3216这个三合一环境光传感器为例学习如何编写Linux下的I2C设备驱动程序。

            本节的思维导图如下:

    一、Linux I2C驱动框架简介

    Linux内核也将 I2C 驱动分为两部分:
    ①、 I2C 总线驱动, I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
    ②、 I2C 设备驱动, I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。


    1、I2C总线驱动

            Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter:

            i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。其中master_xfer 就是 I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。 smbus_xfer 就是 SMBUS 总线的传输函数。

            I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。如果要删除 I2C 适配器的话使用i2c_del_adapter 函数即可。

            一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,不作为本次笔记学习的重点内容。
     

    2、I2C设备驱动

            I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver。i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容,类似于 platform_driver。

    1)、i2c_client 结构体

    1. 217 struct i2c_client {
    2. 218 unsigned short flags; /* 标志 */
    3. 219 unsigned short addr; /* 芯片地址, 7 位,存在低 7 位*/
    4. ......
    5. 222 char name[I2C_NAME_SIZE]; /* 名字 */
    6. 223 struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
    7. 224 struct device dev; /* 设备结构体 */
    8. 225 int irq; /* 中断 */
    9. 226 struct list_head detected;
    10. ......
    11. 230 };

            一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个
    i2c_client。

    2)、i2c_driver 结构体

            i2c_driver 类似 platform_driver,是我们编写 I2C 设备驱动重点要处理的内容:

    1. 161 struct i2c_driver {
    2. 162 unsigned int class;
    3. 163
    4. 164 /* Notifies the driver that a new bus has appeared. You should
    5. 165 * avoid using this, it will be removed in a near future.
    6. 166 */
    7. 167 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
    8. 168
    9. 169 /* Standard driver model interfaces */
    10. 170 int (*probe)(struct i2c_client *, const struct i2c_device_id *);/*当 I2C 设备和驱动匹配成功以后 probe 函数就会执行,和 platform 驱动一样。*/
    11. 171 int (*remove)(struct i2c_client *);
    12. 172
    13. 173 /* driver model interfaces that don't relate to enumeration */
    14. 174 void (*shutdown)(struct i2c_client *);
    15. 175
    16. 176 /* Alert callback, for example for the SMBus alert protocol.
    17. 177 * The format and meaning of the data value depends on the
    18. 178 * protocol.For the SMBus alert protocol, there is a single bit
    19. 179 * of data passed as the alert response's low bit ("event
    20. 180 flag"). */
    21. 181 void (*alert)(struct i2c_client *, unsigned int data);
    22. 182
    23. 183 /* a ioctl like command that can be used to perform specific
    24. 184 * functions with the device.
    25. 185 */
    26. 186 int (*command)(struct i2c_client *client, unsigned int cmd,
    27. void *arg);
    28. 187
    29. 188 struct device_driver driver;/*device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的of_match_table 成员变量,也就是驱动的兼容(compatible)属性。*/
    30. 189 const struct i2c_device_id *id_table;/*id_table 是传统的、未使用设备树的设备匹配 ID 表。*/
    31. 190
    32. 191 /* Device detection callback for automatic device creation */
    33. 192 int (*detect)(struct i2c_client *, struct i2c_board_info *);
    34. 193 const unsigned short *address_list;
    35. 194 struct list_head clients;
    36. 195 };

            I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。 i2c_driver 注册函数为 int i2c_register_driver:

    1. int i2c_register_driver(struct module *owner,/*owner: 一般为 THIS_MODULE。*/
    2. struct i2c_driver *driver)/*driver:要注册的 i2c_driver。*/

            i2c_add_driver 也常常用于注册 i2c_driver:

    1. 587 #define i2c_add_driver(driver) \
    2. 588 i2c_register_driver(THIS_MODULE, driver)
    3. /*i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册
    4. 的 i2c_driver。*/

    i2c_del_driver 函数将前面注册的 i2c_driver 从 Linux 内核中注销掉.

    void i2c_del_driver(struct i2c_driver *driver)
    1. /*i2c_driver 注册流程*/
    2. 1 /* i2c 驱动的 probe 函数 */
    3. 2 static int xxx_probe(struct i2c_client *client,
    4. const struct i2c_device_id *id)
    5. 3 {
    6. 4 /* 函数具体程序 */
    7. 5 return 0;
    8. 6 }
    9. 7
    10. 8 /* i2c 驱动的 remove 函数 */
    11. 9 static int ap3216c_remove(struct i2c_client *client)
    12. 10 {
    13. 11 /* 函数具体程序 */
    14. 12 return 0;
    15. 13 }
    16. 14
    17. 15 /* 传统匹配方式 ID 列表 第 16~19 行, i2c_device_id,无设备树的时候匹配 ID 表。*/
    18. 16 static const struct i2c_device_id xxx_id[] = {
    19. 17 {"xxx", 0},
    20. 18 {}
    21. 19 };
    22. 20
    23. 21 /* 设备树匹配列表 第 22~25 行,of_device_id,设备树所使用的匹配表。*/
    24. 22 static const struct of_device_id xxx_of_match[] = {
    25. 23 { .compatible = "xxx" },
    26. 24 { /* Sentinel */ }
    27. 25 };
    28. 26
    29. 27 /* i2c 驱动结构体 第 28~37 行, i2c_driver,当 I2C 设备和 I2C 驱动匹配成功以后 probe 函数就会执行*/
    30. 28 static struct i2c_driver xxx_driver = {
    31. 29 .probe = xxx_probe,
    32. 30 .remove = xxx_remove,
    33. 31 .driver = {
    34. 32 .owner = THIS_MODULE,
    35. 33 .name = "xxx",
    36. 34 .of_match_table = xxx_of_match,
    37. 35 },
    38. 36 .id_table = xxx_id,
    39. 37 };
    40. 38
    41. 39 /* 驱动入口函数 */
    42. 40 static int __init xxx_init(void)
    43. 41 {
    44. 42 int ret = 0;
    45. 43
    46. 44 ret = i2c_add_driver(&xxx_driver);
    47. 45 return ret;
    48. 46 }
    49. 47
    50. 48 /* 驱动出口函数 */
    51. 49 static void __exit xxx_exit(void)
    52. 50 {
    53. 51 i2c_del_driver(&xxx_driver);
    54. 52 }
    55. 53
    56. 54 module_init(xxx_init);
    57. 55 module_exit(xxx_exit);


    3、I2C设备和驱动匹配过程

            I2C 设备和驱动的匹配过程是由 I2C 核心来完成的, drivers/i2c/i2c-core.c 就是 I2C 的核心
    部分。I2C 核心提供了一些与具体硬件无关的 API 函数,例如:

    1i2c_adapter 注册/注销函数
    int i2c_add_adapter(struct i2c_adapter *adapter)
    int i2c_add_numbered_adapter(struct i2c_adapter *adap)
    void i2c_del_adapter(struct i2c_adapter * adap)
    2i2c_driver 注册/注销函数
    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    int i2c_add_driver (struct i2c_driver *driver)
    void i2c_del_driver(struct i2c_driver *driver)

            设备和驱动的匹配过程也是由 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type,其定义如下:

    1. 736 struct bus_type i2c_bus_type = {
    2. 737 .name = "i2c",
    3. 738 .match = i2c_device_match,/*.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数*/
    4. 739 .probe = i2c_device_probe,
    5. 740 .remove = i2c_device_remove,
    6. 741 .shutdown = i2c_device_shutdown,
    7. 742 };

    i2c_device_match的定义如下:

    1. 457 static int i2c_device_match(struct device *dev, struct
    2. device_driver *drv)
    3. 458 {
    4. 459 struct i2c_client *client = i2c_verify_client(dev);
    5. 460 struct i2c_driver *driver;
    6. 461
    7. 462 if (!client)
    8. 463 return 0;
    9. 464
    10. 465 /* Attempt an OF style match */
    11. 466 if (of_driver_match_device(dev, drv))/*of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节
    12. 点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C
    13. 设备和驱动匹配。*/
    14. 467 return 1;
    15. 468
    16. 469 /* Then ACPI style match */
    17. 470 if (acpi_driver_match_device(dev, drv))/*acpi_driver_match_device 函数用于 ACPI 形式的匹配。*/
    18. 471 return 1;
    19. 472
    20. 473 driver = to_i2c_driver(drv);
    21. 474 /* match on an id table if there is one */
    22. 475 if (driver->id_table)
    23. 476 return i2c_match_id(driver->id_table, client) != NULL;/*i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C
    24. 设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。*/
    25. 477
    26. 478 return 0;
    27. 479 }

            由于本节内容太多,以下的内容将在下节笔记中学习:

    二、I.MX6U的I2C适配器驱动分析

    三、I2C设备驱动编写流程

    1、I2C设备信息描述

    2、I2C设备数据收发处理流程

    四、硬件原理图分析

    五、试验程序编写

    1、修改设备树

    2、AP3216驱动编写

    3、编写测试APP

    六、运行测试

    1、编译驱动程序和测试APP

    2、运行测试


    本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

  • 相关阅读:
    【redis】(一)使用docker安装redis、常用五大基本数据类型、Jedis操作Redis、Spring整合Redis
    微软Win11安卓子系统2204.40000.20.0版本发布!(内容如下)
    一比一还原axios源码(四)—— Axios类
    【FAQ】本地录像视频文件如何推送到视频监控平台EasyCVR进行AI视频智能分析?
    解决windows下WslRegisterDistribution failed with error: 0x80070050的问题
    Vue学习
    java基于SpringBoot+Vue的疫苗接种管理系统 element
    解除百度文库VIP、语雀、知乎付费限制,原来这么简单
    ​​​​​​​ARCGIS API for Python进行城市区域提取
    go 语言爬虫库goQuery 的详细使用(知乎日报详情页解析示例)
  • 原文地址:https://blog.csdn.net/jiage987450/article/details/134286508