一、clk框架簡(jiǎn)介
linux內(nèi)核中實(shí)現(xiàn)了一個(gè)CLK子系統(tǒng),用于對(duì)上層提供各模塊(例如需要時(shí)鐘信號(hào)的外設(shè),USB等)的時(shí)鐘驅(qū)動(dòng)接口,對(duì)下層提供具體SOC的時(shí)鐘操作細(xì)節(jié):

一般情況下,在可運(yùn)行l(wèi)inux的處理器平臺(tái)中,都存在非常復(fù)雜的時(shí)鐘樹(shù)(clock tree)關(guān)系,也一定會(huì)有一個(gè)非常龐大和復(fù)雜的樹(shù)狀圖,用于描述與時(shí)鐘相關(guān)的器件,以及這些器件輸出的clock關(guān)系。查看手冊(cè)都會(huì)存在類似下列的內(nèi)容:

一款處理器中與時(shí)鐘相關(guān)的器件主要包括有:
用于產(chǎn)生 CLOCK 的 Oscillator(有源振蕩器,也稱作諧振蕩器)或者Crystal(無(wú)源振蕩器,也稱晶振)。
用于倍頻的 PLL(鎖相環(huán),Phase Locked Loop)。
用于分頻的Divider。
用于多路選擇的 MUX。
用于CLOCK ENABLE控制的與門。
使用 CLOCK 的硬件模塊(也可稱為CONSUMER)。
linux內(nèi)核中與clk框架相關(guān)源文件如下:
/include/linux/clk.h /include/linux/clkdev.h /include/linux/clk-provider.h /include/linux/clk-conf.h ------------------------------------------------------ /drivers/clk/clk-devres.c /drivers/clk-bulk.c /drivers/clkdev.c /drivers/clk.c /drivers/clk-divider.c /drivers/clk-fixed-factor.c /drivers/clk-fixed-rate.c /drivers/clk-gate.c /drivers/clk-multiplier.c /drivers/clk-mux.c /drivers/clk-composite.c /drivers/clk-fractional-divider.c /drivers/clk-gpio.c /drivers/clk-conf.c
二、clk框架接口
1、基于linux時(shí)鐘子系統(tǒng)對(duì)接底層時(shí)鐘操作的API
linux時(shí)鐘子系統(tǒng)對(duì)接底層,也就是具體硬件常用的API可視為clk provider常用的接口函數(shù),定義在linux/include/linux/clk-provider.h文件中。不同版本linux內(nèi)核中對(duì)于clk-probider.h實(shí)現(xiàn)的而接口存在出入,參見(jiàn)源碼更進(jìn)一步獲取接口和使用方法。
注冊(cè)/注銷時(shí)鐘
//注冊(cè)一個(gè)新的時(shí)鐘。通常在設(shè)備驅(qū)動(dòng)程序中使用,以向時(shí)鐘框架添加新的時(shí)鐘源。 structclk*clk_register(structdevice*dev,structclk_hw*hw); //帶資源管理注冊(cè)時(shí)鐘 structclk*devm_clk_register(structdevice*dev,structclk_hw*hw); //卸載時(shí)鐘 voidclk_unregister(structclk*clk); //帶資源管理卸載時(shí)鐘 voiddevm_clk_unregister(structdevice*dev,structclk*clk);
2、驅(qū)動(dòng)中常使用的API
芯片廠家會(huì)根據(jù)clk框架,對(duì)下層(即底層硬件)設(shè)計(jì)出接口,以供上層驅(qū)動(dòng)接口調(diào)用,在內(nèi)核中,提供的接口主要由/include/linux/clk.h文件導(dǎo)出,使用這些API接口時(shí),需包含linux/clk.h頭文件:
#include
獲取struct clk指針:
structclk*devm_clk_get(structdevice*dev,constchar*id)(推薦使用,可以自動(dòng)釋放) structclk*clk_get(structdevice*dev,constchar*id) staticinlinestructclk*devm_clk_get_optional(structdevice*dev,constchar*id) //(推薦使用,整組獲取,整組開(kāi)關(guān)) staticinlineint__must_checkdevm_clk_bulk_get(structdevice*dev,intnum_clks,structclk_bulk_data*clks) staticinlineint__must_checkdevm_clk_bulk_get_optional(structdevice*dev,intnum_clks,structclk_bulk_data*clks) staticinlineint__must_checkdevm_clk_bulk_get_all(structdevice*dev,structclk_bulk_data**clks)
獲取/設(shè)置時(shí)鐘頻率
//根據(jù)給定的目標(biāo)頻率計(jì)算最接近的可實(shí)現(xiàn)頻率。這個(gè)函數(shù)通常在設(shè)置時(shí)鐘頻率之前調(diào)用,以確保設(shè)置的頻率是硬件支持的頻率之一。 longclk_round_rate(structclk*clk,unsignedlongrate) //獲取時(shí)鐘頻率 unsignedlongclk_get_rate(structclk*clk) //設(shè)置時(shí)鐘頻率 intclk_set_rate(structclk*clk,unsignedlongrate)
準(zhǔn)備/使能clk:
/*開(kāi)時(shí)鐘前調(diào)用,可能會(huì)造成休眠,所以把休眠部分放到這里,可以原子操作的放到enable里*/ intclk_prepare(structclk*clk) /*停止clock后的善后工作,可能會(huì)睡眠。*/ voidclk_unprepare(structclk*clk) /*原子操作,打開(kāi)時(shí)鐘,這個(gè)函數(shù)必須在產(chǎn)生實(shí)際可用的時(shí)鐘信號(hào)后才能返回,不會(huì)睡眠*/ intclk_enable(structclk*clk) /*原子操作,關(guān)閉時(shí)鐘,不會(huì)睡眠*/ voidclk_disable(structclk*clk)
上述兩套API的本質(zhì),是把CLOCK的啟動(dòng)/停止分為Atomic和Non-atomic兩個(gè)階段,以方便實(shí)現(xiàn)和調(diào)用。因此上面所說(shuō)的“不會(huì)睡眠/可能會(huì)睡眠”,有兩個(gè)角度的含義:
一是告訴底層的CLOCK Driver,需把可能引起睡眠的操作,放到Prepare()/Unprepare()中實(shí)現(xiàn),一定不能放到Enable()/Disable()中;
二是提醒上層使用CLOCK的Driver,調(diào)用Prepare/Unprepare 接口時(shí)可能會(huì)睡眠,千萬(wàn)不能在Atomic上下文(例如內(nèi)部包含Mutex 鎖、中斷關(guān)閉、Spinlock 鎖保護(hù)的區(qū)域)調(diào)用,而調(diào)用Enable()/Disable()接口則可放心。
另外,CLOCK的Enable()/Disable()為什么需要睡眠呢?例如Enable PLL CLOCK,在啟動(dòng)PLL后,需要等待它穩(wěn)定,然而PLL的穩(wěn)定時(shí)間是很長(zhǎng)的,因此這段時(shí)間要需要把CPU讓出(進(jìn)程睡眠),不然就會(huì)浪費(fèi)CPU了。
最后,為什么會(huì)實(shí)現(xiàn)合在一起的clk_prepare_enable()/clk_disable_unprepare()接口呢?如果調(diào)用者能確保是在Non-atomic上下文中調(diào)用,就可以順序調(diào)用prepare()/enable()、disable()/unprepared(),為了方便Colck框架就封裝了這兩個(gè)接口。
備注:使用clk_prepare_enable / clk_disable_unprepare,clk_prepare_enable / clk_disable_unprepare(或者clk_enable / clk_disable) 必須成對(duì),以使引用計(jì)數(shù)正確。
三、CLK核心的數(shù)據(jù)結(jié)構(gòu)和API
1、struct clk_notifier
stuct clk_notifier用于將CLK與通知器進(jìn)行關(guān)聯(lián),也就是定義clk的通知器,基于srcu實(shí)現(xiàn)。該結(jié)構(gòu)實(shí)現(xiàn)如下(/linux/include/linux/clk.h):
structclk_notifier{
structclk*clk;//與該通知器關(guān)聯(lián)的clk。
structsrcu_notifier_headnotifier_head;//用于這個(gè)CLK的blocking_notifier_head通知器。
structlist_headnode;
};
常用API:
//注冊(cè)一個(gè)通知塊(notifierblock),以便在指定時(shí)鐘發(fā)生事件(例如頻率變化)時(shí)接收通知。 intclk_notifier_register(structclk*clk,structnotifier_block*nb); //注銷一個(gè)通知塊 intclk_notifier_unregister(structclk*clk,structnotifier_block*nb); //帶資源管理注冊(cè)一個(gè)通知塊(notifierblock),以便在指定時(shí)鐘發(fā)生事件(如頻率變化)時(shí)接收通知。確保在設(shè)備驅(qū)動(dòng)程序卸載時(shí)自動(dòng)注銷通知塊。 intdevm_clk_notifier_register(structdevice*dev,structclk*clk,structnotifier_block*nb);
2、struct clk_core
struct clk_core為clk框架的私有結(jié)構(gòu),定義如下:
structclk_core{
constchar*name;//clk核心名稱。
conststructclk_ops*ops;//該clk核心對(duì)應(yīng)的ops。
structclk_hw*hw;
structmodule*owner;
structdevice*dev;
structdevice_node*of_node;
structclk_core*parent;
structclk_parent_map*parents;
u8num_parents;
u8new_parent_index;
unsignedlongrate;
unsignedlongreq_rate;
unsignedlongnew_rate;
structclk_core*new_parent;
structclk_core*new_child;
unsignedlongflags;
boolorphan;
boolrpm_enabled;
unsignedintenable_count;
unsignedintprepare_count;
unsignedintprotect_count;
unsignedlongmin_rate;
unsignedlongmax_rate;
unsignedlongaccuracy;
intphase;
structclk_dutyduty;
structhlist_headchildren;
structhlist_nodechild_node;
structhlist_headclks;
unsignedintnotifier_count;
#ifdefCONFIG_DEBUG_FS
structdentry*dentry;
structhlist_nodedebug_node;
#endif
structkrefref;
};
從上述結(jié)構(gòu)的組成元素可知,struct clk_core是struct device的子類,因?yàn)橐豢頢OC的時(shí)鐘關(guān)系一般以“樹(shù)狀”進(jìn)行組織,在struct clk_core中提供描述父clk_core和子clk_core的組成元素。
3、struct clk_ops
struct clk_ops描述硬件時(shí)鐘的回調(diào)操作,并由驅(qū)動(dòng)程序通過(guò)clk_*API調(diào)用。該結(jié)構(gòu)定義如下:
structclk_ops{
int(*prepare)(structclk_hw*hw);
void(*unprepare)(structclk_hw*hw);
int(*is_prepared)(structclk_hw*hw);
void(*unprepare_unused)(structclk_hw*hw);
int(*enable)(structclk_hw*hw);
void(*disable)(structclk_hw*hw);
int(*is_enabled)(structclk_hw*hw);
void(*disable_unused)(structclk_hw*hw);
int(*save_context)(structclk_hw*hw);
void(*restore_context)(structclk_hw*hw);
unsignedlong(*recalc_rate)(structclk_hw*hw,
unsignedlongparent_rate);
long(*round_rate)(structclk_hw*hw,unsignedlongrate,
unsignedlong*parent_rate);
int(*determine_rate)(structclk_hw*hw,
structclk_rate_request*req);
int(*set_parent)(structclk_hw*hw,u8index);
u8(*get_parent)(structclk_hw*hw);
int(*set_rate)(structclk_hw*hw,unsignedlongrate,
unsignedlongparent_rate);
int(*set_rate_and_parent)(structclk_hw*hw,
unsignedlongrate,
unsignedlongparent_rate,u8index);
unsignedlong(*recalc_accuracy)(structclk_hw*hw,
unsignedlongparent_accuracy);
int(*get_phase)(structclk_hw*hw);
int(*set_phase)(structclk_hw*hw,intdegrees);
int(*get_duty_cycle)(structclk_hw*hw,
structclk_duty*duty);
int(*set_duty_cycle)(structclk_hw*hw,
structclk_duty*duty);
int(*init)(structclk_hw*hw);
void(*terminate)(structclk_hw*hw);
void(*debug_init)(structclk_hw*hw,structdentry*dentry);
};
prepare:準(zhǔn)備啟動(dòng)時(shí)鐘。該回調(diào)直到時(shí)鐘完全準(zhǔn)備好才會(huì)返回,調(diào)用clk_enable是安全的。這個(gè)回調(diào)的目的是允許時(shí)鐘實(shí)現(xiàn)執(zhí)行任何可能休眠的初始化。在prepare_lock被持有的情況下調(diào)用。
unprepare:將時(shí)鐘從準(zhǔn)備狀態(tài)中釋放出來(lái)。該函數(shù)通常會(huì)撤銷在.prepare回調(diào)中完成的工作。在prepare_lock持有的情況下調(diào)用。
is_prepared:查詢硬件以確定時(shí)鐘是否準(zhǔn)備好。允許此函數(shù)休眠,如果此操作不是設(shè)置后,將使用prepare計(jì)數(shù)。(可選的)
unprepare_unused:自動(dòng)取消時(shí)鐘準(zhǔn)備。只從clk_disable_unused調(diào)用,用于特殊需要的時(shí)鐘準(zhǔn)備。在持有prepare互斥鎖的情況下調(diào)用。這個(gè)函數(shù)可能會(huì)休眠。
enable:自動(dòng)啟用時(shí)鐘。該函數(shù)直到時(shí)鐘正在生成一個(gè)有效的時(shí)鐘信號(hào)之前不能返回,供消費(fèi)者設(shè)備驅(qū)動(dòng)使用。在enable_lock持有情況下調(diào)用,該函數(shù)必須不能睡眠。
disable:自動(dòng)禁用時(shí)鐘。在enable_lock持有情況下調(diào)用,該函數(shù)必須不能睡眠。
is_enabled:查詢硬件以確定時(shí)鐘是否開(kāi)啟。這個(gè)函數(shù)不能休眠。如果此操作不是設(shè)置,則enable計(jì)數(shù)將被使用,該函數(shù)可選。
disable_unused:自動(dòng)禁用時(shí)鐘。只從clk_disable_unused調(diào)用用于特殊需要的gate時(shí)鐘。在enable_lock持有的情況下調(diào)用,這個(gè)函數(shù)不能睡眠。
save_context:保存時(shí)鐘上下文,為斷電做準(zhǔn)備。
restore_context:在電源恢復(fù)后恢復(fù)時(shí)鐘上下文。
recalc_rate:通過(guò)查詢硬件重新計(jì)算該時(shí)鐘的速率。如果驅(qū)動(dòng)程序不能計(jì)算出這個(gè)時(shí)鐘的速率,它必須返回0。如果此callback未設(shè)置,則時(shí)鐘速率將初始化為0(可選的)。
round_rate:給定一個(gè)目標(biāo)速率作為輸入,實(shí)際上返回由時(shí)鐘支持最接近的速率,父速率是一個(gè)input/output參數(shù)。
determine_rate:給定目標(biāo)速率作為輸入,返回實(shí)際上是由時(shí)鐘支撐的最接近的速率。
set_parent:改變這個(gè)時(shí)鐘的輸入源,設(shè)置父時(shí)鐘。
get_parent:查詢硬件以確定時(shí)鐘的父時(shí)鐘。
set_rate:改變這個(gè)時(shí)鐘的速率。
set_rate_and_parent:更改此時(shí)鐘的速率和父時(shí)鐘。
recalc_accuracy:重新計(jì)算一下這個(gè)鐘的精度。時(shí)鐘的準(zhǔn)確性以PPB(十億分之一)表示。父精度為輸入?yún)?shù)。
get_phase:查詢硬件以獲取時(shí)鐘的當(dāng)前相位。
set_phase:將時(shí)鐘信號(hào)的相位按指定的度數(shù)移位。
get_duty_cycle:查詢硬件,獲取時(shí)鐘當(dāng)前占空比。
set_duty_cycle:將占空比應(yīng)用于由分子(第二個(gè)參數(shù))和分母(第三個(gè)參數(shù))指定的時(shí)鐘信號(hào)。
init:執(zhí)行特定于平臺(tái)的初始化魔術(shù)。
terminate:釋放由init分配的資源。
debug_init:為這個(gè)時(shí)鐘設(shè)置特定類型的debugfs條目,在為這個(gè)時(shí)鐘創(chuàng)建了debugfs目錄條目之后,調(diào)用它一次。上述結(jié)構(gòu)中的回調(diào)函數(shù)的實(shí)現(xiàn)需根據(jù)具體情況而定,每個(gè)callback的具體含義根據(jù)名稱可以知道,CLK時(shí)鐘框架對(duì)上層開(kāi)放的API都會(huì)間接調(diào)用到這些callback,下表是一個(gè)clock硬件矩陣表,用于描述特定應(yīng)用場(chǎng)景下需要實(shí)現(xiàn)的callback:

