背景
了解学习下Linux CLK框架,参考官方文档:
https://www.kernel.org/doc/html/v5.7/core-api/kernel-api.html#clock-framework
框架
使用
- clock privider:生产者
- clock consumer:消费者
clock分配:
- fixed rate:输出固定频率,不能开关,不能调节,不能选择parent
- gate:开关,会提供.enable/.disable回调
- devider:设置分频值,会提供.recalc_rate/.set_rate/.round_rate回调
- mux:选择parent,因为会实现.get_parent/.set_parent/.recalc_rate回调
- fixed factor:具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,由于parent clock的频率可以改变,因而fix factor clock也可该改变频率。因此也会提供.recalc_rate/.set_rate/.round_rate等回调。
- composite:mux、divider、gate等clock的组合
fixed rate
这一类clock具有固定的频率,不能开关、不能调整频率、不能选择parent、不需要提供任何的clk_ops回调函数,是最简单的一类clock。
可以直接通过DTS配置的方式支持,clock framework core能直接从DTS中解出clock信息,并自动注册到kernel,不需要任何driver支持。
clock framework使用struct clk_fixed_rate结构抽象这一类clock,另外提供了一个接口,可以直接注册fixed rate clock,如下:
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
u8 flags;
};
extern const struct clk_ops clk_fixed_rate_ops;
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
如果使用DTS的话,clk_register_fixed_rate都不需要,直接在DTS中配置即可,.compatible = "fixed-clock"
。
gata
这一类clock只可开关(会提供.enable/.disable回调),可使用下面接口注册:
struct 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);
- reg:控制该clock开关的寄存器地址(虚拟地址)
- bit_idx:控制clock开关的bit位
- clk_gate_flags:gate clock特有的flag,当前只有一种:CLK_GATE_SET_TO_DISABLE,如果置为的话,标识写1关clk
- lock:如果clock开关时需要互斥,可提供一个spinlock
devider
这一类clock可以设置分频值(因而会提供.recalc_rate/.set_rate/.round_rate回调),可通过下面两个接口注册:
struct 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);
- reg,控制clock分频比的寄存器;
- shift,控制分频比的bit在寄存器中的偏移;
- width,控制分频比的bit位数,默认情况下,实际的divider值是寄存器值加1(如寄存器值=1,实际分频=2)
- clk_divider_flags:ivider clock特有的flag,包括:
- CLK_DIVIDER_ONE_BASED:实际的divider值就是寄存器值(0是无效的,除非设置CLK_DIVIDER_ALLOW_ZERO flag)
- CLK_DIVIDER_POWER_OF_TWO:实际的divider值是寄存器值的2次方
- CLK_DIVIDER_ALLOW_ZERO:divider值可以为0(不改变,视硬件支持而定)
如有需要其他分频方式,就需要使用另外一个接口,如下:
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,和上面接口比较,差别在于divider值和寄存器值得对应关系由一个table决定,该table的原型为:
struct clk_div_table {
unsigned int val; /* 寄存器值 */
unsigned int div; /* 分频值 */
};
mux
这一类clock可以选择多个parent,因为会实现.get_parent/.set_parent/.recalc_rate回调,可通过下面两个接口注册:
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);
- parent_names:描述所有可能的parent clock;
- num_parents,parent clock的个数;
- reg、shift、width:选择parent的寄存器、偏移、宽度,默认情况下,寄存器值为0时,对应第一个parent,依此类推。如有例外,可通过下面的flags,以及另外一个接口实现;
- clk_mux_flags:
- CLK_MUX_INDEX_ONE:寄存器值不是从0开始,而是从1开始;
- CLK_MUX_INDEX_BIT:寄存器值为2的幂
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);
类似 clk_register_divider_table
fixed factor
这一类clock具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,乘以mul,除以div,多用于一些具有固定分频系数的clock。
由于parent clock的频率可以改变,因而fix factor clock也可该改变频率。
因此也会提供.recalc_rate/.set_rate/.round_rate等回调。
struct clk *clk_register_fixed_factor(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned int mult, unsigned int div);
这一类接口和fixed rate clock 类似,不需要提供driver,只需要配置即可.compatible为"fixed-factor-clock"
,并提供"clock-div"、"clock-mult"
和"clock-output-names"
关键字。。
composite
顾名思义,就是mux、divider、gate等clock的组合,可通过下面接口注册:
struct 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);
数据结构
clk_hw
从 clock privider角度描述 clk
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
clk_init_data
struct clk_init_data {
const char *name;
const struct clk_ops *ops;
const char **parent_names;
u8 num_parents;
unsigned long flags;
};
clk_notifier
将一个 clk 和 notifier 关联
struct clk_notifier {
struct clk *clk;
struct srcu_notifier_head notifier_head;
struct list_head node;
};
clk_notifier_data
速率数据通知回调
struct clk_notifier_data {
struct clk *clk;
unsigned long old_rate;
unsigned long new_rate;
};
对于 pre-notifier
,old_rate
是改变前的速率,new_rate
是改变后的速率
clk_bulk_data
用于批量处理clk的数据结构
struct clk_bulk_data {
const char *id;
struct clk *clk;
};
CLK API 提供了一系列 clk_bulk_() API 调用,以方便需要多个 clk 的消费者。此结构用于管理这些调用的数据。
函数
函数总结
int clk_notifier_register(struct clk * clk, struct notifier_block * nb)
int clk_notifier_unregister(struct clk * clk, struct notifier_block * nb)
将 clk 从 notifier_block 中取消注册
/* 获取时钟源频率,单位ppb; 1ppb=10^-9 */
long clk_get_accuracy(struct clk * clk);
/* 设置/获取 相位 */
int clk_set_phase(struct clk * clk, int degrees);
int clk_get_phase(struct clk * clk);
/*
* 调整占空比: num=分子,den=分母
* 获取占空比: scale 用于将比率表示为整数的比例因子
* 返回占空比乘以提供的比例,否则返回 -EERROR
*/
int clk_set_duty_cycle(struct clk * clk, unsigned int num, unsigned int den);
int clk_get_scaled_duty_cycle( struct clk * clk , unsigned int scale );
/*
* 检查两个 clk 是否指向同一个硬件时钟
*/
bool clk_is_match( const struct clk * p, const struct clk * q);
/*
* 准备/取消准备 时钟源 ,不能在原子上下文调用
*/
int clk_prepare(struct clk *clk);
int clk_unprepare(struct clk *clk);
/*
* 查找并获得对时钟生产者的引用 ,不能在中断上下文调用
*/
struct clk *clk_get(struct device *dev, const char *id);
struct clk *clk_get_optional(struct device * dev, const char * id);
int clk_bulk_get(struct device * dev, int num_clks, struct clk_bulk_data * clks);
struct clk *devm_clk_get(struct device *dev, const char *id);
struct clk *devm_clk_get_optional(struct device * dev, const char * id);
struct clk *devm_get_clk_from_child(struct device * dev, struct device_node * np, const char * con_id);
void clk_put(struct clk * clk);
void clk_bulk_put(int num_clks, struct clk_bulk_data * clks);
void devm_clk_put(struct device * dev, struct clk * clk)
/*
* 时钟状态控制
*/
int clk_enable(struct clk * clk);
int clk_bulk_enable(int num_clks, const struct clk_bulk_data * clks);
void clk_disable(struct clk * clk);
void clk_bulk_disable(int num_clks, const struct clk_bulk_data * clks);
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);
int clk_set_rate_exclusive(struct clk * clk, unsigned long rate);
int clk_set_rate_range(struct clk * clk, unsigned long min, unsigned long max);
int clk_set_min_rate(struct clk * clk, unsigned long rate);
int clk_set_max_rate(struct clk * clk, unsigned long rate);
bool clk_has_parent(struct clk * clk, struct clk *parent);
int clk_set_parent(struct clk * clk, struct clk *parent);
struct clk *clk_get_parent(struct clk * clk);
struct clk *clk_get_sys(const char * dev_id, const char * con_id);
int clk_save_context(void);
void clk_restore_context(void);
时钟获取
可以用以下接口:
- clk_get(dev, id):dev和id的任意一个可以为空。如果id为空,则必须有device tree的支持才能获得device对应的clk
- devm_clk_get(dev, id):同 clk_get,只是使用
device resource management
,可以自动释放 - clk_put/get,devm_clk_put/get
- clk_get_sys:类似clk_get,不过使用device的name替代device结构
- of_clk_get、of_clk_get_by_name、of_clk_get_from_provider:从设备树,以index、name等为索引,获取clk
时钟控制
- clk_enable/clk_disable:启动/停止clock,不会睡眠
- clk_prepare/clk_unprepare:启动clock前的准备工作/停止clock后的善后工作。可能会睡眠
- clk_get_rate/clk_set_rate/clk_round_rate,clock频率的获取和设置
- 其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误
- 如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值
- clk_set_parent/clk_get_parent:获取/选择clock的parent clock
- clk_prepare_enable/clk_disable_unprepare:将clk_prepare和clk_enable组合起来,一起调用
prepare/unprepare,enable/disable的说明
- 告诉底层clock driver驱动:请将可能引起休眠的操作放在 prepare/unprepare 中实现
- 提示上层应用,prepare/unprepare 可能会引起休眠
其余接口
int clk_notifier_register(struct clk * clk, struct notifier_block * nb)
int clk_notifier_unregister(struct clk * clk, struct notifier_block * nb)
当某个driver关心某个clock,期望这个clock的rate改变时,通知到自己,就可以注册一个notify。