Linux common clock framework(1)_概述--wowo

1. 前言

common clock framework是用來管理系統clock資源的子系統,根據職能,可分爲三個部分:

  • 1)向其它driver提供操作clocks的通用API。

  • 2)實現clock控制的通用邏輯,這部分和硬件無關。

  • 3)將和硬件相關的clock控制邏輯封裝成操作函數集,交由底層的platform開發者實現,由通用邏輯調用。

因此,蝸蝸會將clock framework的分析文章分爲3篇:

  • 第一篇爲概述和通用API的使用說明,面向的讀者是使用clock的driver開發者,目的是掌握怎麼使用clock framework(就是本文);

  • 第二篇爲底層操作函數集的解析和使用說明,面向的讀者是platform clock driver的開發者,目的是掌握怎麼藉助clock framework管理系統的時鐘資源;

  • 第三篇爲clock framework的內部邏輯解析,面向的讀者是linux kernel愛好者,目的是理解怎麼實現clock framework。

注1:任何framework的職能分類都是如此,因此都可以按照這個模式分析。

2. 概述

如今,可運行Linux的主流處理器平臺,都有非常複雜的clock tree,我們隨便拿一個處理器的spec,查看clock相關的章節,一定會有一個非常龐大和複雜的樹狀圖,這個圖由clock相關的器件,以及這些器件輸出的clock組成。下圖是一個示例:
clock

clock相關的器件包括:用於產生clock的Oscillator(有源振盪器,也稱作諧振盪器)或者Crystal(無源振盪器,也稱晶振);用於倍頻的PLL(鎖相環,Phase Locked Loop);用於分頻的divider;用於多路選擇的Mux;用於clock enable控制的與門;使用clock的硬件模塊(可稱作consumer);等等。

**common clock framework**的管理對象,就是上圖藍色字體描述的clock(在軟件中用struct clk抽象,以後就簡稱clk),主要內容包括(不需要所有clk都支持):

  • 1)enable/disable clk。

  • 2)設置clk的頻率。

  • 3)選擇clk的parent,例如hw3_clk可以選擇osc_clk、pll2_clk或者pll3_clk作爲輸入源。

3. common clock framework提供的通用API

管理clock的最終目的,是讓device driver可以方便的使用,這些是通過include/linux/clk.h中的通用API實現的,如下:

1)struct clk結構

一個系統的clock tree是固定的,因此clock的數目和用途也是固定的。假設上面圖片所描述的是一個系統,它的clock包括osc_clk、pll1_clk、pll2_clk、pll3_clk、hw1_clk、hw2_clk和hw3_clk。我們完全可以通過名字,抽象這7個clock,進行開/關、rate調整等操作。但這樣做有一個缺點:不能很好的處理clock之間的級聯關係,如hw2_clk和hw3_clk都關閉後,pll2_clk才能關閉。因此就引入struct clk結構,以鏈表的形式維護這種關係。

  • 同樣的道理,系統的struct clk,也是固定的,由clock driver在系統啓動時初始化完畢,需要訪問某個clock時,只要獲取它對應的struct clk結構即可。怎麼獲取呢?可以通過名字索引啊!很長一段時間內,kernel及driver就是使用這種方式管理和使用clock的。

  • 最後,設備(由struct device表示)對應的clock(由struct clk表示)也是固定的啊,可不可以找到設備就能找到clock?可以,不過需要藉助device tree。

注2:對使用者(device driver)來說,struct clk只是訪問clock的一個句柄,不用關心它內部的具體形態。

2)clock獲取有關的API

device driver在操作設備的clock之前,需要先獲取和該clock關聯的struct clk指針,獲取的接口如下:

   1: struct clk *clk_get(struct device *dev, const char *id);
   2: struct clk *devm_clk_get(struct device *dev, const char *id);
   3: void clk_put(struct clk *clk);
   4: void devm_clk_put(struct device *dev, struct clk *clk);
   5: struct clk *clk_get_sys(const char *dev_id, const char *con_id);
   6:  
   7: struct clk *of_clk_get(struct device_node *np, int index);
   8: struct clk *of_clk_get_by_name(struct device_node *np, const char *name);
   9: struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec);
  • a)clk_get,以device指針或者id字符串(可以看作name)爲參數,查找clock。

    • a1)dev和id的任意一個可以爲空。如果id爲空,則必須有device tree的支持才能獲得device對應的clk;
    • a2)根據具體的平臺實現,id可以是一個簡單的名稱,也可以 是一個預先定義的、唯一的標識(一般在平臺提供的頭文件中定義,如mach/clk.h);
    • a3)不可以在中斷上下文調用。
  • b)devm_clk_get,和clk_get一樣,只是使用了device resource management,可以自動釋放。

  • c)clk_put、devm_clk_put,get的反向操作,一般和對應的get API成對調用。

  • d)clk_get_sys,類似clk_get,不過使用device的name替代device結構。

  • e)of_clk_get、of_clk_get_by_name、of_clk_get_from_provider,device tree相關的接口,直接從相應的DTS node中,以index、name等爲索引,獲取clk,後面會詳細說明。