參數(shù)說(shuō)明:
y表示強(qiáng)制,必須實(shí)現(xiàn)。
n表示包含該回調(diào)無(wú)效或沒(méi)有必要實(shí)現(xiàn)。
空單元格表示是可選的,或者必須根據(jù)具體情況評(píng)估實(shí)現(xiàn)。
4、struct clk_gate
struct clk_gate用于描述門控時(shí)鐘,該結(jié)構(gòu)定義如下:
structclk_gate{
structclk_hwhw;//處理公共接口和特定于硬件的接口。
void__iomem*reg;//寄存器控制門。
u8bit_idx;//單比特控制門。
u8flags;//特定硬件的falg標(biāo)志。
spinlock_t*lock;//自旋鎖。
};
常用API:
to_clk_gate() clk_register_gate()/clk_unregister_gate()
5、struct clk
struct clk用于描述一個(gè)clk設(shè)備,該結(jié)構(gòu)定義如下:
structclk{
structclk_core*core;//表示clk核心。
structdevice*dev;//clk設(shè)備的父設(shè)備。
constchar*dev_id;//設(shè)備id。
constchar*con_id;
unsignedlongmin_rate;//最小頻率。
unsignedlongmax_rate;//最大頻率。
unsignedintexclusive_count;//獨(dú)占計(jì)數(shù)。
structhlist_nodeclks_node;//clk鏈表。
};
6、struct clk_hw
struct clk_hw用于描述特定硬件實(shí)列的結(jié)構(gòu),該結(jié)構(gòu)定義如下:
structclk_hw{
structclk_core*core;//clk核心。
structclk*clk;//clk設(shè)備。
conststructclk_init_data*init;//描述clk初始化數(shù)據(jù)
};
struct clk_hw中包含了struct clk_core和struct clk??梢钥闯墒莄lk框架中對(duì)clk核心和clk設(shè)備的封裝。
7、struct clk_divider
struct clk_divider描述可調(diào)的分頻時(shí)鐘,該結(jié)構(gòu)定義如下:
structclk_divider{
structclk_hwhw;//處理公共接口和特定硬件的接口
void__iomem*reg;//分頻器的寄存器
u8shift;//分頻位域的偏移量
u8width;//分頻位域的寬度
u8flags;//標(biāo)志
conststructclk_div_table*table;//數(shù)組的值/除數(shù)對(duì),最后一項(xiàng)div=0。
spinlock_t*lock;//注冊(cè)鎖
};
具有影響其輸出頻率的可調(diào)分壓器的時(shí)鐘。實(shí)現(xiàn).recalc_rate,.set_rate和.round_rate。
常用API:
clk_register_divider()/clk_unregister_divider() clk_hw_register_divider()/clk_hw_unregister_divider()
8、struct clk_mux
struct clk_mux用于描述多路復(fù)用器的時(shí)鐘,該結(jié)構(gòu)定義如下:
structclk_mux{
structclk_hwhw;
void__iomem*reg;
constu32*table;
u32mask;
u8shift;
u8flags;
spinlock_t*lock;
};
上述結(jié)構(gòu)中組成元素幾乎與struct clk_divider一樣。
常用API:
voidclk_unregister_mux(structclk*clk); voidclk_hw_unregister_mux(structclk_hw*hw);
9、struct clk_fixed_factor
struct clk_fixed_factor用于倍頻和分頻時(shí)鐘。該結(jié)構(gòu)定義如下:
structclk_fixed_factor{
structclk_hwhw;//處理公共接口和特定硬件的接口。
unsignedintmult;//倍頻器
unsignedintdiv;//分頻器
};
具有固定乘法器和除法器的時(shí)鐘。輸出頻率為父時(shí)鐘速率除以div再乘以mult。在.recalc_rate,.set_rate和.round_rate中實(shí)現(xiàn)。
10、struct clk_fractional_divider
struct clk_fractional_divider用于描述可調(diào)分?jǐn)?shù)的分頻時(shí)鐘,該結(jié)構(gòu)定義如下:
structclk_fractional_divider{
structclk_hwhw;//處理公共接口和特定硬件的接口
void__iomem*reg;//用于分頻器的寄存器
u8mshift;//分頻位域分子的偏移量
u8mwidth;//分頻位域分子的寬度
u8nshift;//分頻位域分母的偏移量
u8nwidth;//分頻位域分母的寬度
u8flags;//標(biāo)志位
void(*approximation)(structclk_hw*hw,//近似方法的callback
unsignedlongrate,unsignedlong*parent_rate,
unsignedlong*m,unsignedlong*n);
spinlock_t*lock;//注冊(cè)鎖
};
11、struct clk_multiplier
struct clk_multiplier結(jié)構(gòu)用于描述可調(diào)的倍頻時(shí)鐘,該結(jié)構(gòu)定義如下:
structclk_multiplier{
structclk_hwhw;//處理公共接口和特定硬件的接口
void__iomem*reg;//倍頻器的寄存器
u8shift;//乘法位域的偏移量
u8width;//乘法位域的寬度
u8flags;//標(biāo)志
spinlock_t*lock;//注冊(cè)鎖
};
12、struct clk_composite
struct clk_composite結(jié)構(gòu)用于描述多路復(fù)用器、分頻器和門控時(shí)鐘的組合時(shí)鐘。該結(jié)構(gòu)定義如下:
structclk_composite{
structclk_hwhw;//處理公共接口和特定硬件的接口
structclk_opsops;//clk對(duì)應(yīng)的ops的callback
structclk_hw*mux_hw;//處理復(fù)合和硬件特定多路復(fù)用時(shí)鐘
structclk_hw*rate_hw;//處理復(fù)合和硬件特定的頻率時(shí)鐘
structclk_hw*gate_hw;//處理之間的組合和硬件特定的門控時(shí)鐘
conststructclk_ops*mux_ops;//對(duì)mux的時(shí)鐘ops
conststructclk_ops*rate_ops;//對(duì)rate的時(shí)鐘ops
conststructclk_ops*gate_ops;//對(duì)gate的時(shí)鐘ops
};
常用API:
to_clk_composite() clk_register_composite()/clk_unregister_composite()
clk核心數(shù)據(jù)結(jié)構(gòu)如下圖所示:

