以Allwinner H3 为例学习Linux 下的时钟框架。
内核版本:linux-4.14.63
1.ccu的初始化
从哪开始?
/* drivers/clk/sunxi-ng/ccu-sun8i-h3.c */
static void __init sun8i_h3_ccu_setup(struct device_node *node)
{
sunxi_h3_h5_ccu_init(node, &sun8i_h3_ccu_desc);
}
CLK_OF_DECLARE(sun8i_h3_ccu, "allwinner,sun8i-h3-ccu",
sun8i_h3_ccu_setup);
看一下这个宏
/* include/linux/clk-provider.h */
#define CLK_OF_DECLARE(name, compat, fn) OF_DECLARE_1(clk, name, compat, fn)
/* include/linux/of.h */
#define OF_DECLARE_1(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
...
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
CLK_OF_DECLARE(sun8i_h3_ccu, “allwinner,sun8i-h3-ccu”,
sun8i_h3_ccu_setup);
宏展开就是:
static const struct of_device_id __of_table_sun8i_h3_ccu \
__used __section(__clk_of_table) \
= { .compatible = "allwinner,sun8i-h3-ccu", \
.data = sun8i_h3_ccu_setup }
__clk_of_table 是 vmlinux.lds 里定义的一个字段:
.init.data : {
KEEP(*(SORT(___kentry+*))) *(.init.data) *(.meminit.data) *(.init.rodata) *(.meminit.rodata) . = ALIGN(8); __clk_of_table = .; KEEP(*(__clk_of_table)) KEEP(*(__clk_of_table_end)) . = ALIGN(8); __reservedmem_of_table = .; KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) . = ALIGN(8); __timer_of_table = .; KEEP(*(__timer_of_table)) KEEP(*(__timer_of_table_end)) . = ALIGN(8); __cpu_method_of_table = .; KEEP(*(__cpu_method_of_table)) KEEP(*(__cpu_method_of_table_end)) . = ALIGN(32); __dtb_start = .; KEEP(*(.dtb.init.rodata)) __dtb_end = .; . = ALIGN(8); __irqchip_of_table = .; KEEP(*(__irqchip_of_table)) KEEP(*(__irqchip_of_table_end)) . = ALIGN(8); __earlycon_table = .; KEEP(*(__earlycon_table)) __earlycon_table_end = .;
. = ALIGN(16); __setup_start = .; KEEP(*(.init.setup)) __setup_end = .;
__initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .; KEEP(*(.initcall0.init)) KEEP(*(.initcall0s.init)) __initcall1_start = .; KEEP(*(.initcall1.init)) KEEP(*(.initcall1s.init)) __initcall2_start = .; KEEP(*(.initcall2.init)) KEEP(*(.initcall2s.init)) __initcall3_start = .; KEEP(*(.initcall3.init)) KEEP(*(.initcall3s.init)) __initcall4_start = .; KEEP(*(.initcall4.init)) KEEP(*(.initcall4s.init)) __initcall5_start = .; KEEP(*(.initcall5.init)) KEEP(*(.initcall5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) KEEP(*(.initcallrootfss.init)) __initcall6_start = .; KEEP(*(.initcall6.init)) KEEP(*(.initcall6s.init)) __initcall7_start = .; KEEP(*(.initcall7.init)) KEEP(*(.initcall7s.init)) __initcall_end = .;
__con_initcall_start = .; KEEP(*(.con_initcall.init)) __con_initcall_end = .;
__security_initcall_start = .; KEEP(*(.security_initcall.init)) __security_initcall_end = .;
. = ALIGN(4); __initramfs_start = .; KEEP(*(.init.ramfs)) . = ALIGN(8); KEEP(*(.init.ramfs.info))
}
问:__clk_of_table 被谁使用?
答:of_clk_init 里使用
/* drivers/clk/clk.c */
void __init of_clk_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np;
struct clock_provider *clk_provider, *next;
bool is_init_done;
bool force = false;
LIST_HEAD(clk_provider_list); // 定义一个链表
if (!matches)
matches = &__clk_of_table; // 从__clk_of_table 位置开始
/* First prepare the list of the clocks providers */
for_each_matching_node_and_match(np, matches, &match) {
struct clock_provider *parent;
parent = kzalloc(sizeof(*parent), GFP_KERNEL);
//...省略
parent->clk_init_cb = match->data; //把 match->data 赋值给 parent->clk_init_cb;里面放的是形如 sun8i_h3_ccu_setup 的函数
parent->np = of_node_get(np);
list_add_tail(&parent->node, &clk_provider_list); //添加到链表
}
while (!list_empty(&clk_provider_list)) { //循环遍历链表直到所有clk setup完成
is_init_done = false;
list_for_each_entry_safe(clk_provider, next,
&clk_provider_list, node) {
if (force || parent_ready(clk_provider->np)) {
//...
clk_provider->clk_init_cb(clk_provider->np); // 调用 match->data
of_clk_set_defaults(clk_provider->np, true);
list_del(&clk_provider->node); //setup ok 后从链表中删除
of_node_put(clk_provider->np);
kfree(clk_provider);
is_init_done = true;
}
}
if (!is_init_done)
force = true;
}
}
问:of_clk_init 何时被调用?
答:head-common.S->start_kernel->time_init->of_clk_init
arch/arm/kernel/head-common.S
start_kernel() //init/main.c
time_init() //arch/arm/kernel/time.c
of_clk_init(NULL);
问:sun8i_h3_ccu_setup 里做了什么?
static void __init sun8i_h3_ccu_setup(struct device_node *node)
{
sunxi_h3_h5_ccu_init(node, &sun8i_h3_ccu_desc);
}
先来看 sun8i_h3_ccu_desc 的定义:
static const struct sunxi_ccu_desc sun8i_h3_ccu_desc = {
.ccu_clks = sun8i_h3_ccu_clks,
.num_ccu_clks = ARRAY_SIZE(sun8i_h3_ccu_clks),
.hw_clks = &sun8i_h3_hw_clks,
.resets = sun8i_h3_ccu_resets,
.num_resets = ARRAY_SIZE(sun8i_h3_ccu_resets),
};
可以看出主要是定义的 ccu_clks 及其数目,hw_clks ,resets 及其数目。
举例来看sun8i_h3_ccu_clks:
static struct ccu_common *sun8i_h3_ccu_clks[] = {
&pll_cpux_clk.common,
&pll_audio_base_clk.common,
&pll_video_clk.common,
&pll_ve_clk.common,
&pll_ddr_clk.common,
...
从名字上可以看出其实就是对应于数据手册的寄存器列表。
pll_audio_base_clk 的定义:
static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_audio_base_clk, "pll-audio-base",
"osc24M", 0x008,
8, 7, /* N */
0, 5, /* M */
BIT(31), /* gate */
BIT(28), /* lock */
0);
宏展开:
#define SUNXI_CCU_NM_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \
_nshift, _nwidth, \
_mshift, _mwidth, \
_gate, _lock, _flags) \
struct ccu_nm _struct = { \
.enable = _gate, \
.lock = _lock, \
.n = _SUNXI_CCU_MULT(_nshift, _nwidth), \
.m = _SUNXI_CCU_DIV(_mshift, _mwidth), \
.common = { \
.reg = _reg, \
.hw.init = CLK_HW_INIT(_name, \
_parent, \
&ccu_nm_ops, \
_flags), \
}, \
}
这里比较重要的一个地方是&ccu_nm_ops,填充了.hw.init.ops ;
来看ccu_nm_ops:
const struct clk_ops ccu_nm_ops = {
.disable = ccu_nm_disable,
.enable = ccu_nm_enable,
.is_enabled = ccu_nm_is_enabled,
.recalc_rate = ccu_nm_recalc_rate,
.round_rate = ccu_nm_round_rate,
.set_rate = ccu_nm_set_rate,
};
这里实现了几个接口,提供给clk_core 调用。
static void __init sunxi_h3_h5_ccu_init(struct device_node *node,
const struct sunxi_ccu_desc *desc)
{
void __iomem *reg;
u32 val;
//...
reg = of_io_request_and_map(node, 0, of_node_full_name(node)); //映射成内存地址
/* Force the PLL-Audio-1x divider to 4 */
val = readl(reg + SUN8I_H3_PLL_AUDIO_REG);
val &= ~GENMASK(19, 16);
writel(val | (3 << 16), reg + SUN8I_H3_PLL_AUDIO_REG);
sunxi_ccu_probe(node, reg, desc); // probe 做了哪些?
/* Gate then ungate PLL CPU after any rate changes */
ccu_pll_notifier_register(&sun8i_h3_pll_cpu_nb);
/* Reparent CPU during PLL CPU rate changes */
ccu_mux_notifier_register(pll_cpux_clk.common.hw.clk,
&sun8i_h3_cpu_nb);
}
int sunxi_ccu_probe(struct device_node *node, void __iomem *reg,
const struct sunxi_ccu_desc *desc)
{
struct ccu_reset *reset;
int i, ret;
//1.对 desc->ccu_clks[x] 进行处理
for (i = 0; i < desc->num_ccu_clks; i++) {
struct ccu_common *cclk = desc->ccu_clks[i];
//...
cclk->base = reg; // 给xxx.common.base 赋值
cclk->lock = &ccu_lock; //公用ccu_lock
}
//2.对desc->hw_clks->hws[x] 进行处理
for (i = 0; i < desc->hw_clks->num ; i++) {
struct clk_hw *hw = desc->hw_clks->hws[i];
if (!hw)
continue;
ret = clk_hw_register(NULL, hw); //重点展开
if (ret) {
pr_err("Couldn't register clock %d - %s\n",
i, clk_hw_get_name(hw));
goto err_clk_unreg;
}
}
ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get,
desc->hw_clks);
if (ret)
goto err_clk_unreg;
//2.对desc->resets 进行处理
reset = kzalloc(sizeof(*reset), GFP_KERNEL);
if (!reset) {
ret = -ENOMEM;
goto err_alloc_reset;
}
reset->rcdev.of_node = node;
reset->rcdev.ops = &ccu_reset_ops;
reset->rcdev.owner = THIS_MODULE;
reset->rcdev.nr_resets = desc->num_resets;
reset->base = reg;
reset->lock = &ccu_lock;
reset->reset_map = desc->resets;
ret = reset_controller_register(&reset->rcdev);
return 0;
/...
}
int clk_hw_register(struct device *dev, struct clk_hw *hw)
{
return PTR_ERR_OR_ZERO(clk_register(dev, hw));
}
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
int i, ret;
struct clk_core *core;
core = kzalloc(sizeof(*core), GFP_KERNEL);
//...
/*填充struct clk_core 结构*/
core->name = kstrdup_const(hw->init->name, GFP_KERNEL);
core->ops = hw->init->ops; // core->ops 很重要,
if (dev && dev->driver)
core->owner = dev->driver->owner;
core->hw = hw;
core->flags = hw->init->flags;
core->num_parents = hw->init->num_parents;
core->min_rate = 0;
core->max_rate = ULONG_MAX;
hw->core = core;
/* allocate local copy in case parent_names is __initdata */
core->parent_names = kcalloc(core->num_parents, sizeof(char *),
GFP_KERNEL);
/* copy each string name in case parent_names is __initdata */
for (i = 0; i < core->num_parents; i++) {
core->parent_names[i] = kstrdup_const(hw->init->parent_names[i],
GFP_KERNEL);
}
//...
INIT_HLIST_HEAD(&core->clks);
hw->clk = __clk_create_clk(hw, NULL, NULL);
/...
ret = __clk_core_init(core); // 重点研究
if (!ret)
return hw->clk;
//...
}
问:__clk_core_init 里做了什么?
答:构造 clk 树结构,没有parent的添加到 clk_root_list,有parent的clk 添加到 parent->children list
static int __clk_core_init(struct clk_core *core)
{
int i, ret = 0;
struct clk_core *orphan;
struct hlist_node *tmp2;
unsigned long rate;
if (!core)
return -EINVAL;
clk_prepare_lock();
/* check to see if a clock with this name is already registered */
//...
/* check that clk_ops are sane. See Documentation/clk.txt */
//...
/*
* parent不为空,则将该节点添加到 parent 的children list里
* parent为空,且num_parents 为0,则添加到clk_root_list (根节点)里
* parent为空,且num_parents 不为0,则添加到clk_orphan_list (孤儿节点)里,后续会给孤儿节点找父亲。
*/
if (core->parent) {
hlist_add_head(&core->child_node,
&core->parent->children);
core->orphan = core->parent->orphan;
} else if (!core->num_parents) {
hlist_add_head(&core->child_node, &clk_root_list);
core->orphan = false;
} else {
hlist_add_head(&core->child_node, &clk_orphan_list);
core->orphan = true;
}
/*
* Set clk's accuracy. The preferred method is to use
* .recalc_accuracy. For simple clocks and lazy developers the default
* fallback is to use the parent's accuracy. If a clock doesn't have a
* parent (or is orphaned) then accuracy is set to zero (perfect
* clock).
*/
if (core->ops->recalc_accuracy)
core->accuracy = core->ops->recalc_accuracy(core->hw,
__clk_get_accuracy(core->parent));
else if (core->parent)
core->accuracy = core->parent->accuracy;
else
core->accuracy = 0;
/*
* Set clk's phase.
* 一般不实现get_phase
*/
if (core->ops->get_phase)
core->phase = core->ops->get_phase(core->hw);
else
core->phase = 0;
/*
* Set clk's rate.
*/
if (core->ops->recalc_rate)
rate = core->ops->recalc_rate(core->hw,
clk_core_get_rate_nolock(core->parent));
else if (core->parent)
rate = core->parent->rate;
else
rate = 0;
core->rate = core->req_rate = rate;
/*
* Enable CLK_IS_CRITICAL clocks so newly added critical clocks
* don't get accidentally disabled when walking the orphan tree and
* reparenting clocks
*/
if (core->flags & CLK_IS_CRITICAL) {
unsigned long flags;
clk_core_prepare(core);
flags = clk_enable_lock();
clk_core_enable(core);
clk_enable_unlock(flags);
}
/*
* 给孤儿节点找父亲
*/
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
struct clk_core *parent = __clk_init_parent(orphan);
/*
* We need to use __clk_set_parent_before() and _after() to
* to properly migrate any prepare/enable count of the orphan
* clock. This is important for CLK_IS_CRITICAL clocks, which
* are enabled during init but might not have a parent yet.
*/
if (parent) {
/* update the clk tree topology */
__clk_set_parent_before(orphan, parent);
__clk_set_parent_after(orphan, parent, NULL);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, 0);
}
}
/*
有init 就调用init ,一般不实现init
*/
if (core->ops->init)
core->ops->init(core->hw);
kref_init(&core->ref);
out:
clk_prepare_unlock();
if (!ret)
clk_debug_register(core);
return ret;
}
问:驱动程序如何使用clk?
答:
1.clk = devm_clk_get(dev,“xxx”) //获取clk
2.clk_prepare_enable(clk) //使能clk
问:clk_prepare_enable 的调用关系?
答:最终调用了core->ops->enable( ),core->ops 是在 clk_register 函数里初始化的)
clk_prepare_enable(clk)
clk_prepare(clk)
clk_enable(clk)
clk_core_enable_lock(clk)
clk_core_enable(clk->core)
clk_core_enable(core->parent) // / 递归使能父时钟。
core->ops->enable(core->hw)
举例来看core->ops->enable
以下面的ccu_nm_enable 为例:
const struct clk_ops ccu_nm_ops = {
.disable = ccu_nm_disable,
.enable = ccu_nm_enable,
.is_enabled = ccu_nm_is_enabled,
.recalc_rate = ccu_nm_recalc_rate,
.round_rate = ccu_nm_round_rate,
.set_rate = ccu_nm_set_rate,
};
static int ccu_nm_enable(struct clk_hw *hw)
{
struct ccu_nm *nm = hw_to_ccu_nm(hw);
return ccu_gate_helper_enable(&nm->common, nm->enable);
}
int ccu_gate_helper_enable(struct ccu_common *common, u32 gate)
{
unsigned long flags;
u32 reg;
if (!gate)
return 0;
spin_lock_irqsave(common->lock, flags);
reg = readl(common->base + common->reg);
writel(reg | gate, common->base + common->reg); //最终设置寄存器。
spin_unlock_irqrestore(common->lock, flags);
return 0;
}
clk_core_enable 深度分析,递归使能父时钟,这里很关键。
static int clk_core_enable(struct clk_core *core)
{
int ret = 0;
lockdep_assert_held(&enable_lock);
if (!core)
return 0;
if (WARN_ON(core->prepare_count == 0))
return -ESHUTDOWN;
if (core->enable_count == 0) {
ret = clk_core_enable(core->parent); // 递归使能父时钟,父时钟如果还有父时钟,逐个往上追溯,
if (ret)
return ret;
trace_clk_enable_rcuidle(core);
if (core->ops->enable)
ret = core->ops->enable(core->hw); // 最后使能自己的时钟
trace_clk_enable_complete_rcuidle(core);
if (ret) {
clk_core_disable(core->parent);
return ret;
}
}
core->enable_count++;
return 0;
}
总结:内核使用 Common Clk Framework (CCF)框架。
其作用是构建了一个时钟树结构,根时钟下有子时钟,子时钟可以再有子时钟。
驱动使用统一的接口去使能一个时钟时,会先使能父时钟,逐层往上直到使能根时钟。(这个递归操作!真是巧妙!)
再来实例看一下 H3 的i2s1 clock 的使能过程。
sun4i-i2s.c
i2s->bus_clk = devm_clk_get(&pdev->dev, "apb");
printk("i2s->bus_clk:name :%s\n",i2s->bus_clk->core->name);
i2s->mod_clk = devm_clk_get(&pdev->dev, "mod");
printk("i2s->mod_clk:name :%s\n",i2s->mod_clk->core->name); //我加的调试打印
static int sun4i_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
//...省略
return clk_prepare_enable(i2s->mod_clk);
}
打印结果如下:
[ 4287.919151] i2s->bus_clk:name :bus-i2s1
[ 4287.923078] i2s->mod_clk:name :i2s1
从打印可知相关的是 名为 “bus-i2s1” “i2s1” 的两个时钟
找到相关代码:
static const char * const i2s_parents[] = { "pll-audio-8x", "pll-audio-4x",
"pll-audio-2x", "pll-audio" };
...
static SUNXI_CCU_MUX_WITH_GATE(i2s1_clk, "i2s1", i2s_parents,
0x0b4, 16, 2, BIT(31), CLK_SET_RATE_PARENT);
可以看出 “i2s1” 的父时钟可以是 “pll-audio-8x”, “pll-audio-4x”, “pll-audio-2x”, “pll-audio” 中的一个,这跟数据手册是对应的:
再查找代码找出"pll-audio" 的父时钟
static CLK_FIXED_FACTOR(pll_audio_clk, "pll-audio",
"pll-audio-base", 4, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR(pll_audio_2x_clk, "pll-audio-2x",
"pll-audio-base", 2, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR(pll_audio_4x_clk, "pll-audio-4x",
"pll-audio-base", 1, 1, CLK_SET_RATE_PARENT);
static CLK_FIXED_FACTOR(pll_audio_8x_clk, "pll-audio-8x",
"pll-audio-base", 1, 2, CLK_SET_RATE_PARENT);
可以看出 “pll-audio” 的父时钟 是 “pll-audio-base”
static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_audio_base_clk, "pll-audio-base",
"osc24M", 0x008,
8, 7, /* N */
0, 5, /* M */
BIT(31), /* gate */
BIT(28), /* lock */
0);
“pll-audio-base” 的父时钟 是 “osc24M”