linux clk驱动框架

来源:互联网 发布:一字棋用极大极小算法 编辑:程序博客网 时间:2024/05/20 14:28

内核中提供了clk common framework子系统,用来完成对clock的统一管理。
我们将从如下几个方面来介绍clk子系统的内容:

1.  clk framework简介2.  clk framework的实现3.  clk和device tree4.  如何添加自己的clock

一、 clk framework简介
clk framework是内核中用来统一管理clock的子系统。代码存在于kernel/driver/clk目录中。
要使用clkframework来实现厂商自己平台上的clock驱动,首先需要在defconfig中使能如下的几个CONFIG来配置内核。

CONFIG_CLKDEV_LOOKUP=yCONFIG_HAVE_CLK_PREPARE=yCONFIG_COMMON_CLK=y

除了这几个以外,还有一个是否打开DEBUG的开关配置:

CONFIG_COMMON_CLK_DEBUG=y

这个DEBUG开关是控制内核是否产生clk的debugfs的,如果配置了这个选项,内核将生成相应的debugfs,在启动后将会挂载于/sys/kernel/debug目录下。
clk framework是一个通用core模块,它主要提供了如下几个功能:

1.  向上提供给其他driver调用的接口API2.  向下提供给clock driver注册的接口API3.  debugfs创建4.  若干个基于dts配置的通用clock模型(通过调用注册接口API)

它的框架图如下所示:
clk framework

上图中的黄色区域都是clk core所实现的功能,灰色区域是clock驱动开发需要做的事情,而绿色区域是其他device driver需要使用clock时要调用到的clk功能。

二、 clk framework的实现
在开始介绍clk framework之前,首先需要了解一下几个重要的结构体:

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

这个结构体主要定义了一些用来操作硬件的回调函数,这个部分是需要厂商开发自己的clock驱动的时候实现的。

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

clk_hw结构体可以看到其中封装了一个clk_ops结构体,它是一个clk驱动需要实现的关键结构,厂商需要实现此结构体,并把它注册到clk framework。clk_hw是联系clk_ops和struct clk的纽带。它一般会被封装到一个厂商自己定义的更大的结构体中,主要是用来建立与struct clk的联系。

struct clk {    const char      *name;    const struct clk_ops    *ops;    struct clk_hw       *hw;    struct clk      *parent;    const char      **parent_names;    struct clk      **parents;    u8          num_parents;    unsigned long       rate;    unsigned long       new_rate;    unsigned long       flags;    unsigned int        enable_count;    unsigned int        prepare_count;    struct hlist_head   children;    struct hlist_node   child_node;    unsigned int        notifier_count;#ifdef CONFIG_COMMON_CLK_DEBUG    struct dentry       *dentry;#endif};

这个是framework core中关键的结构体,core中都是通过这个结构体来管理clk的,它主要是用来抽象clk硬件的差异,并完成一些通用操作的封装。其中的hw成员变量是与之关联的clk_hw结构。由上面的介绍可知,通过struct clk_hw和struct clk就把差异的部分和通用部分给联系 起来了。

介绍了结构体以后,我们就来看一下clk framework提供的具体功能吧。这部分的实现主要在clk.c和clkdev.c两个源文件中。
(1) 向上提供的接口API

struct clk *clk_get(struct device *dev, const char *id);struct clk *devm_clk_get(struct device *dev, const char *id);int clk_enable(struct clk *clk);void clk_disable(struct clk *clk);unsigned long clk_get_rate(struct clk *clk);void clk_put(struct clk *clk);long clk_round_rate(struct clk *clk, unsigned long rate);int clk_set_rate(struct clk *clk, unsigned long rate);int clk_set_parent(struct clk *clk, struct clk *parent);struct clk *clk_get_parent(struct clk *clk);int clk_prepare(struct clk *clk);void clk_unprepare(struct clk *clk);

这些都是比较重要的api接口,主要是在device driver中调用来设置device的clk的。这部分的实现最终会调用到clk_ops中的回调函数来设置硬件并且会更新core中的clk链表。具体实现自行阅读源代码。
除了上面介绍的api,作为一个clk设备,它有可能会改变rate,那么作为device driver的一方需要获取到这个改变,并作出相应的响应,那么就可以通过通知功能的接口来实现,我们可以在感兴趣的clk上注册notifier_block,然后当该clk的rate发生了改变的时候会通过__clk_notify,通知到相应的回调函数,来做相应的处理。

int clk_notifier_register(struct clk *clk, struct notifier_block *nb);int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);

以上是device driver开发中可能会使用到的接口,接下来我们以clk_enbale为例做个简单介绍:

int clk_enable(struct clk *clk){    unsigned long flags;    int ret;    flags = clk_enable_lock();    ret = __clk_enable(clk);    clk_enable_unlock(flags);    return ret;}static int __clk_enable(struct clk *clk){    int ret = 0;    if (!clk)        return 0;    if (WARN_ON(clk->prepare_count == 0))        return -ESHUTDOWN;    if (clk->enable_count == 0) {        ret = __clk_enable(clk->parent);        if (ret)            return ret;        if (clk->ops->enable) {            ret = clk->ops->enable(clk->hw);            if (ret) {                __clk_disable(clk->parent);                return ret;            }        }    }    clk->enable_count++;    return 0;}