四、CLK調(diào)試
參見(jiàn)debugfs文件系統(tǒng)下的文件可推知目前系統(tǒng)中存在的clk情況,使用如下命令:
cat/sys/debug/kernel/clk/clk_summary
查看目前系統(tǒng)的時(shí)鐘樹(shù)(clk_tree)。例如:
可以在用戶空間通過(guò)/sys設(shè)置時(shí)鐘節(jié)點(diǎn):
//getrate: cat/sys/kernel/debug/aclk_gmac0/clk_rate //setrate: echo24000000>/sys/kernel/debug/aclk_gmac0/clk_rate //打開(kāi)clk: echo1>/sys/kernel/debug/aclk_gmac0/clk_enable_count //關(guān)閉clk: echo0>/sys/kernel/debug/aclk_gmac0/clk_enable_count
五、CLK信息導(dǎo)出
1、與debugfs調(diào)試信息相關(guān)的初始化
當(dāng)內(nèi)核支持debugfs且開(kāi)啟對(duì)clk的調(diào)試支持,我們可以在/sys文件系統(tǒng)路徑中的clk目錄下查看關(guān)于系統(tǒng)中所有注冊(cè)的clk信息,例如:

每個(gè)目錄代表一個(gè)clk信息,其目錄下包含如下信息:

