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驱动使用。

image-20241018154127124

clk_register_composite

问题现象

部分时钟,在dts中配置assgined-clock-rates = < >,进内核后发现实际频率和配置值不一致。比如配置配31250000 内核下得到的是 17066666。

DTS配置样例:

1
2
3
4
5
clocks = <&ts_clk TS_CLK_TX5112_CHIP_OCLK_I0>;
clock-names = "mclk";
assigned-clocks = <&ts_clk TS_CLK_TX5112_CHIP_OCLK_I0>;
assigned-clock-parents = <&ts_clk TS_CLK_TX5112_PLL1_D8>;
assigned-clock-rates = <30700000>;

Provider 注册样例:

1
2
3
4
5
6
7
8
DIV_FLAGS(TS_CLK_TX5112_CHIP_OCLK_I0, CHIP_OCLK_CFG, 18, 2, 0, SYNC_INVALID, \
CLK_DIVIDER_POWER_OF_TWO_PLUS_ONE) \
DIV_FLAGS(TS_CLK_TX5112_CHIP_OCLK_I1, CHIP_OCLK_CFG, 22, 2, 0, SYNC_INVALID, \
CLK_DIVIDER_POWER_OF_TWO_PLUS_ONE) \
DIV_FLAGS(TS_CLK_TX5112_CHIP_OCLK_I2, CHIP_OCLK_CFG, 26, 2, 0, SYNC_INVALID, \
CLK_DIVIDER_POWER_OF_TWO_PLUS_ONE) \
DIV_FLAGS(TS_CLK_TX5112_CHIP_OCLK_I3, CHIP_OCLK_CFG, 30, 2, 0, SYNC_INVALID, \
CLK_DIVIDER_POWER_OF_TWO_PLUS_ONE) \

问题定位

检查we位配置

根据经验,一般时钟问题很多都是由于CCF的provider层驱动里,对我们SoC中特殊的sync bit和write enable bit的支持缺失导致的,我首先检查了这部分,发现write enable是成功配置的:

chip_oclk属于comp clk,看到kernel/drivers/clk/clk-divider.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
35
36
37
38
39
40
41
42
43
static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_divider *divider = to_clk_divider(hw);
int value;
unsigned long flags = 0;
u32 val;

value = divider_get_val(rate, parent_rate, divider->table,
divider->width, divider->flags);
if (value < 0)
return value;

if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
else
__acquire(divider->lock);

if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
val = div_mask(divider->width) << (divider->shift + 16);
} else {
val = clk_readl(divider->reg);
val &= ~(div_mask(divider->width) << divider->shift);
}
#ifdef CONFIG_ARCH_TS //write enable 支持
if (divider->we < WE_INVALID)
val |= BIT(divider->we);

if (divider->sync < SYNC_INVALID)
val |= BIT(divider->sync);
#endif
val |= (u32)value << divider->shift;
//printk(KERN_INFO "[%s] wr 0x%p val 0x%x\n",
//__func__, divider->reg, val);
clk_writel(val, divider->reg);

if (divider->lock)
spin_unlock_irqrestore(divider->lock, flags);
else
__release(divider->lock);

return 0;
}

驱动已经支持对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
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
static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct clk_divider *divider = to_clk_divider(hw);
int bestdiv;

/* if read only, just return current value */
if (divider->flags & CLK_DIVIDER_READ_ONLY) { // escape
bestdiv = clk_readl(divider->reg) >> divider->shift;
bestdiv &= div_mask(divider->width);
bestdiv = _get_div(divider->table, bestdiv, divider->flags,
divider->width);
return DIV_ROUND_UP_ULL((u64)*prate, bestdiv);
}

return divider_round_rate(hw, rate, prate, divider->table, //go into here
divider->width, divider->flags);
}

divider_round_rate
divider_round_rate_parent
clk_divider_bestdiv //note here [kernel/drivers/clk/clk-divider.c]
maxdiv = _get_maxdiv(table, width, flags);
if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
parent_rate = *best_parent_rate;
bestdiv = _div_round(table, parent_rate, rate, flags);
bestdiv = bestdiv == 0 ? 1 : bestdiv;
bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
return bestdiv;
}

return DIV_ROUND_UP_ULL((u64)*prate, div);

相关函数的实现:

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
static int _div_round_up(const struct clk_div_table *table,
unsigned long parent_rate, unsigned long rate,
unsigned long flags)
{
int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);

if (flags & CLK_DIVIDER_POWER_OF_TWO)
div = __roundup_pow_of_two(div);
if (table)
div = _round_up_table(table, div);

return div;
}

static int _round_up_table(const struct clk_div_table *table, int div)
{
const struct clk_div_table *clkt;
int up = INT_MAX;

for (clkt = table; clkt->div; clkt++) {
if (clkt->div == div)
return clkt->div;
else if (clkt->div < div)
continue;

if ((clkt->div - div) < (up - div))
up = clkt->div;
}

return up;
}