3)clock控制有關的API

   1: int clk_prepare(struct clk *clk)
   2: void clk_unprepare(struct clk *clk)
   3:  
   4: static inline int clk_enable(struct clk *clk)
   5: static inline void clk_disable(struct clk *clk)
   6:  
   7: static inline unsigned long clk_get_rate(struct clk *clk)
   8: static inline int clk_set_rate(struct clk *clk, unsigned long rate)
   9: static inline long clk_round_rate(struct clk *clk, unsigned long rate)
  10:  
  11: static inline int clk_set_parent(struct clk *clk, struct clk *parent)
  12: static inline struct clk *clk_get_parent(struct clk *clk)
  13:  
  14: static inline int clk_prepare_enable(struct clk *clk)
  15: static inline void clk_disable_unprepare(struct clk *clk)
  • a)clk_enable/clk_disable,啓動/停止clock。不會睡眠

  • b)clk_prepare/clk_unprepare,啓動clock前的準備工作/停止clock後的善後工作。可能會睡眠

  • c)clk_get_rate/clk_set_rate/clk_round_rate,clock頻率的獲取和設置,其中clk_set_rate可能會不成功(例如沒有對應的分頻比),此時會返回錯誤。如果要確保設置成功,則需要先調用clk_round_rate接口,得到和需要設置的rate比較接近的那個值。

  • d)獲取/選擇clock的parent clock。

  • e)clk_prepare_enable,將clk_prepare和clk_enable組合起來,一起調用。clk_disable_unprepare,將clk_disable和clk_unprepare組合起來,一起調用。

注2:prepare/unprepare,enable/disable的說明。

這兩套API的本質,是把clock的啓動/停止分爲atomic和non-atomic兩個階段,以方便實現和調用。因此上面所說的“不會睡眠/可能會睡眠”,有兩個角度的含義:一是告訴底層的clock driver,請把可能引起睡眠的操作,放到prepare/unprepare中實現,一定不能放到enable/disable中;二是提醒上層使用clock的driver,調用prepare/unprepare接口時可能會睡眠哦,千萬不能在atomic上下文(例如中斷處理中)調用哦,而調用enable/disable接口則可放心。

另外,clock的開關爲什麼需要睡眠呢?這裏舉個例子,例如enable PLL clk,在啓動PLL後,需要等待它穩定。而PLL的穩定時間是很長的,這段時間要把CPU交出(進程睡眠),不然就會浪費CPU。

最後,爲什麼會有合在一起的clk_prepare_enable/clk_disable_unprepare接口呢?如果調用者能確保是在non-atomic上下文中調用,就可以順序調用prepare/enable、disable/unprepared,爲了簡單,framework就幫忙封裝了這兩個接口。

4)其它接口

   1: int clk_notifier_register(struct clk *clk, struct notifier_block *nb);
   2: int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);

這兩個notify接口,用於註冊/註銷 clock rate改變的通知。例如某個driver關心某個clock,期望這個clock的rate改變時,通知到自己,就可以註冊一個notify。後面會舉個例子詳細說明。

   1: int clk_add_alias(const char *alias, const char *alias_dev_name, char *id,
   2:                         struct device *dev);

這是一個非主流接口,用於給某個clk起個別名。無論出於何種原因,儘量不要它,保持代碼的簡潔,是最高原則!

4. 通用API的使用說明

結合一個例子(摘錄自“Documentation/devicetree/bindings/clock/clock-bindings.txt”),說明driver怎麼使用clock通用API。

1)首先,在DTS(device tree source)中,指定device需要使用的clock,如下:

   1: /* DTS */
   2: device {
   3:     clocks = <&osc 1>, <&ref 0>;
   4:     clock-names = "baud", "register";
   5: };

該DTS的含義是:

device需要使用兩個clock,“baud”和“regitser”,由clock-names關鍵字指定;

baud取自“osc”的輸出1,register取自“ref”的輸出0,由clocks關鍵字指定。

那麼問題來了,clocks關鍵字中,<&osc 1>樣式的字段是怎麼來的?是由clock的provider,也就是底層clock driver規定的(具體會在下一篇文章講述)。所以使用clock時,一定要找clock driver拿相關的信息(一般會放在“Documentation/devicetree/bindings/clock/”目錄下)。

2)系統啓動後,device tree會解析clock有關的關鍵字,並將解析後的信息放在platform_device相關的字段中。

3)具體的driver可以在probe時,以clock的名稱(不提供也行)爲參數,調用clk get接口,獲取clock的句柄,然後利用該句柄,可直接進行enable、set rate等操作,如下:

   1: /* driver */
   2: int xxx_probe(struct platform_device *pdev)
   3: {
   4:         struct clk *baud_clk;
   5:         int ret; 
   6:  
   7:         baud_clk = devm_clk_get(&pdev->dev, “baud”);
   8:         if (IS_ERR(clk)) {
   9:10:         } 
  11:  
  12:         ret = clk_prepare_enable(baud_clk);
  13:         if (ret) {
  14:15:         } 
  16: }

原創文章,轉發請註明出處。蝸窩科技,www.wowotech.net。

標籤: Linux framework clock API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章