Linux 时钟框架学习笔记

以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,
	...

从名字上可以看出其实就是对应于数据手册的寄存器列表。
h3-ccu
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” 中的一个,这跟数据手册是对应的:
4 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”

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值