內核中提供了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兩種類型的設備的。