文章目录
- 前言
- 一、硬件流程图
- 二、晶振设备树描述
- 三、 I2CX时钟设备树描述
- 四、驱动中获得/使能时钟
- 4.1 流程源码分析
- 4.1.1 devm_clk_get(struct device *dev, const char *id)
- 4.1.2 of_clk_add_provider(注册 of_clock_provider)
- 4.1.3 of_parse_phandle_with_args函数详解
- 4.1.3.1 源码分析
- 4.1.3.2 驱动demo
- 4.2 clk_prepare_enable 函数详解
- 4.2.1 enable = clk_gate2_enable
- 参考
前言
- i2c控制器获取时钟的流程分析
一、硬件流程图
简化如下:
二、晶振设备树描述
先来看看晶振("Clock providers"
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22osc: clock@1 { compatible = "fixed-clock"; reg = <1>; #clock-cells = <0>; clock-frequency = <24000000>; clock-output-names = "osc"; }; /*根据compatible可以找到对应的驱动,驱动程序将晶振的频率记录下来,以后作为计算的基准。*/ /*然后再是PLL的设备节点*/ clks: ccm@020c4000 { compatible = "fsl,imx6ul-ccm"; reg = <0x020c4000 0x4000>; interrupts = <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>; #clock-cells = <1>; clocks = <&ckil>, <&osc>, <&ipp_di0>, <&ipp_di1>; clock-names = "ckil", "osc", "ipp_di0", "ipp_di1"; }; /*设备节点本身非常简单,复杂的是它对应的驱动程序。在驱动程序里面,肯定会根据reg获得寄存器的地址,然后设置各种内容*/ /*我们可以为它们配上一个ID。在设备树中的#clock-cells = <1>;表示 用多少个u32位来描述消费者。在本例中使用一个u32来描述。*/
大部分的芯片为了省电,它的外部模块时钟平时都是关闭的,只有在使用某个模块时,才设置相应的寄存器开启对应的时钟。
这些使用者各有不同,要怎么描述这些使用者呢?
我们可以为它们配上一个ID。在设备树中的#clock-cells = <1>
;表示 用多少个u32
位来描述消费者。在本例中使用一个u32
来描述。
这些ID值由谁提供的?
是由驱动程序提供的,该节点会对应一个驱动程序,驱动程序给硬件(消费者)都分配了一个ID,所以说复杂的操作都留给驱动程序来做。
三、 I2CX时钟设备树描述
消费者(“Clock consumers
”)想使用时钟时,首先要找到时钟的直接提供者,向它发出申请。以I2C
控制器为例:
1
2
3
4
5
6
7
8
9
10i2c1: i2c@021a0000 { #address-cells = <1>; #size-cells = <0>; compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c"; reg = <0x021a0000 0x4000>; interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_I2C1>; status = "disabled"; };
在clock
属性里,首先要确定向谁发出时钟申请,这里是向clocks
发出申请,然后确定想要时钟提供者提供哪一路时钟,这里是IMX6UL_CLK_I2C1
,在驱动程序里定义了该宏,每种宏对应了一个时钟ID
。
因此,我们只需要在设备节点定义clocks
这个属性,这个属性确定时钟提供者,然后确定时钟ID
,也就是向时钟提供者申请哪一路时钟。
那么我这个设备驱动程序,怎么去使用这些时钟呢? 以前的驱动程序:clk_get(NULL, "name")
;clk_prepare_enable(clk)
; 现在的驱动程序:of_clk_get(node, 0)
; clk_prepare_enable(clk)
;
四、驱动中获得/使能时钟
4.1 流程源码分析
4.1.1 devm_clk_get(struct device *dev, const char *id)
我们在设备驱动代码中仅使用以下两个api即打开了对应的时钟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208/* Get I2C clock */ i2c_imx->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(i2c_imx->clk)) { dev_err(&pdev->dev, "can't get I2C clockn"); return PTR_ERR(i2c_imx->clk); } ret = clk_prepare_enable(i2c_imx->clk); if (ret) { dev_err(&pdev->dev, "can't enable I2C clockn"); return ret; } drivers/clk/clk-devres.c( struct clk *devm_clk_get(struct device *dev, const char *id) { struct clk **ptr, *clk; ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) return ERR_PTR(-ENOMEM); clk = clk_get(dev, id); if (!IS_ERR(clk)) { *ptr = clk; devres_add(dev, ptr); } else { devres_free(ptr); } return clk; } ) struct clk *clk_get(struct device *dev, const char *con_id) { const char *dev_id = dev ? dev_name(dev) : NULL; struct clk *clk; /*dev_id为设备的名称,对应设备树节点的 21a0000.i2c(dev.of_node->name)*/ /*con_id为“clock-names”*/ pr_info("[%s]_%snn",__FUNCTION__,dev_id);/*[clk_get]_21a0000.i2c*/ if (dev) { clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id); if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER) return clk; } return clk_get_sys(dev_id, con_id); } static struct clk *__of_clk_get_by_name(struct device_node *np, const char *dev_id, const char *name) { struct clk *clk = ERR_PTR(-ENOENT); /* Walk up the tree of devices looking for a clock that matches */ while (np) { int index = 0; /* * For named clocks, first look up the name in the * "clock-names" property. If it cannot be found, then * index will be an error code, and of_clk_get() will fail. */ if (name) index = of_property_match_string(np, "clock-names", name); clk = __of_clk_get(np, index, dev_id, name); if (!IS_ERR(clk)) { break; } else if (name && index >= 0) { if (PTR_ERR(clk) != -EPROBE_DEFER) pr_err("ERROR: could not get clock %s:%s(%i)n", np->full_name, name ? name : "", index); return clk; } /* * No matching clock found on this node. If the parent node * has a "clock-ranges" property, then we can try one of its * clocks. */ np = np->parent; if (np && !of_get_property(np, "clock-ranges", NULL)) break; } return clk; } /*of_property_match_string查找ext_clock是否在clock-names的属性中,我们从设备树中看出 clock-names的属性值为“ext_clock”,结果返回0,即index为0*/ /*它代表的是属性值的编号。 比如clock-names=“ext_clock”,"xxxx"; 其中index 0为“ext_clock, index 1 为“xxxx”*/ static struct clk *__of_clk_get(struct device_node *np, int index, const char *dev_id, const char *con_id) { struct of_phandle_args clkspec; struct clk *clk; int rc; if (index < 0) return ERR_PTR(-EINVAL); /* struct of_phandle_args { struct device_node *np; //引用到的节点 int args_count; //参数数量 uint32_t args[MAX_PHANDLE_ARGS];参数 };*/ /*clocks = <&clks IMX6UL_CLK_I2C1>;*/ /*clkspec->np=(ccm)(&clks),clkspec->args_count=1,clkspec->args=IMX6UL_CLK_I2C1*/ rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, &clkspec); if (rc) return ERR_PTR(rc); clk = __of_clk_get_from_provider(&clkspec, dev_id, con_id); of_node_put(clkspec.np); return clk; } /*这个 of_parse_phandle_with_args很重要,为什么说很重要,很多人在学习设备设备树的时候不知道clocks = <&clks 156>;这个尖括号里面代表的意思*/ /*struct of_phandle_args clkspec; struct of_phandle_args { struct device_node *np; //引用到的节点 int args_count; //参数数量 uint32_t args[MAX_PHANDLE_ARGS];参数 }; 这个参数很重要,我们暂且记住,后面时钟用到了再说*/ /* __of_clk_get_from_provider(&clkspec, "21a0000.i2c", NULL, true);*/ __of_clk_get_from_provider(&clkspec, dev_id, con_id, true); drivers/clk/clk.c( struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec, const char *dev_id, const char *con_id){ struct of_clk_provider *provider; struct clk *clk = ERR_PTR(-EPROBE_DEFER); if (!clkspec) return ERR_PTR(-EINVAL); /* Check if we have such a provider in our array */ mutex_lock(&of_clk_mutex); list_for_each_entry(provider, &of_clk_providers, link) { if (provider->node == clkspec->np)/*ccm@020c4000*/ /*clks[IMX6UL_CLK_I2C1] = imx_clk_gate2("i2c1", "perclk", base + 0x70, 6);/*以i2c为例*/*/ /*struct clk_hw *__clk_get_hw(struct clk *clk)*/ clk = provider->get(clkspec, provider->data);(IMX6UL_CLK_I2C1,clks[])(获取时钟提供者) if (!IS_ERR(clk)) { /*从clk[]获取消费者时钟*/ /*Linux clock子系统【5】-从imx_clk_mux解析多路复用时钟时钟驱动(provider侧)分析(__clk_create_clk)*/ clk = __clk_create_clk(__clk_get_hw(clk), dev_id, con_id); if (!IS_ERR(clk) && !__clk_get(clk)) { __clk_free_clk(clk); clk = ERR_PTR(-ENOENT); } break; } } mutex_unlock(&of_clk_mutex); return clk; } ) /*遍历of_clk_providers链表,获得struct of_clk_provider *provider;这结构体是用来干嘛的*/ /** * struct of_clk_provider - Clock provider registration structure * @link: Entry in global list of clock providers * @node: Pointer to device tree node of clock provider * @get: Get clock callback. Returns NULL or a struct clk for the * given clock specifier * @data: context pointer to be passed into @get callback */ struct of_clk_provider { struct list_head link; struct device_node *node; struct clk *(*get)(struct of_phandle_args *clkspec, void *data); void *data; }; /*重点,函数指针,获得clk结构体的函数,分析到现在,终于知道了clk是怎么来的*/ struct clk_hw *__clk_get_hw(struct clk *clk) { return !clk ? NULL : clk->core->hw;(这个是怎么来的有什么作用) } struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id, const char *con_id) { struct clk *clk; /* This is to allow this function to be chained to others */ if (!hw || IS_ERR(hw)) return (struct clk *) hw; clk = kzalloc(sizeof(*clk), GFP_KERNEL); if (!clk) return ERR_PTR(-ENOMEM); clk->core = hw->core; clk->dev_id = dev_id; clk->con_id = con_id; clk->max_rate = ULONG_MAX; clk_prepare_lock(); hlist_add_head(&clk->clks_node, &hw->core->clks); clk_prepare_unlock(); return clk; }
4.1.2 of_clk_add_provider(注册 of_clock_provider)
Linux clock子系统【4】-从CLK_OF_DECLARE 解析时钟驱动
4.1.3 of_parse_phandle_with_args函数详解
4.1.3.1 源码分析
1
2
3
4
5
6
7
8of_parse_phandle_with_args __of_parse_phandle_with_args of_find_node_by_phandle of_property_read_u32_array of_find_property_value_of_size of_find_property __of_find_property
1
2
3
4
5
6
7
8
9
10
11[clk_get]_21a0000.i2c list_name=clocks size=8, sizeof(*list)=4 phandle=1_cur_index=0_index=0 ccm_kobj.name=ccm@020c4000 count=1 [i2c_imx_probe]_end
of_parse_phandle_with_args
函数作用:获得节点 phandle 列表中的某个节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179/* struct of_phandle_args *rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", 0, &clkspec);*/ /*参数 np 指向当前节点;list_name 指向节点中 phandle 列表的属性名; cells_name 参数指明 phandle 指向的节点所含的 cells 个数;index 表示 phandle 列 表的索引,0 代表第一个 phandle,1 代表第二个 phandle;out_args 参数用于存储 phandle 中的参数。*/ /*函数首先检查 index 的值,小于 0 直接返回错误。检查通过之后直接调用 __of_parse_phandle_with_args() 函数,然后返回*/ int of_parse_phandle_with_args(const struct device_node *np, const char *list_name, const char *cells_name, int index, struct of_phandle_args *out_args) { if (index < 0) return -EINVAL; return __of_parse_phandle_with_args(np, list_name, cells_name, index, out_args); } EXPORT_SYMBOL(of_parse_phandle_with_args); /** * of_parse_phandle_with_args() - Find a node pointed by phandle in a list * @np: pointer to a device tree node containing a list * @list_name: property name that contains a list * @cells_name: property name that specifies phandles' arguments count * @index: index of a phandle to parse out * @out_args: optional pointer to output arguments structure (will be filled) * * This function is useful to parse lists of phandles and their arguments. * Returns 0 on success and fills out_args, on error returns appropriate * errno value. * * Caller is responsible to call of_node_put() on the returned out_args->node * pointer. * * Example: * * phandle1: node1 { * #list-cells = <2>; * } * * phandle2: node2 { * #list-cells = <1>; * } * * node3 { * list = <&phandle1 1 2 &phandle2 3>; * } * * To get a device_node of the `node2' node you may call this: * of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args); */ static int __of_parse_phandle_with_args(const struct device_node *np, const char *list_name, const char *cells_name, int index, struct of_phandle_args *out_args) { const __be32 *list, *list_end; int rc = 0, size, cur_index = 0; uint32_t count = 0; struct device_node *node = NULL; phandle phandle; /* Retrieve the phandle list property */ /*检索pHandle (读取整数) 链表 属性*/ pr_info("list_name=%sn",list_name);/*list_name=clocks*/ list = of_get_property(np, list_name, &size); if (!list) return -ENOENT; list_end = list + size / sizeof(*list); pr_info("size=%d, sizeof(*list)=%dn",size,sizeof(*list));/*size=8, sizeof(*list)=4*/ /*用函数 of_get_property() 函数获得当前节点的 phandle list 属性的值,存储到 list 变量,然后计算 phandle list 属性结束的值。*/ /* Loop over the phandles until all the requested entry is found */ /*然后遍历节点 phandle list 里面的 cells,每遍历依次,只要 phandle 有效,就调用 of_find_node_by_phandle() 函数获得 phandle 对应的节点,然后读取该节点中 cells_name 名字对应的属性值,存储在 count 变量中。如果 list + count 的值越界, 那么判定位越界。*/ while (list < list_end) { rc = -EINVAL; count = 0; /* * If phandle is 0, then it is an empty entry with no * arguments. Skip forward to the next entry. */ phandle = be32_to_cpup(list++); pr_info("phandle=%d_cur_index=%d_index=%dn",phandle,cur_index,index);/*phandle=1_cur_index=0_index=0*/ if (phandle) { /* * Find the provider node and parse the #*-cells * property to determine the argument length */ node = of_find_node_by_phandle(phandle); pr_info("%s_kobj.name=%sn",node->name,node->kobj.name);/*ccm_kobj.name=ccm@020c4000*/ if (!node) { pr_err("%s: could not find phandlen", np->full_name); goto err; } if (of_property_read_u32(node, cells_name, &count)) { pr_err("%s: could not get %s for %sn", np->full_name, cells_name, node->full_name); goto err; } pr_info("count=%dnn",count);/*#clocks-cell=<1>;conut=1*/ /* * Make sure that the arguments actually fit in the * remaining property data length */ if (list + count > list_end) { pr_err("%s: arguments longer than propertyn", np->full_name); goto err; } /*cur_index 和 index 的比较确保了正在读取指定的 phandle。如果 out_args 存在,那 么函数将 phandle 对应的参数都存储在 out_args 的 args 数组里,然后返回;否则调 用 of_node_put() 函数,释放节点的使用权;如果不是需要找的 phandle,那么继续遍 历下一个*/ /* * All of the error cases above bail out of the loop, so at * this point, the parsing is successful. If the requested * index matches, then fill the out_args structure and return, * or return -ENOENT for an empty entry. */ rc = -ENOENT; if (cur_index == index) { if (!phandle) goto err; if (out_args) { int i; if (WARN_ON(count > MAX_PHANDLE_ARGS)) count = MAX_PHANDLE_ARGS; out_args->np = node; out_args->args_count = count; for (i = 0; i < count; i++) out_args->args[i] = be32_to_cpup(list++); } else { of_node_put(node); } /* Found it! return success */ return 0; } of_node_put(node); node = NULL; list += count; cur_index++; } /* * Unlock node before returning result; will be one of: * -ENOENT : index is for empty phandle * -EINVAL : parsing error on data * [1..n] : Number of phandle (count mode; when index = -1) */ rc = index < 0 ? cur_index : -ENOENT; err: if (node) of_node_put(node); return rc; } /*参数 handle 指向节点中 phandle 的属性值。 函数首先调用 raw_spin_lock_irqsave() 函数加锁。由于 DTB 将所有节点都存放在 of_allnodes 为表头的单链表里,然后调用 for 循环遍历所有节点。每次遍历一个节点, 如果节点 device_node 的 phandle 成员和遍历到的节点一致,那么就找到 phandle 对 应的节点。接着停止 for 循环,调用 of_node_get() 函数添加节点引用数。最后返回 device_node 之前调用 raw_spin_unlock_irqrestore() 函数释放锁。*/ /** * of_find_node_by_phandle - Find a node given a phandle * @handle: phandle of the node to find * * Returns a node pointer with refcount incremented, use * of_node_put() on it when done. */ struct device_node *of_find_node_by_phandle(phandle handle) { struct device_node *np; unsigned long flags; raw_spin_lock_irqsave(&devtree_lock, flags); for (np = of_allnodes; np; np = np->allnext) if (np->phandle == handle) break; of_node_get(np); raw_spin_unlock_irqrestore(&devtree_lock, flags); return np; } EXPORT_SYMBOL(of_find_node_by_phandle);
4.1.3.2 驱动demo
实践目的是在 DTS 文件中构建三个私有节点,第一个私有节点通过 phandle 的方式引用 了第二个和第三个节点,节点二和节点三都存储在第一个节点的 phandle list 属性中, 然后通过 of_parse_phandle_with_args() 函数分贝读取两个节点,函数定义如下:
1
2
3
4
5
6/*这个函数经常用用于从节点的 phandle list 中读取 phandle 对应的节点*/ int of_parse_phandle_with_args(const struct device_node *np, const char *list_name, const char *cells_name, int index, struct of_phandle_args *out_args)
- DTS 文件
由于使用的平台是 ARM32,所以在源码 /arch/arm/boot/dts 目录下创建一个 DTSI 文件,在 root 节点之下创建一个名为 DTS_demo 的子节点。节点默认打开。再创建两个节点,节点的 cells 分别是 3 和 2,具体内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28/* * DTS Demo Code * * (C) 2019.01.06 <buddy.zhang@aliyun.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ / { DTS_demo { compatible = "DTS_demo, BiscuitOS"; status = "okay"; phy-handle = <&phy0 1 2 3 &phy1 4 5>; }; phy0: phy@0 { #phy-cells = <3>; compatible = "PHY0, BiscuitOS"; }; phy1: phy@1 { #phy-cells = <2>; compatible = "PHY1, BiscuitOS"; }; };
创建完毕之后,将其保存并命名为 DTS_demo.dtsi。然后开发者在 Linux 4.20.8 的源 码中,找到 arch/arm/boot/dts/vexpress-v2p-ca9.dts 文件,然后在文件开始地方添 加如下内容:
1
2
3#include "DTS_demo.dtsi"
- 编写对应驱动
准备好 DTSI 文件之后,开发者编写一个简单的驱动,这个驱动作为 DTS_demo 的设备驱 动,在 DTS 机制遍历时会调用匹配成功的驱动,最终运行驱动里面的代码。在驱动的 probe 函数中,首先获得驱动所对应的节点,通过 platform_device 的 of_node 成员传 递。获得驱动对应的节点之后,通过调用 of_parse_phandle_with_args() 函数获得指定 的节点。驱动编写如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117/* * DTS: of_parse_phandle_with_args * * int of_parse_phandle_with_args(const struct device_node *np, * const char *list_name, const char *cells_name, * int index, struct of_phandle_args *out_args) * * int of_device_is_available(const struct device_node *device) * * (C) 2019.01.11 BuddyZhang1 <buddy.zhang@aliyun.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ /* * Private DTS file: DTS_demo.dtsi * * / { * DTS_demo { * compatible = "DTS_demo, BiscuitOS"; * status = "okay"; * phy-handle = <&phy0 1 2 3 &phy1 4 5>; * }; * * phy0: phy@0 { * #phy-cells = <3>; * compatible = "PHY0, BiscuitOS"; * }; * * phy1: phy@1 { * #phy-cells = <2>; * compatible = "PHY1, BiscuitOS"; * }; * }; * * On Core dtsi: * * include "DTS_demo.dtsi" */ #include <linux/init.h> #include <linux/kernel.h> #include <linux/of_platform.h> /* define name for device and driver */ #define DEV_NAME "DTS_demo" /* probe platform driver */ static int DTS_demo_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct of_phandle_args args; int rc, index = 0; const u32 *comp; printk("DTS demo probe entence.n"); /* Read first phandle argument */ rc = of_parse_phandle_with_args(np, "phy-handle", "#phy-cells", 0, &args); if (rc < 0) { printk("Unable to parse phandle.n"); return -1; } comp = of_get_property(args.np, "compatible", NULL); if (comp) printk("%s compatible: %sn", args.np->name, comp); for (index = 0; index < args.args_count; index++) printk("Args %d: %#xn", index, args.args[index]); /* Read second phandle argument */ rc = of_parse_phandle_with_args(np, "phy-handle", "#phy-cells", 1, &args); if (rc < 0) { printk("Unable to parse phandle.n"); return -1; } comp = of_get_property(args.np, "compatible", NULL); if (comp) printk("%s compatible: %sn", args.np->name, comp); for (index = 0; index < args.args_count; index++) printk("Args %d: %#xn", index, args.args[index]); return 0; } /* remove platform driver */ static int DTS_demo_remove(struct platform_device *pdev) { return 0; } static const struct of_device_id DTS_demo_of_match[] = { { .compatible = "DTS_demo, BiscuitOS", }, { }, }; MODULE_DEVICE_TABLE(of, DTS_demo_of_match); /* platform driver information */ static struct platform_driver DTS_demo_driver = { .probe = DTS_demo_probe, .remove = DTS_demo_remove, .driver = { .owner = THIS_MODULE, .name = DEV_NAME, /* Same as device name */ .of_match_table = DTS_demo_of_match, }, }; module_platform_driver(DTS_demo_driver);
启动内核,在启动阶段就会运行驱动的 probe 函数,并打印如下信息:
1
2
3
4
5
6
7
8
9
10[ 3.534323] DTS demo probe entence. [ 3.534359] phy compatible: PHY0, BiscuitOS [ 3.534364] Args 0: 0x1 [ 3.534369] Args 1: 0x2 [ 3.534372] Args 2: 0x3 [ 3.534379] phy compatible: PHY1, BiscuitOS [ 3.534383] Args 0: 0x4 [ 3.534387] Args 1: 0x5
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 确定时钟个数 int nr_pclks = of_count_phandle_with_args(dev->of_node, "clocks", "#clock-cells"); // 获得时钟 for (i = 0; i < nr_pclks; i++) { struct clk *clk = of_clk_get(dev->of_node, i); } // 使能时钟 clk_prepare_enable(clk); // 禁止时钟 clk_disable_unprepare(clk);
4.2 clk_prepare_enable 函数详解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62/* clk_prepare_enable helps cases using clk_enable in non-atomic context. */ linux/clk.h( static inline int clk_prepare_enable(struct clk *clk) { int ret; ret = clk_prepare(clk); if (ret) return ret; ret = clk_enable(clk); if (ret) clk_unprepare(clk); return ret; } static inline int clk_prepare(struct clk *clk) { /*might_sleep(): 指示当前函数可以睡眠。如果它所在的函数处于原子上下文(atomic context)中(如,spinlock, irq-handler…),将打印出堆栈的回溯信息。这个函数主要用来做调试工作,在你不确定不期望睡眠的地方是否真的不会睡眠时,就把这个宏加进去。*/ might_sleep(); return 0; } ) drivers/clk/clk.c( /** * clk_enable - ungate a clock * @clk: the clk being ungated * * clk_enable must not sleep, which differentiates it from clk_prepare. In a * simple case, clk_enable can be used instead of clk_prepare to ungate a clk * if the operation will never sleep. One example is a SoC-internal clk which * is controlled via simple register writes. In the complex case a clk ungate * operation may require a fast and a slow part. It is this reason that * clk_enable and clk_prepare are not mutually exclusive. In fact clk_prepare * must be called before clk_enable. Returns 0 on success, -EERROR * otherwise. */ int clk_enable(struct clk *clk) { unsigned long flags; int ret; flags = clk_enable_lock(); ret = __clk_enable(clk); clk_enable_unlock(flags); return ret; } static int __clk_enable(struct clk *clk) { if (!clk) return 0; return clk_core_enable(clk->core); } static int clk_core_enable(struct clk_core *clk) { ret = clk_core_enable(clk->parent); clk->ops->enable(clk->hw); } )
4.2.1 enable = clk_gate2_enable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37arch/arm/mach-imx/clk-gate2.c( static struct clk_ops clk_gate2_ops = { .enable = clk_gate2_enable, .disable = clk_gate2_disable, .disable_unused = clk_gate2_disable_unused, .is_enabled = clk_gate2_is_enabled, }; static int clk_gate2_enable(struct clk_hw *hw) { clk_gate2_do_shared_clks(hw, true); return 0; } static void clk_gate2_disable(struct clk_hw *hw) { clk_gate2_do_shared_clks(hw, false); } static void clk_gate2_do_shared_clks(struct clk_hw *hw, bool enable) { struct clk_gate2 *gate = to_clk_gate2(hw); clk_gate2_do_hardware(gate, enable); } /*struct clk *clks[IMX6UL_CLK_I2C1] = imx_clk_gate2("i2c1", "perclk", base + 0x70, 6);/*以i2c为例*/*/ #define CCM_CCGR_FULL_ENABLE 0x3 static void clk_gate2_do_hardware(struct clk_gate2 *gate, bool enable) { u32 reg; (ccm_reg=(0x020c4000+0x70 0x4000) reg = readl(gate->reg); if (enable) reg |= CCM_CCGR_FULL_ENABLE << gate->bit_idx; else reg &= ~(CCM_CCGR_FULL_ENABLE << gate->bit_idx); writel(reg, gate->reg); } )
参考
of_parse_phandle_with_args函数详解
转载:Linux CCF框架简要分析和API调用
https://www.likecs.com/show-204547770.html
最后
以上就是长情书本最近收集整理的关于Linux clock子系统【3】-i2c控制器打开时钟的流程分析(devm_clk_get)(consumer侧)前言一、硬件流程图二、晶振设备树描述三、 I2CX时钟设备树描述四、驱动中获得/使能时钟参考的全部内容,更多相关Linux内容请搜索靠谱客的其他文章。
发表评论 取消回复