嵌入式Linux驅動筆記(十四)------詳解clock時鐘(CCF)框架及clk_get函式
你好!這裡是風箏的部落格,
歡迎和我一起交流。
我在找資料的時候,發現網上大部分文章都是說:
在s3c244x_init_clocks函式裡:
void __init s3c244x_init_clocks(int xtal)
{
s3c24xx_register_baseclocks(xtal); //完成祖宗級別時鐘的註冊
s3c244x_setup_clocks();//填充祖宗級別時鐘結構,方便以後呼叫
s3c2410_baseclk_add();//新增一些外設時鐘結構到list中,並且關閉它們方便省電
}
可是我在我的kernel4.8.17裡面,
已經沒有了s3c24xx_register_baseclocks函式,
沒有了s3c244x_setup_clocks函式,
沒有了s3c2410_baseclk_add函式,,,,,,
而且arch\arm\plat-samsung\目錄下也沒用了clock.c檔案…………
好像說是從Linux3.10核心開始就沒有了吧,然後正式的使用CCF(Common Clock Framewrok)框架了。
在以前Clock部分,雖然也提供了相應的API根據名字去獲取Clock,設定頻率,獲取父時鐘,設定父時鐘的函式,但是這些API都是由每個SoC單獨實現,導致了程式碼的差異很大,於是就引入了一個新的通用的時鐘框架來解決這個問題。由TI的工程師Mike Turquette提供了Common Clock Framewrok,讓具體SoC實現clk_ops成員函式並通過clk_register、clk_register_clkdev註冊時鐘源以及源與裝置對應關係,具體的clock驅動都統一遷移到drivers/clk目錄。因此現在的時鐘框架就採用CCF方式,使用CCF前提是核心配置了CONFIG_COMMON_CLK。
所以,以kernel4.8.17為例,我們重新看下clock註冊部分,分析它的框架:
在mach-smdk2440.c檔案裡,呼叫關係為:
smdk2440_init_time
->s3c2440_init_clocks(12000000)
->s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR)
看下s3c2410_common_clk_init函式,重點分析:
void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f,
int current_soc,
void __iomem *base)
{
struct samsung_clk_provider *ctx;
reg_base = base;
if (np) {
reg_base = of_iomap(np, 0);
if (!reg_base)
panic("%s: failed to map registers\n", __func__);
}
ctx = samsung_clk_init(np, reg_base, NR_CLKS);
/* Register external clocks only in non-dt cases */
if (!np)
s3c2410_common_clk_register_fixed_ext(ctx, xti_f);
if (current_soc == S3C2410) {
if (_get_rate("xti") == 12 * MHZ) {
s3c2410_plls[mpll].rate_table = pll_s3c2410_12mhz_tbl;
s3c2410_plls[upll].rate_table = pll_s3c2410_12mhz_tbl;
}
/* Register PLLs. */
samsung_clk_register_pll(ctx, s3c2410_plls,
ARRAY_SIZE(s3c2410_plls), reg_base);//專門用於鎖相環的註冊
} else { /* S3C2440, S3C2442 */
if (_get_rate("xti") == 12 * MHZ) {
/*
* plls follow different calculation schemes, with the
* upll following the same scheme as the s3c2410 plls
*/
s3c244x_common_plls[mpll].rate_table =
pll_s3c244x_12mhz_tbl;
s3c244x_common_plls[upll].rate_table =
pll_s3c2410_12mhz_tbl;
}
/* Register PLLs. */
samsung_clk_register_pll(ctx, s3c244x_common_plls,
ARRAY_SIZE(s3c244x_common_plls), reg_base);
}
/* Register common internal clocks. */
samsung_clk_register_mux(ctx, s3c2410_common_muxes,
ARRAY_SIZE(s3c2410_common_muxes));
samsung_clk_register_div(ctx, s3c2410_common_dividers,
ARRAY_SIZE(s3c2410_common_dividers));
samsung_clk_register_gate(ctx, s3c2410_common_gates,
ARRAY_SIZE(s3c2410_common_gates));
if (current_soc == S3C2440 || current_soc == S3C2442) {
samsung_clk_register_div(ctx, s3c244x_common_dividers,
ARRAY_SIZE(s3c244x_common_dividers));
samsung_clk_register_gate(ctx, s3c244x_common_gates,
ARRAY_SIZE(s3c244x_common_gates));
samsung_clk_register_mux(ctx, s3c244x_common_muxes,
ARRAY_SIZE(s3c244x_common_muxes));
samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor,
ARRAY_SIZE(s3c244x_common_ffactor));
}
/* Register SoC-specific clocks. */
switch (current_soc) {
case S3C2410:
samsung_clk_register_div(ctx, s3c2410_dividers,
ARRAY_SIZE(s3c2410_dividers));
samsung_clk_register_fixed_factor(ctx, s3c2410_ffactor,
ARRAY_SIZE(s3c2410_ffactor));
samsung_clk_register_alias(ctx, s3c2410_aliases,
ARRAY_SIZE(s3c2410_aliases));
break;
case S3C2440:
samsung_clk_register_mux(ctx, s3c2440_muxes,
ARRAY_SIZE(s3c2440_muxes));
samsung_clk_register_gate(ctx, s3c2440_gates,
ARRAY_SIZE(s3c2440_gates));
break;
case S3C2442:
samsung_clk_register_mux(ctx, s3c2442_muxes,
ARRAY_SIZE(s3c2442_muxes));
samsung_clk_register_fixed_factor(ctx, s3c2442_ffactor,
ARRAY_SIZE(s3c2442_ffactor));
break;
}
/*
* Register common aliases at the end, as some of the aliased clocks
* are SoC specific.
*/
samsung_clk_register_alias(ctx, s3c2410_common_aliases,
ARRAY_SIZE(s3c2410_common_aliases));
if (current_soc == S3C2440 || current_soc == S3C2442) {
samsung_clk_register_alias(ctx, s3c244x_common_aliases,
ARRAY_SIZE(s3c244x_common_aliases));
}
s3c2410_clk_sleep_init();
samsung_clk_of_add_provider(np, ctx);
}
這裡我們傳入的引數np為NULL,xti_f為12000000,current_soc為1。
進入這個函式,分析:
第8行:條件不成立。
第14行:初始化時鐘,主要就是給ctx分配空間和填充一些資料,如暫存器基地址。
第18行:註冊通用的外部固定時鐘,呼叫關係為:
s3c2410_common_clk_register_fixed_ext
->samsung_clk_register_fixed_rate
->clk_register_fixed_rate
->clk_register_fixed_rate_with_accuracy[注1]
->clk_hw_register_fixed_rate_with_accuracy
->clk_hw_register
->clk_register[注2]
->clk_register_clkdev
->__clk_register_clkdev
->vclkdev_create
->__clkdev_add[注3]
->samsung_clk_register_alias[注4]
->clk_register_clkdev
[注1]:在clk_hw_register_fixed_rate_with_accuracy主要就是設定一些引數,如name、flags、parent_names、num_parents之類的。
[注2]:在clk_register函式裡就會初始化core->clks雜湊連結串列,建立一個hw->clk連結到core->clks上,並將hw->clk返回到clk_register_fixed_rate函式上,注意,hw->clk有兩個要注意的成員變數:dev_id(device ID)和con_id(connection ID)。
[注3]:在__clkdev_add函式裡就會:list_add_tail(&cl->node, &clocks);
將cl->node連結到clocks,注意,cl上同樣有dev_id和con_id,同時注意clocks這個連結串列!
[注4]:在samsung_clk_register_alias函式,註冊時鐘的別名(比如別名spi,他是掛在PCLK上),同樣呼叫到clk_register_clkdev,最終進入[注3],將list->alias作為con_id,連結到clocks上。
繼續:
第20行:我們current_soc為1,S3C2410為0,if不成立。
第43行:專門用於鎖相環的註冊。
第48~52行:
samsung_clk_register_mux:時鐘選擇
samsung_clk_register_div:時鐘分頻
samsung_clk_register_gate:時鐘門控,註冊的時鐘只可以開關,通過.enable/.disable回撥
其實這些函式都大同小異,我們以samsung_clk_register_gate來看:
samsung_clk_register_gate(ctx, s3c2410_common_gates , ARRAY_SIZE(s3c2410_common_gates));
看下s3c2410_common_gates陣列:
struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0),
GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0),
GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0),
GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0),
GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0),
GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0),
GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0),
GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0),
GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),
GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0),
GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0),
GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0),
GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0),
GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0),
GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),
};//格式:id、name、parent_name、offset、bit_idx、flags、gate_flags
這些就是註冊的時鐘源了。
samsung_clk_register_gate函式的呼叫關係如下:
samsung_clk_register_gate
->clk_register_gate
->clk_hw_register_gate[注5]
->clk_hw_register
->clk_register[注2]
[注5]:在clk_hw_register_gate函式,同樣是設定一些引數,如name、flags、parent_names、num_parents之類的以及填充:init.ops = &clk_gate_ops。
const struct clk_ops clk_gate_ops = {
.enable = clk_gate_enable,
.disable = clk_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
這裡就是填充回撥函式了,在clk_enable/clk_disable時會呼叫到。
[注2]:在上面提到過。
繼續:
第56~62行:
因為2440和2410是相似的,這是在2410的基礎上對2440進行補充。
第94行:註冊時鐘別名,這就比較重要了:
samsung_clk_register_alias(ctx, s3c2410_common_aliases , ARRAY_SIZE(s3c2410_common_aliases));
看下s3c2410_common_aliases陣列:
struct samsung_clock_alias s3c2410_common_aliases[] __initdata = {
ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"),
ALIAS(PCLK_ADC, NULL, "adc"),
ALIAS(PCLK_RTC, NULL, "rtc"),
ALIAS(PCLK_PWM, NULL, "timers"),
ALIAS(HCLK_LCD, NULL, "lcd"),
ALIAS(HCLK_USBD, NULL, "usb-device"),
ALIAS(HCLK_USBH, NULL, "usb-host"),
ALIAS(UCLK, NULL, "usb-bus-host"),
ALIAS(UCLK, NULL, "usb-bus-gadget"),
ALIAS(ARMCLK, NULL, "armclk"),
ALIAS(UCLK, NULL, "uclk"),
ALIAS(HCLK, NULL, "hclk"),
ALIAS(MPLL, NULL, "mpll"),
ALIAS(FCLK, NULL, "fclk"),
ALIAS(PCLK, NULL, "watchdog"),
ALIAS(PCLK_SDI, NULL, "sdi"),
ALIAS(HCLK_NAND, NULL, "nand"),
ALIAS(PCLK_I2S, NULL, "iis"),
ALIAS(PCLK_I2C, NULL, "i2c"),
};//格式:id、dev_name、alias
samsung_clk_register_alias函式在之前[注4]說過,就是將con_id(這裡即是alias)連結到clocks上。
好了,到這就分析結束了,那我們怎麼獲取時鐘呢?
當然是非常大眾化的clk_get函式了:
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk *clk;
if (dev) {
clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
return clk;
}
return clk_get_sys(dev_id, con_id);
}
通常來說,第一個引數設定為NULL即可。
__of_clk_get_by_name是關於dts裝置樹的API。
進入clk_get_sys函式看看:
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
struct clk_lookup *cl;
struct clk *clk = NULL;
mutex_lock(&clocks_mutex);
cl = clk_find(dev_id, con_id);
if (!cl)
goto out;
clk = __clk_create_clk(cl->clk_hw, dev_id, con_id);
if (IS_ERR(clk))
goto out;
if (!__clk_get(clk)) {
__clk_free_clk(clk);
cl = NULL;
goto out;
}
out:
mutex_unlock(&clocks_mutex);
return cl ? clk : ERR_PTR(-ENOENT);
}
這裡有兩個函式:clk_find和__clk_create_clk
看下clk_find函式:
static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)
{
struct clk_lookup *p, *cl = NULL;
int match, best_found = 0, best_possible = 0;
if (dev_id)
best_possible += 2;
if (con_id)
best_possible += 1;
list_for_each_entry(p, &clocks, node) {
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
if (match > best_found) {
cl = p;
if (match != best_possible)
best_found = match;
else
break;
}
}
return cl;
}
裡面的list_for_each_entry就是遍歷clocks這條連結串列,
可以看下這篇:Linux下連結串列的使用及探究
然後比較clocks這條連結串列上的名字和dev_id以及con_id,如果dev_id匹配,那麼match+=2,如果con_id匹配,那麼match+=1,當match > best_found說明找到了這個clock的結構體,當然,找到了並不意味著最優解,當match == best_possible才是最完美的情況。
這裡可以看出:匹配的優先程度是:dev+con > dev only > con only
之後這個clock結構體會給與到__clk_create_clk函式使用:
struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,
const char *con_id)
{
struct clk *clk;
/* This is to allow this function to be chained to others */
if (IS_ERR_OR_NULL(hw))
return (struct clk *) hw;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
clk->core = hw->core;
clk->dev_id = dev_id;
clk->con_id = con_id;
clk->max_rate = ULONG_MAX;
clk_prepare_lock();
hlist_add_head(&clk->clks_node, &hw->core->clks);
clk_prepare_unlock();
return clk;
}
將之新增到雜湊表上進行管理。
最後看下得到clk結構體後是怎麼使能時鐘的:clk_enable函式:
呼叫關係為:
clk_enable
->clk_core_enable_lock
->clk_core_enable
static int clk_core_enable(struct clk_core *core)
{
int ret = 0;
lockdep_assert_held(&enable_lock);
if (!core)
return 0;
if (WARN_ON(core->prepare_count == 0))
return -ESHUTDOWN;
if (core->enable_count == 0) {
ret = clk_core_enable(core->parent);
if (ret)
return ret;
trace_clk_enable_rcuidle(core);
if (core->ops->enable)
ret = core->ops->enable(core->hw);
trace_clk_enable_complete_rcuidle(core);
if (ret) {
clk_core_disable(core->parent);
return ret;
}
}
core->enable_count++;
return 0;
}
在裡面,一開始先判斷core的有效性, 然後繼續呼叫自己進行進行遞迴處理,傳入的引數為core->parent,要知道,使能一個裝置的clock,一定要保定它父裝置的clock是使能的。
之後就掉呼叫core->ops->enable回撥函式進行使能了!
最後的最後,給出一些API函式:
struct clk *__clk_lookup(const char *name) 通過時鐘名找到時鐘
static inline int clk_enable(struct clk *clk) 使能時鐘,不會睡眠
static inline void clk_disable(struct clk *clk) 禁止時鐘,不會睡眠
static inline int clk_prepare_enable(struct clk *clk) 使能時鐘,可能會睡眠
static inline void clk_disable_unprepare(struct clk *clk) 禁止時鐘,可能會睡眠
static inline unsigned long clk_get_rate(struct clk *clk) 獲取時脈頻率
static inline int clk_set_rate(struct clk *clk, unsigned long rate) 設定時脈頻率
static inline long clk_round_rate(struct clk *clk, unsigned long rate) 獲取最接近的時脈頻率
static inline int clk_set_parent(struct clk *clk, struct clk *parent) 設定時鐘的父時鐘
static inline struct clk *clk_get_parent(struct clk *clk) 獲取時鐘的父時鐘
相關文章
- 嵌入式Linux驅動筆記(十七)------詳解V4L2框架(UVC驅動)Linux筆記框架
- 嵌入式Linux驅動筆記(十二)------通俗易懂式分析瞭解spi框架Linux筆記框架
- 嵌入式Linux驅動筆記(十)------通俗易懂式分析瞭解i2c框架Linux筆記框架
- 嵌入式筆記5.1 定時器詳解筆記定時器
- 嵌入式Linux驅動筆記(十六)------裝置驅動模型(kobject、kset、ktype)Linux筆記模型Object
- 嵌入式Linux驅動筆記(十八)------淺析V4L2框架之ioctlLinux筆記框架
- 嵌入式Linux驅動學習筆記(十六)------裝置驅動模型(kobject、kset、ktype)Linux筆記模型Object
- 嵌入式Linux驅動筆記(十一)------i2c裝置之mpu6050驅動Linux筆記
- 嵌入式Linux驅動筆記(十三)------spi裝置之RFID-rc522驅動Linux筆記
- 嵌入式Linux驅動筆記(九)------dts裝置樹在2440使用Linux筆記
- Linux系統時鐘(System Clock)和硬體(Real Time Clock,簡稱RTC)時鐘Linux
- linux時間函式詳解Linux函式
- 向嵌入式Linux移植實時裝置驅動程式(轉)Linux
- 嵌入式Linux驅動筆記(十五)------編譯使用tslib支援LCD觸控式螢幕Linux筆記編譯
- Swift學習筆記(三十四)——函式型別Swift筆記函式型別
- 世界時鐘:World Clock Deluxe macUXMac
- canvas標籤clock(時鐘)案例Canvas
- 嵌入式Linux驅動學習筆記(十五)------編譯使用tslib支援LCD觸控式螢幕Linux筆記編譯
- 嵌入式Linux中的LED驅動控制(續)Linux
- 嵌入式Linux中platform平臺裝置模型的框架(實現LED驅動)LinuxPlatform模型框架
- RealSence 驅動及ROS包配置筆記ROS筆記
- 基於Linux的tty架構及UART驅動詳解Linux架構
- World Clock Deluxe for Mac,世界時鐘豪華版UXMac
- 嵌入式Linux中的LED驅動控制(基於misc)Linux
- 痞子衡嵌入式:嵌入式裡通用微秒(microseconds)計時函式框架設計與實現ROS函式框架
- Django筆記二十四之資料庫函式之比較和轉換函式Django筆記資料庫函式
- linux signal函式詳解Linux函式
- linux中fork()函式詳解Linux函式
- SPI轉can晶片CSM300詳解以及Linux驅動移植除錯筆記晶片Linux除錯筆記
- 『動善時』JMeter基礎 — 28、JMeter函式助手詳解JMeter函式
- PHP筆記:建構函式與解構函式PHP筆記函式
- 學習筆記-----一時間函式筆記函式
- ArmSoM系列板卡 嵌入式Linux驅動開發實戰指南 之 字元裝置驅動Linux字元
- linux系統時間程式設計(9) 計算程式片段執行時間clock函式Linux程式設計函式
- 嵌入式Linux中的LED驅動控制(裝置樹方式)Linux
- Clock saver for Mac(博朗手錶時鐘屏保)Mac
- Mac世界時鐘豪華版——World Clock Deluxe for macMacUX
- Python零基礎學習筆記(二十四)——函式Python筆記函式