加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • Clock Provider
    • Clock Consumer
    • 總結(jié)
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

一文搞懂 | Linux 時(shí)鐘子系統(tǒng)

2021/12/28
1514
閱讀需 21 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

Clock 時(shí)鐘就是 SoC 中的脈搏,由它來(lái)控制各個(gè)部件按各自的節(jié)奏跳動(dòng)。比如,CPU主頻設(shè)置,串口的波特率設(shè)置,I2S的采樣率設(shè)置,I2C的速率設(shè)置等等。這些不同的clock設(shè)置,都需要從某個(gè)或某幾個(gè)時(shí)鐘源頭而來(lái),最終開(kāi)枝散葉,形成一顆時(shí)鐘樹。可通過(guò) cat /sys/kernel/debug/clk/clk_summary 查看這棵時(shí)鐘樹。

內(nèi)核中用 CCF 框架來(lái)管理 clock,如下所示,右邊是 clock 提供者,即 Clock Provider;中間是 CCF;左邊是設(shè)備驅(qū)動(dòng)的 clock 使用者,即 Clock Consumer。

Clock Provider

  • 根節(jié)點(diǎn)一般是 Oscillator(有源振蕩器)或者 Crystal(無(wú)源振蕩器)。中間節(jié)點(diǎn)有很多種,包括 PLL(鎖相環(huán),用于提升頻率的),Divider(分頻器,用于降頻的),Mux(從多個(gè)clock path中選擇一個(gè)),Gate(用來(lái)控制ON/OFF的)。葉節(jié)點(diǎn)是使用 clock 做為輸入的、有具體功能的 HW block。

根據(jù) clock 的特點(diǎn),clock framework 將 clock 分為 fixed rate、gate、devider、mux、fixed factor、composite 六類。

 

數(shù)據(jù)結(jié)構(gòu)

上面六類本質(zhì)上都屬于clock device,內(nèi)核把這些 clock HW block 的特性抽取出來(lái),用 struct clk_hw 來(lái)表示,具體如下:

struct clk_hw {
  //指向CCF模塊中對(duì)應(yīng) clock device 實(shí)例
 struct clk_core *core;
  //clk是訪問(wèn)clk_core的實(shí)例。每當(dāng)consumer通過(guò)clk_get對(duì)CCF中的clock device(也就是clk_core)發(fā)起訪問(wèn)的時(shí)候都需要獲取一個(gè)句柄,也就是clk
 struct clk *clk;
  //clock provider driver初始化時(shí)的數(shù)據(jù),數(shù)據(jù)被用來(lái)初始化clk_hw對(duì)應(yīng)的clk_core數(shù)據(jù)結(jié)構(gòu)。
 const struct clk_init_data *init;
};

struct clk_init_data {
  //該clock設(shè)備的名字
 const char  *name;
  //clock provider driver進(jìn)行具體的 HW 操作
 const struct clk_ops *ops;
  //描述該clk_hw的拓?fù)浣Y(jié)構(gòu)
 const char  * const *parent_names;
 const struct clk_parent_data *parent_data;
 const struct clk_hw  **parent_hws;
 u8   num_parents;
 unsigned long  flags;
};

以固定頻率的振動(dòng)器 fixed rate 為例,它的數(shù)據(jù)結(jié)構(gòu)是:

struct clk_fixed_rate {
  //下面是fixed rate這種clock device特有的成員
  struct        clk_hw hw;
  //基類
  unsigned long    fixed_rate;
  unsigned long    fixed_accuracy;
  u8        flags;
};

其他的特定的clock device大概都是如此,這里就不贅述了。

這里用一張圖描述這些數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系:

 

注冊(cè)方式

理解了數(shù)據(jù)結(jié)構(gòu),我們?cè)倏聪旅款?clock device 的注冊(cè)方式。

1. fixed rate clock

這一類clock具有固定的頻率,不能開(kāi)關(guān)、不能調(diào)整頻率、不能選擇parent,是最簡(jiǎn)單的一類clock。可以直接通過(guò) DTS 配置的方式支持。也可以通過(guò)接口,可以直接注冊(cè) 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只可開(kāi)關(guān)(會(huì)提供.enable/.disable回調(diào)),可使用下面接口注冊(cè):

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可以設(shè)置分頻值(因而會(huì)提供.recalc_rate/.set_rate/.round_rate回調(diào)),可通過(guò)下面兩個(gè)接口注冊(cè):

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可以選擇多個(gè)parent,因?yàn)闀?huì)實(shí)現(xiàn).get_parent/.set_parent/.recalc_rate回調(diào),可通過(guò)下面兩個(gè)接口注冊(cè):

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,多用于一些具有固定分頻系數(shù)的clock。由于parent clock的頻率可以改變,因而fix factor clock也可該改變頻率,因此也會(huì)提供.recalc_rate/.set_rate/.round_rate等回調(diào)??赏ㄟ^(guò)下面接口注冊(cè):

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的組合,可通過(guò)下面接口注冊(cè):

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

這些注冊(cè)函數(shù)最終都會(huì)通過(guò)函數(shù) clk_register 注冊(cè)到 Common Clock Framework 中,返回為 struct  clk 指針。如下所示:

然后將返回的 struct clk 指針,保存在一個(gè)數(shù)組中,并調(diào)用 of_clk_add_provider 接口,告知 Common Clock Framework。

Clock Consumer

獲取 clock

即通過(guò) clock 名稱獲取 struct clk 指針的過(guò)程,由 clk_get、devm_clk_get、clk_get_sys、of_clk_get、of_clk_get_by_name、of_clk_get_from_provider 等接口負(fù)責(zé)實(shí)現(xiàn),這里以 clk_get 為例,分析其實(shí)現(xiàn)過(guò)程:

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) {
  //通過(guò)掃描所有“clock-names”中的值,和傳入的name比較,如果相同,獲得它的index(即“clock-names”中的第幾個(gè)),調(diào)用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 通過(guò)便利 of_clk_providers 鏈表,并調(diào)用每一個(gè) provider 的 get 回調(diào)函數(shù),獲取 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 對(duì)應(yīng)起來(lái)了。

操作 clock

//啟動(dòng)clock前的準(zhǔn)備工作/停止clock后的善后工作??赡軙?huì)睡眠。
int clk_prepare(struct clk *clk)
void clk_unprepare(struct clk *clk)
 
//啟動(dòng)/停止clock。不會(huì)睡眠。
static inline int clk_enable(struct clk *clk)
static inline void clk_disable(struct clk *clk)

//clock頻率的獲取和設(shè)置
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組合起來(lái),一起調(diào)用。將clk_disable和clk_unprepare組合起來(lái),一起調(diào)用
static inline int clk_prepare_enable(struct clk *clk)
static inline void clk_disable_unprepare(struct clk *clk)

總結(jié)

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

針對(duì)嵌入式人工智能,物聯(lián)網(wǎng)等專業(yè)技術(shù)分享和交流平臺(tái),內(nèi)容涉及arm,linux,android等各方面。

Arm64 ?;厮?>
				</a>
							</li>
						<li id= 查看更多