clk_enable会调用到__clk_enable函数,这个函数中会反复迭代调用自身来使能parent clk。并最后调用到了ops->enable回调函数。其他api自行阅读。

我们使用这些API的一般顺序为,通过clk_get获取到跟设备相关的struct clk结构体,接着再调用其他的api来针对它进行处理。

(2) 向下提供给clock driver注册的接口API
Clk framework向下提供了注册clk设备的api,主要是平台厂商实现自己的clk驱动时使用到的。
主要接口如下:

struct clk *clk_register(struct device *dev, struct clk_hw *hw);struct clk *__clk_register(struct device *dev, struct clk_hw *hw);void clk_unregister(struct clk *clk);

注意clk_register和__clk_register之间的区别,clk_register会自己申请struct clk结构体并对它进行初始化。而__clk_register是静态定义了一个struct clk的结构体,所以它不会再申请内存来存放struct clk结构体。
作为了一个clk设备,我们注册clk设备会建立clk之间的拓扑关系,但是除了这个以外,驱动还有一个重要的事情要做,那就是建立struct device和struct clk之间的关系,还记得上面讲到的clk_get接口吗,我们看一下它的完整api:

struct clk *clk_get(struct device *dev, const char *con_id);

可以看到其他驱动程序在获取并操作跟设备相关的clk时,会传入device结构和connectid来获取相应的struct clk结构,所以我们必须想办法来建立这种关系。其实这种关系是通过如下的api来建立的:

int clk_register_clkdev(struct clk *clk, const char *con_id, const char *dev_fmt, ...);

这个接口会建立一个struct clk_lookup结构体并加入的core的clocks链表中,这样每次调用clk_get的时候都会遍历这个链表来找到匹配的clk设备。我们看一下struct clk_lookup:

static LIST_HEAD(clocks);......struct clk_lookup {    struct list_head    node;    const char      *dev_id;    const char      *con_id;    struct clk      *clk;};

有关这部分的内容都在clkdev.c源文件中,读者可以自行研读。除了这种注册方式还有另外一种方法来建立这种关系,这种新方式是基于dts来建立关系的,也就是说必须内核中使能了CONFIG_OF才会编译进来,它的注册方式为:

int of_clk_add_provider(struct device_node *np, struct clk *(*clk_src_get)(struct of_phandle_args *args,void *data),void *data);

这种就是通过解析dts文件中属性的配置来获取device相关的clk设备的。具体参考代码。至于我们在驱动中使用哪种方式,还要看驱动是否支持dts来决定。

(3) debugfs的创建
debugfs的创建有两个函数,分别如下:

int __init clk_debug_init(void);int clk_debug_register(struct clk *clk);

第一个clk_debug_init函数是在系统启动时调用的,他会首先创建clk debugfs的入口。
而clk_debug_register则是在clk_register中会调用的,也就是说他是在注册clk设备的时候调用的,他会更新clk之间的拓扑关系,并更新debugfs。
(4) 若干clk通用设备实现
Clk framework为了简化clk设备的开发,也按照clk的不同特性实现了几个clk驱动模型,这样厂商可以根据自己clk的特点直接调用相应模型的注册函数就能快速实现一个clk驱动,而不必重复造轮子。当然这样的模型并不能包含所有的clk设备,一些厂商也会自己来实现clk驱动,而不套用相关模型。
一些模型api:

struct clk *clk_register_fixed_rate(struct device *dev, const char *name,        const char *parent_name, unsigned long flags,        unsigned long fixed_rate);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 *clk_register_divider(struct device *dev, const char *name,        const char *parent_name, unsigned long flags,        void __iomem *reg, u8 shift, u8 width,        u8 clk_divider_flags, spinlock_t *lock);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);struct clk *clk_register_fixed_factor(struct device *dev, const char *name,        const char *parent_name, unsigned long flags,        unsigned int mult, unsigned int div);struct clk *clk_register_composite(struct device *dev, const char *name,        const char **parent_names, int num_parents,        struct clk_hw *mux_hw, const struct clk_ops *mux_ops,        struct clk_hw *rate_hw, const struct clk_ops *rate_ops,        struct clk_hw *gate_hw, const struct clk_ops *gate_ops,        unsigned long flags);

如上等等。

三、 与device tree的关系
说起dts,就不得不在代码中指定相应的of_device_id,因为dts中定义的设备是通过这个结构来进行驱动和设备匹配的。

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.c

extern struct of_device_id __clk_of_table[];static const struct of_device_id __clk_of_table_sentinel    __used __section(__clk_of_table_end);

上面这一段需要借助内核编译的lds文件来解读,其中传入了参数给编译器来确定变量的存放位置。
_section(__clk_of_table)意思就是把该变量存入__clk_of_table段中。而在lds文件中可以看到该段的定义,并且该段是以__clk_of_table_end为结尾的。由上面的定义可以知道,在编译内核的时候,会把所有的__clk_of_table##name变量都保存在__clk_of_table中,并且__clk_of_table的最后是__clk_of_table_end来结束的。

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

