系统的时钟结构
低频时钟
低频的作用是给整个系统提供一个基础的时钟源,低频时钟通常有四种,HSE(外部高配晶振)、HSI(内部高配晶振)、LSE(外部低频晶振)、LSI(内部低频晶振)。

锁相环
锁相环也叫PLL,作用是将低频时钟倍频到高频、经过PLL的时钟通常会达到几百兆或者1G以上。

分频器
分频器也叫DIV/PSC,作用是将时钟进行分配,得到频率不一样的时钟。

时钟复用器
时钟复用器也叫MUX,作用是选择不同的时钟源

门控
门控也叫GATE,作用是控制时钟的开关

内核CLK对象
clk_hw
c
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
struct clk_hw 是从 struct clk 遍历到对应硬件特定结构的句柄。clk_hw通常嵌入到一个自定义的时钟结构体里面。
clk_init_data
c
struct clk_init_data {
const char *name;
const struct clk_ops *ops;
/* 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;
};
- name:时钟名字
- ops:时钟操作函数
- parent_names:父时钟名字
- num_parents:有多少个父时钟
- flags:标志位
clk_ops
时钟操作函数,包括读取频率,设置频率,使能时钟等操作
c
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);
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);
};
- enable:使能时钟
- disable:不使能时钟
- is_enabled:查询时钟是否已经使能
- recalc_rate:重新计算当前时钟的实际输出频率
- round_rate:四舍五入:给定一个目标频率和父时钟频率,返回本时钟实际能实现的最近频率(不改硬件,只查询)。
- determine_rate:round_rate 的升级版,更灵活。传入 struct clk_rate_request(含目标频率、父时钟频率、可选父时钟索引),驱动可以同时建议改父时钟来达到更精确的频率。
- set_rate:设置时钟频率
- set_parent:选择父时钟
- get_parent:获取父时钟
clk flags
可以通过设置标志位来识别该时钟的特性
c
#define CLK_SET_RATE_GATE BIT(0) /* must be gated across rate change */
#define CLK_SET_PARENT_GATE BIT(1) /* must be gated across re-parent */
#define CLK_SET_RATE_PARENT BIT(2) /* propagate rate change up one level */
#define CLK_IGNORE_UNUSED BIT(3) /* do not gate even if unused */
/* unused */
/* unused */
#define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */
#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */
#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */
#define CLK_RECALC_NEW_RATES BIT(9) /* recalc rates after notifications */
#define CLK_SET_RATE_UNGATE BIT(10) /* clock needs to run to set rate */
#define CLK_IS_CRITICAL BIT(11) /* do not gate, ever */
/* parents need enable during gate/ungate, set rate and re-parent */
#define CLK_OPS_PARENT_ENABLE BIT(12)
/* duty cycle call may be forwarded to the parent clock */
#define CLK_DUTY_CYCLE_PARENT BIT(13)

时钟初始化宏
内核定义了很多的时钟初始化宏,可以帮助驱动开发者快速的初始化一个时钟,驱动开发者需要根据系统的时钟框架选用不同的初始化宏。
c
#define CLK_HW_INIT(_name, _parent, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_names = (const char *[]) { _parent }, \
.num_parents = 1, \
.ops = _ops, \
})
#define CLK_HW_INIT_HW(_name, _parent, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_hws = (const struct clk_hw*[]) { _parent }, \
.num_parents = 1, \
.ops = _ops, \
})
/*
* This macro is intended for drivers to be able to share the otherwise
* individual struct clk_hw[] compound literals created by the compiler
* when using CLK_HW_INIT_HW. It does NOT support multiple parents.
*/
#define CLK_HW_INIT_HWS(_name, _parent, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_hws = _parent, \
.num_parents = 1, \
.ops = _ops, \
})
#define CLK_HW_INIT_FW_NAME(_name, _parent, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_data = (const struct clk_parent_data[]) { \
{ .fw_name = _parent }, \
}, \
.num_parents = 1, \
.ops = _ops, \
})
#define CLK_HW_INIT_PARENTS(_name, _parents, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_names = _parents, \
.num_parents = ARRAY_SIZE(_parents), \
.ops = _ops, \
})
#define CLK_HW_INIT_PARENTS_HW(_name, _parents, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_hws = _parents, \
.num_parents = ARRAY_SIZE(_parents), \
.ops = _ops, \
})
#define CLK_HW_INIT_PARENTS_DATA(_name, _parents, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_data = _parents, \
.num_parents = ARRAY_SIZE(_parents), \
.ops = _ops, \
})
#define CLK_HW_INIT_NO_PARENT(_name, _ops, _flags) \
(&(struct clk_init_data) { \
.flags = _flags, \
.name = _name, \
.parent_names = NULL, \
.num_parents = 0, \
.ops = _ops, \
})
#define CLK_FIXED_FACTOR(_struct, _name, _parent, \
_div, _mult, _flags) \
struct clk_fixed_factor _struct = { \
.div = _div, \
.mult = _mult, \
.hw.init = CLK_HW_INIT(_name, \
_parent, \
&clk_fixed_factor_ops, \
_flags), \
}
#define CLK_FIXED_FACTOR_HW(_struct, _name, _parent, \
_div, _mult, _flags) \
struct clk_fixed_factor _struct = { \
.div = _div, \
.mult = _mult, \
.hw.init = CLK_HW_INIT_HW(_name, \
_parent, \
&clk_fixed_factor_ops, \
_flags), \
}
/*
* This macro allows the driver to reuse the _parent array for multiple
* fixed factor clk declarations.
*/
#define CLK_FIXED_FACTOR_HWS(_struct, _name, _parent, \
_div, _mult, _flags) \
struct clk_fixed_factor _struct = { \
.div = _div, \
.mult = _mult, \
.hw.init = CLK_HW_INIT_HWS(_name, \
_parent, \
&clk_fixed_factor_ops, \
_flags), \
}
#define CLK_FIXED_FACTOR_FW_NAME(_struct, _name, _parent, \
_div, _mult, _flags) \
struct clk_fixed_factor _struct = { \
.div = _div, \
.mult = _mult, \
.hw.init = CLK_HW_INIT_FW_NAME(_name, \
_parent, \
&clk_fixed_factor_ops, \
_flags), \
}
clk_hw_onecell_data
c
struct clk_hw_onecell_data {
unsigned int num; // 时钟总数
struct clk_hw *hws[]; // 柔性数组,存放 struct clk_hw 指针
};
这个结构体用于将系统的时钟全部放到一个hws的结构体里面,然后一次性全部注册进时钟框架
内核CLK注册
clk_hw_register
c
int clk_hw_register(struct device *dev, struct clk_hw *hw)
这个函数的作用是将时钟注册进时钟框架
clk_hw_register_clkdev
c
int clk_hw_register_clkdev(struct clk_hw *hw, const char *con_id,
const char *dev_id)
把时钟加入 clkdev 查找表,让消费者可以通过名字找到对应的时钟
of_clk_add_hw_provider
c
int of_clk_add_hw_provider(
struct device_node *np,
struct clk_hw *(*get)(struct of_phandle_args *clkspec, void *data),
void *data
);
这个函数意思是当驱动通过设备树解析时钟节点时,由get这个函数进行解析,内核默认提供一个函数去解析节点,of_clk_hw_onecell_get
内核CLK API函数
- clk_get:获取时钟函数
- clk_prepare_enable:准备+使能
- clk_set_rate:设置时钟频率
- clk_get_rate:获取时钟频率
- clk_set_parent:设置父时钟
- clk_get_parent:获取父时钟