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"的单元格意味着包括该回调要么无效要么不必要。空单元格要么是可选的,要么必须根据具体情况进行评估。
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操作。驱动程序的实现必须考虑到这种情况,但在这种情况下,代码流通常由驱动程序控制。
请注意,当超出通用时钟框架的代码需要访问时钟操作使用的资源时,还必须考虑锁定。这超出了本文档的范围。