linux clk

转载:Linux common clock framework(2)_clock provider - Red_Point - 博客园

TCC803X时钟框架:

总线:

   

 外设:

 

不管总线还是外设时钟,都有相应的寄存器配置:

 注意:以PCLKCTRL_DDIB01为例,开头P表示外设。DDIBUS:Display Driver Interface BUS.

以LCD0的时钟为例,其对应的寄存器0x1400,00F4:

clock framework将clock分为fixed rate、gate、devider、mux、fixed factor、composite六类,每一类clock都有相似的功能、相似的控制方式,因而可以使用相同的逻辑统一处理,这充分体现了面向对象的思想。参考引用文章:3.2.2 clock分类及register

dts中clk配置(删减版):

        clocks {
                compatible = "telechips,ckc";
                #address-cells = <1>;
                #size-cells = <1>;

                osc24m: oscillator {
                        compatible = "fixed-clock";
                        #clock-cells = <0>;
                        clock-frequency = <24000000>;
                        clock-output-names = "osc24m";
                };

                clk_pll: pll_clk {
                        compatible = "telechips,clk-pll";
                        #clock-cells = <1>;
                        clocks = <&osc24m>;
                        clock-output-names = "pll0", "pll1", "pll2", "pll3",
                                             "pll4";
                };

                clk_ext0: clk_ext@0 {
                        compatible = "fixed-clock";
                        #clock-cells = <0>;
                        clock-frequency = <24000000>;
                        clock-output-names = "clk_ext0";
                };

                clk_ext1: clk_ext@1 {
                        compatible = "fixed-clock";
                        #clock-cells = <0>;
                        clock-frequency = <24000000>;
                        clock-output-names = "clk_ext1";
                };

                clk_fbus: clk@14000008 {
                        compatible = "telechips,clk-fbus";
                        #clock-cells = <1>;
                        clock-indices =
                                <FBUS_CPU0>, <FBUS_CPU1>, <FBUS_CBUS>, <FBUS_CMBUS>,
             
                        clock-output-names =
                                "fbus_cpu0", "fbus_cpu1", "fbus_cbus", "fbus_cmbus",
                           
                        telechips,clock-flags =
                              <0>,                                       /* FBUS_CPU0 */
                              <CLK_IGNORE_UNUSED>,                       /* FBUS_CPU1 */
                              <(CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED)>, /* FBUS_CBUS */
                              <(CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED)>, /* FBUS_CMBUS */
                              <(CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED)>, 
                };

                clk_peri: clk@140000d0 {
                        compatible = "telechips,clk-peri";
                        #clock-cells = <1>;
                        clock-indices =
                                <PERI_TCX>, <PERI_TCT>, <PERI_TCZ>,
                                <PERI_LCDTIMER>, <PERI_LCD0>, <PERI_LCD1>,
                                <PERI_LCD2>, <PERI_CPUINTERFACE0>,
                                <PERI_CPUINTERFACE1>, <PERI_HDMI_CORE>,
                                <PERI_HDMI_AUDIO>, <PERI_HDMI_PCLK>,
                           
                        clock-output-names =
                                "peri_tcx", "peri_tct", "peri_tcz",
                                "peri_lcdtimer", "peri_lcd0", "peri_lcd1",
                                "peri_lcd2", "peri_cpuinterface0",
                                "peri_cpuinterface1", "peri_hdmi_core",
                                "peri_hdmi_audio", "peri_hdmi_pclk",
                             
                        telechips,clock-flags =
                                <CLK_IGNORE_UNUSED>,    /* PERI_TCX */
                                <CLK_IGNORE_UNUSED>,    /* PERI_TCT */
                                <CLK_IGNORE_UNUSED>,    /* PERI_TCZ */
                                <CLK_IGNORE_UNUSED>,    /* PERI_LCDTIMER */
                                <CLK_IGNORE_UNUSED>,    /* PERI_LCD0 */
                                <CLK_IGNORE_UNUSED>,    /* PERI_LCD1 */
                                <CLK_IGNORE_UNUSED>,    /* PERI_LCD2 */
                                <CLK_IGNORE_UNUSED>,    /* PERI_CPUINTERFACE0 */
                                <CLK_IGNORE_UNUSED>,    /* PERI_CPUINTERFACE1 */
                                <CLK_IGNORE_UNUSED>,    /* PERI_HDMI_CORE */
                                <CLK_IGNORE_UNUSED>,    /* PERI_HDMI_AUDIO */
                                <CLK_IGNORE_UNUSED>,    /* PERI_HDMI_PCLK */
                          
                };

                clk_io: clk@16051000 {
                        compatible = "telechips,clk-iobus";
                        #clock-cells = <1>;
                        clocks = <&clk_fbus FBUS_IO>;
                        clock-indices =
                                <IOBUS_ASRC_DMA>, <IOBUS_ASRC>, <IOBUS_IC_TC>,
                                <IOBUS_CEC1>, <IOBUS_DMA0>, <IOBUS_DMA1>,
                              
                        clock-output-names =
                                "iobus_asrc_dma", "iobus_asrc", "iobus_ic_tc",
                                "iobus_cec1", "iobus_dma0", "iobus_dma1",
                            
                        telechips,clock-flags =
                                <CLK_IGNORE_UNUSED>,    /* IOBUS_ASRC_DMA */
                                <CLK_IGNORE_UNUSED>,    /* IOBUS_ASRC */
                                <CLK_IGNORE_UNUSED>,    /* IOBUS_IC_TC */
                                <CLK_IGNORE_UNUSED>,    /* IOBUS_CEC1 */
                                <CLK_IGNORE_UNUSED>,    /* IOBUS_DMA0 */
                                <CLK_IGNORE_UNUSED>,    /* IOBUS_DMA1 */
                };
            }

