linux clock

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/flaoter/article/details/73505973

linux kernel在3.4之后加入了CCF(common clock framework)来统一管理clock,对外提供了统一的接口供其它驱动模块调用,对内封装了clock驱动。linux的clock是基于provider/consumer模型的,CCF提供时钟,外部驱动模块使用时钟。本节叙述的顺序是先对clock的基本原理进行说明,然后对CCF和clock驱动进行解析,最后是外部模块的调用实例。

1 原理

clock是设备或模块使用的时钟,时钟频率是hz,并不是定时器或计数器timer。
clock的外部来源是石英晶体振荡器或RC振荡电路等,本文中使用的平台的clock源有两个,分别是26M和32k晶振。
clock供给模块大约需要如图所示的几个层次,
(1)外部时钟源,此处是外部26M时钟
(2)PLL指的是锁相环,可以通过倍频产生高频信号,常见的PLL设计人员一般通过命名对它们的用途进行区分。DPLL通常用于给DDR供给时钟,MPLL通常给cpu提供时钟,TWPLL是一个通用的时钟。除了倍频后的高频信号,此处还包括PLL分频后得到的时钟
(3)实际的模块,如图中的DDR,MCU等,每一个模块可以有一个或多个时钟源
clock_level
可以看出,clock的结构类似树形结构,连线上右侧的节点都是左侧节点的子节点,在linux驱动中也是通过一个clock tree的框架对clock进行注册的。

2 CCF

按clock特点,linux 3.10中将时钟分为几类:
(1) fixed clock, 固定频率时钟
(2) gate clock, 门控时钟
(3) mux clock, 多选一时钟
(4) fixed factor clock, 固定的factor, clock的频率由parent clock的频率乘以mul, 除以div得出
(5) divider clock, 可以分频的时钟,但时钟源只有一个, clock频率是parent clock除以div
(6) composite clock, 就是综合mux, gate, divider等功能的clock
clock的软件框架图如图所示,浅颜色的是与clock相关的模块。clock的硬件在驱动中通过device node进行描述,open firmware对其进行管理,clock驱动通过open firmware的接口对clock tree进行注册,并且为common clock framework提供接口。外部模块如Display, MM等直接调用CCF的接口使用clock,并不关注clock驱动的细节。此外,出于debug需求,CCF还应在debugfs提供一套文件接口用于调试。
clock_arch
CCF相当于clock驱动的core层,提供了如下几个功能:
(1) 提供给其他驱动使用的API
(2) 提供给clock driver调用的接口用于注册设备
(3) debugfs
(4) misc
下面列出一些常见函数

clk_register/clk_unregister (1)
clk_get/clk_put  (2)   //获取时钟
clk_prepare/clk_unprepare (2)  //使用时钟前的准备
clk_enable/clk_disable (2)  //使能时钟
clk_get_parent/clk_set_parent (2) //获取时钟的parent
clk_get_rate/clk_set_rate (2) //获取时钟频率 
clk_debug_init (3)
of_clk_init   //与DT接口,下面会详述

涉及到一个重要的结构体struct clk,注册时需要将如下结构体进行填充。但是这些都是CCF的工作,由linux kernel进行负责。我们只需要了解它的含义即可。

struct clk {
    const char      *name;  //时钟的名字
    const struct clk_ops    *ops; //时钟的操作函数集合
    struct clk_hw       *hw;  //硬件特有的结构体
    struct clk      *parent;  //当前的父clock
    const char      **parent_names; //父clocks的名字
    struct clk      **parents;  //父clocks的clock数组
    u8          num_parents; //父clock的数量
    unsigned long       rate; //当前时钟速率
    unsigned long       new_rate;  //要设置的新速率
    unsigned long       flags;
    unsigned int        enable_count;
    unsigned int        prepare_count;
    struct hlist_head   children;   //clock的子clocks
    struct hlist_node   child_node;
    unsigned int        notifier_count;
#ifdef CONFIG_COMMON_CLK_DEBUG
    struct dentry       *dentry;
#endif
};

下图引用自一篇嵌入式linux会议的文档,说明了clock tree基于device tree创建的过程。
clk_framework
DT文件中对clock的描述大约如下:

        clk_pwm0: clk_pwm0 {
            compatible = "vendor, muxed-clock";
            #clock-cells = <0>;
            reg = <0x5020002c 0x1 0x50010000 0x10>; 
            clocks = <&ext_32k>, <&ext_26m>,  <&clk_48m><&clk_96m>;
            clock-output-names = "clk_pwm0";
        };

