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)。

linux 通用时钟框架CCF,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。