基于rk3588

编程入门 行业动态 更新时间:2024-10-24 10:25:28

基于rk3588

基于rk3588

i2c驱动框架学习(一)

声明:本文基于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        0713 #define I2C_ALF_SUSPEND_REPORTED    1714 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_ASYNC1621 subsys_initcall_sync(rk3x_i2c_driver_init);1622 #else1623 subsys_initcall(rk3x_i2c_driver_init);1624 #endif1625 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 #else1632 module_platform_driver(rk3x_i2c_driver);1633 #endif1634 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         patible = "rockchip,rv1108-i2c",1385         .data = &rv1108_soc_data1386     },1387     {1388         patible = "rockchip,rv1126-i2c",1389         .data = &rv1126_soc_data1390     },1391     {1392         patible = "rockchip,rk3066-i2c",1393         .data = &rk3066_soc_data1394     },1395     {1396         patible = "rockchip,rk3188-i2c",1397         .data = &rk3188_soc_data1398     },1399     {1400         patible = "rockchip,rk3228-i2c",1401         .data = &rk3228_soc_data1402     },1403     {1404         patible = "rockchip,rk3288-i2c",1405         .data = &rk3288_soc_data1406     },1407     {1408         patible = "rockchip,rk3399-i2c",1409         .data = &rk3399_soc_data1410     },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_adapter1533 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_node6:     of_chosen = of_find_node_by_path("/chosen"); // 找到/chosen节点对应的device_node7:     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 out57:          * the 'stem' string */58:         while (isdigit(*(end-1)) && end > start) //对于"i2c2",end最终会指向字符'2'的地址59:             end--;60:         len = end - start; // 获得"i2c"的长度(不包含结尾的数字2),就是361:  62:         if (kstrtoint(end, 10, &id) < 0) // 将end指向的字符'2'转化为数字2,赋值给id63:             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_node5:     ap->id = id; // id的值是26:     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结构并返回。

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

下来我们再分析i2c主控制器的通信的算法内容。请看一章内容。若有错误请读者帮忙指出来

更多推荐

基于rk3588

本文发布于:2024-02-27 04:29:54,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1705175.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!