Skip to main content

Clock

Clock Functionality and Usage Guide.

Overview

The Clock module manages and controls system clock signals.

Functional Description

To manage clocks effectively, Linux provides the Common Clock Framework (CCF) for centralized clock management. This framework offers a unified interface for device drivers, allowing them to operate without needing to know the specific hardware implementation details of the clock. The structure includes the following components:

  • clock provider: Corresponding to the right side of the diagram, the clock controller, which is responsible for providing various clocks required by the system.
  • clock consumer: Corresponding to the left side of the diagram, the device drivers that use the clocks.
  • clock framework: The CCF core that exposes unified APIs for clock operations to consumers; implementing the core logic of clock management, encapsulating hardware-related clock control logic into a set of operation functions, and leaving their implementation to the clock provider.
  • device tree: CCF allows the declaration of available clocks and their associations with devices in the device tree

Clock-related components in the system include:

  • Oscillator (active, aka resonator) or Crystal (passive, aka crystal oscillator) for generating clocks.
  • PLL (Phase Locked Loop) for frequency multiplication.
  • Divider for frequency division.
  • Mux for clock source selection.
  • Gate for clock enable/disable control.

There may be many such hardware modules in the system, organized in a tree structure. Linux manages them as a clock tree, with the root node typically being a crystal oscillator, followed by PLLs, then muxes or dividers, and finally gate controllers as leaf nodes. CCF implements several basic clock types, such as fixed-rate clocks, gate clocks, divider clocks, and mux clocks. Generally, to facilitate usage, additional clock types are implemented based on the clock tree design.

Source Code Structure

Clock Controller Driver Source Code

The Clock controller driver code is located in the drivers/clk/spacemit directory:

drivers/clk/spacemit
|-- ccu_ddn.c # Source code for ddn clock type
|-- ccu_ddn.h
|-- ccu_ddr.c # Source code for ddr clock type
|-- ccu_ddr.h
|-- ccu_dpll.c # Source code for dpll clock type
|-- ccu_dpll.h
|-- ccu_mix.c # Source code for mix clock type
|-- ccu_mix.h
|-- ccu_pll.c # Source code for pll clock type
|-- ccu_pll.h
|-- ccu-spacemit-k1x.c # k1 clock controller driver
|-- ccu-spacemit-k1x.h
|-- Kconfig
|-- Makefile

The clock controller driver implements five types of clocks:

  • pll type: Phase Locked Loop (PLL) type.
  • dpll type: DDR-related PLL type.
  • ddn type: Fractional divider type, with one level of division (denominator) and one level of multiplication (numerator).
  • mix type: Mixed type, supporting any combination of gate, mux, and divider.
  • ddr type: Special clock type related to DDR.

Clock Index Definitions

The clock index definitions are located in the dt-bindings directory:

include/dt-bindings/clock/spacemit-k1x-clock.h

Configuration

The configuration mainly includes driver enabling and DTS (Device Tree Source) configuration.

CONFIG Configuration

CONFIG_COMMON_CLK: This option provides support for the Common Clock Framework (CCF). By default, this option is enabled (y).

Device Drivers
Common Clock Framework (COMMON_CLK[=y])

CONFIG_SPACEMIT_K1X_CCU: This option provides support for the K1 Clock controller driver. By default, this option is enabled (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";
};
...
};
};


Interface

API

The Common Clock Framework (CCF) provides a set of generic clock operation interfaces for device drivers.

  • get
    Obtain a 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 aforementioned interface, if the second parameter is omitted, it will default to obtaining the first clock configured in the "clocks" item in the DTS.

  • put
    Release the 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, which usually involves some preliminary 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, which usually involves 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
    Clock 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
    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: This is a combination of clk_prepare and clk_enable. It is recommended to use this interface for enabling the clock after preparing it. clk_disable_unprepare: This is a combination of clk_unprepare and clk_disable. It is recommended to use this interface for disabling the clock and performing any necessary cleanup.

  • set rate
    Set the 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
    Obtain 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 the 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
    Obtain the handle of the current parent clock.