从这段用来初始化驱动的函数可以看出来我们自己创建的clk驱动,需要先通过CLK_OF_DECLARE来定义相应的of_device_id,并且要把相应的驱动初始化函数func的地址传给data。这样在匹配到相应的设备时就会直接调用驱动初始化函数了。

四、 如何创建自己的clk设备
我们以全志的sunxi平台为例,它的clk驱动是在driver/clk/sunxi/目录下
在clk-sunxi.c中:

401 /* Matches for of_clk_init */402 static const __initconst struct of_device_id clk_match[] = {403     {.compatible = "allwinner,sun4i-osc-clk", .data = sunxi_osc_clk_setup,},404     {}405 };406 407 /* Matches for factors clocks */408 static const __initconst struct of_device_id clk_factors_match[] = {409     {.compatible = "allwinner,sun4i-pll1-clk", .data = &pll1_data,},410     {.compatible = "allwinner,sun4i-apb1-clk", .data = &apb1_data,},411     {}412 };413 414 /* Matches for divider clocks */415 static const __initconst struct of_device_id clk_div_match[] = {416     {.compatible = "allwinner,sun4i-axi-clk", .data = &axi_data,},417     {.compatible = "allwinner,sun4i-ahb-clk", .data = &ahb_data,},418     {.compatible = "allwinner,sun4i-apb0-clk", .data = &apb0_data,},419     {}420 };421 422 /* Matches for mux clocks */423 static const __initconst struct of_device_id clk_mux_match[] = {424     {.compatible = "allwinner,sun4i-cpu-clk", .data = &cpu_data,},425     {.compatible = "allwinner,sun4i-apb1-mux-clk", .data = &apb1_mux_data,},426     {}427 };428 429 /* Matches for gate clocks */430 static const __initconst struct of_device_id clk_gates_match[] = {431     {.compatible = "allwinner,sun4i-axi-gates-clk", .data = &axi_gates_data,},432     {.compatible = "allwinner,sun4i-ahb-gates-clk", .data = &ahb_gates_data,},433     {.compatible = "allwinner,sun4i-apb0-gates-clk", .data = &apb0_gates_data,},434     {.compatible = "allwinner,sun4i-apb1-gates-clk", .data = &apb1_gates_data,},435     {}436 };437 438 static void __init of_sunxi_table_clock_setup(const struct of_device_id *clk_match,439                           void *function)440 {441     struct device_node *np;442     const struct div_data *data;443     const struct of_device_id *match;444     void (*setup_function)(struct device_node *, const void *) = function;445 446     for_each_matching_node(np, clk_match) {447         match = of_match_node(clk_match, np);448         data = match->data;449         setup_function(np, data);450     }451 }452 453 void __init sunxi_init_clocks(void)454 {455     /* Register all the simple sunxi clocks on DT */456     of_clk_init(clk_match);457 458     /* Register factor clocks */459     of_sunxi_table_clock_setup(clk_factors_match, sunxi_factors_clk_setup);460 461     /* Register divider clocks */462     of_sunxi_table_clock_setup(clk_div_match, sunxi_divider_clk_setup);463 464     /* Register mux clocks */465     of_sunxi_table_clock_setup(clk_mux_match, sunxi_mux_clk_setup);466 467     /* Register gate clocks */468     of_sunxi_table_clock_setup(clk_gates_match, sunxi_gates_clk_setup);469 }

这段代码主要就是定义of_device_id以及相应的主初始化函数。我们通过grep来查一下sunxi_init_clocks在哪里有调用使用到。

经过查找,在kernel/arch/arm/mach-sunxi/sunxi.c中有调用:

98 static void __init sunxi_timer_init(void) 99 {100     sunxi_init_clocks();101     clocksource_of_init();102 }103 104 static void __init sunxi_dt_init(void)105 {106     sunxi_setup_restart();107 108     of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);109 }110 111 static const char * const sunxi_board_dt_compat[] = {112     "allwinner,sun4i-a10",113     "allwinner,sun5i-a13",114     NULL,115 };116 117 DT_MACHINE_START(SUNXI_DT, "Allwinner A1X (Device Tree)")118     .init_machine   = sunxi_dt_init,119     .map_io     = sunxi_map_io,120     .init_irq   = irqchip_init,121     .init_time  = sunxi_timer_init,122     .dt_compat  = sunxi_board_dt_compat,123 MACHINE_END

在sunxi_timer_init中有调用到sunxi_init_clocks函数来初始化clk驱动。

题外话
系统起来的时候会bringup运行到kernel_start,在这个函数中会逐一对系统资源进行初始化,它也会根据bootloader传入的参数来匹配machine,这里的machine也就是上面各个平台都会自己实现的部分,上面的两个宏定义DT_MACHINE_START和DT_MACHINE_END之间就是对machine的定义。可以看到machine也是通过dt_compat来进行匹配的。从上面的信息可以看到,这一套内核时同时兼容allwinner,sun4i-a10和allwinner,sun5i-a13两种类型的设备的。

0 0
原创粉丝点击