最近为情所困~hhh,文章耽误了,上一篇文章本人介绍了kernel下的power domain framework,这篇文章介绍下kernel的clk framework。
soc的时钟结构都是非常复杂的往往拥有多个时钟源,通过PLL进行分频与倍频,可以产生不同频率的时钟供各个模块使用,那么kernel是如何管理soc的时钟的呢,本篇文章将梳理一下kernel实现的时钟框架~
老规矩,从使用clk framework开始,先会用,再去分析~
如何使用clk framework?
和power domain类似,想要使用平台中某个时钟,只需在dts中引用该时钟节点,在驱动代码中调用devm_clk_get()
函数获取时钟即可,来举个例子(三星平台)更加的清晰:
//provider,提供时钟源,这个由芯片厂家完成
clocks: clock-controller@4c000000 {
compatible = "samsung,s3c2440-clock";
reg = <0x4c000000 0x20>;
#clock-cells = <1>; //clock-cell表示引用该时钟时需要传进的参数个数
};
//cousumer,引用时钟源,即时钟使用者
serial@50000000 {
compatible = "samsung,s3c2440-uart";
clock-names = "uart";
clocks = <&clocks PCLK_UART0>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_data>;
};
//linux-4.9\drivers\tty\serial\samsung.c
s3c24xx_serial_init_port
ourport->clk = clk_get(&platdev->dev, "uart"); //驱动代码里调用clk_get函数获取时钟
上面的代码里贴出了如何使用clk framework,作为使用者只需在dts里引用厂家提供的时钟节点,然后在驱动代码里调用clk_get
函数即可,非常的简单~,较其它平台,三星的代码里,只提供了一个时钟源节点,这里有点奇怪,后面分析代码时,会解答这个疑惑
那么为何只需引用该节点,调用clk_get
函数,即完成了时钟的初始化等操作呢?这里面发生了什么?接下来就需要看看厂家是如何注册一个clk_framework的了
如何注册一个clk framework?
还是老规矩,选择一个平台,去看看人家的bsp工程师是如何注册一个自家平台的clk framework,以三星平台为例吧,各个平台注册的clk framework在\linux-4.9\drivers\clk
路径下
对于芯片厂商,要做的就是按照kernel提供的框架填充数据结构就完了,所以从硬件的角度出发,可以猜测注册一个clk framework要做的事情就是描述clk的级联关系,提供clk的分频、倍频等跟时钟相关的操作函数。
分析下代码,看看是否是这么操作~
和power domain一样,kernel对外提供of_clk_add_provider()
函数向内核注册一个clk provider,由该函数的名称可知,该函数与dts相关,先暂时搁置一下dts,来看看samsung如何使用该函数的,samsung使用samsung_clk_of_add_provider(np, ctx)
包装了一下of_clk_add_provider()
,这样的话分析起来就简单点啦,重点看samsung函数的入参就可以啦。
void __init samsung_clk_of_add_provider(struct device_node *np,
struct samsung_clk_provider *ctx) //可以看到三星传入了 ctx这个结构体,这个结构体就是data指针
{
if (np) {
if (of_clk_add_provider(np, of_clk_src_onecell_get,
&ctx->clk_data))
panic("could not register clk provider\n");
}
}
/**
* of_clk_add_provider() - Register a clock provider for a node
* @np: Device node pointer associated with clock provider
* @clk_src_get: callback for decoding clock
* @data: context pointer for @clk_src_get callback.
*/
int of_clk_add_provider(struct device_node *np,
struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
void *data),
void *data)
/**
* struct samsung_clk_provider: information about clock provider
* @reg_base: virtual address for the register base.
* @clk_data: holds clock related data like clk* and number of clocks.
* @lock: maintains exclusion between callbacks for a given clock-provider.
*/
struct samsung_clk_provider {
void __iomem *reg_base;
struct clk_onecell_data clk_data;
spinlock_t lock;
};
从代码层面分析,可以看出samsung调用 of_clk_add_provider
函数向内核注册一个clk framework,而samsung实现的一个结构体就是struct clk_onecell_data
,那么是不是可以推论出struct clk_onecell_data
就对应一个clk provider,接下来深入分析一下struct clk_onecell_data
结构体,是不是如我们推测的这样~
struct clk_onecell_data {
struct clk **clks;
unsigned int clk_num;
};
struct clk {
struct clk_core *core;
const char *dev_id;
const char *con_id;
unsigned long min_rate;
unsigned long max_rate;
struct hlist_node clks_node;
};
struct clk_core {
const char *name;
const struct clk_ops *ops;
struct clk_hw *hw;
struct module *owner;
struct clk_core *parent;
const char **parent_names;
struct clk_core **parents;
u8 num_parents;
u8 new_parent_index;
unsigned long rate;
unsigned long req_rate;
unsigned long new_rate;
struct clk_core *new_parent;
struct clk_core *new_child;
unsigned long flags;
bool orphan;
unsigned int enable_count;
unsigned int prepare_count;
unsigned long min_rate;
unsigned long max_rate;
unsigned long accuracy;
int phase;
struct hlist_head children;
struct hlist_node child_node;
struct hlist_head clks;
unsigned int notifier_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct hlist_node debug_node;
#endif
struct kref ref;
};
/**
* struct clk_hw - handle for traversing from a struct clk to its corresponding
* hardware-specific structure. struct clk_hw should be declared within struct
* clk_foo and then referenced by the struct clk instance that uses struct
* clk_foo's clk_ops
*
* @core: pointer to the struct clk_core instance that points back to this
* struct clk_hw instance
*
* @clk: pointer to the per-user struct clk instance that can be used to call
* into the clk API
*
* @init: pointer to struct clk_init_data that contains the init data shared
* with the common clock framework.
*/
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
/**
* struct clk_init_data - holds init data that's common to all clocks and is
* shared between the clock provider and the common clock framework.
*
* @name: clock name
* @ops: operations this clock supports
* @parent_names: array of string names for all possible parents
* @num_parents: number of possible parents
* @flags: framework-level hints and quirks
*/
struct clk_init_data {
const char *name;
const struct clk_ops *ops;
const char * const *parent_names;
u8 num_parents;
unsigned long flags;
};
对 struct clk_onecell_data
结构体层层剖析,可以发现其包含了几个重要的结构体,分别是struct clk
、struct clk_core
、struct clk_hw
以及struct clk_init_data
,要分析出这些important struct,那么接下来就需要看看samsung如何构建这些结构体的实例的,接着分析代码!
s3c2410_common_clk_init
ctx = samsung_clk_init(np, reg_base, NR_CLKS);
/* setup the essentials required to support clock lookup using ccf */
struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np,
void __iomem *base, unsigned long nr_clks)
{
struct samsung_clk_provider *ctx;
struct clk **clk_table;
int i;
ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL); //分配一个struct samsung_clk_provider结构体实例,并清0该内存
if (!ctx)
panic("could not allocate clock provider context.\n");
clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
if (!clk_table)
panic("could not allocate clock lookup table\n");
for (i = 0; i < nr_clks; ++i)
clk_table[i] = ERR_PTR(-ENOENT);
ctx->reg_base = base;
ctx->clk_data.clks = clk_table; //初始化clk指针
ctx->clk_data.clk_num = nr_clks; //初始化clk num
spin_lock_init(&ctx->lock);
return ctx;
}
由上面的代码中可以看出samsung_clk_init
函数仅仅是分配了相关结构体的内存,并没有进行实际的初始化,比如仅仅给结构体struct clk_onecell_data
的成员做了指针的赋值操作,那么实际的赋值操作在哪里进行的呢?接着看代码…
s3c2410_common_clk_init
if (current_soc == S3C2440 || current_soc == S3C2442) {
samsung_clk_register_div(ctx, s3c244x_common_dividers,
ARRAY_SIZE(s3c244x_common_dividers));
samsung_clk_register_gate(ctx, s3c244x_common_gates,
ARRAY_SIZE(s3c244x_common_gates));
samsung_clk_register_mux(ctx, s3c244x_common_muxes,
ARRAY_SIZE(s3c244x_common_muxes));
samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor,
ARRAY_SIZE(s3c244x_common_ffactor));
}
//在s3c2410_common_clk_init函数中,有很多register函数,我们选择其中之一进行分析...
/* register a list of gate clocks */
void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx,
const struct samsung_gate_clock *list,
unsigned int nr_clk)
{
struct clk *clk;
unsigned int idx, ret;
for (idx = 0; idx < nr_clk; idx++, list++) {
clk = clk_register_gate(NULL, list->name, list->parent_name,
list->flags, ctx->reg_base + list->offset,
list->bit_idx, list->gate_flags, &ctx->lock); //实际的注册gate函数
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n", __func__,
list->name);
continue;
}
/* register a clock lookup only if a clock alias is specified */
if (list->alias) {
ret = clk_register_clkdev(clk, list->alias,
list->dev_name);
if (ret)
pr_err("%s: failed to register lookup %s\n",
__func__, list->alias);
}
samsung_clk_add_lookup(ctx, clk, list->id);
}
}
//来看看clk_register_gate函数
struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock)
{
struct clk_hw *hw;
hw = clk_hw_register_gate(dev, name, parent_name, flags, reg,
bit_idx, clk_gate_flags, lock);
if (IS_ERR(hw))
return ERR_CAST(hw);
return hw->clk;
}
//来看看clk_hw_register_gate函数
/**
* clk_hw_register_gate - register a gate clock with the clock framework
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of this clock's parent
* @flags: framework-specific flags for this clock
* @reg: register address to control gating of this clock
* @bit_idx: which bit in the register controls gating of this clock
* @clk_gate_flags: gate-specific flags for this clock
* @lock: shared register lock for this clock
*/
struct clk_hw *clk_hw_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock)
{
struct clk_gate *gate;
struct clk_hw *hw;
struct clk_init_data init;
int ret;
if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
if (bit_idx > 15) {
pr_err("gate bit exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the gate */
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_gate_ops; //最重要的地方,初始化驱动提供的操作函数
init.flags = flags | CLK_IS_BASIC;
init.parent_names = (parent_name ? &parent_name: NULL);
init.num_parents = (parent_name ? 1 : 0);
/* struct clk_gate assignments */
gate->reg = reg;
gate->bit_idx = bit_idx;
gate->flags = clk_gate_flags;
gate->lock = lock;
gate->hw.init = &init;
hw = &gate->hw; //结构体进行关联
ret = clk_hw_register(dev, hw);
if (ret) {
kfree(gate);
hw = ERR_PTR(ret);
}
return hw;
}
//在来看看samsung_clk_add_lookup函数
/* add a clock instance to the clock lookup table used for dt based lookup */
void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk,
unsigned int id)
{
if (ctx->clk_data.clks && id)
ctx->clk_data.clks[id] = clk;
}
上面代码量有点大,我们简单的梳理一下,时钟的功能有很多,比如gate、mux、div等等,这里三星根据时钟的功能依次调用对应的注册函数进行注册,当然这里这些时钟的相关信息比如频率、名字等等samsung的bsp工程师都已经做成一个数组了,感兴趣的可以再看看代码,这里不再赘述,在samsung_clk_register_gate
函数里,通过for循环依次注册gate clock,该函数里做了哪些事情呢,该函数分配一个struct clk_hw
结构体,初始化该结构体内的成员,**最重要的就是初始化驱动提供的gate的操作函数ops!!!**同时初始化一个struct clk_gate
结构体实例。最终将struct clk_gate
与struct clk_hw
实例进行关联,调用clk_hw_register
注册一个clk_hw!所以无论是gate、mux还是其它的注册,最终都是调用clk_hw_register
函数注册了一个clk_hw,因为结构体之间是有关联的。
最后通过samsung_clk_add_lookup函数,将注册成功返回的hw->clk指针记录到struct clk_onecell_data中的clk数组中,方便consumer进行查找,在这里我们也可以推测中,consumer获取的clk就是hw->clk。
provider总结
通过以上的分析,可以总结出以下信息:
struct clk_onecell_data
结构体可以认为是包含了一个单板的所有clk信息(当然这只是三星是这么干的,不同的厂家实现会有所区别),其两个成员变量分别是struct clk数组及数组个数,所以注册provider需要实现这个结构体,以便consumer拿到clk。struct clk_hw
该结构体对应一个具体的clk,因为其包含了clk的操作函数。
浅析内核clk framework的实现
provider实现
深入of_clk_add_provider
函数进行分析,通过下面的代码可以看到该函数非常的简单,分配一个provider,初始化其成员,一共就三个成员,以作了相应注释,比较重要的可能就是node,后续内核应该就是根据consumer引用了哪个node去返回provider。
/**
* of_clk_add_provider() - Register a clock provider for a node
* @np: Device node pointer associated with clock provider
* @clk_src_get: callback for decoding clock
* @data: context pointer for @clk_src_get callback.
*/
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; //分配一个provider实例
int ret;
cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL);
if (!cp)
return -ENOMEM;
cp->node = of_node_get(np); //provider node,对应前文的clocks
cp->data = data; //拿到私有数据
cp->get = clk_src_get; //初始化get函数
mutex_lock(&of_clk_mutex);
list_add(&cp->link, &of_clk_providers); //添加到provider链表中
mutex_unlock(&of_clk_mutex);
pr_debug("Added clock from %s\n", np->full_name);
ret = of_clk_set_defaults(np, true);
if (ret < 0)
of_clk_del_provider(np);
return ret;
}
consumer实现
来分析下clk_get
函数,这个函数深入分析就略微复杂点了,大家看注释吧 - - 一点点看~
这里一些dts的操作函数,还有具体的查找方式本人也还没有弄清楚,后续本人弄清楚了会继续更新本章,但是总体看下来就是根据clk name为入参,最终一步步找到provider。
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk *clk;
if (dev) {
clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id); //通过名字查找时钟
if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
return clk;
}
return clk_get_sys(dev_id, con_id);
}
static struct clk *__of_clk_get_by_name(struct device_node *np,
const char *dev_id,
const char *name)
{
struct clk *clk = ERR_PTR(-ENOENT);
/* Walk up the tree of devices looking for a clock that matches */
while (np) {
int index = 0;
/*
* For named clocks, first look up the name in the
* "clock-names" property. If it cannot be found, then
* index will be an error code, and of_clk_get() will fail.
*/
if (name)
index = of_property_match_string(np, "clock-names", name);
clk = __of_clk_get(np, index, dev_id, name);
if (!IS_ERR(clk)) {
break;
} else if (name && index >= 0) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
pr_err("ERROR: could not get clock %s:%s(%i)\n",
np->full_name, name ? name : "", index);
return clk;
}
/*
* No matching clock found on this node. If the parent node
* has a "clock-ranges" property, then we can try one of its
* clocks.
*/
np = np->parent;
if (np && !of_get_property(np, "clock-ranges", NULL))
break;
}
return clk;
}
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
struct clk_lookup *cl;
struct clk *clk = NULL;
mutex_lock(&clocks_mutex);
cl = clk_find(dev_id, con_id);
if (!cl)
goto out;
clk = __clk_create_clk(cl->clk_hw, dev_id, con_id);
if (IS_ERR(clk))
goto out;
if (!__clk_get(clk)) {
__clk_free_clk(clk);
cl = NULL;
goto out;
}
out:
mutex_unlock(&clocks_mutex);
return cl ? clk : ERR_PTR(-ENOENT);
}
static struct clk *__of_clk_get(struct device_node *np, int index,
const char *dev_id, const char *con_id)
{
struct of_phandle_args clkspec;
struct clk *clk;
int rc;
if (index < 0)
return ERR_PTR(-EINVAL);
rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
&clkspec);
if (rc)
return ERR_PTR(rc);
clk = __of_clk_get_from_provider(&clkspec, dev_id, con_id); //获取provider
of_node_put(clkspec.np);
return clk;
}
小结
本章本人完全是从代码上去分析clk framework,总的来说clk framework较power domain要复杂点,因为需要结合单板的clk tree 去看,但是本人在写该篇博客之前,没有去看2440的时钟树,所以导致很多结构体和思想理解的还不够透彻,所以本章如果有误,还望大家们指出,同时希望大牛们可以留言对本章内容指点一二~
最后,注意到kernel的framework与dts都强相关,所以要深入理解framework的话,需要对dts的操作函数很熟悉,所以下周本人决定总结一些出现频率较多的dts操作函数,以便后续分析代码。
前面提到使用2440单板的时钟,我们需要在dts里引用一个时钟节点即可,现在可以解答该问题了,因为samsung只注册了一个provider进去,samsung将所有的clk都包含在了一个struct clk_onecell_data
结构体里,而其它bsp厂家,是按照功能分类的,比如sprd(紫光展锐),按照时钟功能进行分类,所有gate clk构建一个struct clk_onecell_data
,并注册成一个provider,这样的话consumer就可以根据功能引用节点,感兴趣的可以看看sprd的dts,可以发现往往一个consumer会引用多个provider进行配置,我觉得这样会更好点~
希望身体健康,坚持运动!