Linux clock subsystem

Clock clock is the pulse in SoC, which controls each component to beat according to its own rhythm. For example, CPU main 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 finally open branches and leaves to form a clock tree. You can use cat /sys/kernel/debug/clk/clk_summary view the clock tree.

The CCF framework is used to manage clocks in the kernel, as shown below. On the right is the Clock Provider, that is, the Clock Provider; In the middle is CCF; On the left is the device driven clock user, namely Clock Consumer.

Clock Provider

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

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

data structure

The above six types essentially belong to clock device. The kernel extracts the characteristics of these clock HW block s and uses struct clk_hw, as follows:

struct clk_hw {
  //Point to the corresponding clock device instance in the CCF module
 struct clk_core *core;
  //CLK is access CLK_ An instance of core. Whenever a consumer passes CLK_ When get initiates access to the clock device (that is, clk_core) in CCF, it needs to obtain a handle, that is, CLK
 struct clk *clk;
  //clock provider driver initialization data, which is used to initialize CLK_ CLK corresponding to HW_ Core data structure.
 const struct clk_init_data *init;
};

struct clk_init_data {
  //The name of the clock device
 const char  *name;
  //clock provider driver performs specific HW operations
 const struct clk_ops *ops;
  //Describe the CLK_ Topology of 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 rate vibrator as an example. Its data structure is:

struct clk_fixed_rate {
  //The following are the unique members of clock device such as fixed rate
  struct        clk_hw hw;
  //Base class
  unsigned long    fixed_rate;
  unsigned long    fixed_accuracy;
  u8        flags;
};

This is probably the case for other specific clock device s, which will not be repeated here.

Here, a diagram is used to describe the relationship between these data structures:

Registration method

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

1. fixed rate clock

This kind of clock has a fixed frequency and cannot be switched, adjusted or selected as a parent. It is the simplest kind of clock. It can be directly supported through DTS configuration. You can also register 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 kind of clock can only be switched (a. enable/.disable callback will be provided), and can be registered using the following interface:

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 kind of clock can set the frequency division value (so it will provide a. recalc_rate/.set_rate/.round_rate callback). It 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 kind of clock can select multiple parents because it will be implemented get_ parent/. set_ parent/. recalc_ The rate callback 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 kind of clock has fixed factors (i.e. multiplier and divider). The frequency of the clock is multiplied by mul and divided by div by the frequency of the parent clock. It is mostly used for some clocks with fixed frequency division coefficient. Since the frequency of parent clock can be changed, fix factor clock can also change the frequency, so it will also be provided recalc_rate/.set_rate/.round_rate and other callbacks. You can register through the following interfaces:

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 clock s, which 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 registration functions will eventually pass through the function clk_register is registered in the Common Clock Framework and returned as a struct CLK pointer. As follows:

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

Clock Consumer

Get clock

That is, the process of obtaining the struct clk pointer through the clock name_ get,devm_clk_get,clk_get_sys,of_clk_get,of_clk_get_by_name,of_clk_get_ from_ Providers and other interfaces are responsible for the implementation. Here, CLK is used_ Take get as an example to analyze its implementation process:

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) {
  //By scanning the values in all "clock names" and comparing them with the passed in name, if they are the same, obtain its index (i.e. the number of "clock names") and call of_clk_get, get the clock pointer.
  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 < 0)
                return ERR_PTR(-EINVAL);
 
        rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
                                        &clkspec);
        if (rc)
                return ERR_PTR(rc);
       //Get clock pointer
        clk = of_clk_get_from_provider(&clkspec);
        of_node_put(clkspec.np);
        return clk;
}

of_clk_get_from_provider through convenience_ clk_ Providers linked list, and call the get callback function of each provider to get the clock pointer. As follows:

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

So far, the of in Consumer and Provider_ clk_ add_ The Provider corresponds.

Operation clock

//Preparations before starting the clock / aftermath after stopping the clock. May sleep.
int clk_prepare(struct clk *clk)
void clk_unprepare(struct clk *clk)
 
//Start / stop clock. No sleep.
static inline int clk_enable(struct clk *clk)
static inline void clk_disable(struct clk *clk)

//Acquisition and setting of clock frequency
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)

//Gets / selects the parent clock of the clock
static inline int clk_set_parent(struct clk *clk, struct clk *parent)
static inline struct clk *clk_get_parent(struct clk *clk)
 
//Set clk_prepare and clk_enable is combined and called together. Set clk_disable and clk_unprepare is combined and called together
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)

summary

Added by tripleaaa on Tue, 04 Jan 2022 21:01:51 +0200