简介
这里讲的时钟是给soc各组件提供时钟的树状框架,并不是内核使用的时间,和其他模块一样,clk也有框架,用以适配不同的平台。适配层之上是客户代码和接口,也就是各模块(如需要时钟信号的外设,usb等)的驱动。适配层之下是具体的soc平台的时钟操作细节。
内核中另外一个具有类似树状框架特点的是regulator框架。对比regulator框架,clk框架不确定性更大,内核中仅仅提供了少数的适配规范,struct clk都是各平台自己的clk驱动实现。在3.4.5内核里基本上还是这种状态,但是新的3.10内核很多soc的clk驱动已经改为common clock framework(CCF)。各平台采用CCF的的clock驱动都统一在drivers/clk目录。
common clock framework由Mike Turquette在2012.5引入kernel 3.4。
CCF相关的内核配置宏
1 | CONFIG_COMMON_CLK |
CCF core
CCF core主要代码在drivers/clk/clk.c里。主要维护时钟树
以及操作,互斥锁
,通知链
。
主要结构体定义
只有定义了CONFIG_COMMON_CLK才会有CCF框架。
1 | //include/linux/clk- private.h |
时钟的基本种类
CCF将soc抽象出5个基本种类,可以快捷的定义
固定速率 | 不能设置的时钟 |
---|---|
门时钟 | 和上级时钟同频,只能打开和关闭操作 |
MUX | 多选一 |
固定倍频 | 上级时钟的频率有固定倍频或者分频,不能关闭 |
分频 | 上级时钟的频率分频,可以选择不同的分频比 |
5种时钟类型都有不同的注册函数和结构体,如MUX时钟
结构体毫无例外是封装包含struct clk_hw,然后加上该种类的特性的成员。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25struct clk_mux {
struct clk_hw hw;
void __iomem *reg;
u32 *table;
u32 mask;
u8 shift;
u8 flags;
spinlock_t *lock;
};
struct clk *clk_register_mux(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_mux_flags, spinlock_t *lock);
一般SOC都有大量的时钟,用数组变量定义批量时钟是最方便的,但是内核不推荐这样做。新开发的驱动用clk_init_data和clk_register()定义。
时钟标准驱动层
CCF提供的API,实际是调用了clk_ops的实际操作函数,这些函数是按照5种基本的时钟分类来的。
值得注意的是,一般的驱动框架,比如网卡,usb,regulator,都是内核的core层提供管理逻辑,由芯片驱动提供实际的操作。但是clk的实际操作是由CCF API完成,而不是芯片驱动完成的。之所以能够做到这一点,是因为芯片的时钟操作方法比较类似。soc平台注册时钟的时候,只需要提供操作的信息,就可以由CCF的统一操作函数对这些信息进行操作。
以MUX的clk_set_parent分析为例
1 | clk_set_parent->__clk_set_parent->clk->(ops->set_parent) |
ops->set_parent的定义如下,在注册时钟的时候就设置好了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34const struct clk_ops clk_mux_ops = {
.get_parent = clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
};
static int clk_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_mux *mux = to_clk_mux(hw);
u32 val;
unsigned long flags = 0;
if (mux->table)
index = mux->table[index];
else {
if (mux->flags & CLK_MUX_INDEX_BIT)
index = (1 << ffs(index));
if (mux->flags & CLK_MUX_INDEX_ONE)
index++;
}
if (mux->lock)
spin_lock_irqsave(mux->lock, flags);
val = readl(mux->reg);
val &= ~(mux->mask << mux->shift);
val |= index << mux->shift;
writel(val, mux->reg);
if (mux->lock)
spin_unlock_irqrestore(mux->lock, flags);
return 0;
}
可见,平台代码并没有提供实际的ops,只是提供table,bit和reg等信息就可以了。CCF的ops可以直接调用writel操作硬件。
驱动样例分析
准备5类时钟信息
每个soc有很多时钟,按照CCF的5个种类分开定义.
1 | struct samsung_mux_clock { |
参考
1 | MUX(none, "mout_mpll_fout", mout_mpll_fout_p, PLL_DIV2_SEL, 4, 1), |
实际上就是利用宏简化赋值代码。mout_mpll_fout展开如下
1 | struct samsung_mux_clock –》 |
结合时钟标准驱动层int clk_set_parent(struct clk clk, struct clk parent)来看。对mout_mpll_fout设置mux的方法分为以下几个步骤:
- 将本clk和父clk为参数输入clk_set_parent
- 用for循环在本clk的parents成员数组查找指针和入参clk *parent相等的。返回数组的index
- 找到偏移为PLL_DIV2_SEL的寄存器,将index左移4bit设置为1就可以。
从上面可以看出,定义clk的时候,父时钟的顺序必须和寄存器设置的顺序匹配才可以。不支持这种规律的芯片,是不能用CCF的。
注册5类时钟
1 | void __init exynos5250_clk_init(struct device_node *np) |
准备非5类时钟信息
出了标准的5类时钟类型,不标准的时钟类型需要单独准备clk_init_data init;
注册非5类时钟
1 | apll = samsung_clk_register_pll35xx("fout_apll", "fin_pll", |
clk api的使用方法
和regulator框架类似,首先调用clk_get()得到struct clk ,然后将struct clk 作为入参调用CCF提供的API,如int clk_prepare(struct clk *clk)。