linux 通用时钟框架CCF
linux 通用时钟框架CCF
简介
这里讲的时钟是给soc各组件提供时钟的树状框架,并不是内核使用的时间,和其他模块一样,clk也有框架,用以适配不同的平台。适配层之上是客户代码和接口,也就是各模块(如需要时钟信号的外设,usb等)的驱动。适配层之下是具体的soc平台的时钟操作细节。
内核中另外一个具有类似树状框架特点的是regulator框架。对比regulator框架,clk框架不确定性更大,内核中仅仅提供了少数的适配规范,struct clk都是各平台自己的clk驱动实现。 在3.4.5内核里基本上还是这种状态,但是新的3.10内核很多soc的clk驱动已经改为common clock framework(CCF)。各平台采用CCF的的clock驱动都统一在drivers/clk目录。
common clockframework由Mike Turquette在2012.5引入kernel 3.4。
下图引用自Emb edded LinuxConference 2013上Gregory CLEMENT的一篇介绍elc2013_clement.pdf。本图的衍生示意图也都是来自于这篇文章。
内核版本: linux-linaro-stable-android-3.10.37-2014.04
CCF相关的内核配置宏
CONFIG_COMMON_CLK
CCF core
CCF core主要代码在drivers/clk/clk.c里。主要维护时钟树以及操作,互斥锁,通知链。
主要结构体定义
只有定义了CONFIG_COMMON_CLK才会有CCF框架。
include/linux/clk- private.h:
#ifdef CONFIG_COMMON_CLK
struct clk {
constchar *name; //名字用来在全局链表里查找clk用的
conststruct clk_ops *ops; //抽象的标准ops操作
structclk_hw *hw;
structclk *parent;
constchar **parent_names;
structclk **parents;
u8 num_parents;
unsignedlong rate;
unsignedlong new_rate;
unsignedlong flags;
unsignedint enable_count;
unsignedint prepare_count;
structhlist_head children;
structhlist_node child_node;
unsignedint notifier_count;
#ifdef CONFIG_COMMON_CLK_DEBUG
structdentry *dentry;
#endif
};
…
#endif
/**
*struct clk_ops - Callback operations forhardware clocks; these are to
* beprovided by the clock implementation, and will be called by drivers
*through the clk_* api.
*
*@prepare: Prepare the clock for enabling.This must not return until
* the clock is fully prepared, and it‘ssafe to call clk_enable.
* This callback is intended to allowclock implementations to
* do any initialisation that may sleep.Called with
* prepare_lock held.
*
*@unprepare: Release the clock fromits prepared state. This will typically
* undo any work done in the @preparecallback. Called with
* prepare_lock held.
*
*@is_prepared: Queries the hardware to determine if the clock is prepared.
* This function is allowed to sleep.Optional, if this op is not
* set then the prepare count will beused.
*
*@unprepare_unused: Unprepare the clock atomically. Only called from
* clk_disable_unused for prepareclocks with special needs.
* Called with prepare mutex held.This function may sleep.
*
*@enable: Enable the clock atomically.This must not return until the
* clock is generating a valid clocksignal, usable by consumer
* devices. Called with enable_lockheld. This function must not
* sleep.
*
*@disable: Disable the clock atomically.Called with enable_lock held.
* This function must not sleep.
*
*@is_enabled: Queries the hardware todetermine if the clock is enabled.
* This function must not sleep.Optional, if this op is not
* set then the enable count will beused.
*
*@disable_unused: Disable the clock atomically. Only called from
* clk_disable_unused for gate clockswith special needs.
* Called with enable_lock held. This function must not
* sleep.
*
*@recalc_rate Recalculate the rate ofthis clock, by querying hardware. The
* parent rate is an inputparameter. It is up to the caller to
* ensure that the prepare_mutex is heldacross this call.
* Returns the calculated rate. Optional, but recommended - if
* this op is not set then clock ratewill be initialized to 0.
*
*@round_rate: Given a target rate asinput, returns the closest rate actually
* supported by the clock.
*
*@get_parent: Queries the hardware todetermine the parent of a clock. The
* return value is a u8 which specifiesthe index corresponding to
* the parent clock. This index can be applied to either the
* .parent_names or .parentsarrays. In short, this function
* translates the parent value read fromhardware into an array
* index. Currently only called when the clock isinitialized by
* __clk_init. This callback is mandatory for clocks with
* multiple parents. It is optional (and unnecessary) for clocks
* with 0 or 1 parents.
*
* @set_parent: Change the input source of this clock; forclocks with multiple
* possible parents specify a new parentby passing in the index
* as a u8 corresponding to the parentin either the .parent_names
* or .parents arrays. This function in affect translates an
* array index into the value programmedinto the hardware.
* Returns 0 on success, -EERRORotherwise.
*
*@set_rate: Change the rate of thisclock. The requested rate is specified
* by the second argument, whichshould typically be the return
* of .round_rate call. The third argument gives the parent rate
* which is likely helpful for most.set_rate implementation.
* Returns 0 on success, -EERRORotherwise.
*
*The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow
*implementations to split any work between atomic (enable) and sleepable
*(prepare) contexts. If enabling a clockrequires code that might sleep,
*this must be done in clk_prepare. Clockenable code that will never be
*called in a sleepable context may be implemented in clk_enable.
*
*Typically, drivers will call clk_prepare when a clock may be needed later
*(eg. when a device is opened), and clk_enable when the clock is actually
* required(eg. from an interrupt). Note that clk_prepare MUST have been
*called before clk_enable.
*/
struct clk_ops {
int (*prepare)(struct clk_hw *hw); //开时钟前调用,可能会造成休眠
void (*unprepare)(struct clk_hw *hw);
int (*is_prepared)(struct clk_hw *hw);
void (*unprepare_unused)(struct clk_hw*hw);
int (*enable)(struct clk_hw *hw);
void (*disable)(struct clk_hw *hw);
int (*is_enabled)(struct clk_hw *hw);
void (*disable_unused)(struct clk_hw *hw);
unsignedlong (*recalc_rate)(struct clk_hw *hw,
unsignedlong parent_rate); //查询硬件,重新计算频率
long (*round_rate)(struct clk_hw *hw,unsigned long,
unsignedlong *); //计算最接近要求的频率
int (*set_parent)(struct clk_hw *hw, u8index); //MUX会使用
u8 (*get_parent)(struct clk_hw *hw); //MUX会使用
int (*set_rate)(struct clk_hw *hw,unsigned long,
unsigned long);
void (*init)(struct clk_hw *hw);
};
/**
*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 {
constchar *name;
conststruct clk_ops *ops;
constchar **parent_names;
u8 num_parents;
unsignedlong flags;
};
/**
*struct clk_hw - handle for traversing from a struct clk to its corresponding
*hardware-specific structure. structclk_hw should be declared within struct
*clk_foo and then referenced by the struct clk instance that uses struct
*clk_foo‘s clk_ops
*
*@clk: pointer to the struct clk instance that points back to this struct
*clk_hw instance
*
*@init: pointer to struct clk_init_data that contains the init data shared
*with the common clock framework.
*/
struct clk_hw {
structclk *clk;
conststruct clk_init_data *init;
};
时钟的基本种类
固定速率 |
|
门时钟 |
和上级时钟同频,只能打开和关闭操作 |
MUX |
多选一 |
固定倍频 |
上级时钟的频率有固定倍频或者分频,不能关闭 |
分频 |
上级时钟的频率分频,可以选择不同的分频比 |
5种时钟类型都有不同的注册函数和结构体,如MUX时钟
结构体毫无例外是封装包含struct clk_hw,然后加上该种类的特性的成员。
struct clk_mux {
structclk_hw hw;
void__iomem *reg;
u32 *table;
u32 mask;
u8 shift;
u8 flags;
spinlock_t *lock;
};
struct clk *clk_register_mux(struct device*dev, const char *name,
constchar **parent_names, u8 num_parents, unsigned long flags,
void__iomem *reg, u8 shift, u8 width,
u8clk_mux_flags, spinlock_t *lock);
一般SOC都有大量的时钟,用数组变量定义批量时钟是最方便的,但是内核不推荐这样做。新开发的驱动用clk_init_data和clk_register()定义。
时钟标准驱动层
CCF提供的API,实际是调用了clk_ops的实际操作函数,这些函数是按照5种基本的时钟分类来的。
值得注意的是,一般的驱动框架,比如网卡,usb,regulator,都是内核的core层提供管理逻辑,由芯片驱动提供实际的操作。但是clk的实际操作是由CCF API完成,而不是芯片驱动完成的。之所以能够做到这一点,是因为芯片的时钟操作方法比较类似。soc平台注册时钟的时候,只需要提供操作的信息,就可以由CCF的统一操作函数对这些信息进行操作。
以MUX的clk_set_parent分析为例
clk_set_parent->__clk_set_parent->clk->(ops->set_parent)
ops->set_parent的定义如下,在注册时钟的时候就设置好了。
const struct clk_ops clk_mux_ops = {
.get_parent= clk_mux_get_parent,
.set_parent= clk_mux_set_parent,
};
static int clk_mux_set_parent(struct clk_hw*hw, u8 index)
{
structclk_mux *mux = to_clk_mux(hw);
u32val;
unsignedlong flags = 0;
if(mux->table)
index= mux->table[index];
else{
if(mux->flags & CLK_MUX_INDEX_BIT)
index= (1 << ffs(index));
if(mux->flags & CLK_MUX_INDEX_ONE)
index++;
}
if(mux->lock)
spin_lock_irqsave(mux->lock,flags);
val= readl(mux->reg);
val&= ~(mux->mask << mux->shift);
val|= index << mux->shift;
writel(val,mux->reg);
if(mux->lock)
spin_unlock_irqrestore(mux->lock,flags);
return0;
}
可见,平台代码并没有提供实际的ops,只是提供table,bit和reg等信息就可以了。CCF的ops可以直接调用writel操作硬件。
驱动样例分析
准备5类时钟信息
每个soc有很多时钟,按照CCF的5个种类分开定义.
struct samsung_mux_clock {
unsignedint id;
constchar *dev_name;
constchar *name;
constchar **parent_names;
u8 num_parents;
unsignedlong flags;
unsignedlong offset;
u8 shift;
u8 width;
u8 mux_flags;
constchar *alias;
};
struct samsung_mux_clockexynos5250_mux_clks[] __initdata = {
MUX_A(none,"mout_apll", mout_apll_p, SRC_CPU, 0, 1, "mout_apll"),
MUX(none,"mout_mpll_fout", mout_mpll_fout_p, PLL_DIV2_SEL, 4, 1),
MUX_A(none,"sclk_mpll", mout_mpll_p, SRC_CORE1, 8, 1, "mout_mpll"),
……
}
参考MUX(none, "mout_mpll_fout", mout_mpll_fout_p,PLL_DIV2_SEL, 4, 1),
#define __MUX(_id, dname, cname, pnames, o,s, w, f, mf, a) \
{ \
.id = _id, \
.dev_name = dname, \
.name = cname, \
.parent_names = pnames, \
.num_parents = ARRAY_SIZE(pnames), \
.flags = f, \
.offset = o, \
.shift = s, \
.width = w, \
.mux_flags = mf, \
.alias = a, \
}
#define MUX(_id, cname, pnames, o, s, w) \
__MUX(_id,NULL, cname, pnames, o, s, w, 0, 0, NULL)
实际上就是利用宏简化赋值代码。mout_mpll_fout展开如下
struct samsung_mux_clock –》
{ \
.id = none, \
.dev_name = NULL, \
.name = "mout_mpll_fout", \
.parent_names = mout_mpll_fout_p, \
.num_parents = ARRAY_SIZE(mout_mpll_fout_p), \
.flags = 0, \
.offset = PLL_DIV2_SEL, \
.shift = 4, \
.width = 1, \
.mux_flags = NULL, \
.alias = NULL, \
}
结合时钟标准驱动层int clk_set_parent(struct clk *clk, struct clk *parent)来看。对mout_mpll_fout设置mux的方法分为以下几个步骤:
1. 将本clk和父clk为参数输入clk_set_parent
2. 用for循环在本clk的parents成员数组查找指针和入参clk*parent相等的。返回数组的index
3. 找到偏移为PLL_DIV2_SEL的寄存器,将index左移4bit设置为1就可以。
从上面可以看出,定义clk的时候,父时钟的顺序必须和寄存器设置的顺序匹配才可以。不支持这种规律的芯片,是不能用CCF的。
注册5类时钟
void __init exynos5250_clk_init(structdevice_node *np)
{
…
samsung_clk_register_fixed_rate(exynos5250_fixed_rate_clks,
ARRAY_SIZE(exynos5250_fixed_rate_clks));
samsung_clk_register_fixed_factor(exynos5250_fixed_factor_clks,
ARRAY_SIZE(exynos5250_fixed_factor_clks));
samsung_clk_register_mux(exynos5250_mux_clks,
ARRAY_SIZE(exynos5250_mux_clks));
…
}
准备非5类时钟信息
出了标准的5类时钟类型,不标准的时钟类型需要单独准备clk_init_data init;
注册非5类时钟
apll= samsung_clk_register_pll35xx("fout_apll", "fin_pll",
reg_base+ 0x100);
struct samsung_clk_pll35xx {
structclk_hw hw;
constvoid __iomem *con_reg;
};
struct clk * __initsamsung_clk_register_pll35xx(const char *name,
constchar *pname, const void __iomem *con_reg)
{
structsamsung_clk_pll35xx *pll;
structclk *clk;
structclk_init_data init;
//如果是标准类型,调用标准类型的注册函数里会分配时钟结构体的内存
pll= kzalloc(sizeof(*pll), GFP_KERNEL);
if(!pll) {
pr_err("%s:could not allocate pll clk %s\n", __func__, name);
returnNULL;
}
//配置clk_init_data
init.name= name;
init.ops= &samsung_pll35xx_clk_ops;
init.flags= CLK_GET_RATE_NOCACHE;
init.parent_names= &pname;
init.num_parents= 1;
pll->hw.init= &init;
pll->con_reg= con_reg;
//通用注册函数,标准类型的注册函数最终也是调用这个
clk= clk_register(NULL, &pll->hw);
if(IS_ERR(clk)) {
pr_err("%s:failed to register pll clock %s\n", __func__,
name);
kfree(pll);
}
//注册到clocks全局链表。clk_register_clkdev会申请structclk_lookup,不用caller关心。
if(clk_register_clkdev(clk, name, NULL))
pr_err("%s:failed to register lookup for %s", __func__, name);
returnclk;
}
//由于是私有函数,可以随便写了。
static const struct clk_opssamsung_pll35xx_clk_ops = {
.recalc_rate= samsung_pll35xx_recalc_rate,
};
clk api的使用方法
和regulator框架类似,首先调用clk_get()得到struct clk*,然后将struct clk *作为入参调用CCF提供的API,如int clk_prepare(struct clk *clk)。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。