通用时钟框架

作者:

Mike Turquette <mturquette@ti.com>

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

简介和接口拆分

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

接口本身分为两半,每一半都屏蔽了另一半的细节。 首先是 struct clk 的通用定义,它统一了框架级别的记账和基础设施,这些记账和基础设施传统上在各种平台中重复。 第二个是 clk.h api 的通用实现,在 drivers/clk/clk.c 中定义。 最后是 struct clk_ops,其操作由 clk api 实现调用。

接口的后半部分由使用 struct clk_ops 注册的特定于硬件的回调以及对特定时钟建模所需的相应特定于硬件的结构组成。 对于本文档的其余部分,对 struct clk_ops 中回调的任何引用,例如 .enable 或 .set_rate,都意味着该代码的特定于硬件的实现。 同样,对 struct clk_foo 的引用可以方便地简写为假设的“foo”硬件的特定于硬件位的实现。

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

通用数据结构和 api

下面是 drivers/clk/clk.c 中的通用 struct 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 本身定义了几个面向驱动程序的函数,这些函数在 struct clk 上运行。 该 api 记录在 include/linux/clk.h 中。

利用通用 struct clk_core 的平台和设备使用 struct clk_core 中的 struct 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 实现

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

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

struct clk_gate 包含 struct clk_hw hw 以及关于哪个寄存器和位控制此 clk 门控的特定于硬件的知识。 这里不需要任何关于时钟拓扑或记帐的信息,例如 enable_count 或 notifier_count。 这一切都由通用框架代码和 struct 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

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

clk_register(...)

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

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

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

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

tp_printk trace_event=clk:clk_disable

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

锁定

通用时钟框架使用两个全局锁,prepare 锁和 enable 锁。

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

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

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

这实际上从锁定的角度将操作分为两组。

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

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

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