從內(nèi)核源碼角度,創(chuàng)建debugfs調(diào)試目錄或文件由clk_debug_init()完成:
staticint__initclk_debug_init(void)
{
structclk_core*core;
#ifdefCLOCK_ALLOW_WRITE_DEBUGFS
pr_warn("
");
pr_warn("********************************************************************
");
pr_warn("**NOTICENOTICENOTICENOTICENOTICENOTICENOTICE**
");
pr_warn("****
");
pr_warn("**WRITEABLEclkDebugFSSUPPORTHASBEENENABLEDINTHISKERNEL**
");
pr_warn("****
");
pr_warn("**Thismeansthatthiskernelisbuilttoexposeclkoperations**
");
pr_warn("**suchasparentorratesetting,enabling,disabling,etc.**
");
pr_warn("**touserspace,whichmaycompromisesecurityonyoursystem.**
");
pr_warn("****
");
pr_warn("**Ifyouseethismessageandyouarenotdebuggingthe**
");
pr_warn("**kernel,reportthisimmediatelytoyourvendor!**
");
pr_warn("****
");
pr_warn("**NOTICENOTICENOTICENOTICENOTICENOTICENOTICE**
");
pr_warn("********************************************************************
");
#endif
rootdir=debugfs_create_dir("clk",NULL);
debugfs_create_file("clk_summary",0444,rootdir,&all_lists,
&clk_summary_fops);
debugfs_create_file("clk_dump",0444,rootdir,&all_lists,
&clk_dump_fops);
debugfs_create_file("clk_orphan_summary",0444,rootdir,&orphan_list,
&clk_summary_fops);
debugfs_create_file("clk_orphan_dump",0444,rootdir,&orphan_list,
&clk_dump_fops);
mutex_lock(&clk_debug_lock);
hlist_for_each_entry(core,&clk_debug_list,debug_node)
clk_debug_create_one(core,rootdir);
inited=1;
mutex_unlock(&clk_debug_lock);
return0;
}
clk_debug_init()函數(shù)由late_initcall()(/drivers/clk.c)導(dǎo)出。
六、clk驅(qū)動(dòng)設(shè)計(jì)
1、底層驅(qū)動(dòng)(clk-provider)
對(duì)于一款SOC,特定廠家都會(huì)針對(duì)時(shí)鐘編寫對(duì)應(yīng)的驅(qū)動(dòng)。包括用于以下功能的文件:
用于倍頻的 PLL(鎖相環(huán),Phase Locked Loop)。
用于分頻的Divider。
用于多路選擇的 MUX。
用于CLOCK ENABLE控制的與門。
使用 CLOCK 的硬件模塊(也可稱為CONSUMER)。
在設(shè)計(jì)這些clk驅(qū)動(dòng)時(shí),本質(zhì)上是實(shí)現(xiàn)對(duì)應(yīng)struct clk_ops下的callback后,調(diào)用clk_register注冊(cè)進(jìn)linux內(nèi)核的時(shí)鐘框架。不同的時(shí)鐘器件在內(nèi)核中都存在與之對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),且開(kāi)放有對(duì)應(yīng)的API接口,將其注冊(cè)到內(nèi)核中。
例如Nxp的Imx6ul這款SOC,在/arch/arm/mach-imx/clk-imx6ull.c中則實(shí)現(xiàn)了對(duì)應(yīng)時(shí)鐘框架的底層驅(qū)動(dòng),由imx6ul_clocks_init()實(shí)現(xiàn):
CLK_OF_DECLARE(imx6ul,"fsl,imx6ul-ccm",imx6ul_clocks_init);
存在下圖類似的SOC時(shí)鐘描述語(yǔ)句:
上述語(yǔ)句中無(wú)論是imx_clk_mux()還是imx_clk_pllv3()都會(huì)調(diào)用clk_register()向內(nèi)核注冊(cè)時(shí)鐘資源。
2、驅(qū)動(dòng)層clk
當(dāng)?shù)讓樱╟lk-provider)設(shè)計(jì)完成后,在驅(qū)動(dòng)層(也稱為消費(fèi)者(Consumer))則可以很方便的獲取對(duì)應(yīng)的clk句柄,并可以進(jìn)行enable/disable時(shí)鐘等操作了。
常用API有:
//查找并獲取對(duì)時(shí)鐘產(chǎn)生器的引用 structclk*clk_get(structdevice*dev,constchar*id); structclk*devm_clk_get(structdevice*dev,constchar*id); //當(dāng)時(shí)鐘源處于運(yùn)行狀態(tài)時(shí),通知系統(tǒng) intclk_enable(structclk*clk); //當(dāng)時(shí)鐘源不再使用時(shí),通知系統(tǒng) voidclk_disable(structclk*clk); clk_prepare_enable()
在內(nèi)核源碼中,可以發(fā)現(xiàn)很多的驅(qū)動(dòng)設(shè)計(jì)都會(huì)使用到時(shí)鐘子系統(tǒng),用于對(duì)外設(shè)的控制和開(kāi)啟/停止。
例如,在Nxp提供的一個(gè)名為imx.c(/drivers/tty/serial)的通用uart驅(qū)動(dòng)中,在.probe中則會(huì)首先進(jìn)行時(shí)鐘相關(guān)的操作:
staticintserial_imx_probe(structplatform_device*pdev)
{
structimx_port*sport;
void__iomem*base;
intret=0;
structresource*res;
inttxirq,rxirq,rtsirq;
sport=devm_kzalloc(&pdev->dev,sizeof(*sport),GFP_KERNEL);
if(!sport)
return-ENOMEM;
ret=serial_imx_probe_dt(sport,pdev);
if(ret>0)
serial_imx_probe_pdata(sport,pdev);
elseif(ret0)
??return?ret;
?res?=?platform_get_resource(pdev,?IORESOURCE_MEM,?0);
?base?=?devm_ioremap_resource(&pdev->dev,res);
if(IS_ERR(base))
returnPTR_ERR(base);
rxirq=platform_get_irq(pdev,0);
txirq=platform_get_irq(pdev,1);
rtsirq=platform_get_irq(pdev,2);
sport->port.dev=&pdev->dev;
sport->port.mapbase=res->start;
sport->port.membase=base;
sport->port.type=PORT_IMX,
sport->port.iotype=UPIO_MEM;
sport->port.irq=rxirq;
sport->port.fifosize=32;
sport->port.ops=&imx_pops;
sport->port.rs485_config=imx_rs485_config;
sport->port.rs485.flags=
SER_RS485_RTS_ON_SEND|SER_RS485_RX_DURING_TX;
sport->port.flags=UPF_BOOT_AUTOCONF;
init_timer(&sport->timer);
sport->timer.function=imx_timeout;
sport->timer.data=(unsignedlong)sport;
sport->clk_ipg=devm_clk_get(&pdev->dev,"ipg");
if(IS_ERR(sport->clk_ipg)){
ret=PTR_ERR(sport->clk_ipg);
dev_err(&pdev->dev,"failedtogetipgclk:%d
",ret);
returnret;
}
sport->clk_per=devm_clk_get(&pdev->dev,"per");
if(IS_ERR(sport->clk_per)){
ret=PTR_ERR(sport->clk_per);
dev_err(&pdev->dev,"failedtogetperclk:%d
",ret);
returnret;
}
sport->port.uartclk=clk_get_rate(sport->clk_per);
if(sport->port.uartclk>IMX_MODULE_MAX_CLK_RATE){
ret=clk_set_rate(sport->clk_per,IMX_MODULE_MAX_CLK_RATE);
if(ret0)?{
???dev_err(&pdev->dev,"clk_set_rate()failed
");
returnret;
}
}
sport->port.uartclk=clk_get_rate(sport->clk_per);
/*
*AllocatetheIRQ(s)i.MX1hasthreeinterruptswhereaslater
*chipsonlyhaveoneinterrupt.
*/
if(txirq>0){
ret=devm_request_irq(&pdev->dev,rxirq,imx_rxint,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;
ret=devm_request_irq(&pdev->dev,txirq,imx_txint,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;
}else{
ret=devm_request_irq(&pdev->dev,rxirq,imx_int,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;
}
imx_ports[sport->port.line]=sport;
platform_set_drvdata(pdev,sport);
returnuart_add_one_port(&imx_reg,&sport->port);
}
在上述代碼中,與時(shí)鐘相關(guān)的操作有四個(gè)地方:
獲取了時(shí)鐘,在這個(gè)通用uart驅(qū)動(dòng)中的其他相關(guān)的回調(diào)中,則會(huì)依托于時(shí)鐘實(shí)現(xiàn)這些回調(diào)函數(shù),例如imx_startup():

綜上所述,可見(jiàn)時(shí)鐘框架在linux設(shè)備驅(qū)動(dòng)的實(shí)現(xiàn)中非常重要,只要與外設(shè)相關(guān)的驅(qū)動(dòng)實(shí)現(xiàn)幾乎都需要使用到時(shí)鐘框架。
-
內(nèi)核
+關(guān)注
關(guān)注
4文章
1474瀏覽量
43088 -
Linux
+關(guān)注
關(guān)注
88文章
11806瀏覽量
219493 -
時(shí)鐘
+關(guān)注
關(guān)注
11文章
1999瀏覽量
135222
原文標(biāo)題:聊聊linux時(shí)鐘子系統(tǒng)
文章出處:【微信號(hào):嵌入式小生,微信公眾號(hào):嵌入式小生】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
Linux下輸入子系統(tǒng)上報(bào)觸摸屏坐標(biāo)
深度搜索Linux操作系統(tǒng):系統(tǒng)構(gòu)建和原理解析
Windows10內(nèi)置Linux子系統(tǒng)使用
如何使用Linux內(nèi)核中的input子系統(tǒng)
基于Linux內(nèi)核輸入子系統(tǒng)的驅(qū)動(dòng)研究
Linux內(nèi)核輸入子系統(tǒng)的驅(qū)動(dòng)研究
詳細(xì)了解Linux設(shè)備模型中的input子系統(tǒng)
Linux系統(tǒng)中NFC子系統(tǒng)架構(gòu)分析
linux-usb子系統(tǒng)的核心描述
Linux內(nèi)核之LED子系統(tǒng)(一)
Linux reset子系統(tǒng)有什么功能
Linux clock子系統(tǒng)是什么
時(shí)鐘子系統(tǒng)中clock驅(qū)動(dòng)實(shí)例
深度解析linux時(shí)鐘子系統(tǒng)
評(píng)論