Linux CLK框架了解

背景

了解学习下Linux CLK框架,参考官方文档:

https://www.kernel.org/doc/html/v5.7/core-api/kernel-api.html#clock-framework

框架

使用

  1. clock privider:生产者
  2. clock consumer:消费者

clock分配:

  1. fixed rate:输出固定频率,不能开关,不能调节,不能选择parent
  2. gate:开关,会提供.enable/.disable回调
  3. devider:设置分频值,会提供.recalc_rate/.set_rate/.round_rate回调
  4. mux:选择parent,因为会实现.get_parent/.set_parent/.recalc_rate回调
  5. fixed factor:具有固定的factor(即multiplier和divider),clock的频率是由parent clock的频率,由于parent clock的频率可以改变,因而fix factor clock也可该改变频率。因此也会提供.recalc_rate/.set_rate/.round_rate等回调。
  6. 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);
  1. reg:控制该clock开关的寄存器地址(虚拟地址)
  2. bit_idx:控制clock开关的bit位
  3. clk_gate_flags:gate clock特有的flag,当前只有一种:CLK_GATE_SET_TO_DISABLE,如果置为的话,标识写1关clk
  4. 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);
  1. reg,控制clock分频比的寄存器;
  2. shift,控制分频比的bit在寄存器中的偏移;
  3. width,控制分频比的bit位数,默认情况下,实际的divider值是寄存器值加1(如寄存器值=1,实际分频=2)
  4. clk_divider_flags:ivider clock特有的flag,包括:
    1. CLK_DIVIDER_ONE_BASED:实际的divider值就是寄存器值(0是无效的,除非设置CLK_DIVIDER_ALLOW_ZERO flag)
    2. CLK_DIVIDER_POWER_OF_TWO:实际的divider值是寄存器值的2次方
    3. 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);
  1. parent_names:描述所有可能的parent clock;
  2. num_parents,parent clock的个数;
  3. reg、shift、width:选择parent的寄存器、偏移、宽度,默认情况下,寄存器值为0时,对应第一个parent,依此类推。如有例外,可通过下面的flags,以及另外一个接口实现;
  4. clk_mux_flags:
    1. CLK_MUX_INDEX_ONE:寄存器值不是从0开始,而是从1开始;
    2. 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-notifierold_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);

时钟获取

可以用以下接口:

  1. clk_get(dev, id):dev和id的任意一个可以为空。如果id为空,则必须有device tree的支持才能获得device对应的clk
  2. devm_clk_get(dev, id):同 clk_get,只是使用 device resource management,可以自动释放
  3. clk_put/get,devm_clk_put/get
  4. clk_get_sys:类似clk_get,不过使用device的name替代device结构
  5. of_clk_get、of_clk_get_by_name、of_clk_get_from_provider:从设备树,以index、name等为索引,获取clk

时钟控制

  1. clk_enable/clk_disable:启动/停止clock,不会睡眠
  2. clk_prepare/clk_unprepare:启动clock前的准备工作/停止clock后的善后工作。可能会睡眠
  3. clk_get_rate/clk_set_rate/clk_round_rate,clock频率的获取和设置
    1. 其中clk_set_rate可能会不成功(例如没有对应的分频比),此时会返回错误
    2. 如果要确保设置成功,则需要先调用clk_round_rate接口,得到和需要设置的rate比较接近的那个值
  4. clk_set_parent/clk_get_parent:获取/选择clock的parent clock
  5. clk_prepare_enable/clk_disable_unprepare:将clk_prepare和clk_enable组合起来,一起调用

prepare/unprepare,enable/disable的说明

  1. 告诉底层clock driver驱动:请将可能引起休眠的操作放在 prepare/unprepare 中实现
  2. 提示上层应用,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。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值