Linux CCF Consumer
如果没有时钟消费者来使用时钟,那么提供时钟的驱动程序将不起任何作用。提供者启动的主要目的就是为消费者分配时钟资源,这些时钟被用于各种用途。Linux内核提供了相应的API来完成时钟的使用。消费者需要在其代码中包含#clock-cells
属性决定的,例如以下UART节点的时钟描述
1 | uart1: serial@02020000 { |
以上设备树节点描述了UART需要两路时钟,clocks
属性用于声明设备需要的时钟源,关联到提供者的#clock-cells
属性。clock-names
用于命名时钟,这个名字可以被时钟消费者的驱动程序在代码中匹配时钟
消费者有一个基于底层硬件时钟的精简和可移植的API。接下来,我们将看看消费者驱动程序执行的常见操作及其相关的API
Grabbing and releasing clocks
以下函数用于获取和释放时钟资源
1 | struct clk *clk_get(struct device *dev, const char *id); |
dev
是使用时钟的设备,id
是设备树中时钟的编号,可以在平台相关的头文件中或dts中。clk_get
获取成功则返回一个struct clk
的结构体指针。对于消费者而言,该结构体可以理解为一个操作时钟的句柄,用来引用一个时钟节点,不需要关心内部的成员结构。其他的所有API都需要该句柄。clk_put
释放时钟线。前两个API定义在drivers/clk/clkdev.c中,另外两个API定义在drivers/clk/clk.c中。devm_clk_get/devm_clk_put
是资源管理版本的clk_get/clk_put
。clk_get_sys
类似clk_get
,不过使用device的name替代device结构。of_clk_get
、of_clk_get_by_name
、of_clk_get_from_provider
是设备树相关的接口,直接从相应的DTS node中以index、name等为索引,获取clk
Preparing/unpreparing clocks
以下函数用于准备和去准备时钟资源
1 | int clk_prepare(struct clk *clk); |
这两个函数可能会睡眠,意味着不能在原子上下文中调用。应该总是在clk_enable
之前调用clk_prepare
,在某些场景中驱动程序在使能或禁用时钟时的上下文是不能睡眠的
Enabling/disabling
当获取到时钟后,要使能/禁用时钟,可以使用以下函数
1 | static inline int clk_enable(struct clk *clk); |
clk_enable
不能睡眠,用于使能时钟,返回0表示成功,clk_disable
用于禁用时钟。内核还提供了以下两个API用于合并prepare+enable以及disable/unprepare操作
1 | static inline int clk_prepare_enable(struct clk *clk); |
Rate functions
对于可以更改频率的时钟,可以使用如下函数来设置时钟频率
1 | static inline unsigned long clk_get_rate(struct clk *clk); |
如果clk
为空,clk_get_rate
会返回0,否则将会返回时钟的频率值,单位为Hz,该函数返回的是在内部缓存的频率。如果时钟设置了CLK_GET_RATE_NOCACHE
标志,将会重新计算(通过调用recalc_rate
)并返回真实的频率。clk_set_rate
用于设置频率,然而并不是所有值都是可设置的,为了检查输入目标值是否支持,需要先使用clk_round_rate
1 | rounded_rate = clk_round_rate(clk, target_rate); |
返回值是与目标值最为接近的该时钟实际支持的频率,然后将其设置给时钟
1 | ret = clk_set_rate(clkp, rounded_rate); |
修改时钟频率发生错误可能是以下几种情况
- 该时钟节点是一个固定时钟
- 该时钟被多个驱动使用,引用计数大于1
- 该时钟被多个子节点使用
Parent functions
有些时钟节点是其他节点的子节点,存在一个父子关系,要修改节点的父节点,可以使用以下函数
1 | int clk_set_parent(struct clk *clk, struct clk *parent); |
clk_set_parent
将时钟的父节点改为parent
,clk_get_parent
获取时钟的父节点
示例
以下以一个示例说明消费者API如何使用。首先在DTS中指定某个设备要使用的clock
1 | device { |
该DTS表示device需要两个时钟,id分别为”baud”和”register”,”baud”来自时钟”osc”的输出1,”register”来自”ref”的输出0。而”osc”和”ref”的定义会在CCF设备树相关文章中介绍
在该设备的probe函数中,可以通过如下方式使用时钟
1 | int xxx_probe(struct platform_device *pdev) |