static unsigned int _get_maxdiv(const struct clk_div_table *table, u8 width,
unsigned long flags)
{
if (flags & CLK_DIVIDER_ONE_BASED)
return div_mask(width);
if (flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << div_mask(width);
if (table)
return _get_table_maxdiv(table, width);
return div_mask(width) + 1;
}

可以看到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,其中确实有这样的关系:

image-20241018153854120

问题就在这,虽然自定义了一个新的FLAG宏,但是在clk-divider.c的这些基础组件中,支持并不完善,没有完整的添加判断。

解决

_get_div函数中有对table的判断,可以再CLK Provider层注册时,提供这个table,构造寄存器值与分频系数div的POWER_OF_TWO_PLUS_ONE关系

_get_table_div遍历表中提供的div与val键值对,返回目标val下对应的div值

1
2
3
4
5
6
7
8
9
10
static unsigned int _get_table_div(const struct clk_div_table *table,
unsigned int val)
{
const struct clk_div_table *clkt;

for (clkt = table; clkt->div; clkt++)
if (clkt->val == val)
return clkt->div;
return 0;
}

注册:

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
static struct clk_div_table oclk_div_table[] = {

{ 0, 1 },

{ 1, 3 },

{ 2, 5 },

{ 3, 9 },

{.div = 0}, //note here
};

#define DIV_TABLE(id, off, shift, width, we, sync, table) DIV_FLAGS(id, off, shift, width, we, sync, 0, table)

static const struct ts_div_params ts_tx5112_divs[] = {
#define DIV_FLAGS(id, _off, _shift, _width, _we, _sync, _flags, _table) \
[DIVIFY(id)] = { \
.off = (_off), \
.shift = (_shift), \
.width = (_width), \
.we = (_we), \
.sync = (_sync), \
.flags = (_flags), \
.table = (_table), \
},
DIV_LIST
#undef DIV_FLAGS
};

DIV_TABLE(TS_CLK_TX5112_CHIP_OCLK_I0, CHIP_OCLK_CFG, 18, 2, 0, SYNC_INVALID, oclk_div_table) \

DIV_TABLE(TS_CLK_TX5112_CHIP_OCLK_I1, CHIP_OCLK_CFG, 22, 2, 0, SYNC_INVALID, oclk_div_table) \

DIV_TABLE(TS_CLK_TX5112_CHIP_OCLK_I2, CHIP_OCLK_CFG, 26, 2, 0, SYNC_INVALID, oclk_div_table) \

DIV_TABLE(TS_CLK_TX5112_CHIP_OCLK_I3, CHIP_OCLK_CFG, 30, 2, 0, SYNC_INVALID, oclk_div_table) \

将table提供给这类divider clk后,_get_div通过查表获取到正确的dividier值,dts中的频率可以正常配置进去。

修改之前的log:

bestdiv都是固定的4

1
2
3
4
5
6
7
8
9
10
11
12
13
[    1.958283] best_parent_rate:250000000, rate:30800000
[ 1.964241] clk_divider_bestdiv bestdiv:4 parent_rate:250000000
[ 1.971363] divider_round_rate_parent:4 prate:250000000
[ 1.971390] _func:clk_composite_determine_rate parent_name:pll0_d8 parent_rate:250000000, req->rate:30800000,tmp_rate:62500000
[ 1.988833] best_parent_rate:153600000, rate:30800000
[ 1.994780] clk_divider_bestdiv bestdiv:4 parent_rate:153600000
[ 2.001955] divider_round_rate_parent:4 prate:153600000
[ 2.001981] _func:clk_composite_determine_rate parent_name:pll1_d8 parent_rate:153600000, req->rate:30800000,tmp_rate:38400000
[ 2.018835] best_parent_rate:24000000, rate:30800000
[ 2.024686] clk_divider_bestdiv bestdiv:1 parent_rate:24000000
[ 2.031791] divider_round_rate_parent:1 prate:24000000
[ 2.031819] _func:clk_composite_determine_rate parent_name:osc24m_clk parent_rate:24000000, req->rate:30800000,tmp_rate:24000000
[ 2.049194] req->rate = best_rate:24000000

修改后的log:

1
2
3
4
5
6
7
8
9
10
11
12
13
[    1.953459] best_parent_rate:250000000, rate:30800000
[ 1.960406] clk_divider_bestdiv bestdiv:9 parent_rate:250000000
[ 1.967505] divider_round_rate_parent:9 prate:250000000
[ 1.967532] _func:clk_composite_determine_rate parent_name:pll0_d8 parent_rate:250000000, req->rate:30800000,tmp_rate:27777778
[ 1.984943] best_parent_rate:153600000, rate:30800000
[ 1.991672] clk_divider_bestdiv bestdiv:5 parent_rate:153600000
[ 1.998901] divider_round_rate_parent:5 prate:153600000
[ 1.998928] _func:clk_composite_determine_rate parent_name:pll1_d8 parent_rate:153600000, req->rate:30800000,tmp_rate:30720000
[ 2.015785] best_parent_rate:24000000, rate:30800000
[ 2.022420] clk_divider_bestdiv bestdiv:1 parent_rate:24000000
[ 2.029599] divider_round_rate_parent:1 prate:24000000
[ 2.029626] _func:clk_composite_determine_rate parent_name:osc24m_clk parent_rate:24000000, req->rate:30800000,tmp_rate:24000000
[ 2.047011] req->rate = best_rate:30720000