[经验] Linux Common Clock Framework (3)

黄土马家   2017-9-15 16:19 楼主
1.struct clk结构

/*include/linux/clk-private.h*/

struct clk {
constchar *name;
conststruct clk_ops *ops;
struct clk_hw *hw;
struct clk *parent;
constchar **parent_names;
struct clk **parents;
u8 num_parents;
unsignedlong rate;
unsignedlong new_rate;
unsignedlong flags;
unsignedint enable_count;
unsignedint prepare_count;
struct hlist_head children;
struct hlist_node child_node;
unsignedint notifier_count;
#ifdef CONFIG_COMMON_CLK_DEBUG
struct dentry *dentry;
#endif
};
name, ops, hw, parents_name,num_parents,flags, 可参考Linux common clock framework(2)_clockprovider中的相关描述;
parent,保存了该clock当前的parent clockstruct clk指针;
parents,一个指针数组,保存了所有可能的parent clockstruct clk指针;
rate,当前的clock rate
new_rate,新设置的clock rate,之所要保存在这里,是因为set rate过程中有一些中间计算,后面再详解;
enable_count, prepare_count,该clockenableprepare的次数,用于确保enable/disable以及prepare/unprepare的成对调用;
children,该clockchildren clocks(孩儿们),以链表的形式组织;
child_node,一个list node,自己作为child时,挂到parentchildren list时使用;
notifier_count,记录注册到notifier的个数。
Linux common clock framework(2)_clockprovider中已经讲过,clockprovider需要将系统的clocktree的形式组织起来,分门别类,并在系统初始化时,通过provider的初始化接口,或者clockframework coreDTS接口,将所有的clock注册到kernel
clock的注册,统一由clk_regitser接口实现,但基于该接口,kernel也提供了其它更为便利注册接口,下面将会一一描述。
2. clk_regitser
clk_register是所有register接口的共同实现,负责将clock注册到kernel,并返回代表该clockstructclk指针。分析该接口之前,我们先看一下下面的内容:
1.jpg
上面是kernelclk_register接口可能的实现位置,由此可以看出,clk_register“include/linux/clk-provider.h”中声明,却可能在不同的C文件中实现。其它clockAPI也类似。这说明了什么?
这恰恰呼应了“Linuxcommon clock framework”“common”一词。
在旧的kernel中,clockframework只是规定了一系列的API声明,具体API的实现,由各个machine代码完成。这就导致每个machine目录下,都有一个类似clock.c的文件,以比较相似的逻辑,实现clockprovider的功能。显然,这里面有很多冗余代码。
后来,kernel将这些公共代码,以clockprovider的形式(上面drivers/clk/clk.c文件)抽象出来,就成了我们所说的commonclock framework
后面所有的描述,都会以commonclock framework的核心代码为基础,其它的,就不再涉及了。
下面是clk_register的实现:
/*clk_register- allocate a new clock, register it and return an opaque cookie
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
* clk_register is the primary interface forpopulating the clock tree with new
* clock nodes. It returns a pointer to the newly allocated struct clk which
* cannot be dereferenced by driver code butmay be used in conjunction with the
* rest of the clock API. In the event of an error clk_register willreturn an
* error code; drivers must test for an error code aftercalling clk_register. */
struct clk*clk_register(struct device *dev, structclk_hw *hw)
{
int i,ret;
struct clk*clk;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if(!clk) {
pr_err("%s: could not allocate clk\n", __func__);
ret = -ENOMEM;
gotofail_out;
}
clk->name =kstrdup(hw->init->name, GFP_KERNEL);
if(!clk->name) {
pr_err("%s: could not allocateclk->name\n", __func__);
ret = -ENOMEM;
gotofail_name;
}
clk->ops =hw->init->ops;
if(dev && dev->driver)
clk->owner =dev->driver->owner;
clk->hw = hw;
clk->flags =hw->init->flags;
clk->num_parents =hw->init->num_parents;
hw->clk = clk;
/* allocate local copy in case parent_names is __initdata*/
clk->parent_names = kcalloc(clk->num_parents,sizeof(char *),
GFP_KERNEL);
if(!clk->parent_names) {
pr_err("%s: could not allocateclk->parent_names\n",__func__);
ret = -ENOMEM;
gotofail_parent_names;
}
/* copy each string name in case parent_names is__initdata */
for (i= 0; i < clk->num_parents; i++) {
clk->parent_names =kstrdup(hw->init->parent_names,
GFP_KERNEL);
if(!clk->parent_names) {
pr_err("%s: could not copyparent_names\n", __func__);
ret = -ENOMEM;
gotofail_parent_names_copy;
}
}
ret = __clk_init(dev, clk);
if(!ret)
returnclk;
fail_parent_names_copy:
while(--i >= 0)
kfree(clk->parent_names);
kfree(clk->parent_names);
fail_parent_names:
kfree(clk->name);
fail_name:
kfree(clk);
fail_out:
returnERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(clk_register);
该接口接受一个structclk_hw指针,该指针包含了将要注册的clock的信息(具体可参考Linux common clock framework(2)_clockprovider),在内部分配一个structclk变量后,将clock信息保存在变量中,并返回给调用者。实现逻辑如下:
分配structclk空间;
根据structclk_hw指针提供的信息,初始化clknameopshwflagsnum_parentsparents_names等变量;
调用内部接口__clk_init,执行后续的初始化操作。
3. 通用API的实现
3.1 clock get
clockget是通过clock名称获取structclk指针的过程,由clk_getdevm_clk_getclk_get_sysof_clk_getof_clk_get_by_nameof_clk_get_from_provider等接口负责实现,这里以clk_get为例,分析其实现过程(位于drivers/clk/clkdev.c中)。
1clk_get
struct clk *clk_get(structdevice *dev, constchar *con_id)
{
constchar*dev_id = dev ? dev_name(dev) : NULL;
struct clk*clk;
if(dev) {
clk =of_clk_get_by_name(dev->of_node, con_id);
if (!IS_ERR(clk) && __clk_get(clk))
returnclk;
}
returnclk_get_sys(dev_id, con_id);
}
如果提供了structdevice指针,则调用of_clk_get_by_name接口,通过devicetree接口获取clock指针。否则,如果没有提供设备指针,或者通过devicetree不能正确获取clock,则进一步调用clk_get_sys
这两个接口的定义如下。
2)of_clk_get_by_name
我们在Linux common clockframework(2)_clockprovider中已经提过,clock consumer会在本设备的DTS中,以clocksclock-names为关键字,定义所需的clock。系统启动后,devicetree会简单的解析,以structdevice_node指针的形式,保存在本设备的of_node变量中。
of_clk_get_by_name,就是通过扫描所有“clock-names”中的值,和传入的name比较,如果相同,获得它的index(即“clock-names”中的第几个),调用of_clk_get,取得clock指针。
1: struct clk*of_clk_get_by_name(struct device_node *np, constchar*name)
2: {
3: struct clk*clk = ERR_PTR(-ENOENT);
4:
5: /* Walk up the tree of devices looking for a clock thatmatches */
6: while(np) {
7: int index = 0;
8:
9: /*
10: * For named clocks, first lookup the name in the
11: * "clock-names"property. If it cannot be found, then
12: * index will be an error code,and of_clk_get() will fail.
13: */
14: if (name)
15: index =of_property_match_string(np, "clock-names", name);
16: clk =of_clk_get(np, index);
17: if (!IS_ERR(clk))
18: break;
19: elseif (name && index >= 0) {
20: pr_err("ERROR: could not get clock%s:%s(%i)\n",
21: np->full_name, name ? name : "", index);
22: return clk;
23: }
24:
25: /*
26: * No matching clock found onthis node. If the parent node
27: * has a"clock-ranges" property, then we can try one of its
28: * clocks.
29: */
30: np = np->parent;
31: if (np && !of_get_property(np, "clock-ranges", NULL))
32: break;
33: }
34:
35: returnclk;
36: }
6~33行,是一个while循环,用于扫描所有的device_node
14~15行,只要name不为空,管它三七二十一,直接以name为参数,去和“clock-names”匹配,获得一个index
16~18行,以返回的index为参数,调用of_clk_get。这个index可能是invalid,不过无所谓,最糟糕就是不能获得clock指针。如果成功获取,则退出,或者继续;
19~22行,一个警告,如果nameindex均合法,但是不能获得指针,则视为异常状况;
25~32行,尝试”clock-ranges“熟悉,比较冷门,不介绍它。
再看一下of_clk_get接口。
1:struct clk *of_clk_get(structdevice_node *np, int index)
2: {
3: structof_phandle_args clkspec;
4: struct clk*clk;
5: int rc;
6:
7: if(index < 0)
8: return ERR_PTR(-EINVAL);
9:
10: rc =of_parse_phandle_with_args(np, "clocks", "#clock-cells",index,
11: &clkspec);
12: if(rc)
13: return ERR_PTR(rc);
14:
15: clk =of_clk_get_from_provider(&clkspec);
16: of_node_put(clkspec.np);
17: returnclk;
18: }
10~13行,通过of_parse_phandle_with_args接口,将index转换为structof_phandle_args类型的参数句柄;
15行,调用of_clk_get_from_provider,获取clock指针。
of_clk_get_from_provider的实现位于drivers/clk/clk.c,通过便利of_clk_providers链表,并调用每一个providerget回调函数,获取clock指针。如下:

1:struct clk *of_clk_get_from_provider(structof_phandle_args *clkspec)

2: {
3: structof_clk_provider *provider;
4: struct clk*clk = ERR_PTR(-ENOENT);
5:
6: /* Check if we have such a provider in our array */
7: mutex_lock(&of_clk_lock);
8: list_for_each_entry(provider, &of_clk_providers, link) {
9: if (provider->node == clkspec->np)
10: clk = provider->get(clkspec,provider->data);
11: if (!IS_ERR(clk))
12: break;
13: }
14: mutex_unlock(&of_clk_lock);
15:
16: returnclk;
17: }
3:分析到这里之后,consumer侧的获取流程已经很清晰,再结合Linux common clockframework(2)_clock provider中所介绍的of_clk_add_provider接口,整个流程都融汇贯通了。篇幅所限,有关of_clk_add_provider接口的实现,本文就不再分析了,感兴趣的读者可以自行阅读kernel代码。
3.4 clock rate有关的实现
clockrate有关的实现包括getsetround三类,让我们依次说明。
1clk_get_rate负责获取某个clock的当前rate,代码如下:
/**
*clk_get_rate - return the rate of clk
*@clk: the clk whose rate is being returned
*
*Simply returns the cached rate of the clk, unless CLK_GET_RATE_NOCACHE flag
*is set, which means a recalc_rate will be issued.
*If clk is NULL then returns 0.
*/
unsignedlong clk_get_rate(struct clk*clk)
{
unsignedlong rate;
clk_prepare_lock();
if(clk && (clk->flags & CLK_GET_RATE_NOCACHE))
__clk_recalc_rates(clk, 0);
rate = __clk_get_rate(clk);
clk_prepare_unlock();
returnrate;
}
EXPORT_SYMBOL_GPL(clk_get_rate);
a)如果该clock设置了CLK_GET_RATE_NOCACHE标志,获取rate前需要先调用__clk_recalc_rates接口,根据当前硬件的实际情况,重新计算rate __clk_recalc_rates的逻辑是:如果提供了recalc_rateops,以parentclockrate为参数,调用该ops,否则,直接获取parentclock值;然后,递归recalc所有childclock
b)调用__clk_get_rate返回实际的rate值。
2clk_round_rate,返回该clock支持的,和输入rate最接近的rate值(不做任何改动),实际是由内部函数__clk_round_rate实现,代码如下:
1:unsignedlong __clk_round_rate(struct clk*clk, unsignedlong rate)
2: {
3: unsignedlong parent_rate = 0;
4:
5: if(!clk)
6: return 0;
7:
8: if(!clk->ops->round_rate) {
9: if (clk->flags & CLK_SET_RATE_PARENT)
10: return __clk_round_rate(clk->parent, rate);
11: else
12: return clk->rate;
13: }
14:
15: if(clk->parent)
16: parent_rate =clk->parent->rate;
17:
18: returnclk->ops->round_rate(clk->hw, rate, &parent_rate);
19: }
a18行,如果该clock提供了round_rateops,直接调用该ops
需要说明的是,round_rateops接受两个参数,一个是需要roundrate,另一个时parentrate(以指针的形式提供)。它的意义是,对有些clock来说,如果需要得到一个比较接近的值,需要同时roundparent clock,因此会在该指针中返回round后的parentclock
b9~10行,如果clock没有提供round_rateops,且设置了CLK_SET_RATE_PARENT标志,则递归roundparent clock,背后的思考是,直接使用parentclock所能提供的最接近的rate
c11~12,最后一种情况,直接返回原值,意味着无法round
3clk_set_rate
setrate的逻辑比较复杂,代码如下:
1:int clk_set_rate(struct clk*clk, unsignedlong rate)
2: {
3: struct clk*top, *fail_clk;
4: int ret= 0;
5:
6: /* prevent racing with updates to the clock topology */
7: clk_prepare_lock();
8:
9: /* bail early if nothing to do */
10: if(rate == clk->rate)
11: goto out;
12:
13: if((clk->flags & CLK_SET_RATE_GATE) && clk->prepare_count) {
14: ret = -EBUSY;
15: goto out;
16: }
17:
18: /* calculate new rates and get the topmost changed clock*/
19: top =clk_calc_new_rates(clk, rate);
20: if(!top) {
21: ret = -EINVAL;
22: goto out;
23: }
24:
25: /* notify that we are about to change rates */
26: fail_clk =clk_propagate_rate_change(top, PRE_RATE_CHANGE);
27: if(fail_clk) {
28: pr_warn("%s: failed to set %s rate\n", __func__,
29: fail_clk->name);
30: clk_propagate_rate_change(top, ABORT_RATE_CHANGE);
31: ret = -EBUSY;
32: goto out;
33: }
34:
35: /* change the rates */
36: clk_change_rate(top);
37:
38: out:
39: clk_prepare_unlock();
40:
41: returnret;
42: }
a9~16,进行一些合法性判断。
b19~23行,调用clk_calc_new_rates接口,将需要设置的rate缓存在new_rate字段。
同时,获取设置该rate的话,需要修改到的最顶层的clock。背后的逻辑是:如果该clockrate改变,有可能需要通过改动parentclockrate来实现,依次递归。
c25~23,发送rate将要改变的通知。如果有clock不能接受改动,即set rate失败,再发送rate更改停止的通知。
d)调用clk_change_rate,从最topclock开始,依次设置新的rate
4clock rateset2种场景,一是只需修改自身的配置,即可达到rateset的目的。第二种是需要同时修改parentrate(可向上递归)才能达成目的。看似简单的逻辑,里面却包含非常复杂的系统设计的知识。大家在使用clockframework,知道有这回事即可,并尽可能的不要使用第二种场景,以保持系统的简洁性。
本帖最后由 黄土马家 于 2017-9-15 16:20 编辑

回复评论 (1)

谢谢分享

点赞  2021-3-26 15:24
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复