通用时钟框架

作者:

Mike Turquette <mturquette@ti.com>

本文档旨在解释通用时钟框架的细节,以及如何将平台移植到此框架。它还不是 include/linux/clk.h 中时钟 api 的详细说明,但也许有一天它会包含这些信息。

简介和接口拆分

通用时钟框架是用于控制当今各种设备上可用的时钟节点的接口。这可能以时钟门控、速率调整、多路复用或其他操作的形式出现。此框架通过 CONFIG_COMMON_CLK 选项启用。

接口本身分为两部分,每一部分都屏蔽了其对应部分的细节。首先是结构体 clk 的通用定义,它统一了传统上在各种平台之间重复的框架级核算和基础设施。其次是在 drivers/clk/clk.c 中定义的 clk.h api 的通用实现。最后是结构体 clk_ops,其操作由 clk api 实现调用。

接口的第二部分由结构体 clk_ops 注册的特定于硬件的回调以及对特定时钟建模所需的相应特定于硬件的结构组成。对于本文档的其余部分,对结构体 clk_ops 中回调的任何引用,例如 .enable 或 .set_rate,都表示该代码的特定于硬件的实现。同样,对结构体 clk_foo 的引用是表示假设的 “foo” 硬件的特定于硬件的位的实现的简便速记。

将此接口的两个部分联系在一起的是结构体 clk_hw,它在结构体 clk_foo 中定义,并在结构体 clk_core 中指向。这允许在通用时钟接口的两个离散部分之间轻松导航。

通用数据结构和 api

下面是 drivers/clk/clk.c 中通用结构体 clk_core 的定义,为简洁起见进行了修改

struct clk_core {
        const char              *name;
        const struct clk_ops    *ops;
        struct clk_hw           *hw;
        struct module           *owner;
        struct clk_core         *parent;
        const char              **parent_names;
        struct clk_core         **parents;
        u8                      num_parents;
        u8                      new_parent_index;
        ...
};

上面的成员构成了 clk 树拓扑的核心。clk api 本身定义了几个面向驱动程序的函数,这些函数在结构体 clk 上运行。该 api 在 include/linux/clk.h 中记录。

利用通用结构体 clk_core 的平台和设备使用结构体 clk_core 中的结构体 clk_ops 指针来执行 clk-provider.h 中定义的操作的特定于硬件的部分

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 rate,
                                        unsigned long *parent_rate);
        int             (*determine_rate)(struct clk_hw *hw,
                                          struct clk_rate_request *req);
        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 rate,
                                    unsigned long parent_rate);
        int             (*set_rate_and_parent)(struct clk_hw *hw,
                                    unsigned long rate,
                                    unsigned long parent_rate,
                                    u8 index);
        unsigned long   (*recalc_accuracy)(struct clk_hw *hw,
                                        unsigned long parent_accuracy);
        int             (*get_phase)(struct clk_hw *hw);
        int             (*set_phase)(struct clk_hw *hw, int degrees);
        void            (*init)(struct clk_hw *hw);
        void            (*debug_init)(struct clk_hw *hw,
                                      struct dentry *dentry);
};

硬件 clk 实现

通用结构体 clk_core 的优势来自其 .ops 和 .hw 指针,它们将结构体 clk 的细节从特定于硬件的位中抽象出来,反之亦然。为了说明,请考虑 drivers/clk/clk-gate.c 中简单的可门控 clk 实现

struct clk_gate {
        struct clk_hw   hw;
        void __iomem    *reg;
        u8              bit_idx;
        ...
};

结构体 clk_gate 包含结构体 clk_hw hw 以及关于哪个寄存器和位控制此 clk 的门控的特定于硬件的知识。这里不需要关于时钟拓扑或核算的任何信息,例如 enable_count 或 notifier_count。所有这些都由通用框架代码和结构体 clk_core 处理。

让我们逐步执行从驱动程序代码启用此 clk 的过程

struct clk *clk;
clk = clk_get(NULL, "my_gateable_clk");

clk_prepare(clk);
clk_enable(clk);

clk_enable 的调用图非常简单

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_gate_set_bit 的定义

