基于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
发布评论