基于rk3588----i2c驱动框架学习-总线驱动

基于rk3588----i2c驱动框架学习-总线驱动 V 2024-02-05 11:02:54 1135

声明:本文基于linux 5.10版本内核 rk3588sdk内核源码

i2c介绍

I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,Linux 下的 I2C 驱动是有框架的,我们需要按照指定的框架去编写 I2C 设备驱动。但是我们也要知道内核中帮我们干了那些事情。这样更清楚的去编写i2c从机驱动。

为了符合 Linux 的驱动分离与分层的思想,Linux内核将 I2C 驱动分为两部分:
I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动。
I2C 设备驱动,I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。

i2c总线驱动

什么是i2c总线驱动,他干了一下什么事情呢。下来先了解一下相关结构体吧

I2C总线适配器,即soc中的I2C总线控制器,硬件上每一对I2C总线都对应一个适配器来控制它。在Linux内核代码中,每一个adapter提供了一个描述它的结构(struct i2c_adapter),再通过i2c core层将i2c设备与i2c adapter关联起来。主要用来完成i2c总线控制器相关的数据通信,此结构体在芯片厂商提供的代码中维护。

 697 struct i2c_adapter {
   698     struct module *owner;
   699     unsigned int class;       /* classes to allow probing for */
   700     const struct i2c_algorithm *algo;   //很重要根据rk的SOC 实现的通信算法
   701     void *algo_data;
   702 
   703     /* data fields that are valid for all devices   */
   704     const struct i2c_lock_operations *lock_ops;
   705     struct rt_mutex bus_lock;
   706     struct rt_mutex mux_lock;
   707 
   708     int timeout;            /* in jiffies */
   709     int retries;
   710     struct device dev;      /* 每一个i2c控制器对应的设备 */
   711     unsigned long locked_flags; /* owned by the I2C core */
   712 #define I2C_ALF_IS_SUSPENDED        0
   713 #define I2C_ALF_SUSPEND_REPORTED    1
   714 
   715     int nr;
   716     char name[48];     //i2c控制器的名字
   717     struct completion dev_released;
   718 
   719     struct mutex userspace_clients_lock;
   720     struct list_head userspace_clients;
   721 
   722     struct i2c_bus_recovery_info *bus_recovery_info;
   723     const struct i2c_adapter_quirks *quirks;
   724 
   725     struct irq_domain *host_notify_domain;
   726 };

I2C总线数据通信算法,通过管理I2C总线控制器,实现对I2C总线上数据的发送和接收等操作。亦可以理解为I2C总线控制器(适配器adapter)对应的驱动程序,每一个适配器对应一个驱动程序,用来描述适配器和设备之间的通信方法,由芯片厂商去实现的。
先来看一下有厂商维护的 struct i2c_algorithm *algo;

   519 struct i2c_algorithm {
   529     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
   530                int num);                   // 实现i2c通信协议函数
   531     int (*master_xfer_atomic)(struct i2c_adapter *adap,
   532                    struct i2c_msg *msgs, int num);  //根据函数名可以猜想是原子操作
   533     int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
   534               unsigned short flags, char read_write,
   535               u8 command, int size, union i2c_smbus_data *data);  
                   //是基于smbus 协议吧 作者还不太了解
   536     int (*smbus_xfer_atomic)(struct i2c_adapter *adap, u16 addr,
   537                  unsigned short flags, char read_write,
   538                  u8 command, int size, union i2c_smbus_data *data);
   539 
   541     u32 (*functionality)(struct i2c_adapter *adap);  
   547 };

知道了这两个结构体 那么小琛带你看一下rk是如何实现自己i2c总线驱动的。(在夹杂一些platform相关知识)。
看驱动我不太了解大家怎么看 首先 我根据自己的阅读习惯进行一步一步的阅读去了解i2c框架
rk实现的总线驱动文件在 i2c-rk3x.c文件中

  1616 static int __init rk3x_i2c_driver_init(void)
  1617 {
  1618     return platform_driver_register(&rk3x_i2c_driver);
  1619 }
  1620 #ifdef CONFIG_INITCALL_ASYNC
  1621 subsys_initcall_sync(rk3x_i2c_driver_init);
  1622 #else
  1623 subsys_initcall(rk3x_i2c_driver_init);
  1624 #endif
  1625 
  1626 static void __exit rk3x_i2c_driver_exit(void)
  1627 {
  1628     platform_driver_unregister(&rk3x_i2c_driver);
  1629 }
  1630 module_exit(rk3x_i2c_driver_exit);
  1631 #else
  1632 module_platform_driver(rk3x_i2c_driver);
  1633 #endif
  1634 
  1635 MODULE_DESCRIPTION("Rockchip RK3xxx I2C Bus driver");
  1636 MODULE_AUTHOR("Max Schwarz <max.schwarz@online.de>");
  1637 MODULE_LICENSE("GPL v2");