注:从dts看,telechips每个时钟如果有父时钟,那就只有1个。

代码分析:

CLK_OF_DECLARE(tcc_clk_peri, "telechips,clk-peri", tcc_peri_init);
#define CLK_OF_DECLARE(name, compat, fn) OF_DECLARE_1(clk, name, compat, fn)
#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  }
extern struct of_device_id __clk_of_table;		// include\linux\clk-provider.h
time_init
    of_clk_init(NULL);
void __init of_clk_init(const struct of_device_id *matches)
	
	if (!matches)
		matches = &__clk_of_table;
		
	for_each_matching_node_and_match( , matches, &match) {
		struct clock_provider *parent;

		if (!of_device_is_available(np))
			continue;

		parent = kzalloc(sizeof(*parent), GFP_KERNEL);
		if (!parent) {
			list_for_each_entry_safe(clk_provider, next,
						 &clk_provider_list, node) {
				list_del(&clk_provider->node);
				of_node_put(clk_provider->np);
				kfree(clk_provider);
			}
			of_node_put(np);
			return;
		}

		parent->clk_init_cb = match->data;
		parent->np = of_node_get(np);
		list_add_tail(&parent->node, &clk_provider_list);
	}
static struct clk_ops tcc_peri_ops = {
	.enable		= tcc_peri_enable,
	.disable	= tcc_peri_disable,
	.recalc_rate	= tcc_peri_recalc_rate,
	.round_rate	= tcc_round_rate,
	.set_rate	= tcc_peri_set_rate,
	.is_enabled	= tcc_peri_is_enabled,
	.debug_init	= tcc_clk_debug_init,
};

static void __init tcc_peri_init(struct device_node *np)
{
	tcc_clk_register(np, &tcc_peri_ops);
}

