通用时钟框架 【ChatGPT】

发布时间 2023-12-10 20:09:28作者: 摩斯电码

Common Clk Framework

作者 Mike Turquette mturquette@ti.com

本文旨在解释通用时钟框架的细节,以及如何将平台移植到该框架上。它尚未详细解释了include/linux/clk.h中的时钟API,但也许将来会包括这些信息。

介绍和接口分离

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

接口本身分为两半,每一半都屏蔽了其对应部分的细节。首先是通用的struct clk定义,它统一了传统上在各种平台上重复出现的框架级记账和基础设施。其次是在drivers/clk/clk.c中定义的clk.h API的通用实现。最后是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;
        ...
};

上述成员构成了时钟树拓扑的核心。时钟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);
};

硬件时钟实现

通用struct clk_core的强大之处在于其抽象了struct clk的细节,同时也抽象了硬件特定部分,反之亦然。举例来说,考虑一下在drivers/clk/clk-gate.c中的简单可门控时钟实现:

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

struct clk_gate包含了struct clk_hw hw以及关于控制该时钟门控的寄存器和位的硬件特定知识。这里不需要任何有关时钟拓扑或记账的信息,比如enable_count或notifier_count。所有这些都由通用框架代码和struct clk_core处理。

让我们从驱动程序代码中启用这个时钟:

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);
        [解析为...]
                clk_gate_enable(hw);
                [解析为to_clk_gate(hw)的struct clk gate]
                        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)

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

支持自己的时钟硬件

当实现对一种新类型的时钟的支持时,只需要包含以下头文件:

#include <linux/clk-provider.h>

要为您的平台构建一个时钟硬件结构,您必须定义以下内容:

struct clk_foo {
        struct clk_hw hw;
        ... 这里放置硬件特定数据 ...
};

为了利用您的数据,您需要为您的时钟支持有效的操作:

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

        ... 在foo 上执行操作 ...

        return 0;
};

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

image

1(1,2) 无论是round_rate还是determine_rate都是必需的。

最后,使用特定于硬件的注册函数在运行时注册您的时钟。此函数只需填充struct clk_foo的数据,然后通过调用将常见的struct 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,它也被设计为允许在原子上下文中使用。然而,除非您想在核心中使用使能状态的信息做其他事情,否则在核心中持有使能锁并没有什么意义。否则,查看时钟是否已启用是对启用状态的一次性读取,该状态在函数返回后很容易发生变化,因为锁已释放。因此,使用此API的用户需要处理将状态的读取与其用于的任何内容进行同步,以确保在此期间启用状态不会更改。

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

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

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

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

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