很明显是通过platform_driver_register去注册的驱动使用了initcall函数相关内容是链接脚本和 内核启动中的start_kernel函数相关有兴趣的可以看一下其他博主后续将会把这写内容补上。
步入正题吧因为是存在设备驱动分离现象所有的platform_devise设备时内核加载的时候把所有的含有compatible属性的节点转换为platfrom_devise的 最后i2c控制器加载的时候把驱动进行匹配直接调用 .probe

  1600 static const struct dev_pm_ops rk3x_i2c_pm_ops = {
  1601     SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(rk3x_i2c_suspend_noirq,
  1602                       rk3x_i2c_resume_noirq)
  1603 };
  1604 
  1605 static struct platform_driver rk3x_i2c_driver = {
  1606     .probe   = rk3x_i2c_probe,
  1607     .remove  = rk3x_i2c_remove,
  1608     .driver  = {
  1609         .name  = "rk3x-i2c",
  1610         .of_match_table = rk3x_i2c_match,
  1611         .pm = &rk3x_i2c_pm_ops,
  1612     },
  1613 };
  1382 static const struct of_device_id rk3x_i2c_match[] = {
  1383     {
  1384         .compatible = "rockchip,rv1108-i2c",
  1385         .data = &rv1108_soc_data
  1386     },
  1387     {
  1388         .compatible = "rockchip,rv1126-i2c",
  1389         .data = &rv1126_soc_data
  1390     },
  1391     {
  1392         .compatible = "rockchip,rk3066-i2c",
  1393         .data = &rk3066_soc_data
  1394     },
  1395     {
  1396         .compatible = "rockchip,rk3188-i2c",
  1397         .data = &rk3188_soc_data
  1398     },
  1399     {
  1400         .compatible = "rockchip,rk3228-i2c",
  1401         .data = &rk3228_soc_data
  1402     },
  1403     {
  1404         .compatible = "rockchip,rk3288-i2c",
  1405         .data = &rk3288_soc_data
  1406     },
  1407     {
  1408         .compatible = "rockchip,rk3399-i2c",
  1409         .data = &rk3399_soc_data
  1410     },
  1411     {},
  1412 };

这就是驱动相关的设备信息当然他会和设备树进行匹配 和获取相关的设备树资源

  1430      static int rk3x_i2c_probe(struct platform_device *pdev)   
  1431        {
  1432       i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
  1433     if (!i2c)
  1434         return -ENOMEM;
  1435 
  1436     match = of_match_node(rk3x_i2c_match, np);       //?
  1437     i2c->soc_data = match->data;
  1438 
  1439     /* use common interface to get I2C timing properties */
  1440     i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);  //?
  1441 
  1442     strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));   //拷贝名称
  1443     i2c->adap.owner = THIS_MODULE;
  1444     i2c->adap.algo = &rk3x_i2c_algorithm;   //所谓的通信算法
  1445     i2c->adap.retries = 3;   //重复通信传输3次
  1446     i2c->adap.dev.of_node = np;
  1447     i2c->adap.algo_data = i2c;
  1448     i2c->adap.dev.parent = &pdev->dev;     
             i2c->regs = devm_platform_ioremap_resource(pdev, 0);  //获取硬件资源
           irq = platform_get_irq(pdev, 0);   //获取中断号
  1518     ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq, //注册中断
  1519                    0, dev_name(&pdev->dev), i2c);
            .....           // 使能时钟
            ret = i2c_add_adapter(&i2c->adap);  //很重要 先来主要看该函数
}