/**
* 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
    Obtain the frequency that is closest to the target frequency and 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

To use the clock functionality in a module, you need to configure the clocks and clock-names properties in the DTS file, and then use the CCF API for clock-related operations in the driver.

  • Configure dts Locate the corresponding clock indices in include/dt-bindings/clock/spacemit-k1x-clock.h and configure them in the module's DTS file. Taking can0 as an example, can0 has two clocks: one is the module operation 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 clock indices for can0
    clock-names = "per","ipg"; # Configure the names corresponding to the clocks, which can be used by the driver to obtain the corresponding clocks
    resets = <&reset RESET_CAN0>;
    fsl,clk-source = <0>;
    status = "disabled";
    };

  • Add Header Files and Clock Handles

    #include <linux/clk.h>
    struct flexcan_priv {

    struct clk *clk_ipg;
    struct clk *clk_per;
    };
  • Obtaining Clocks
    Typically, during the driver's probe stage, clock handles are obtained using devm_clk_get. If the driver's probe fails or during the remove stage, the driver automatically releases the corresponding clock handles.

            clk_ipg = devm_clk_get(&pdev->dev, "ipg");               # Obtain 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"); # Obtain the clock handle corresponding to the operating clock CLK_CAN0.
    if (IS_ERR(clk_per)) {
    dev_err(&pdev->dev, "no per clock defined\n");
    return PTR_ERR(clk_per);
    }

  • Enable the clock. Enable the clock node using clk_prepare_enable

            if (priv->clk_ipg) {
    err = clk_prepare_enable(priv->clk_ipg); # Enable the bus clock CLK_CAN0_BUS.
    if (err)
    return err;
    }

    if (priv->clk_per) {
    err = clk_prepare_enable(priv->clk_per); # Enable the operating clock CLK_CAN0.
    if (err)
    clk_disable_unprepare(priv->clk_ipg);
    }

  • Obtain the clock frequency. Get the clock frequency using clk_get_rate.

    clock_freq = clk_get_rate(clk_per);                  # Obtain the current frequency of the operating clock CLK_CAN0.
  • Set the clock frequency. Modify the clock frequency using clk_set_rate, where the first parameter is the clock handle struct clk*, and the second parameter is the target frequency.

    clk_set_rate(clk_per, clock_freq);                   # Set the frequency of the operating clock CLK_CAN0.
  • Disable the clock. Disable the clock using clk_disable_unprepare.

    clk_disable_unprepare(priv->clk_per);                # Disable the operating clock CLK_CAN0.
    clk_disable_unprepare(priv->clk_ipg); # Disable the bus clock CLK_CAN0_BUS.

Debugging

You can use debugfs for debugging purposes.

  • Print the Clock Tree The file /sys/kernel/debug/clk/clk_summary is commonly used to print the clock tree structure. It provides information on the status, frequency, parent clocks, and other details of each clock node

    root# cat /sys/kernel/debug/clk/clk_summary
  • Viewing Specific Clock Nodes You can also view the status, frequency, parent clock, and other information for specific clock nodes. For example, to view details for the can0_clk:


    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 # Viewing Enable Status
    0
    root:/sys/kernel/debug/clk/can0_clk# cat clk_rate # Viewing Current Frequency
    20000000
    root:/sys/kernel/debug/clk/can0_clk# cat clk_parent # Viewing the Current Parent Clock
    pll3_20
    root:/sys/kernel/debug/clk/can0_clk#
  • Modifying Clock Configuration Define CLOCK_ALLOW_WRITE_DEBUGFS in drivers/clk/clk.c to enable write access to debugfs clock nodes. Otherwise, you only have read permissions.

    /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 # Read/write
    -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 # Read/write
    -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 # Read/write
    /sys/kernel/debug/clk/can0_clk # cat clk_rate # View the frequency.
    20000000
    /sys/kernel/debug/clk/can0_clk # echo 40000000 > clk_rate # Set the frequency to 40 MHz.
    /sys/kernel/debug/clk/can0_clk # cat clk_rate # Confirm the setting result
    40000000
    /sys/kernel/debug/clk/can0_clk # cat clk_parent # View the parent clock.
    pll3_40
    /sys/kernel/debug/clk/can0_clk # echo 0 > clk_parent # Set the parent clock to the clock source with index 0.
    /sys/kernel/debug/clk/can0_clk # cat clk_parent # Confirm the setting result.
    pll3_20
    /sys/kernel/debug/clk/can0_clk # cat clk_prepare_enable # View the prepare_enable status.
    0
    /sys/kernel/debug/clk/can0_clk # echo 1 > clk_prepare_enable # Prepare and enable the clock node.
    /sys/kernel/debug/clk/can0_clk # cat clk_prepare_enable # Confirm the setting result
    1
    /sys/kernel/debug/clk/can0_clk # echo 0 > clk_prepare_enable # Unprepare and disable the clock node.
    /sys/kernel/debug/clk/can0_clk # cat clk_prepare_enable # Confirm the setting result.
    0
    /sys/kernel/debug/clk/can0_clk #

FAQ