Linux Clock Framework

Table of Contents

Clock关系图

在Linux Clock的代码中,经常会有以下几个结构体,下面的图展示了这几种结构体之间的关系


struct clk

这个结构体是对外的接口,驱动程序都会直接获取这个结构体,并作为一个handle来完成对clock的各种操作.
该结构体内容仅对linux clock关系系统内部可见

struct clk_core

这个结构体作为内部管理clock的核心数据,负责处理Clock之间的关系,child,parent都会通过此结构体来维护他们之间的联系。
该结构体内容仅对linux clock关系系统内部可见

struct clk_hw

这个结构体作为Clock Provider的核心数据,Clock的驱动程序一般通过内嵌的方式来使用。Clock Framework在调用具体的Clock硬件时也会通过这个数据结构来做对应的操作。

Clock操作步骤

Clock Provider编译期初始化

通过CLK_OF_DECLARE来定义Clock是比较常用的注册手段,通过这个宏来注册时,系统会通过特定的链接脚本来将对应的数据链接到特定位址,并在运行时找到他们。
include/asm-generic/vmlinux.lds.h

#define ___OF_TABLE(cfg, name)  _OF_TABLE_##cfg(name)
#define __OF_TABLE(cfg, name)   ___OF_TABLE(cfg, name)
#define OF_TABLE(cfg, name)     __OF_TABLE(IS_ENABLED(cfg), name)
#define _OF_TABLE_0(name)
#define _OF_TABLE_1(name)                               \
    . = ALIGN(8);                                       \
    VMLINUX_SYMBOL(__##name##_of_table) = .;    \
    KEEP(*(__##name##_of_table))                        \
        KEEP(*(__##name##_of_table_end))

#define TIMER_OF_TABLES()       OF_TABLE(CONFIG_TIMER_OF, timer)
#define IRQCHIP_OF_MATCH_TABLE() OF_TABLE(CONFIG_IRQCHIP, irqchip)
#define CLK_OF_TABLES()         OF_TABLE(CONFIG_COMMON_CLK, clk)
#define IOMMU_OF_TABLES()       OF_TABLE(CONFIG_OF_IOMMU, iommu)
#define RESERVEDMEM_OF_TABLES() OF_TABLE(CONFIG_OF_RESERVED_MEM, reservedmem)
#define CPU_METHOD_OF_TABLES()  OF_TABLE(CONFIG_SMP, cpu_method)
#define CPUIDLE_METHOD_OF_TABLES() OF_TABLE(CONFIG_CPU_IDLE, cpuidle_method)
  1. CLK_OF_TABLES这个宏依赖于CONFIG_COMMON_CLK, 当这个配置选上之后,最终将会展开为_OF_TABLE_1
  2. 最终会生成symbol ,在头文件中有定义
  3. 对应的,在编译期生成对应的of_device_id数组:

    /*
     * Struct used for matching a device
     */
    struct of_device_id {
        char        name[32];
        char        type[32];
        char        compatible[128];
        const void *data;
    };
    

初始化

of_clk_init

调用路径: start_kernel->time_init->of_clk_init
功能:

  • 遍历节点,准备待初始化的时钟数据

    遍历device-tree中的所由节点,并将符合compatible属性的节点放入生成clock_provider对象,最后放入的链表中。

  • 遍历所由的clk_provider节点,调用初始化函数,
    parent_ready
    这个函数用来检查对应clock的parents是否准备就绪。如果获取clock时出现错误,这个函数就会认为clock已经准备好,所以如果dts配置错误,获取驱动返回错误码(-EPROBE_DEFER除外),系统都会认为对应的clock已经准备好
    force
    这个变量用来在初始化时保证系统会调用到那些parents没有准备好的clock初始化函数。
  • 处理Assigned clock parents and rates
    assigned-clock
    对这些clock设置parent,和rate
    assigned-clock-parents
    这个用来对指定的clock配置parent
    assigned-clock-rates
    对应clock的默认频率

Clock管理

Linux内核中区分clock provider以及clock
provider用来提供获取clock的功能,获取clock都需要通过provider来获得

provider

Linux系统通过两个全局的列表来管理clock provider,所有获取clock的操作都需要从这两个链表中获取。

  • of_clk_providers列表

    这个列表一般在clock初始化的时候进行初始化(也可以在任何位址通过of_clk_add_hw_provider, of_clk_add_provider)来插入。
    由于匹配数据中包含了device_node节点,所以可以从device-tree node中进行精准匹配,并且可以指定参数,来获取到不同的clock.

  • clocks列表

    kernel source for clocks list
    这个列表通过struct clk_lookup结构体来管理,通过clkdev_alloc等API来进行添加clock

clks

通过clock provider既可获得clock,linux中将clock放入全局的列表中进行管理。
当通过debugfs节点/sys/kernel/debug/clk/clk_summary和/sys/kernel/debug/clk/clk_dump查看clock信息时就是从这个地方来获取的clock信息。

获取clock

  • device函数
    clk_get

    struct clk *clk_get(struct device *dev, const char *id);
    int __must_check clk_bulk_get(struct device *dev, int num_clks,
                struct clk_bulk_data *clks);
    int __must_check devm_clk_bulk_get(struct device *dev, int num_clks,
                    struct clk_bulk_data *clks);
    struct clk *devm_clk_get(struct device *dev, const char *id);
    struct clk *devm_get_clk_from_child(struct device *dev,
                    struct device_node *np, const char *con_id);
    

    上述API的入口都是clk_get,devm和bulk都是对clk_get的简单封装,当系统获取clock时,会从两个地方获取时钟,分别为:

    1. 通过of系列函数从of_clk_providers列表获取
      当系统可以从of_clk_providers匹配到clock时,就不会再尝试从全局的clocks列表来获取clock了。
    2. 全局的clocks列表,通过clock名字以及dev_id进行匹配,具体的匹配逻辑细节可以从 kernel_src:drivers/clk/clkdev.c#clk_find来查看
  • of函数
    of_clk_*

    struct clk *of_clk_get(struct device_node *np, int index);
    struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
    struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);
    

clk_prepare

此函数内部会获取全局的mutex lock (可重入)
kernel source for clk_prepare_lock
Clock在开启,关闭时可能会需要一定的准备条件,并且可能需要等待硬件状态稳定,不能立刻生效。所以Linux内核中提供了clk_prepare函数来处理这种状况。
例如:PLL在开启,切换频率时,需要等待时钟重新锁定1 , 2。这个过程通常需要一段时间,因此驱动在快速开启时钟时,不适合进行pll的lock操作。因此pll的开启操作一般在prepare阶段完成。
kernel source for drivers/clk/imx/clk-frac-pll.c
对于scmi的clock,系统也是一样具有类似的问题,由于scmi需要和其他的异构核或者firmware进行交互,耗时比较长. 因此开关clock的操作都在prepare/unprepare中完成,并且没有提供enable/disable的接口。
kernel source for scmi_clk_ops
参考资料: 3

clk_enable

这个很好理解,通过这个函数完成实际的开关操作。
需要注意的是,这个函数在调用时会获取一把全局的spinlock enable_lock, (此lock已做了重入支持)
kernel source for clk_enable_lock
call_graph

clk_enable(clk);
    clk->ops->enable(clk->hw);
    [resolves to...]
        clk_gate_enable(hw);
        [resolves struct clk gate with to_clk_gate(hw)]
            clk_gate_set_bit(gate);

clk_set_rate

kernel source for clk_set_rate
这个也很容易理解,设置频率,但是实现比较复杂,因为需要处理parent关系,并且针对多parent的情况也需要可以正常处理。
call_graph

clk_set_rate(clk, rate);
    clk_calc_new_rates(core, rate);
        clk->ops->determine_rate
        clk->ops->round_rate
        clk_calc_new_rates(parent, rate); // parent clock
  • determine_rate
    这个接口函数是最优先使用的ops,可以处理多parent的情况,mux lock中就通过clk_mux_determine_rate来处理多parent的情况。

Footnotes:

Contact me via :)
虚怀乃若谷,水深则流缓。