时钟子系统
框架结构
- 内核使用CCF(common clock framework)对时钟进行管理,linux时钟子系统框架结构如下图所示:
- 右侧为时钟的提供者clock provider,比如各种有/无源晶振
- 中间为CCF,将时钟与对应的设备匹配
- 左侧为时钟的使用者clock consumer,即各个设备
clock consumer
- clock子系统向clock comsumer提供一套通用API用于访问底层时钟,从而屏蔽底层硬件的差异,在进行设备驱动的编写时可以使用以下API函数对设备所需的时钟进行操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20struct clk *clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get(struct device *dev, const char *id);
int clk_prepare(struct clk *clk); //启动clock前的准备工作,可能会睡眠
void clk_unprepare(struct clk *clk); //停止clock之后的善后工作,可能会睡眠
int clk_enable(struct clk *clk); //启动clock,不会睡眠
void clk_disable(struct clk *clk); //停止clock,不会睡眠
/*clock的频率获取和设置*/
unsigned long clk_get_rate(struct clk *clk);
long clk_round_rate(struct clk *clk, unsigned long rate);
int clk_set_rate(struct clk *clk, unsigned long rate);
/*获取或者设置clock的parent*/
int clk_set_parent(struct clk *clk, struct clk *parent);
struct clk *clk_get_parent(struct clk *clk);
int clk_prepare_enable(struct clk *clk); //将preparea和enable组合起来
void clk_disable_unprepare(struct clk *clk);//将unprepare和disable组合起来 - 需要注意的是,在进行设备驱动编写时,如果需要对时钟进行操作,必须先使用
clk_get
或者devm_clk_get
函数获取对应的时钟,会返回一个struct clk *
结构体,即为一个clk对象,在使用其他API时均通过传入此clk对象对具体的clk进行操作。
CCF(common clock framework)
- CCF是linux时钟子系统的核心,将其抽象为一个
struct clk_core
,每一个注册的时钟设备都对应一个clk_core
。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
34
35
36
37
38
39struct clk_core {
const char *name;
const struct clk_ops *ops;
struct clk_hw *hw;
struct module *owner;
struct device *dev;
struct device_node *of_node;
struct clk_core *parent;
struct clk_parent_map *parents;
u8 num_parents;
u8 new_parent_index;
unsigned long rate;
unsigned long req_rate;
unsigned long new_rate;
struct clk_core *new_parent;
struct clk_core *new_child;
unsigned long flags;
bool orphan;
bool rpm_enabled;
bool need_sync;
bool boot_enabled;
unsigned int enable_count;
unsigned int prepare_count;
unsigned int protect_count;
unsigned long min_rate;
unsigned long max_rate;
unsigned long accuracy;
int phase;
struct clk_duty duty;
struct hlist_head children;
struct hlist_node child_node;
struct hlist_head clks;
unsigned int notifier_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct hlist_node debug_node;
#endif
struct kref ref;
};
clock provider
- 时钟子系统将所有的clock_provider分为6类
- fixed_clk:固定频率时钟
- fixed_factor_clk:固定分频系数时钟
- devider:分频器
- gate:门,控制时钟使能/失能
- mux:多路选择器,用于选择时钟源
- composite:从多个时钟中选一个父时钟,并对其进行分频
数据结构
将所有的clock_provider中共有的硬件特性抽象出来,使用
struct clk_hw
描述一个具体的硬件时钟1
2
3
4
5struct clk_hw {
struct clk_core *core; //和clk_hw互相包含 point back
struct clk *clk; //指向consummer中的clk实例,用于 call clk API
const struct clk_init_data *init; //contains the init data shared with the common clock framework
};struct clk_init_data
描述时钟的初始化数据1
2
3
4
5
6
7
8
9
10struct clk_init_data {
const char *name;
const struct clk_ops *ops; //操作函数集合 consummer调用的API函数实际上就是此函数集的抽象
/* Only one of the following three should be assigned */
const char * const *parent_names;
const struct clk_parent_data *parent_data;
const struct clk_hw **parent_hws;
u8 num_parents;
unsigned long flags;
};struct clk_ops
时钟操作函数集合,定义在kernel/include/linux/clk-provider.h
中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
29struct 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);
int (*save_context)(struct clk_hw *hw);
void (*restore_context)(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);
int (*get_duty_cycle)(struct clk_hw *hw, struct clk_duty *duty);
int (*set_duty_cycle)(struct clk_hw *hw, struct clk_duty *duty);
int (*init)(struct clk_hw *hw);
void (*terminate)(struct clk_hw *hw);
void (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
int (*pre_rate_change)(struct clk_hw *hw, unsigned long rate, unsigned long new_rate);
int (*post_rate_change)(struct clk_hw *hw, unsigned long old_rate,unsigned long rate);
};- 常用的函数,需要在clock驱动中实现
function description recalc_rate 通过查询硬件,重新计算此时钟的速率。可选,但建议——如果未设置此操作,则时钟速率初始化为0 round_rate 给定目标速率作为输入,返回时钟实际支持的最接近速率 set_rate 更改此时钟的速率,请求的速率由第二个参数指定,该参数通常应该是调用.round_rate返回;第三个参数给出了父速率,这对大多数.set_rate实现有帮助;成功返回0,否则返回-EERROR enable … disable … 各个数据结构之间的关系如下图所示:
注册方式
- fixed rate clock
- 这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent,是最简单的一类clock。可以直接通过 DTS 配置的方式支持。也可以通过接口,可以直接注册 fixed rate clock,如下:
1
2
3
4CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
- 这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent,是最简单的一类clock。可以直接通过 DTS 配置的方式支持。也可以通过接口,可以直接注册 fixed rate clock,如下:
- gate clock
- 这类clock只能选择开关,通过提供
.enable
和.disable
回调函数实现,可通过下面接口进行注册:1
2
3
4struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx, u8 clk_gate_flags,
spinlock_t *lock);
- 这类clock只能选择开关,通过提供
- divider clock
- 这类clock可以设置分频值,因此会提供
.recal_rate
、.set_rate
、.round_date
,可通过下面两个接口注册1
2
3
4
5
6
7
8
9struct clk *clk_register_divider(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width, u8 clk_divider_flags,
spinlock_t *lock);
struct clk *clk_register_divider_table(struct device *dev, const char *name,
const char *parent_name, unsigned long flags, void __iomem *reg,
u8 shift, u8 width, u8 clk_divider_flags,
const struct clk_div_table *table, spinlock_t *lock);
- 这类clock可以设置分频值,因此会提供
- mux clock
- 这类clock可以设置多个parent,实现
.get_parent
、.set_parent
、.recal_rate
回调函数,可通过下面两个接口注册:1
2
3
4
5
6
7
8
9struct 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);
struct clk *clk_register_mux_table(struct device *dev, const char *name,
const char **parent_names, u8 num_parents, unsigned long flags,
void __iomem *reg, u8 shift, u32 mask, u8 clk_mux_flags,
u32 *table, spinlock_t *lock);
- 这类clock可以设置多个parent,实现
- fixed factor clock
- 这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供
.recalc_rate
、.set_rate
、.round_rate
等回调。可通过下面接口注册:1
2
3struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned int mult, unsigned int div);
- 这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。由于parent clock的频率可以改变,因而fix factor clock也可该改变频率,因此也会提供
- composite clock
- 其余各类clock的组合
1
2
3
4
5
6struct clk *clk_register_composite(struct device *dev, const char *name,
const char **parent_names, int num_parents,
struct clk_hw *mux_hw, const struct clk_ops *mux_ops,
struct clk_hw *rate_hw, const struct clk_ops *rate_ops,
struct clk_hw *gate_hw, const struct clk_ops *gate_ops,
unsigned long flags);
- 其余各类clock的组合
时钟子系统http://example.com/2023/04/09/时钟子系统/