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