Common Clock Framework系統結構--wowo

一、前言

之前,wowo同學已經發表了關於CCF(Common Clock Framework)的三份文檔,相信大家對CCF有一定的瞭解了,本文就是在閱讀那三份文檔的基礎上,針對Linux 4.4.6內核的內核代碼實現,記錄自己對CCF的理解,並對CCF進行系統結構層面的歸納和整理。

本文內容包括三個部分

  • 第二章給出了整個CCF相關的block diagram圖
  • 隨後在第三章對各個模塊進行功能層面的描述
  • 最後,第四章給出了各個block之間的接口描述

另外,在閱讀CCF代碼的過程中,我準備用兩份文檔來分享我對CCF的理解。這一份是系統結構,另外一份是邏輯解析。

二、軟件框架

1、CCF 相關的block diagram

CCF的框架如下圖所示:

clk-arch

上圖中,位於圖片中心的那個青色的Block就是CCF模塊,和硬件無關,實現了通用的clock設備的邏輯。其他的軟件模塊都不屬於CCF的範疇,但是會和CCF有接口進行通信。隨後的兩個章節會對各個block以及接口進行描述。

三、軟件框架圖中各個block的功能描述

1、HW clock device tree

Clock device是指那些能夠產生clock信號、控制clock信號的設備,例如晶振、分頻器什麼的,本小節主要是從HW 工程師的角度來描述這些設備極其拓撲結構。系統中的clock distribution形成了類似文件系統那樣的樹狀結構,和文件系統樹不同的是:clock tree有多個根節點,形成多個clock tree,而文件系統樹只有一個根節點;文件系統樹的中間節點是目錄,葉節點是文件,而clock tree相對會複雜一些,它包括如下的的節點

  • (1)根節點一般是Oscillator(有源振盪器)或者Crystal(無源振盪器,即大家經常說的晶振)。

  • (2)中間節點有很多種,包括PLL(鎖相環,用於提升頻率的),Divider(分頻器,用於降頻的),mux(從多個clock path中選擇一個),開關(用來控制ON/OFF的)。

  • (3)葉節點是使用clock做爲輸入的、有具體功能的HW block。

瞭解了clock tree的結構之後,我們來看看具體的操作。對於葉節點,或者說clock consumer而言,沒有什麼可以控制的,HW block只是享用這個clock source而已。這裏需要注意的是:雖然HW clock的datasheet往往有clock gating的內容(或者一些針對clock source進行分頻的內容,概念類似),但是,本質上負責clock gating的那個硬件模塊需要獨立出來,稱爲爲clock tree中的一箇中間節點。而對中間節點的設定包括:

(1)ON/OFF控制

(2)頻率設定、相位控制等

(3)從衆多輸入的clock source中選擇一個做爲輸出。

2、HW-specific Clock provider driver

這個模塊是真正和系統中實際的clock device打交道的模塊。與其說Clock provider driver,不如說是clock provider drivers(複數),主要用來驅動系統中的clock tree中的各個節點上clock device(不包括clock consumer device),只要該HW block對外提供時鐘信號,那麼它就是一個clock provider,就有對應的clock provider driver。

上一小節我們已經瞭解到,系統中的clock device非常多種,什麼VCO啦、什麼分頻器啦,什麼複用器啦,其功能各不相同,但是本質上都屬於clock device,Linux kernel把這些clock HW block的特性抽取出來,用struct clk_hw 來表示,具體如下:

struct clk_hw { 
    struct clk_core *core;--------(1struct clk *clk;-----------(2const struct clk_init_data *init;----(3};
  • (1)指向CCF模塊中對應clock device實例。由於系統中的clk_hw和clk_core實例是一一對應的,因此,struct clk_core中也有指回clk_hw的數據成員。

  • (2)clk是訪問clk_core的實例,每當consumer通過clk_get對CCF中的clock device(也就是clk_core)發起訪問的時候都需要獲取一個句柄,也就是clk。每一個用戶訪問都會有一個clk句柄,同樣的,底層模塊對其訪問亦然。因此,這裏clk是底層clk_hw訪問clk_core的句柄實例。

  • (3)在底層clock provider driver初始化的過程中,會調用clk_register接口函數註冊clk_hw。當然,這時候需要設定一些初始數據,而這些初始數據被抽象成一個struct clk_init_data數據結構。在初始化過程中,clk_init_data的數據被用來初始化clk_hw對應的clk_core數據結構,當初始化完成之後,clk_init_data則沒有存在的意義了,具體struct clk_init_data的定義如下:

struct clk_init_data { 
    const char        *name;--------------(1const struct clk_ops    *ops;------------(2const char        * const *parent_names;-------(3) 
    u8            num_parents; 
    unsigned long        flags;--------------(4};
  • (1)該clock設備的名字。

  • (2)consumer訪問clock設備的時候往往是首先獲取clk句柄,然後找到clk_core,之後即可通過clk_core的struct clk_ops中的callback函數可以進入具體的clock provider driver層進行具體的HW 操作(比如 .enable/.disable)。struct clk_ops這個數據結構是底層驅動必須要準備好的數據之一,在註冊的時候通過clk_init_data帶入CCF層。

  • (3)描述該clk_hw的拓撲結構,通過這樣的信息,CCF層可以建立clk_core的拓撲結構來跟蹤實際clock設備的拓撲。

  • (4)CCF級別的flag。

struct clk_hw應該是clock device的基類,所有的具體的clock device都應該由它派生出來,例如固定頻率的振動器,它的數據結構是:

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

其他的特定的clock device大概都是如此,這裏就不贅述了。在構建自己硬件平臺的clock provider驅動的時候,如果能夠使用clock device的派生類,儘量不要使用struct clk_hw這個基類,儘量不要重複造輪子。

3、CCF模塊

CCF模塊,wowo的文章已經說了很多,我這裏從文件的角度來說一說該模塊。CCF模塊的文件彙整如下:

文件名 類別 對應的框架圖中的元素 描述
clk.h 接口 common clock interface , clk devm interface 爲各種clock consumer模塊提供通用的clock控制接口
clkdev.h 接口 clkdev interface 爲各種clock provider模塊提供的clkdev接口,主要用於consumer尋找CCF中的clk數據結構。
clk-provider.h 接口 common provider interface ,specific provider interface 爲各種硬件相關的的clock provider模塊提供註冊、註銷的接口。
clk.c 模塊 clk CCF的核心模塊,對上提供查找,管理各種clk實例的功能,對內,提供管理和各種clock device(struct clk_core)的功能,對下,接收來自底層硬件的註冊註銷請求,維護clk、clk_core和clk_hw之間的連接。
clkdev.c 模塊 devclk 該模塊主要用來維護clk結構和其對應的clk名字之間的關係,用於clk的lookup。
clk-devres.c 模塊 clk-devres 設備自動管理clk資源模塊(參考device resource management)
clk-conf.c 模塊 clk-conf 該模塊主要用來實現在啓動階段的時候,通過分析DTS來設定一下default參數
clk-xxx.c 模塊 clk-xxx 這些模塊包括:clk-divider.c clk-fixed-factor.c clk-fixed-rate.c clk-gate clk-multiplier.c clk-mux.c clk-composite.c clk-fractional-divider.c clk-gpio.c。這些都是CCF模塊對某一類clock設備進行抽象,方便具體clock provider driver工程師撰寫driver的時候不至於從頭開始。
drivers/clk目錄其他文件 N/A N/A 這些文件都是具體硬件平臺上的各種clock provider 驅動

此外,CCF模塊的核心數據結構就有兩個:

(1)struct clk_core。這個數據結構是CCF層對clock device的抽象,每一個實際的硬件clock device(struct clk_hw)都會對應一個clk_core,CCF模塊負責建立整個抽象的clock tree的樹狀結構並維護這些數據。具體如何維護呢?這裏不得不給出幾個鏈表頭的定義,如下:

static HLIST_HEAD(clk_root_list); 
static HLIST_HEAD(clk_orphan_list);

CCF layer有2條全局的鏈表:clk_root_listclk_orphan_list。所有設置了CLK_IS_ROOT屬性的clock 都會掛在clk_root_list中,而這個鏈表中的每一個階段又展成一個樹狀結構。(這和硬件拓撲是吻合的,clock tree實際上是有多個根節點的,多條樹狀結構)。其它clock,如果有valid的parent ,則會掛到parent的“children”鏈表中,如果沒有valid的parent,則會掛到clk_orphan_list中。

(2)struct clk。這個數據結構和consumer的訪問有關,基本上,每一個user對clock device的訪問都會創建一個訪問句柄,這個句柄就是clk。不同的user訪問同樣的clock device的時候,雖然是同一個struct clk_core實例,但是其訪問的句柄(clk)是不一樣的。CCF如何管理clk呢?這裏說來就話長了,在DTS還沒有引入kenrel的時代,struct clk_lookup用來管理clk數據,具體該數據結構定義如下:

struct clk_lookup { 
    struct list_head    node;-------掛入clocks鏈表 
    const char        *dev_id;-------dev_id和con_id是用來尋找適合的clk的 
    const char        *con_id; 
    struct clk        *clk;---------對應的clk 
    struct clk_hw        *clk_hw; 
};

對於底層的clock provider驅動而言,除了調用clk_register函數註冊到common clock framework中,還會調用clk_register_clkdev將該clk和一個名字捆綁起來(否則clock consumer並不知道如何定位到該clk),在CCF layer,clocks全局鏈表用來維護系統中所有的struct clk_lookup實例。通過這樣的機制,clock consumer可以通過名字獲取clk。

引入device tree之後,情況發生了一些變化。基本上每一個clock provider都會變成dts中的一個節點,也就是說,每一個clk都有一個設備樹中的device node與之對應。在這種情況下,與其捆綁clk和一個“名字”,不如捆綁clk和device node,具體的數據結構如下

struct of_clk_provider { 
    struct list_head link; ------掛入of_clk_providers全局鏈表

    struct device_node *node;----該clock device的DTS節點 
    struct clk *(*get)(struct of_phandle_args *clkspec, void *data);--獲取對應clk數據結構的函數 
    void *data; 
};

因此,對於底層provider driver而言,原來的clk_register + clk_register_clkdev的組合變成了clk_register + of_clk_add_provider的組合,在CCF layer保存了of_clk_providers全局鏈表來管理所有的DTS節點和clk的對應關係。

我們再看clock consumer這一側:這時候,使用名字檢索clk已經過時了,畢竟已經有了強大的device tree。我們可以通過clock consumer對應的struct device_node尋找爲他提供clock signal那個clock設備對應的device node(clock屬性和clock-names屬性),當然,如果consumer有多個clock signal來源,那麼在尋找的時候需要告知是要找哪一個時鐘源(用connection ID標記)。當找了provider對應的device node之後,一切都變得簡單了,從全局的clock provide鏈表中找到對應clk就OK了。在引入強大的設備樹之後,clkdev模塊按理說應該退出歷史舞臺了。

4、clock consumer driver

對於clock consumer driver而言,它其實就是調用CCF接口和DTS的接口來完成下面的功能:

(1)初始化的時候,調用common clk interface來對其clock設備(如果有需要,可能波及clock設備的上游設備)進行設定,以便讓該HW block正常運作起來。

(2)根據用戶需求(例如用戶修改波特率),在運行時,clock consumer driver也會調用common clk interface來對其clock設備進行修改(例如修改clock rate,相位等)

(3)配合系統電源管理(TODO)。

5、設備樹

具體請參考本站的相關文檔。

四、接口描述

1、consumer dts interface

clock consumer的屬性列表如下:

屬性 是否必須提供? 描述
clocks 必須 該屬性描述clock consumer設備使用的clock source,或者clock input(可能不止一個哦)。 該屬性是一個數組,數組中每一個具體的entry對應一個clock source。而clock source是由phandle和clock specifier來描述。phandle指向一個clock provider的device node,如果該provider的#clock-cells等於0,那麼說明該provider就一個output,那麼就不需要clock specifier來進一步描述。如果該provider的#clock-cells不等於0,那麼clock specifier必須提供,以便指明本設備到底使用provider輸出時鐘源的哪一路。
clock-names 可選 同樣的,該屬性也似描述設備使用的clock source信息的,也是一個數組,是一個字符串數組,每一個字符串描述一個clock source,對應着clocks中phandle和clock specifier。 之所以提供clock-names這個屬性其實是爲了編程方便,驅動程序可以通過比較直觀的clock name來找到該設備的輸入時鐘源信息。
clock-ranges 可選 該屬性值爲空,主要用來說明該設備的下級設備可以繼承該設備的clock source。例如B設備是A設備的sub node,A設備如果有clock-ranges屬性,那麼B設備在尋找其clock source的時候,如果在本node定義的clock相關屬性中沒有能夠找到,那麼可以去A設備去繼續尋找(也就是說,B設備會繼承A設備的clock source相關的屬性,也就是clocks或者clock-names這兩個屬性)。

2、provider dts interface

clock provider的屬性列表如下:

屬性 是否必須提供? 描述
#clock-cells 必須 我們上面說過了,一個HW block(clock consumer)的時鐘源可以通過phandle和clock specifier來描述,phandle指向一個clock provider的device node,很明確,但是定位到clock provider並不行,因此有的provider會提供多路clock output給其他HW block使用,因此需要所謂的clock specifier來進一步描述。這裏#clock-cells就是說明使用多少個cell(u32)來描述clock specifier。 如果等於0,說明provider就一個clock output,不需要specifier,如果等於1,說明provider有多個clock output(能用u32標識)。等於2或者更大的情況應該不存在,一個provider不可能提供超過2^32個clock output。
clock-output-names 可選 如果clock provider能提供多路時鐘輸出,那麼給每一個clock output起個適合人類閱讀的名字是不錯的選擇,這也就是clock-output-names的目的。clock consumer中提供的clock specifier是一個index,通過這個index可以在clock-output-names屬性值中找到對應的時鐘源的名字。
clock-indices 可選 如果不提供這個屬性,那麼clock-output-names和index的對應關係就是0,1,2……。如果這個對應關係不是線性的,那麼可以通過clock-indices屬性來定義映射到clock-output-names的index。

3、clock config interface

初始化時可以通過dts來設定clock parent以及clock rate,具體屬性如下:

屬性 是否必須提供? 描述
assigned-clocks 可選 這個屬性列出了需要進行設定的clock,其值是一個phandle+clock specifier數組
assigned-clock-parent 可選 準備要設定的parent列表。“兒子”在哪裏呢?assigned-clocks中定義的,注意,是一一對應的。例如: 1. assigned-clocks:A, B,C; 2.assigned-clock-parent:A_parent,B_parent,C_parent;
assigned-clock-rate 可選 要設定的頻率列表,同樣的,和assigned-clocks也是一一對應的。

4、其他接口

請參考窩窩同學的文檔,這裏不再贅述。

五、參考文獻

[1] Documentation/devicetree/bindings/clock/clock-bindings.txt

[2] Documentation/clk.txt

原創文章,轉發請註明出處。蝸窩科技,www.wowotech.net。
標籤: framework clock common

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