int tcc_clk_register(struct device_node *np, struct clk_ops *ops)
	struct clk_onecell_data *clk_data;
	struct tcc_clk *tcc_clk;
	int num_clks;
	num_clks = of_property_count_strings(np, "clock-output-names");	
	clk_data = kzalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
	clk_data->clks = kcalloc(num_clks, sizeof(struct clk *), GFP_KERNEL);
	for (i = 0; i < num_clks; i++) {
		struct clk_init_data init = { };
		const char *clk_name;
		u32 index;
		u32 flags = 0;
		tcc_clk = kzalloc(sizeof(tcc_clk), GFP_KERNEL);
		of_property_read_string_index(np, "clock-output-names",i, &clk_name); 
		of_property_read_u32_index(np, "clock-indices",i, &index);			 
		of_property_read_u32_index(np, "telechips,clock-flags",i, &flags);	
		init.name = clk_name;
        init.num_parents = of_clk_get_parent_count(np);
		parent_names = kzalloc(sizeof(char *) * init.num_parents,GFP_KERNEL);
		of_clk_parent_fill(np, parent_names, init.num_parents);
		init.parent_names = parent_names;												
		init.flags = flags;													  /*重点*/														
		init.ops = ops;
		tcc_clk->hw.init = &init;											  /*重点*/
		tcc_clk->ops = ops;
		tcc_clk->id = index;
		clk = clk_register(NULL, &tcc_clk->hw);
        ret = clk_register_clkdev(clk, clk_name, NULL);
        clk_data->clks[i] = clk;
	}
    clk_data->clk_num = i;
	return of_clk_add_provider(np, tcc_onecell_get, clk_data);
int of_clk_parent_fill(struct device_node *np, const char **parents,
		       unsigned int size)
{
	unsigned int i = 0;

	while (i < size && (parents[i] = of_clk_get_parent_name(np, i)) != NULL)
		i++;

	return i;
}

const char *of_clk_get_parent_name(struct device_node *np, int index)
{
	struct of_phandle_args clkspec;
	struct property *prop;
	const char *clk_name;
	const __be32 *vp;
	u32 pv;
	int rc;
	int count;
	struct clk *clk;

	rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,&clkspec);

	index = clkspec.args_count ? clkspec.args[0] : 0;
	count = 0;

	/* if there is an indices property, use it to transfer the index
	 * specified into an array offset for the clock-output-names property.
	 */
	of_property_for_each_u32(clkspec.np, "clock-indices", prop, vp, pv) {
		if (index == pv) {
			index = count;
			break;
		}
		count++;
	}
	
	if (of_property_read_string_index(clkspec.np, "clock-output-names",index,
                    &clk_name) < 0) {
	
		clk = of_clk_get_from_provider(&clkspec);
		if (IS_ERR(clk)) {
			if (clkspec.args_count == 0)
				clk_name = clkspec.np->name;
			else
				clk_name = NULL;
		} else {
			clk_name = __clk_get_name(clk);
			clk_put(clk);
		}
	}

	of_node_put(clkspec.np);
	return clk_name;
}
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
{
	return __of_clk_get_from_provider(clkspec, NULL, __func__);
}

struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
				       const char *dev_id, const char *con_id)
{
	struct of_clk_provider *provider;
	struct clk *clk = ERR_PTR(-EPROBE_DEFER);
	struct clk_hw *hw;

	list_for_each_entry(provider, &of_clk_providers, link) {
		if (provider->node == clkspec->np) {
			hw = __of_clk_get_hw_from_provider(provider, clkspec);
			clk = __clk_create_clk(hw, dev_id, con_id);
		}

		if (!IS_ERR(clk)) {
			break;
		}
	}

	return clk;
}
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
	int i, ret;
	struct clk_core *core;

	core = kzalloc(sizeof(*core), GFP_KERNEL);
	core->name = kstrdup_const(hw->init->name, GFP_KERNEL);
	core->ops = hw->init->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);
	}

	/* avoid unnecessary string look-ups of clk_core's possible parents. */
	core->parents = kcalloc(core->num_parents, sizeof(*core->parents),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;

}
struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,
			     const char *con_id)
{
	struct clk *clk;

	clk = kzalloc(sizeof(*clk), GFP_KERNEL);

	clk->core = hw->core;
	clk->dev_id = dev_id;
	clk->con_id = kstrdup_const(con_id, GFP_KERNEL);
	clk->max_rate = ULONG_MAX;

	hlist_add_head(&clk->clks_node, &hw->core->clks);

	return clk;
}
static int __clk_core_init(struct clk_core *core)
{
	int i, ret = 0;
	struct clk_core *orphan;
	struct hlist_node *tmp2;
	unsigned long rate;

	/* check to see if a clock with this name is already registered */
	if (clk_core_lookup(core->name)) {}

	core->parent = __clk_init_parent(core);

	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;
	}

	
	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.
	 * Since a phase is by definition relative to its parent, just
	 * query the current clock phase, or just assume it's in phase.
	 */
	if (core->ops->get_phase)
		core->phase = core->ops->get_phase(core->hw);
	else
		core->phase = 0;

	/*
	 * Set clk's rate.  The preferred method is to use .recalc_rate.  For
	 * simple clocks and lazy developers the default fallback is to use the
	 * parent's rate.  If a clock doesn't have a parent (or is orphaned)
	 * then rate is set to zero.
	 */
	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);
	}

	/*
	 * walk the list of orphan clocks and reparent any that newly finds a
	 * parent.
	 */
	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);
		}
	}

	/*
	 * optional platform-specific magic
	 *
	 * The .init callback is not used by any of the basic clock types, but
	 * exists for weird hardware that must perform initialization magic.
	 * Please consider other ways of solving initialization problems before
	 * using this callback, as its use is discouraged.
	 */
	if (core->ops->init)
		core->ops->init(core->hw);



}
static struct clk_core *__clk_init_parent(struct clk_core *core)
{
	u8 index = 0;

	if (core->num_parents > 1 && core->ops->get_parent)
		index = core->ops->get_parent(core->hw);

	return clk_core_get_parent_by_index(core, index);
}
int clk_register_clkdev(struct clk *clk, const char *con_id,
	const char *dev_id)
{
	struct clk_lookup *cl;

	if (dev_id)
		cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, "%s",
					   dev_id);
	else
		cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, NULL);

	return cl ? 0 : -ENOMEM;
}
static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw,
						const char *con_id,
						const char *dev_id, ...)
{
	struct clk_lookup *cl;
	va_list ap;

	va_start(ap, dev_id);
	cl = vclkdev_create(hw, con_id, dev_id, ap);
	va_end(ap);

	return cl;
}
static struct clk_lookup * vclkdev_create(struct clk_hw *hw, const char *con_id,
                                             const char *dev_fmt,va_list ap)
{
	struct clk_lookup *cl;

	cl = vclkdev_alloc(hw, con_id, dev_fmt, ap);
	if (cl)
		__clkdev_add(cl);

	return cl;
}
static struct clk *tcc_onecell_get(struct of_phandle_args *clkspec, void *data)
{
	struct clk_onecell_data *clk_data = data;
	unsigned int idx = clkspec->args[0];
	int i;

	for (i = 0; i < clk_data->clk_num; i++) {
		struct clk *clk = clk_data->clks[i];
		struct tcc_clk *tcc_clk;

		tcc_clk = to_tcc_clk(__clk_get_hw(clk));
		if (idx == tcc_clk->id)
			return clk;
	}
	return NULL;
}
int of_clk_add_provider(struct device_node *np,
			struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,void *data),
			void *data)
{
	struct of_clk_provider *cp;
	int ret;

	cp = kzalloc(sizeof(*cp), GFP_KERNEL);

	cp->node = of_node_get(np);
	cp->data = data;
	cp->get = clk_src_get;

	list_add(&cp->link, &of_clk_providers);

	ret = of_clk_set_defaults(np, true);

	return ret;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值