Home >System Tutorial >LINUX >Understand in one article | Linux clock subsystem

Understand in one article | Linux clock subsystem

WBOY
WBOYforward
2024-02-12 09:42:021282browse

Clock The clock is the pulse in the SoC, which controls each component to run at its own pace. For example, CPU frequency setting, serial port baud rate setting, I2S sampling rate setting, I2C rate setting, etc. These different clock settings need to come from one or several clock sources, and ultimately form a clock tree. You can view this clock tree through the cat /sys/kernel/debug/clk/clk_summary command.

The CCF framework is used in the kernel to manage clocks. As shown in the figure below, the right side is the clock provider, that is, Clock Provider; the middle is CCF; the left side is the clock consumer of the device driver, that is, Clock Consumer.

一文搞懂 | Linux 时钟子系统

Clock Provider

  • The root node is usually an Oscillator (active oscillator) or Crystal (passive oscillator).
  • There are many kinds of intermediate nodes, including PLL (phase-locked loop, used to increase frequency), Divider (frequency divider, used to reduce frequency), Mux (select one from multiple clock paths), Gate (used to control ON/OFF).
  • Leaf nodes are HW blocks with specific functions that use clock as input.

According to the characteristics of the clock, the clock framework divides the clock into six categories: fixed rate, gate, devider, mux, fixed factor, and composite.

一文搞懂 | Linux 时钟子系统

data structure

The above six categories are essentially clock devices. The kernel extracts the characteristics of these clock HW blocks and uses struct clk_hw to represent them. The details are as follows:

struct clk_hw {
  //指向CCF模块中对应 clock device 实例
 struct clk_core *core;
  //clk是访问clk_core的实例。每当consumer通过clk_get对CCF中的clock device(也就是clk_core)发起访

问的时候都需要获取一个句柄,也就是clk
 struct clk *clk;
  //clock provider driver初始化时的数据,数据被用来初始化clk_hw对应的clk_core数据结构。
 const struct clk_init_data *init;
};

struct clk_init_data {
  //该clock设备的名字
 const char  *name;
  //clock provider driver进行具体的 HW 操作
 const struct clk_ops *ops;
  //描述该clk_hw的拓扑结构
 const char  * const *parent_names;
 const struct clk_parent_data *parent_data;
 const struct clk_hw  **parent_hws;
 u8   num_parents;
 unsigned long  flags;
};

Take the fixed frequency vibrator fixed rate as an example. Its data structure is:

struct clk_fixed_rate {
  //下面是fixed rate这种clock device特有的成员
  struct        clk_hw hw;
  //基类
  unsigned long    fixed_rate;
  unsigned long    fixed_accuracy;
  u8        flags;
};

This is probably the case for other specific clock devices, so I won’t go into details here.

Here is a diagram describing the relationship between these data structures:

一文搞懂 | Linux 时钟子系统

way to register

After understanding the data structure, let’s look at the registration method of each type of clock device.

1. fixed rate clock

This type of clock has a fixed frequency. It cannot be switched on or off, the frequency cannot be adjusted, and the parent cannot be selected. It is the simplest type of clock. It can be supported directly through DTS configuration. You can also register a fixed rate clock directly through the interface, as follows:

CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);

struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
                const char *parent_name, unsigned long flags,
                unsigned long fixed_rate);

2. gate clock

This type of clock can only be turned on and off (.enable/.disable callbacks will be provided), and you can use the following interface to register:

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);

3. divider clock

This type of clock can set the frequency division value (therefore providing .recalc_rate/.set_rate/.round_rate callbacks), which can be registered through the following two interfaces:

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);
                
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);

4. mux clock

This type of clock can select multiple parents because it will implement the .get_parent/.set_parent/.recalc_rate callback and can be registered through the following two interfaces:

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);
                
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);

5. fixed factor clock

This type of clock has a fixed factor (i.e. multiplier and divider). The frequency of the clock is the frequency of the parent clock, multiplied by mul, and divided by div. It is mostly used for some clocks with fixed frequency division coefficients. Since the frequency of the parent clock can be changed, the frequency of the fix factor clock can also be changed, so callbacks such as .recalc_rate/.set_rate/.round_rate are also provided. You can register through the following interface:

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);

6. composite clock

As the name suggests, it is a combination of mux, divider, gate and other clocks. It can be registered through the following interface:

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);

These registered functions will eventually be registered in the Common Clock Framework through the function clk_register, returning a struct clk pointer. As follows:

一文搞懂 | Linux 时钟子系统

Then save the returned struct clk pointer in an array, and call the of_clk_add_provider interface to inform the Common Clock Framework.

Clock Consumer

获取 clock

即通过 clock 名称获取 struct clk 指针的过程,由 clk_get、devm_clk_get、clk_get_sys、of_clk_get、of_clk_get_by_name、of_clk_get_from_provider 等接口负责实现,这里以 clk_get 为例,分析其实现过程:

struct clk *clk_get(struct device *dev, const char *con_id)
{
 const char *dev_id = dev ? dev_name(dev) : NULL;
 struct clk *clk;

 if (dev) {
  //通过扫描所有“clock-names”中的值,和传入的name比较,如果相同,获得它的index(即“clock-names”中的

第几个),调用of_clk_get,取得clock指针。
  clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
  if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
   return clk;
 }

 return clk_get_sys(dev_id, con_id);
}
struct clk *of_clk_get(struct device_node *np, int index)
{
        struct of_phandle_args clkspec;
        struct clk *clk;
        int rc;
 
        if (index return ERR_PTR(-EINVAL);
 
        rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
                                        &clkspec);
        if (rc)
                return ERR_PTR(rc);
       //获取clock指针
        clk = of_clk_get_from_provider(&clkspec);
        of_node_put(clkspec.np);
        return clk;
}

of_clk_get_from_provider 通过便利 of_clk_providers 链表,并调用每一个 provider 的 get 回调函数,获取 clock 指针。如下:

struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
{
        struct of_clk_provider *provider;
        struct clk *clk = ERR_PTR(-ENOENT);
 
        /* Check if we have such a provider in our array */
        mutex_lock(&of_clk_lock);
        list_for_each_entry(provider, &of_clk_providers, link) {
                if (provider->node == clkspec->np)
                        clk = provider->get(clkspec, provider->data);
                if (!IS_ERR(clk))
                        break;
        }
        mutex_unlock(&of_clk_lock);
 
        return clk;
}

至此,ConsumerProvider 里讲的 of_clk_add_provider 对应起来了。

操作 clock

//启动clock前的准备工作/停止clock后的善后工作。可能会睡眠。
int clk_prepare(struct clk *clk)
void clk_unprepare(struct clk *clk)
 
//启动/停止clock。不会睡眠。
static inline int clk_enable(struct clk *clk)
static inline void clk_disable(struct clk *clk)

//clock频率的获取和设置
static inline unsigned long clk_get_rate(struct 
clk *clk)
static inline int clk_set_rate(struct clk *clk, unsigned long rate)
static inline long clk_round_rate(struct clk *clk, unsigned long rate)

//获取/选择clock的parent clock
static inline int clk_set_parent(struct clk *clk, struct clk *parent)
static inline struct clk *clk_get_parent(struct clk *clk)
 
//将clk_prepare和clk_enable组合起来,一起调用。将clk_disable和clk_unprepare组合起来,一起调用
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)

总结

一文搞懂 | Linux 时钟子系统

The above is the detailed content of Understand in one article | Linux clock subsystem. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:lxlinux.net. If there is any infringement, please contact admin@php.cn delete