一文搞懂 Linux 時鐘子系統
Clock 時鐘就是 SoC 中的脈搏,由它來控制各個部件按各自的節奏跳動。比如,CPU 主頻設置,串口的波特率設置,I2S 的採樣率設置,I2C 的速率設置等等。這些不同的 clock 設置,都需要從某個或某幾個時鐘源頭而來,最終開枝散葉,形成一顆時鐘樹。可通過 cat /sys/kernel/debug/clk/clk_summary 查看這棵時鐘樹。
內核中用 CCF 框架來管理 clock,如下所示,右邊是 clock 提供者,即 Clock Provider;中間是 CCF;左邊是設備驅動的 clock 使用者,即 Clock Consumer。
Clock Provider
-
根節點一般是 Oscillator(有源振盪器)或者 Crystal(無源振盪器)。
-
中間節點有很多種,包括 PLL(鎖相環,用於提升頻率的),Divider(分頻器,用於降頻的),Mux(從多個 clock path 中選擇一個),Gate(用來控制 ON/OFF 的)。
-
葉節點是使用 clock 做爲輸入的、有具體功能的 HW block。
根據 clock 的特點,clock framework 將 clock 分爲 fixed rate、gate、devider、mux、fixed factor、composite 六類。
數據結構
上面六類本質上都屬於 clock device,內核把這些 clock HW block 的特性抽取出來,用 struct clk_hw 來表示,具體如下:
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;
};
以固定頻率的振動器 fixed rate 爲例,它的數據結構是:
struct clk_fixed_rate {
//下面是fixed rate這種clock device特有的成員
struct clk_hw hw;
//基類
unsigned long fixed_rate;
unsigned long fixed_accuracy;
u8 flags;
};
其他的特定的 clock device 大概都是如此,這裏就不贅述了。
這裏用一張圖描述這些數據結構之間的關係:
註冊方式
理解了數據結構,我們再看下每類 clock device 的註冊方式。
1. fixed rate clock
這一類 clock 具有固定的頻率,不能開關、不能調整頻率、不能選擇 parent,是最簡單的一類 clock。可以直接通過 DTS 配置的方式支持。也可以通過接口,可以直接註冊 fixed rate clock,如下:
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
這一類 clock 只可開關(會提供. enable/.disable 回調),可使用下面接口註冊:
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
這一類 clock 可以設置分頻值(因而會提供. recalc_rate/.set_rate/.round_rate 回調),可通過下面兩個接口註冊:
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
這一類 clock 可以選擇多個 parent,因爲會實現. get_parent/.set_parent/.recalc_rate 回調,可通過下面兩個接口註冊:
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
這一類 clock 具有固定的 factor(即 multiplier 和 divider),clock 的頻率是由 parent clock 的頻率,乘以 mul,除以 div,多用於一些具有固定分頻係數的 clock。由於 parent clock 的頻率可以改變,因而 fix factor clock 也可該改變頻率,因此也會提供. recalc_rate/.set_rate/.round_rate 等回調。可通過下面接口註冊:
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
顧名思義,就是 mux、divider、gate 等 clock 的組合,可通過下面接口註冊:
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);
這些註冊函數最終都會通過函數 clk_register 註冊到 Common Clock Framework 中,返回爲 struct clk 指針。如下所示:
然後將返回的 struct clk 指針,保存在一個數組中,並調用 of_clk_add_provider 接口,告知 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 < 0)
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;
}
至此,Consumer 與 Provider 裏講的 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)
總結
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PkEUp3WYFIZHdPstvYJ0eA