在查看zynq的clk時鐘驅動時,在源碼文件clkc.c中我們看到匹配屬性字段”xlnx,ps7-clkc”,該字段匹配zynq-7000.dtsi的時鐘子節點的compatible關鍵字屬性相匹配,時鐘的setup函數為zynq_clk_setup,查看整個源碼包沒有發現有調用該函數的痕跡,但是發現該函數被宏CLK_OF_DECLARE引用了。
首先看一下CLK_OF_DECLARE宏,它的定義位于“include/linux/clk-provider.h”中,負責在指定的section中(以__clk_of_table開始的位置),定義structof_device_id類型的變量,并由of_clk_init(在函數 zynq_timer_init (mach-zynq/common.c)中被調用)接口解析、匹配,如果匹配成功,則執行相應的回調函數(這里為of_fixed_clk_setup);初始化的時候,device tree負責讀取DTS,并和這些變量的名字(這里為" xlnx,ps7-clkc ")匹配,如果匹配成功,則執行相應的回調函數(這里為of_fixed_clk_setup);of_fixed_clk_setup會解析兩個DTS字段"clock-frequency"和"clock-output-names",然后調用clk_register_fixed_rate,注冊clock。注意,注冊時的flags為CLK_IS_ROOT,說明目前只支持ROOT類型的clock通過DTS注冊;最后,調用of_clk_add_provider接口,將該clock添加到provider_list中,方便后續的查找使用。該接口會在后面再詳細介紹。
在of_clk_init被調用之前,zynq_timer_init 先調用了zynq_clock_init函數,該函數實現功能如下:
1. 根據字段“compatible”匹配“xlnx,ps7-clkc”判斷節點np是否存在
2. 判斷np是否有地址內存定義
3. 判斷np是否存在父節點slcr
4. 將節點np的物理地址賦值給全局變量zynq_clkc_base,該變量是void *指針,并通過__iomem修飾,強制定義鏈接區域
5. 通過of_node_put函數將np和slcr的refcount減1,我們查到of_node_put函數的說明,但我發現它調用了kobject_put,該函數簡要說明如下:當一個kobject對象的引用ref被減少到0時,程序就會釋放這個kobject相關的資源,所以在減少引用的函數中就應該有調用釋放資源的相關代碼,在下面內核代碼中也可以看到。
下面我們正式來看下函數of_clk_init,該函數被調用時傳遞進來的參數matches為NULL,該函數具體實現了以下功能:
1. 初始化全局鏈表clk_provider_list,
2. 如果matches為空,就把__clk_of_table的地址賦值給matches,對應我們上面談到的宏CLK_OF_DECLARE
3. 通過宏for_each_matching_node_and_match來捕獲matches指向的of_device_id型指針數組中所有成員,當捕獲數組成員時,執行操作如下:
3.1. 創造一個clock_provider對象
3.2. 把捕獲道德數組成員的data和np指針分別賦值給clock_provider對象的clk_init_cb和np成員
3.3. 最后把clock_provider對象添加到全局鏈表clk_provider_list中
4. 判斷如果clk_provider_list不為空,執行如下操作:
4.1. 遍歷并去除該鏈表中的所有成員,并通過宏定義獲取包含該成員的對象的指針,對象為clock_provider
4.2. 判斷是否是否強制處理,一般我們都處理所有節點,然后判斷clock_provider中np的父節點是否能使用,如果以上判斷成立,執行以下操作:
4.2.1 通過調用clk_provider->clk_init_cb(clk_provider->np),初始化clock_provider中的時鐘節點,具體函數為zynq_clk_setup,此處不做具體討論
4.2.2 接著對父節點和子節點做時鐘匹配(暫時不理解)
4.2.3 摧毀該節點和對象clock_provider
我在此處有一個疑問,為什么大費周章的去創造和銷毀對象clock_provider,為什么不直接處理?希望有讀者來解答一下。
接下來看到函數zynq_clk_setup,由上面看到我們把節點指針np傳遞了進去,具體實現功能如下:
1. 取出np中所有對象clock-output-names的數字中的字符串的指針,這些都是時鐘的名字,在設備樹文件zynq-7000.dtsi中被定義
2. 構建系統時鐘樹,具體如下,先看圖:
由圖中不難看出,ps_clk進來以后直接連接了3個時鐘鎖相環,分別是:ARM PLL、I/O PLL、 DDR PLL,其他所有的時鐘如CPU時鐘和外設時鐘,都是從這幾個模塊中輸出的,也就是為什么,會有代碼cpu_parents[0] = clk_output_name[armpll]等的原因了,至于為什么有些時鐘作為時鐘源使用了2次,比如ARM PLL,這個時鐘除了給CPU提供時鐘以外,還給內部互聯接口提供時鐘,所以引用了2次;而I/O PLL不僅負責PS端的I/O設備,還負責PL部分I/O設備,所以也使用了2次。
3. 接著取出fclk-enable和ps-clk-frequency的32位整型值,其中ps_clk的頻率為33.333333MHz,也可以從原理圖來驗證這一點
4. 通過clk_register_fixed_rate注冊頻率固定的時鐘,此處注冊了ps_clk,類型為CLK_IS_ROOT,作為根時鐘,沒有父節點,temp它的固有頻率;并將注冊結果生成clk 對象指針賦值給全局變量ps_clk,該時鐘會被注冊進全局鏈表clk_root_list中
5. 接下來注冊3個時鐘鎖相環,這里xilinx實現了函數clk_register_zynq_pll,專門用于鎖相環的注冊,以下我們詳細研究一下該函數,以此向下看:
5.1 函數在棧里面構造了類型為clk_init_data的對象initd,包含以下屬性:1. 該鎖相環的名字,2. 父節點的名字,3. 類型為clk_ops的結構體指針,4. 父節點的數量,5. 類型
5.2 構造了類型為 zynq_pll的對象pll,并對該對象進行了初始化
5.3 此處啟動了pll自旋鎖,配置了時鐘寄存器,清除了該寄存器的PLL_BYPASS_QUAL位,此為在硬件啟動時為1,起到關閉BYPASS功能使用的作用;再解鎖自旋鎖
5.4 通過函數clk_register注冊并獲取一個時鐘對象指針,在該函數中會構建一個類型為clk_core對象core,獲取initd的各個成員的值,所以最后initd已經沒有存在的必必要了,所以直接在棧里面構造該對象,這2個PLL時鐘會被提添加到他們父節點parent->children鏈表中
5.5 通過函數clk_register_mux注冊有n個父節點的時鐘,可以顯實現以下的回調,get_parent/.set_parent/.recalc_rate,我們深入看一下這個函數最后會干嘛,進去看了下,和clk_register_zynq_pll類似,最后也是通過clk_register注冊這個時鐘
5.6 以此類推,注冊了ddr pll和io pll
5.7 注冊了cpu_mux,通過clk_register_divider注冊這一類函數可以設置分頻,通過.recalc_rate/.set_rate/.round_rate回調
5.8 通過clk_register_gate注冊了CPU的時鐘源,通過clk_register_gate注冊的時鐘只可以開關,通過.enable/.disable回調
5.9 通過clk_register_fixed_factor注冊了CPU時鐘的鎖相環,這一類clock具有固定的factor(即multiplier和divider),clock的頻率是由parent clock的頻率,乘以mul,除以div,多用于一些具有固定分頻系數的clock。由于parent clock的頻率可以改變,因而fix factor clock也可該改變頻率,因此也會提供.recalc_rate/.set_rate/.round_rate等回調
5.10 以此類推,可以看到此處還使用了函數clk_prepare_enable,用來是能該時鐘的相關功能操作
5.11 以此類推,注冊了timer時鐘、DDR時鐘和Peripheral時鐘,其中的函數zynq_clk_register_periph_clk只是把上面的各種時鐘的注冊方法進行了進一步的封裝
... ...
5.12 函數統一檢測所有注冊的時鐘是否有效
5.13 把時鐘指針數組和數量交給全局變量clk_data
5.14 通過函數of_clk_add_provider把整個時鐘注冊進全局鏈表of_clk_providers,只要有全局變量就能被其他的函數簡單的調用了
這些過程綜合來說,就是就是從設備樹文件,取出各種各樣的時鐘,按照芯片的時鐘樹結構,將他們組合起來,變得和上面的時鐘框圖“一樣”,組成父子關系;接著把這些時鐘的按照特性,通過的相關的函數組織起來,例如是否可以開關、是否集成鎖相環等,防止以后對時鐘進行不允許的操作,當然了需要立刻打開的時鐘就直接打開了;最后把整個時鐘樹作為一個節點注冊到全局鏈表of_clk_add_provider上面(如果沒有注冊全局變量,沒法記錄,也就沒法調用了)。
評論
查看更多