static void clk_gate_set_bit(struct clk_gate *gate)
{
        u32 reg;

        reg = __raw_readl(gate->reg);
        reg |= BIT(gate->bit_idx);
        writel(reg, gate->reg);
}

请注意,to_clk_gate 定义为

#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw)

这种抽象模式用于每个时钟硬件表示。

支持您自己的 clk 硬件

在实现对新型时钟的支持时,只需包含以下头文件

#include <linux/clk-provider.h>

要为您的平台构建 clk 硬件结构,您必须定义以下内容

struct clk_foo {
        struct clk_hw hw;
        ... hardware specific data goes here ...
};

要利用您的数据,您需要支持 clk 的有效操作

struct clk_ops clk_foo_ops = {
        .enable         = &clk_foo_enable,
        .disable        = &clk_foo_disable,
};

使用 container_of 实现以上函数

#define to_clk_foo(_hw) container_of(_hw, struct clk_foo, hw)

int clk_foo_enable(struct clk_hw *hw)
{
        struct clk_foo *foo;

        foo = to_clk_foo(hw);

        ... perform magic on foo ...

        return 0;
};

下面是一个矩阵,详细说明了基于时钟的硬件功能哪些 clk_ops 是强制性的。标记为“y”的单元格表示强制性,标记为“n”的单元格表示包含该回调无效或不必要。空单元格是可选的,或者必须根据具体情况进行评估。

时钟硬件特性

门控

更改速率

单亲

多路复用器

.prepare

.unprepare

.enable

y

.disable

y

.is_enabled

y

.recalc_rate

y

.round_rate

y [1]

.determine_rate

y [1]

.set_rate

y

.set_parent

n

y

n

.get_parent

n

y

n

.recalc_accuracy

.init

最后,使用特定于硬件的注册函数在运行时注册您的时钟。此函数只需填充结构体 clk_foo 的数据,然后通过调用将通用结构体 clk 参数传递给框架

clk_register(...)

有关示例,请参见 drivers/clk/clk-*.c 中的基本时钟类型。

禁用未使用的时钟的时钟门控

有时在开发过程中,能够绕过未使用的时钟的默认禁用会很有用。例如,如果驱动程序没有正确启用时钟,而是依赖于引导加载程序启动的时钟,则绕过禁用意味着驱动程序将在问题解决时保持功能。

您可以通过使用这些参数启动内核来查看哪些时钟已被禁用

tp_printk trace_event=clk:clk_disable

要绕过此禁用,请在内核的 bootargs 中包含“clk_ignore_unused”。

锁定

通用时钟框架使用两个全局锁,即准备锁和启用锁。

启用锁是一个自旋锁,在对 .enable、.disable 操作的调用中保持。因此,不允许这些操作休眠,并且允许在原子上下文中使用对 clk_enable()clk_disable() API 函数的调用。

对于 clk_is_enabled() API,它也被设计为允许在原子上下文中使用。但是,除非您想在持有该锁的同时使用启用状态的信息来做其他事情,否则在核心中持有启用锁实际上没有任何意义。否则,查看 clk 是否启用是对启用状态的单次读取,这很容易在函数返回后更改,因为该锁已释放。因此,此 API 的用户需要处理启用状态的读取与他们使用它的任何内容的同步,以确保启用状态在此期间不会更改。

准备锁是一个互斥锁,在对所有其他操作的调用中保持。所有这些操作都允许休眠,并且不允许在原子上下文中使用对相应 API 函数的调用。

从锁定的角度来看,这有效地将操作分为两组。

驱动程序无需手动保护同一组操作之间共享的资源,无论这些资源是否被多个时钟共享。但是,对两组操作之间共享的资源的访问需要由驱动程序进行保护。此类资源的一个示例是控制时钟频率和时钟启用/禁用状态的寄存器。

时钟框架是可重入的,即允许驱动程序从其时钟操作的实现中调用时钟框架函数。例如,这可能会导致一个时钟的 .set_rate 操作在另一个时钟的 .set_rate 操作中被调用。这种情况必须在驱动程序实现中考虑,但在这种情况下,代码流程通常由驱动程序控制。

请注意,当通用时钟框架之外的代码需要访问时钟操作使用的资源时,也必须考虑锁定。这不属于本文档的范围。