Clock
Introduction to the functions and usage of Clock.
Module Introduction
Clock is the controller of the clock module.
Function Introduction
To manage clocks well, Linux provides a clock management framework called the Common Clock Framework (CCF), which provides a unified operation interface for device drivers so that device drivers do not need to care about the specific details of clock hardware implementation.
Its structure includes the following parts:
clock provider: Corresponds to the right part of the above diagram, i.e., the clock controller, responsible for providing various clocks required by the system.
clock consumer: Corresponds to the left part of the above diagram, i.e., some device drivers that use clocks.
clock framework: The core part of CCF, provides general APIs for clock consumers to operate clocks; implements the core logic of clock management, encapsulates hardware-related clock control logic into a set of operation functions, and is implemented by the clock provider.
device tree: CCF allows the declaration of available clocks and their association with devices in the device tree.
Clock system related devices include:
- Oscillator (active oscillator, also known as crystal oscillator) or Crystal (passive oscillator, also known as crystal)
- PLL (Phase Locked Loop) for frequency multiplication
- Divider for frequency division
- Mux for clock source selection
- Gate for clock switch control
There may be many such hardware modules in the system, forming a tree structure. Linux manages them as a clock tree, with the root node usually being a crystal oscillator, followed by pll, then mux or div, and finally the leaf nodes are usually gates. CCF implements various basic clock types, such as fixed_rate clock, gate clock, divider clock, and mux clock. Generally, for ease of use, some clock types are implemented according to the clock tree design.
Source Code Structure Introduction
Clock Controller Driver Source Code
The clock controller driver code is under drivers/clk/spacemit:
drivers/clk/spacemit
|-- ccu_ddn.c # ddn clock type source code
|-- ccu_ddn.h
|-- ccu_ddr.c # ddr clock type source code
|-- ccu_ddr.h
|-- ccu_dpll.c # dpll clock type source code
|-- ccu_dpll.h
|-- ccu_mix.c # mix clock type source code
|-- ccu_mix.h
|-- ccu_pll.c # pll clock type source code
|-- ccu_pll.h
|-- ccu-spacemit-k1x.c # k1 clock controller driver
|-- ccu-spacemit-k1x.h
|-- Kconfig
|-- Makefile
The clock controller driver implements 5 types of clocks:
- pll type, phase-locked loop type
- dpll type, ddr-related phase-locked loop type
- ddn type, fractional divider, with one stage of division (denominator) and one stage of multiplication (numerator)
- mix type, hybrid type, supports any combination of gate/mux/divider
- ddr type, special ddr-related clock type
Clock index definitions
Clock index definitions are under dt-bindings:
include/dt-bindings/clock/spacemit-k1x-clock.h
Configuration Introduction
Mainly includes driver enable configuration and dts configuration
CONFIG Configuration
CONFIG_COMMON_CLK provides support for the Common Clock Framework. By default, this option is Y.
Device Drivers
Common Clock Framework (COMMON_CLK[=y])
CONFIG_SPACEMIT_K1X_CCU provides support for the K1 Clock controller driver. By default, this option is Y.
Device Drivers
Common Clock Framework (COMMON_CLK[=y])
Clock support for Spacemit k1x SoCs (SPACEMIT_K1X_CCU [=y])
DTS Configuration
The dts configuration for the clock controller is as follows:
/ {
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 Description
API Introduction
CCF provides general clock operation interfaces for device drivers
- get
Get clock handle
/*
* 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);
For the above interfaces, if the second parameter is omitted, the first clock configured in the "clocks" entry in dts will be obtained by default.
- put
Release clock handle
/*
* 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 the clock, usually some preparation work before enabling the clock
/**
* 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 the clock, usually some cleanup work after disabling the clock
/**
* 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
Enable the clock
/**
* 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
Disable the clock
/**
* 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 is a combination of clk_prepare and clk_enable, clk_disable_unprepare is a combination of clk_unprepare and clk_disable, it is recommended to use these two interfaces.
- set rate
Set clock frequency
/**
* 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);
- get rate
Get the current clock frequency
/**
* clk_get_rate - obtain the current clock rate (in Hz) for a clock source.
* This is only valid once the clock source has been enabled.
* @clk: clock source
*/
unsigned long clk_get_rate(struct clk *clk);
- set parent
Set parent clock
/**
* clk_set_parent - set the parent clock source for this clock
* @clk: clock source
* @parent: parent clock source
* Returns success (0) or negative errno.
*/
int clk_set_parent(struct clk *clk, struct clk *parent);
- get parent
Get the current parent clock handle
/**
* clk_get_parent - get the parent clock source for this clock
* @clk: clock source
* Returns struct clk corresponding to parent clock source, or
* valid IS_ERR() condition containing errno.
*/
struct clk *clk_get_parent(struct clk *clk);
- round rate
Get the frequency closest to the target frequency that the clock controller can provide
/**
* clk_round_rate - adjust a rate to the exact rate a clock can provide
* @clk: clock source
* @rate: desired clock rate in Hz
* This answers the question "if I were to pass @rate to clk_set_rate(),
* what clock rate would I end up with?" without changing the hardware
* in any way. In other words:
* rate = clk_round_rate(clk, r);
* and:
* clk_set_rate(clk, r);
* rate = clk_get_rate(clk);
* are equivalent except the former does not modify the clock hardware
* in any way.
* Returns rounded clock rate in Hz, or negative errno.
*/
long clk_round_rate(struct clk *clk, unsigned long rate);
Usage Example
If a module needs to use clock functions, it needs to configure the clocks and clock-names properties in dts, and then use the CCF API in the driver to perform clock-related operations.
- Configure dts
Find the corresponding clock index in include/dt-bindings/clock/spacemit-k1x-clock.h and configure it in the module dts. Take can0 as an example, can0 has two clocks, one is the module working clock CLK_CAN0, and the other is the bus clock CLK_CAN0_BUS. The dts configuration is as follows:
flexcan0: fdcan@d4028000 {
compatible = "spacemit,k1x-flexcan";
reg = <0x0 0xd4028000 0x0 0x4000>;
interrupts = <16>;
interrupt-parent = <&intc>;
clocks = <&ccu CLK_CAN0>,<&ccu CLK_CAN0_BUS>; # configure the index of can0 clock
clock-names = "per","ipg"; # configure the names corresponding to clocks, the driver can get the corresponding clock by this string
resets = <&reset RESET_CAN0>;
fsl,clk-source = <0>;
status = "disabled";
};
- Add header file and clk handle
#include <linux/clk.h>
struct flexcan_priv {
struct clk *clk_ipg;
struct clk *clk_per;
};
- Get clock
Generally, the clock handle is obtained through devm_clk_get during the driver probe phase. When the driver probe fails or is removed, the driver automatically releases the corresponding clock handle.
clk_ipg = devm_clk_get(&pdev->dev, "ipg"); # get the clock handle corresponding to the bus clock CLK_CAN0_BUS
if (IS_ERR(clk_ipg)) {
dev_err(&pdev->dev, "no ipg clock defined\n");
return PTR_ERR(clk_ipg);
}
clk_per = devm_clk_get(&pdev->dev, "per"); # get the clock handle corresponding to the working clock CLK_CAN0
if (IS_ERR(clk_per)) {
dev_err(&pdev->dev, "no per clock defined\n");
return PTR_ERR(clk_per);
}
- Enable clock
Enable the clock node through clk_prepare_enable
if (priv->clk_ipg) {
err = clk_prepare_enable(priv->clk_ipg); # enable bus clock CLK_CAN0_BUS
if (err)
return err;
}
if (priv->clk_per) {
err = clk_prepare_enable(priv->clk_per); # enable working clock CLK_CAN0
if (err)
clk_disable_unprepare(priv->clk_ipg);
}
- Get clock frequency
Get the clock frequency through clk_get_rate
clock_freq = clk_get_rate(clk_per); # get the current frequency of working clock CLK_CAN0
- Set clock frequency
Modify the clock frequency through clk_set_rate, the first parameter is the clock handle struct clk*, the second parameter is the target frequency
clk_set_rate(clk_per, clock_freq); # set the frequency of working clock CLK_CAN0
- Disable clock
Disable the clock through clk_disable_unprepare
clk_disable_unprepare(priv->clk_per); # disable working clock CLK_CAN0
clk_disable_unprepare(priv->clk_ipg); # disable bus clock CLK_CAN0_BUS
Debug Introduction
Debugging can be done through debugfs
- Print clock tree
/sys/kernel/debug/clk/clk_summary is commonly used to print the clock tree structure, view the status, frequency, parent clock, etc. of each clock node.
root# cat /sys/kernel/debug/clk/clk_summary
- View specific clock node
You can also view the status, frequency, parent clock, etc. of a specific clock node. Take can0_clk as an example:
root:/sys/kernel/debug/clk/can0_clk # ls -l
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_accuracy
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_duty_cycle
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_enable_count
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_flags
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_max_rate
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_min_rate
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_notifier_count
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_parent
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_phase
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_possible_parents
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_prepare_count
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_prepare_enable
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_protect_count
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_rate
root:/sys/kernel/debug/clk/can0_clk# cat clk_prepare_count #查看enable状态
0
root:/sys/kernel/debug/clk/can0_clk# cat clk_rate #查看当前频率
20000000
root:/sys/kernel/debug/clk/can0_clk# cat clk_parent #查看当前父时钟
pll3_20
root:/sys/kernel/debug/clk/can0_clk#
- 改变时钟配置
在driver/clk/clk.c中加上CLOCK_ALLOW_WRITE_DEBUGFS宏定义,就可以对debugfs下的一些clk节点进行写操作,否则只有读操作权限
/sys/kernel/debug/clk/can0_clk # ls -l
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_accuracy
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_duty_cycle
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_enable_count
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_flags
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_max_rate
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_min_rate
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_notifier_count
-rw-r--r-- 1 root root 0 Jan 1 08:03 clk_parent #可读可写
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_phase
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_possible_parents
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_prepare_count
-rw-r--r-- 1 root root 0 Jan 1 08:03 clk_prepare_enable #可读可写
-r--r--r-- 1 root root 0 Jan 1 08:03 clk_protect_count
-rw-r--r-- 1 root root 0 Jan 1 08:03 clk_rate #可读可写
/sys/kernel/debug/clk/can0_clk # cat clk_rate #查看频率
20000000
/sys/kernel/debug/clk/can0_clk # echo 40000000 > clk_rate #设置频率为40MHz
/sys/kernel/debug/clk/can0_clk # cat clk_rate #确认设置结果
40000000
/sys/kernel/debug/clk/can0_clk # cat clk_parent #查看父时钟
pll3_40
/sys/kernel/debug/clk/can0_clk # echo 0 > clk_parent #设置父时钟为index为0的时钟源
/sys/kernel/debug/clk/can0_clk # cat clk_parent #确认设置结果
pll3_20
/sys/kernel/debug/clk/can0_clk # cat clk_prepare_enable #查看prepare_enable状态
0
/sys/kernel/debug/clk/can0_clk # echo 1 > clk_prepare_enable #prepare并enable时钟节点
/sys/kernel/debug/clk/can0_clk # cat clk_prepare_enable #确认设置结果
1
/sys/kernel/debug/clk/can0_clk # echo 0 > clk_prepare_enable #unprepare并disable时钟节点
/sys/kernel/debug/clk/can0_clk # cat clk_prepare_enable #确认设置结果
0
/sys/kernel/debug/clk/can0_clk #