很多博主看到i2c_add_adapter函数就没有在分析了说和SOC厂商有关,确实如果每个人都在乎自己的一亩三分地的,那技术栈也就只局限仅仅呢么一部分 想想i2c设备树中i2c从机设备挂载节点下是如何进行注册这个设备的,我们光关心从机驱动了。

i2c_add_adapter 
    i2c_register_adapter
  1533 int i2c_add_adapter(struct i2c_adapter *adapter)
  1534 {
  1535     struct device *dev = &adapter->dev;
  1536     int id;
  1537 
  1538     if (dev->of_node) {
  1539         id = of_alias_get_id(dev->of_node, "i2c");  //获取i2c设备树节点
  1540         if (id >= 0) {
  1541             adapter->nr = id;    //第几个控制器
  1542             return __i2c_add_numbered_adapter(adapter);  //把i2c控制器注册
  1543         }
  1544     }
  1545 
  1546     mutex_lock(&core_lock);
  1547     id = idr_alloc(&i2c_adapter_idr, adapter,
  1548                __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
  1549     mutex_unlock(&core_lock);
  1550     if (WARN(id < 0, "couldn't get idr"))
  1551         return id;
  1552 
  1553     adapter->nr = id;
  1554 
  1555     return i2c_register_adapter(adapter);
  1556 }
2032 int of_alias_get_id(struct device_node *np, const char *stem)
2033 {
2034     struct alias_prop *app;
2035     int id = -ENODEV;
2036 
2037     mutex_lock(&of_mutex);
2038     list_for_each_entry(app, &aliases_lookup, link) {   // 遍历全局链表aliases_lookup
2039         if (strcmp(app->stem, stem) != 0)  // 找到 stem  "i2c" 的alias_prop
2040             continue;
2041 
2042         if (np == app->np) {  // 判断这个alias_prop指向的device_node是不是跟传入的匹配
2043             id = app->id;  // 获得 id  就是第几个i2c控制器
2044             break;
2045         }
2046     }
2047     mutex_unlock(&of_mutex);
2048 
2049     return id;
2050 }

那么aliases_lookup链表是如何添加这些i2c控制器的下来我们将看看
在设备树中我们有aliiases节点

  23     aliases {
  37         i2c0 = &i2c0;
  38         i2c1 = &i2c1;
  39         i2c2 = &i2c2;
  40         i2c3 = &i2c3;
  41         i2c4 = &i2c4;
  42         i2c5 = &i2c5;
  43    }

在内核启动的时候
start_kernel
—> setup_arch
—> unflatten_device_tree
—> of_alias_scan

 1: void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))
   2: {
   3:     struct property *pp;
   4:  
   5:     of_aliases = of_find_node_by_path("/aliases"); // 找到/aliases节点对应的device_node
   6:     of_chosen = of_find_node_by_path("/chosen"); // 找到/chosen节点对应的device_node
   7:     if (of_chosen == NULL) // 如果没有/chosen的话,就找/chosen@0节点
   8:         of_chosen = of_find_node_by_path("/chosen@0");
   9:  
  10:     if (of_chosen) {
  11:         /* linux,stdout-path and /aliases/stdout are for legacy compatibility */
  12:         const char *name = of_get_property(of_chosen, "stdout-path", NULL);
  13:         if (!name)
  14:             name = of_get_property(of_chosen, "linux,stdout-path", NULL);
  15:         if (IS_ENABLED(CONFIG_PPC) && !name)
  16:             name = of_get_property(of_aliases, "stdout", NULL);
  17:         if (name)
  18:             of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
  19:     }
  20:  
  21:     if (!of_aliases)
  22:         return;
  23:  
  24:     for_each_property_of_node(of_aliases, pp) { // 遍历/aliases节点的属性,以属性i2c2 = "/i2c@13880000";为例
  25:         const char *start = pp->name; // 属性的名字,如"i2c2"
  26:         const char *end = start + strlen(start); // 名字的结尾,*end是'\0'
  27:         struct device_node *np;
  28:         struct alias_prop *ap;
  29:         int id, len;
  30:  
  31:         /* 不处理名字是name、phandle、linux,phandle的属性 */
  32:         if (!strcmp(pp->name, "name") || 
  33:             !strcmp(pp->name, "phandle") ||
  34:             !strcmp(pp->name, "linux,phandle"))
  35:             continue;
  36:  
  37:         np = of_find_node_by_path(pp->value); 
  38:         /*
  39:             根据属性的值(如"/i2c@13880000")获得这个值对应的节点
  40:             i2c@13880000 {
  41:                 #address-cells = <0x1>;
  42:                 #size-cells = <0x0>;
  43:                 compatible = "samsung,s3c2440-i2c";
  44:                 reg = <0x13880000 0x100>;
  45:                 interrupts = <0x0 0x3c 0x0>;
  46:                 clocks = <0x7 0x13f>;
  47:                 clock-names = "i2c";
  48:                 pinctrl-names = "default";
  49:                 pinctrl-0 = <0x22>;
  50:                 status = "disabled";
  51:             };        
  52:         */
  53:         if (!np)
  54:             continue;
  55:  
  56:         /* walk the alias backwards to extract the id and work out
  57:          * the 'stem' string */
  58:         while (isdigit(*(end-1)) && end > start) //对于"i2c2",end最终会指向字符'2'的地址
  59:             end--;
  60:         len = end - start; // 获得"i2c"的长度(不包含结尾的数字2),就是3
  61:  
  62:         if (kstrtoint(end, 10, &id) < 0) // 将end指向的字符'2'转化为数字2,赋值给id
  63:             continue;
  64:  
  65:         /* Allocate an alias_prop with enough space for the stem */
  66:         ap = dt_alloc(sizeof(*ap) + len + 1, 4); // 分配内存,多分配的"len+1"用于存放stem的名字
  67:         if (!ap)
  68:             continue;
  69:         memset(ap, 0, sizeof(*ap) + len + 1);
  70:         ap->alias = start; // ap->alias指向字符串"i2c2"
  71:         of_alias_add(ap, np, id, start, len);
  72:     }
   1: static void of_alias_add(struct alias_prop *ap, struct device_node *np,
   2:              int id, const char *stem, int stem_len)
   3: {
   4:     ap->np = np; // np是"/i2c@13880000"对应的节点device_node
   5:     ap->id = id; // id的值是2
   6:     strncpy(ap->stem, stem, stem_len); // 由于stem_len是3,所以ap->stem被赋值为"i2c"
   7:     ap->stem[stem_len] = 0;
   8:     list_add_tail(&ap->link, &aliases_lookup); // 将这个ap加入到全局aliases_lookup链表中
   9:     pr_debug("adding DT alias:%s: stem=%s id=%i node=%s\n",
  10:          ap->alias, ap->stem, ap->id, of_node_full_name(np));
  11: }

在內核中可以看到很多地方都會調用of_alias_get_id,他的作用就是根据传入的device node,在alias中找到对应的唯一编号,如:
of_alias_get_id(pdev->dev.of_node, “spi”)
of_alias_get_id(node, “fimc”)
of_alias_get_id(pdev->dev.of_node, “serial”)
of_alias_get_id(pdev->dev.of_node, “uart”)
of_alias_get_id(dev->of_node, “gpio”)

static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    return i2c_register_adapter(adap);
}

i2c_register_adapter根据这个名称可以看出这是根据设备树描述的硬件i2c控制器而生成的一个i2c_adapter,并注册到系统中,这个i2c_adapter负责i2c底层数据收发。

static int i2c_register_adapter(struct i2c_adapter *adap)
{
    // ...
    of_i2c_register_devices(adap);
    // ...
}

看 of_i2c_register_devices 是关键 为每一个i2c下的节点注册对应为对应的i2c_device 其实他就是i2c_client

void of_i2c_register_devices(struct i2c_adapter *adap)
{
    // ...

    // 轮询每个子节点
    for_each_available_child_of_node(bus, node) {
        if (of_node_test_and_set_flag(node, OF_POPULATED))
            continue;

        client = of_i2c_register_device(adap, node);
        if (IS_ERR(client)) {
            dev_warn(&adap->dev,
                    "Failed to create I2C device for %pOF\n",
                    node);
            of_node_clear_flag(node, OF_POPULATED);
        }
    }
    // ...
}

在of_i2c_register_device()函数中,从device_node节点中获取各种属性的值记录在info结构体中
然后将info传递给i2c_new_device(),生成一个对应的i2c_client结构并返回。

分析到这里了 我们就知道从设备驱动该如何去写了。

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
V
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
V
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区