compatible用于定义时钟类型
“#clocks-cells”为0代表只有一个输出时钟,为1代表有多个输出时钟
clock-output-names是输出时钟的名字
clocks为输入时钟的名字
reg为使用的选择和使能寄存器
在编译时将of_device_id类型的”__clk_of_table##name”变量保存在__clk_of_table段
clk-provider.h

#define CLK_OF_DECLARE(name, compat, fn)            \
    static const struct of_device_id __clk_of_table_##name  \
        __used __section(__clk_of_table)        \
        = { .compatible = compat, .data = fn };

一个使用的例子如下:

CLK_OF_DECLARE(muxed_clock, "vendor, muxed-clock", of_muxed_clk_setup);

在CCF的入口函数of_clk_init中,会遍历dt中的device node,一旦与of_clock_table中变量的compatible属性匹配,就会调用data对应的函数,即此例中的of_muxed_clk_setup进行clock的注册。
clk.c

void __init of_clk_init(const struct of_device_id *matches)
{
    struct device_node *np;

    if (!matches)
        matches = __clk_of_table;

    for_each_matching_node(np, matches) {
        const struct of_device_id *match = of_match_node(matches, np);
        of_clk_init_cb_t clk_init_cb = match->data;
        clk_init_cb(np);
    }
}

3 clock driver

clock driver方面需要实现的数据结构如下。

struct clk_hw {
    struct clk *clk;
    const struct clk_init_data *init;
};

struct clk_init_data {
    const char      *name;
    const struct clk_ops    *ops;
    const char      **parent_names;
    u8          num_parents;
    unsigned long       flags;
};

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);
    unsigned long   (*recalc_rate)(struct clk_hw *hw,
                    unsigned long parent_rate);
    long        (*round_rate)(struct clk_hw *hw, unsigned long,
                    unsigned long *);
    int     (*set_parent)(struct clk_hw *hw, u8 index);
    u8      (*get_parent)(struct clk_hw *hw);
    int     (*set_rate)(struct clk_hw *hw, unsigned long,
                    unsigned long);
    void        (*init)(struct clk_hw *hw);
};

以clk-mux为例,驱动中会对clk_init_data,clk_ops和clk_hw数据结构的成员进行填充,此外每种类型的时钟还会定义一种与之对应的数据结构,如此例中的struct clk_mux,之后就可以调用CCF提供的接口函数clk_register进行时钟的注册。
CCF中提供的API clk_get_parent, clk_set_parent最终调用的就是如下ops中的函数。

const struct clk_ops clk_mux_ops = {
    .get_parent = clk_mux_get_parent,
    .set_parent = clk_mux_set_parent,
};
EXPORT_SYMBOL_GPL(clk_mux_ops);

struct clk *clk_register_mux_table(struct device *dev, const char *name,
        const char **parent_names, u8 num_parents, unsigned long flags,
        void __iomem *reg, u8 shift, u32 mask,
        u8 clk_mux_flags, u32 *table, spinlock_t *lock)
{
    struct clk_mux *mux;
    struct clk *clk;
    struct clk_init_data init;

    /* allocate the mux */
    mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
    if (!mux) {
        pr_err("%s: could not allocate mux clk\n", __func__);
        return ERR_PTR(-ENOMEM);
    }

    init.name = name;
    init.ops = &clk_mux_ops;
    init.flags = flags | CLK_IS_BASIC;
    init.parent_names = parent_names;
    init.num_parents = num_parents;

    /* struct clk_mux assignments */
    mux->reg = reg;
    mux->shift = shift;
    mux->mask = mask;
    mux->flags = clk_mux_flags;
    mux->lock = lock;
    mux->table = table;
    mux->hw.init = &init;

    clk = clk_register(dev, &mux->hw);

    if (IS_ERR(clk))
        kfree(mux);

    return clk;
}

struct clk *clk_register_mux(struct device *dev, const char *name,
        const char **parent_names, u8 num_parents, unsigned long flags,
        void __iomem *reg, u8 shift, u8 width,
        u8 clk_mux_flags, spinlock_t *lock)
{
    u32 mask = BIT(width) - 1;

    return clk_register_mux_table(dev, name, parent_names, num_parents,
                      flags, reg, shift, mask, clk_mux_flags,
                      NULL, lock);
}

4 Demo

int i2c_clk_init(struct i2c *pi2c)
{ 
    struct clk *clk;
    clk=clk_get(&pdev->dev, "i2c_clk"); //获取需要操作的clock结构体实例
    clk_set_parent(clk, clk_get(NULL, "pll")); //设置clock的source,最终频率由此分频得到
    clk_set_rate(clk, 4000000); //设置频率
    clk_enable(clk); //使能时钟

}

猜你喜欢

转载自blog.csdn.net/flaoter/article/details/73505973