基于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 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结构并返回。
分析到这里了 我们就知道从设备驱动该如何去写了。
- 分享
- 举报
-
浏览量:869次2023-06-12 14:34:15
-
浏览量:614次2023-11-15 17:25:34
-
浏览量:1323次2023-09-18 11:05:06
-
浏览量:4763次2018-05-07 20:47:52
-
2020-08-05 20:23:18
-
浏览量:1694次2020-06-23 17:33:04
-
浏览量:10594次2021-03-03 17:39:18
-
浏览量:3777次2020-08-30 09:57:38
-
浏览量:2657次2024-02-26 14:13:38
-
浏览量:4517次2021-05-14 09:48:13
-
浏览量:1782次2024-01-02 22:42:19
-
浏览量:1224次2024-02-26 13:58:45
-
浏览量:2396次2023-12-21 11:18:25
-
浏览量:3458次2022-09-03 09:03:36
-
浏览量:3415次2022-05-27 10:41:52
-
浏览量:7519次2023-12-27 20:28:48
-
浏览量:815次2023-11-15 11:24:03
-
2022-05-17 09:00:32
-
浏览量:3985次2020-10-21 15:44:26
-
广告/SPAM
-
恶意灌水
-
违规内容
-
文不对题
-
重复发帖
V
感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~
举报类型
- 内容涉黄/赌/毒
- 内容侵权/抄袭
- 政治相关
- 涉嫌广告
- 侮辱谩骂
- 其他
详细说明