內核中提供了clk common framework子系統,用來完成對clock的統一管理。
我們將從如下幾個方面來介紹clk子系統的內容:
1. clk framework簡介
2. clk framework的實現
3. clk和device tree
4. 如何添加自己的clock
一、 clk framework簡介
clk framework是內核中用來統一管理clock的子系統。代碼存在於kernel/driver/clk目錄中。
要使用clkframework來實現廠商自己平台上的clock驅動,首先需要在defconfig中使能如下的幾個CONFIG來配置內核。
CONFIG_CLKDEV_LOOKUP=y CONFIG_HAVE_CLK_PREPARE=y CONFIG_COMMON_CLK=y
除了這幾個以外,還有一個是否打開DEBUG的開關配置:
CONFIG_COMMON_CLK_DEBUG=y
這個DEBUG開關是控制內核是否產生clk的debugfs的,如果配置了這個選項,內核將生成相應的debugfs,在啟動後將會掛載於/sys/kernel/debug目錄下。
Clk framework是一個通用core模塊,它主要提供了如下幾個功能:
1. 向上提供給其他driver調用的接口API
2. 向下提供給clock driver注冊的接口API
3. debugfs創建
4. 若干個基於dts配置的通用clock實現(通過調用注冊接口API)
它的框架圖如下所示:
上圖中的黃色區域都是clk core所實現的功能,灰色區域是clock驅動開發需要做的事情,而綠色區域是其他device driver需要使用clock時要調用到的clk功能。
二、 clk framework的實現
在開始介紹clk framework之前,首先需要了解一下幾個重要的結構體:
struct clk_ops { int (*prepare)(struct clk_hw *hw); void (*unprepare)(struct clk_hw *hw); int (*is_prepared)(struct clk_hw *hw); void (*unprepare_unused)(struct clk_hw *hw); int (*enable)(struct clk_hw *hw); void (*disable)(struct clk_hw *hw); int (*is_enabled)(struct clk_hw *hw); void (*disable_unused)(struct clk_hw *hw); unsigned long (*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate); long (*round_rate)(struct clk_hw *hw, unsigned long, unsigned long *); int (*set_parent)(struct clk_hw *hw, u8 index); u8 (*get_parent)(struct clk_hw *hw); int (*set_rate)(struct clk_hw *hw, unsigned long, unsigned long); void (*init)(struct clk_hw *hw); };
這個結構體主要定義了一些用來操作硬件的回調函數,這個部分是需要廠商開發自己的clock驅動的時候實現的。
struct clk_hw { struct clk *clk; const struct clk_init_data *init; }; struct clk_init_data { const char *name; const struct clk_ops *ops; const char **parent_names; u8 num_parents; unsigned long flags; };
Clk_hw結構體可以看到其中封裝了一個clk_ops結構體,它是一個clk驅動需要實現的關鍵結構,廠商需要實現此結構體,並把它注冊到clk framework。clk_hw是聯系clk_ops和struct clk的紐帶。它一般會被封裝到一個廠商自己定義的更大的結構體中,主要是用來建立與struct clk的聯系。
struct clk { const char *name; const struct clk_ops *ops; struct clk_hw *hw; struct clk *parent; const char **parent_names; struct clk **parents; u8 num_parents; unsigned long rate; unsigned long new_rate; unsigned long flags; unsigned int enable_count; unsigned int prepare_count; struct hlist_head children; struct hlist_node child_node; unsigned int notifier_count; #ifdef CONFIG_COMMON_CLK_DEBUG struct dentry *dentry; #endif };
這個是framework core中關鍵的結構體,core中都是通過這個結構體來管理clk的,它主要是用來抽象clk硬件的差異,並完成一些通用操作的封裝。其中的hw成員變量是與之關聯的clk_hw結構。由上面的介紹可知,通過struct clk_hw和struct clk就把差異的部分和通用部分給聯系 起來了。
介紹了結構體以後,我們就來看一下clk framework提供的具體功能吧。這部分的實現主要在clk.c和clkdev.c兩個源文件中。
(1) 向上提供的接口API
struct clk *clk_get(struct device *dev, const char *id); struct clk *devm_clk_get(struct device *dev, const char *id); int clk_enable(struct clk *clk); void clk_disable(struct clk *clk); unsigned long clk_get_rate(struct clk *clk); void clk_put(struct clk *clk); long clk_round_rate(struct clk *clk, unsigned long rate); int clk_set_rate(struct clk *clk, unsigned long rate); int clk_set_parent(struct clk *clk, struct clk *parent); struct clk *clk_get_parent(struct clk *clk); int clk_prepare(struct clk *clk); void clk_unprepare(struct clk *clk);
這些都是比較重要的api接口,主要是在device driver中調用來設置device的clk的。這部分的實現最終會調用到clk_ops中的回調函數來設置硬件並且會更新core中的clk鏈表。具體實現自行閱讀源代碼。
除了上面介紹的api,作為一個clk設備,它有可能會改變rate,那麼作為device driver的一方需要獲取到這個改變,並作出相應的響應,那麼就可以通過通知功能的接口來實現,我們可以在感興趣的clk上注冊notifier_block,然後當該clk的rate發生了改變的時候會通過__clk_notify,通知到相應的回調函數,來做相應的處理。
int clk_notifier_register(struct clk *clk, struct notifier_block *nb); int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb);
以上是device driver開發中可能會使用到的接口,接下來我們以clk_enbale為例做個簡單介紹:
int clk_enable(struct clk *clk) { unsigned long flags; int ret; flags = clk_enable_lock(); ret = __clk_enable(clk); clk_enable_unlock(flags); return ret; } static int __clk_enable(struct clk *clk) { int ret = 0; if (!clk) return 0; if (WARN_ON(clk->prepare_count == 0)) return -ESHUTDOWN; if (clk->enable_count == 0) { ret = __clk_enable(clk->parent); if (ret) return ret; if (clk->ops->enable) { ret = clk->ops->enable(clk->hw); if (ret) { __clk_disable(clk->parent); return ret; } } } clk->enable_count++; return 0; }
Clk_enable會調用到__clk_enable函數,這個函數中會反復迭代調用自身來使能parent clk。並最後調用到了ops->enable回調函數。其他api自行閱讀。
我們使用這些API的一般順序為,通過clk_get傳入一個clk name,或獲取到相應的struct clk結構體,接著再調用其他的api來針對它進行處理。
(2) 向下提供給clock driver注冊的接口API
Clk framework向下提供了注冊clk設備的api,主要是平台廠商實現自己的clk驅動時使用到的。
主要接口如下:
struct clk *clk_register(struct device *dev, struct clk_hw *hw); struct clk *__clk_register(struct device *dev, struct clk_hw *hw); void clk_unregister(struct clk *clk);
注意clk_register和__clk_register之間的區別,clk_register會自己申請struct clk結構體並對它進行初始化。而__clk_register是靜態定義了一個struct clk的結構體,所以它不會再申請內存來存放struct clk結構體。
(3) debugfs的創建
debugfs的創建有兩個函數,分別如下:
int __init clk_debug_init(void); int clk_debug_register(struct clk *clk);
第一個clk_debug_init函數是在系統啟動時調用的,他會首先創建clk debugfs的入口。
而clk_debug_register則是在clk_register中會調用的,也就是說他是在注冊clk設備的時候調用的,他會更新clk之間的拓撲關系,並更新debugfs。
(4) 若干clk通用設備實現
Clk framework為了簡化clk設備的開發,也按照clk的不同特性實現了幾個clk驅動模型,這樣廠商可以根據自己clk的特點直接調用相應模型的注冊函數就能快速實現一個clk驅動,而不必重復造輪子。當然這樣的模型並不能包含所有的clk設備,一些廠商也會自己來實現clk驅動,而不套用相關模型。
一些模型api:
struct clk *clk_register_fixed_rate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate); struct clk *clk_register_gate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 bit_idx, u8 clk_gate_flags, spinlock_t *lock); struct clk *clk_register_divider(struct device *dev, const char *name, const char *parent_name, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_divider_flags, spinlock_t *lock); struct clk *clk_register_mux(struct device *dev, const char *name, const char **parent_names, u8 num_parents, unsigned long flags, void __iomem *reg, u8 shift, u8 width, u8 clk_mux_flags, spinlock_t *lock); struct clk *clk_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div); struct clk *clk_register_composite(struct device *dev, const char *name, const char **parent_names, int num_parents, struct clk_hw *mux_hw, const struct clk_ops *mux_ops, struct clk_hw *rate_hw, const struct clk_ops *rate_ops, struct clk_hw *gate_hw, const struct clk_ops *gate_ops, unsigned long flags);
如上等等。
三、 與device tree的關系
說起dts,就不得不在代碼中指定相應的of_device_id,因為dts中定義的設備是通過這個結構來進行驅動和設備匹配的。
Clk-provider.h #define CLK_OF_DECLARE(name, compat, fn) \ static const struct of_device_id __clk_of_table_##name \ __used __section(__clk_of_table) \ = { .compatible = compat, .data = fn }; Clk.c extern struct of_device_id __clk_of_table[]; static const struct of_device_id __clk_of_table_sentinel __used __section(__clk_of_table_end);
上面這一段需要借助內核編譯的lds文件來解讀,其中傳入了參數給編譯器來確定變量的存放位置。
_section(__clk_of_table)意思就是把該變量存入__clk_of_table段中。而在lds文件中可以看到該段的定義,並且該段是以__clk_of_table_end為結尾的。由上面的定義可以知道,在編譯內核的時候,會把所有的__clk_of_table##name變量都保存在__clk_of_table中,並且__clk_of_table的最後是__clk_of_table_end來結束的。
void __init of_clk_init(const struct of_device_id *matches) { struct device_node *np; if (!matches) matches = __clk_of_table; for_each_matching_node(np, matches) { const struct of_device_id *match = of_match_node(matches, np); of_clk_init_cb_t clk_init_cb = match->data; clk_init_cb(np); } }
從這段用來初始化驅動的函數可以看出來我們自己創建的clk驅動,需要先通過CLK_OF_DECLARE來定義相應的of_device_id,並且要把相應的驅動初始化函數func的地址傳給data。這樣在匹配到相應的設備時就會直接調用驅動初始化函數了。
四、 如何創建自己的clk設備
我們以全志的sunxi平台為例,它的clk驅動是在driver/clk/sunxi/目錄下
在clk-sunxi.c中:
401 /* Matches for of_clk_init */ 402 static const __initconst struct of_device_id clk_match[] = { 403 {.compatible = "allwinner,sun4i-osc-clk", .data = sunxi_osc_clk_setup,}, 404 {} 405 }; 406 407 /* Matches for factors clocks */ 408 static const __initconst struct of_device_id clk_factors_match[] = { 409 {.compatible = "allwinner,sun4i-pll1-clk", .data = &pll1_data,}, 410 {.compatible = "allwinner,sun4i-apb1-clk", .data = &apb1_data,}, 411 {} 412 }; 413 414 /* Matches for divider clocks */ 415 static const __initconst struct of_device_id clk_div_match[] = { 416 {.compatible = "allwinner,sun4i-axi-clk", .data = &axi_data,}, 417 {.compatible = "allwinner,sun4i-ahb-clk", .data = &ahb_data,}, 418 {.compatible = "allwinner,sun4i-apb0-clk", .data = &apb0_data,}, 419 {} 420 }; 421 422 /* Matches for mux clocks */ 423 static const __initconst struct of_device_id clk_mux_match[] = { 424 {.compatible = "allwinner,sun4i-cpu-clk", .data = &cpu_data,}, 425 {.compatible = "allwinner,sun4i-apb1-mux-clk", .data = &apb1_mux_data,}, 426 {} 427 }; 428 429 /* Matches for gate clocks */ 430 static const __initconst struct of_device_id clk_gates_match[] = { 431 {.compatible = "allwinner,sun4i-axi-gates-clk", .data = &axi_gates_data,}, 432 {.compatible = "allwinner,sun4i-ahb-gates-clk", .data = &ahb_gates_data,}, 433 {.compatible = "allwinner,sun4i-apb0-gates-clk", .data = &apb0_gates_data,}, 434 {.compatible = "allwinner,sun4i-apb1-gates-clk", .data = &apb1_gates_data,}, 435 {} 436 }; 437 438 static void __init of_sunxi_table_clock_setup(const struct of_device_id *clk_match, 439 void *function) 440 { 441 struct device_node *np; 442 const struct div_data *data; 443 const struct of_device_id *match; 444 void (*setup_function)(struct device_node *, const void *) = function; 445 446 for_each_matching_node(np, clk_match) { 447 match = of_match_node(clk_match, np); 448 data = match->data; 449 setup_function(np, data); 450 } 451 } 452 453 void __init sunxi_init_clocks(void) 454 { 455 /* Register all the simple sunxi clocks on DT */ 456 of_clk_init(clk_match); 457 458 /* Register factor clocks */ 459 of_sunxi_table_clock_setup(clk_factors_match, sunxi_factors_clk_setup); 460 461 /* Register divider clocks */ 462 of_sunxi_table_clock_setup(clk_div_match, sunxi_divider_clk_setup); 463 464 /* Register mux clocks */ 465 of_sunxi_table_clock_setup(clk_mux_match, sunxi_mux_clk_setup); 466 467 /* Register gate clocks */ 468 of_sunxi_table_clock_setup(clk_gates_match, sunxi_gates_clk_setup); 469 }
這段代碼主要就是定義of_device_id以及相應的主初始化函數。我們通過grep來查一下sunxi_init_clocks在哪裡有調用使用到。
經過查找,在kernel/arch/arm/mach-sunxi/sunxi.c中有調用:
98 static void __init sunxi_timer_init(void) 99 { 100 sunxi_init_clocks(); 101 clocksource_of_init(); 102 } 103 104 static void __init sunxi_dt_init(void) 105 { 106 sunxi_setup_restart(); 107 108 of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); 109 } 110 111 static const char * const sunxi_board_dt_compat[] = { 112 "allwinner,sun4i-a10", 113 "allwinner,sun5i-a13", 114 NULL, 115 }; 116 117 DT_MACHINE_START(SUNXI_DT, "Allwinner A1X (Device Tree)") 118 .init_machine = sunxi_dt_init, 119 .map_io = sunxi_map_io, 120 .init_irq = irqchip_init, 121 .init_time = sunxi_timer_init, 122 .dt_compat = sunxi_board_dt_compat, 123 MACHINE_END
在sunxi_timer_init中有調用到sunxi_init_clocks函數來初始化clk驅動。
題外話
系統起來的時候會bringup運行到kernel_start,在這個函數中會逐一對系統資源進行初始化,它也會根據bootloader傳入的參數來匹配machine,這裡的machine也就是上面各個平台都會自己實現的部分,上面的兩個宏定義DT_MACHINE_START和DT_MACHINE_END之間就是對machine的定義。可以看到machine也是通過dt_compat來進行匹配的。從上面的信息可以看到,這一套內核時同時兼容allwinner,sun4i-a10和allwinner,sun5i-a13兩種類型的設備的。