Clock
介绍 Clock 的功能和使用方法。
模块介绍
Clock 是系统中的时钟控制器模块,负责时钟的管理与分发。
功能介绍
Linux为了做好时钟管理,提供了一个时钟管理框架 Common Clock Framework(以下简称CCF),为设备驱动提供统一的操作接口,使设备驱动不必关心时钟硬件实现的具体细节。
CCF 框架包括以下核心组成部分:
clock provider:对应上图的右侧部分,即 clock controller,负责提供系统所需的各种时钟。
clock consumer:对应上图的左侧部分,即使用时钟的一些设备驱动。
clock framework:CCF 的核心部分,向 clock consumers 提供操作 clock 的通用 API;实现时钟管理的核心逻辑,将与硬件相关的 clock 控制逻辑封装成操作函数集,交由 clock provider 实现。
device tree:CCF 允许在设备树中声明可用的时钟与设备的关联。
Clock 系统相关的器件包括:
- Oscillator / Crystal:有源或无源晶振,作为根时钟源。
- PLL(Phase Locked Loop):锁相环倍频,提高基础频率。
- Divider:分频器,降低频率。
- MUX:多路选择器,切换时钟源。
- GATE:时钟开关,控制时钟通断。
系统中可能存在很多个这样的硬件模块,呈树形结构,Linux 将他们管理成一个时钟树(clock-tree),系统中的时钟树(Clock Tree)通常以晶振(Oscillator/Crystal)为起点,逐层经由 PLL(倍频)、MUX(选择)、DIV(分频)及 GATE(控制)等节点,最终输出给设备使用。CCF 实现了多种基础时钟类型,例如:
- 固定速率时钟 fixed_rate clock
- 门控时钟 gate clock
- 分频器时钟 divider clock
- 复用器时钟 mux clock
一般为了方便使用,会根据时钟树设计,实现一些时钟类型。
源码结构介绍
Clock 控制器驱动源码
Clock 控制器驱动代码在 drivers/clk/spacemit
目录下:
drivers/clk/spacemit
|-- ccu_ddn.c #ddn时钟类型源码
|-- ccu_ddn.h
|-- ccu_ddr.c #ddr时钟类型源码
|-- ccu_ddr.h
|-- ccu_dpll.c #dpll时钟类型源码
|-- ccu_dpll.h
|-- ccu_mix.c #mix时钟类型源码
|-- ccu_mix.h
|-- ccu_pll.c #pll时钟类型源码
|-- ccu_pll.h
|-- ccu-spacemit-k1x.c #k1 clock controller驱动
|-- ccu-spacemit-k1x.h
|-- Kconfig
|-- Makefile
Clock 控制器驱动实现了五种类型的时钟模块:
- PLL 类型,锁相环类型
- DPLL 类型,DDR 相关的锁相环类型
- DDN 类型,分数 divider,有一级除频,对应分母,一级倍频,对应分子
- MIX 类型,混合类型,支持 gate/mux/divider 的任一种或者随意组合
- DDR 类型,DDR 相关的特殊时钟类型
各时钟index定义
各时钟 index 定义在 dt-bindings
下:
include/dt-bindings/clock/spacemit-k1x-clock.h
配置介绍
主要包括 驱动使能配置 和 DTS配置
CONFIG 配置
CONFIG_COMMON_CLK
:为 Common Clock Framework 提供支持,默认情况下,此选项为Y
Device Drivers
Common Clock Framework (COMMON_CLK[=y])
CONFIG_SPACEMIT_K1X_CCU
:为 K1 Clock 控制器驱动提供支持,默认情况下,此选型为Y
Device Drivers
Common Clock Framework (COMMON_CLK[=y])
Clock support for Spacemit k1x SoCs (SPACEMIT_K1X_CCU [=y])
DTS 配置
clock controller 的 DTS 配置如下:
/ {
clocks {
#address-cells = <0x2>;
#size-cells = <0x2>;
ranges;
vctcxo_24: clock-vctcxo_24 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <24000000>;
clock-output-names = "vctcxo_24";
};
vctcxo_3: clock-vctcxo_3 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <3000000>;
clock-output-names = "vctcxo_3";
};
vctcxo_1: clock-vctcxo_1 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <1000000>;
clock-output-names = "vctcxo_1";
};
pll1_2457p6_vco: clock-pll1_2457p6_vco {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <2457600000>;
clock-output-names = "pll1_2457p6_vco";
};
clk_32k: clock-clk32k {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <32000>;
clock-output-names = "clk_32k";
};
pll_clk_cluster0: clock-pll_clk_cluster0 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <10000000>;
clock-output-names = "pll_clk_cluster0";
};
pll_clk_cluster1: clock-pll_clk_cluster1 {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <10000000>;
clock-output-names = "pll_clk_cluster1";
};
};
soc: soc {
...
ccu: clock-controller@d4050000 {
compatible = "spacemit,k1x-clock";
reg = <0x0 0xd4050000 0x0 0x209c>,
<0x0 0xd4282800 0x0 0x400>,
<0x0 0xd4015000 0x0 0x1000>,
<0x0 0xd4090000 0x0 0x1000>,
<0x0 0xd4282c00 0x0 0x400>,
<0x0 0xd8440000 0x0 0x98>,
<0x0 0xc0000000 0x0 0x4280>,
<0x0 0xf0610000 0x0 0x20>,
<0x0 0xc0880000 0x0 0x2050>,
<0x0 0xc0888000 0x0 0x30>;
reg-names = "mpmu", "apmu", "apbc", "apbs", "ciu", "dciu", "ddrc", "apbc2", "rcpu", "rcpu2";
clocks = <&vctcxo_24>, <&vctcxo_3>, <&vctcxo_1>, <&pll1_2457p6_vco>,
<&clk_32k>;
clock-names = "vctcxo_24", "vctcxo_3", "vctcxo_1", "pll1_2457p6_vco",
"clk_32k";
#clock-cells = <1>;
status = "okay";
};
...
};
};
接口描述
API 介绍
CCF 为设备驱动提供了通用的时钟操作的接口:
get
:获取时钟句柄
/*
* clk_get - get clk
* @dev: device
* @id: clock name of dts "clock-names"
*/
struct clk *clk_get(struct device *dev, const char *id);
/*
* clk_get - get clk
* @dev: device
* @id: clock name of dts "clock-names"
*/
struct clk *clk_get(struct device *dev, const char *id);
/*
* devm_clk_get - get clk
* @dev:device
* @id:clock name of dts "clock-names"
*/
struct clk *devm_clk_get(struct device *dev, const char *id);
/*
* of_clk_get_by_name - get clk by name
* @np:device_node
* @id:clock name of dts "clock-names"
*/
struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
上述接口,第 2 个参数缺省时,默认返回 DTS clocks
列表中的第 1 个时钟。
put
:释放时钟句柄
/*
* clk_put - put clk
* @dev: device
* @id: clock name of dts "clock-names"
*/
void clk_put(struct clk *clk);
/*
* devm_clk_put - put clk
* @dev: device
* @id: clock name of dts "clock-names"
*/
void devm_clk_put(struct device *dev, struct clk *clk);
prepare
:prepare 时钟,通常是在 enable 操作之前进行的准备步骤。
/**
* clk_prepare - prepare a clock source
* @clk: clock source
* This prepares the clock source for use.
* Must not be called from within atomic context.
*/
int clk_prepare(struct clk *clk);
unprepare
:unprepare 时钟,通常是在 disable 操作之后的一些善后工作
/**
* clk_unprepare - undo preparation of a clock source
* @clk: clock source
* This undoes a previously prepared clock. The caller must balance
* the number of prepare and unprepare calls.
* Must not be called from within atomic context.
*/
void clk_unprepare(struct clk *clk);
enable
:使能时钟
/**
* clk_enable - inform the system when the clock source should be running.
* @clk: clock source
* If the clock can not be enabled/disabled, this should return success.
* May be called from atomic contexts.
* Returns success (0) or negative errno.
*/
int clk_enable(struct clk *clk);
disable
:关闭时钟
/**
* clk_disable - inform the system when the clock source is no longer required.
* @clk: clock source
* Inform the system that a clock source is no longer required by
* a driver and may be shut down.
* May be called from atomic contexts.
* Implementation detail: if the clock source is shared between
* multiple drivers, clk_enable() calls must be balanced by the
* same number of clk_disable() calls for the clock source to be
* disabled.
*/
void clk_disable(struct clk *clk);
clk_prepare_enable
是 clk_prepare
和 clk_enable
的组合,clk_disable_unprepare
是 clk_unprepare
和 clk_disable
的组合,推荐使用这两个接口
set rate
:设置时钟频率
/**
* clk_set_rate - set the clock rate for a clock source
* @clk: clock source
* @rate: desired clock rate in Hz
* Updating the rate starts at the top-most affected clock and then
* walks the tree down to the bottom-most clock that needs updating.
* Returns success (0) or negative errno.
*/
int clk_set_rate(struct clk *clk, unsigned long rate);