歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> linux clk的驅動框架

linux clk的驅動框架

日期:2017/3/1 11:46:56   编辑:關於Linux

內核中提供了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 framework

上圖中的黃色區域都是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兩種類型的設備的。

Copyright © Linux教程網 All Rights Reserved