CCF - CLK配置失败问题 - Flag误用
关于部分时钟在dts中配置频率,到内核下发现配置错误的问题
CCF框架逻辑
ccf(common clock framework)是uboot/kernel提供的系统clock管理框架,它将时钟的使用抽象为3个对象:
- provider - 提供时钟的模块
- consumer - 使用时钟的模块
- ccf - 管理时钟的框架
ccf实现以下功能:
(1) 向consumer提供操作clocks的通用API;
(2) 实现clock控制的通用逻辑(硬件无关);
(3) 向provider提供注册时钟的API,将特定的provider纳入ccf框架管理。
consumer和provider,都只跟ccf交互,互相看不到对方,唯一的交集是Device Tree的配置,consumer需要在dts中指定其依赖的具体时钟,而时钟ID由provider提供。
驱动要做的事情,是将特定Soc的硬件时钟树,转换成provider驱动,即实现硬件相关的时钟操作接口,将时钟树上的各个时钟注册到ccf,以方便consumer驱动使用。
clk_register_composite
问题现象
部分时钟,在dts中配置assgined-clock-rates = < >,进内核后发现实际频率和配置值不一致。比如配置配31250000 内核下得到的是 17066666。
DTS配置样例:
1 | clocks = <&ts_clk TS_CLK_TX5112_CHIP_OCLK_I0>; |
Provider 注册样例:
1 | DIV_FLAGS(TS_CLK_TX5112_CHIP_OCLK_I0, CHIP_OCLK_CFG, 18, 2, 0, SYNC_INVALID, \ |
问题定位
检查we位配置
根据经验,一般时钟问题很多都是由于CCF的provider层驱动里,对我们SoC中特殊的sync bit和write enable bit的支持缺失导致的,我首先检查了这部分,发现write enable是成功配置的:
chip_oclk属于comp clk,看到kernel/drivers/clk/clk-divider.c
1 | static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate, |
驱动已经支持对we位的判断,provider注册这几个时钟时,也提供了we位的信息。
追溯assign-clock-配置过程
通过不断加log,加dump_stack,看assign-clock-配置过程
[ kernel/drivers/clk/clk-conf.c ]
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 int of_clk_set_defaults(struct device_node *node, bool clk_supplier)
rc = __set_clk_parents(node, clk_supplier);
of_parse_phandle_with_args(node, "assigned-clock-parents", "#clock-cells", index, &clkspec);
pclk = of_clk_get_from_provider(&clkspec);
clk = of_clk_get_from_provider(&clkspec);
rc = clk_set_parent(clk, pclk);
clk_core_set_parent(clk->core, parent ? parent->core : NULL);
if (parent) {
p_index = clk_fetch_parent_index(core, parent);
if (p_index < 0) {
pr_debug("%s: clk %s can not be parent of clk %s\n",
__func__, parent->name, core->name);
ret = p_index;
goto out;
}
p_rate = parent->rate;
}
ret = __clk_set_parent(core, parent, p_index);
if (parent && core->ops->set_parent)
ret = core->ops->set_parent(core->hw, p_index);
rc = __set_clk_rates(node, clk_supplier);
of_property_for_each_u32(node, "assigned-clock-rates", prop, cur, rate) {
rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells", index, &clkspec);
clk = of_clk_get_from_provider(&clkspec);
rc = clk_set_rate(clk, rate);
clk_core_set_rate_nolock(struct clk_core *core, unsigned long req_rate) <----
ore->ops->round_rate(core->hw, req->rate, &req->best_parent_rate);
top = clk_calc_new_rates(core, rate);
clk_divider_round_rate //note here
if (core->flags & CLK_SET_RATE_PARENT)
-> clk_core_round_rate_nolock 递归调用,尝试所有parent --->
clk_change_rate(top);
clk_divider_set_rate
clk_divider_round_rate函数:
1 | static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, |
相关函数的实现:
1 | static int _div_round_up(const struct clk_div_table *table, |
可以看到provider在注册时,使用了自定义的CLK_DIVIDER_POWER_OF_TWO_PLUS_ONE宏,但是这个宏在上面这个函数中没有做判断,导致他总是走最后return val+1,这里在当时log调试时发现是一个固定的值。
CLK_DIVIDER_POWER_OF_TWO_PLUS_ONE本意为寄存器值n与分频系数值div的关系为: div = 2^n+1
查看我们CRGU的regfile,其中确实有这样的关系:
问题就在这,虽然自定义了一个新的FLAG宏,但是在clk-divider.c的这些基础组件中,支持并不完善,没有完整的添加判断。
解决
_get_div函数中有对table的判断,可以再CLK Provider层注册时,提供这个table,构造寄存器值与分频系数div的POWER_OF_TWO_PLUS_ONE关系
_get_table_div遍历表中提供的div与val键值对,返回目标val下对应的div值
1 | static unsigned int _get_table_div(const struct clk_div_table *table, |
注册:
1 | static struct clk_div_table oclk_div_table[] = { |
将table提供给这类divider clk后,_get_div通过查表获取到正确的dividier值,dts中的频率可以正常配置进去。
修改之前的log:
bestdiv都是固定的4
1 | [ 1.958283] best_parent_rate:250000000, rate:30800000 |
修改后的log:
1 | [ 1.953459] best_parent_rate:250000000, rate:30800000 |