從零開始之驅動發開、linux驅動(三十八、Linux common clock framework(3)_實現邏輯分析)
1. 前言
前面兩篇clock framework的分析文章,分別從clock consumer和clock provider的角度,介紹了Linux kernel怎麼管理系統的clock資源,以及device driver怎麼使用clock資源。本文將深入到clock framework的內部,分析相關的實現邏輯。
注:本文使用的kernel版本為linux-3.16.57。雖然最新版本的kernel增加了一些內容,但主要邏輯沒有改變,就不緊跟kernel的步伐了。
2. struct clk結構
到目前為止,我們還沒有仔細介紹過struct clk這個代表了一個clock的資料結構呢。對consumer和provider來說,可以不關心,但對內部實現邏輯來說,就不得不提了:
struct clk { const char *name; const struct clk_ops *ops; struct clk_hw *hw; struct module *owner; struct clk *parent; const char **parent_names; struct clk **parents; u8 num_parents; u8 new_parent_index; unsigned long rate; unsigned long new_rate; struct clk *new_parent; struct clk *new_child; unsigned long flags; unsigned int enable_count; unsigned int prepare_count; unsigned long accuracy; struct hlist_head children; struct hlist_node child_node; unsigned int notifier_count; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; #endif struct kref ref; };
name, ops, hw, parents_name, num_parents, flags, 可參考在上一節已經說過;
parent,儲存了該clock當前的parent clock的struct clk指標;
parents,一個指標陣列,儲存了所有可能的parent clock的struct clk指標;
rate,當前的clock rate;
new_rate,新設定的clock rate,之所要儲存在這裡,是因為set rate過程中有一些中間計算,後面再詳解;
enable_count, prepare_count,該clock被enable和prepare的次數,用於確保enable/disable以及prepare/unprepare的成對呼叫;
children,該clock的children clocks(孩兒們),以連結串列的形式組織;
child_node,一個list node,自己作為child時,掛到parent的children list時使用;
notifier_count,記錄註冊到notifier的個數。
3. clock regitser/unregister
在上一節中分析過,clock provider需要將系統的clock以tree的形式組織起來,分門別類,並在系統初始化時,通過provider的初始化介面,或者clock framework core的DTS介面,將所有的clock註冊到kernel。
clock的註冊,統一由clk_regitser介面實現,但基於該介面,kernel也提供了其它更為便利註冊介面,下面將會一一描述。
3.1 clk_regitser
clk_register是所有register介面的共同實現,負責將clock註冊到kernel,並返回代表該clock的struct clk指標。分析該介面之前,我們先看一下下面的內容:
上面是kernel中clk_register介面可能的實現位置,由此可以看出,clk_register在“include/linux/clk-provider.h”中宣告,卻可能在不同的C檔案中實現。其它clock API也類似。這說明了什麼?
這恰恰呼應了“Linux common clock framework”中“common”一詞。
在舊的kernel中,clock framework只是規定了一系列的API宣告,具體API的實現,由各個machine程式碼完成。這就導致每個machine目錄下,都有一個類似clock.c的檔案,以比較相似的邏輯,實現clock provider的功能。顯然,這裡面有很多冗餘程式碼。
後來,kernel將這些公共程式碼,以clock provider的形式(上面drivers/clk/clk.c檔案)抽象出來,就成了我們所說的common clock framework。
後面所有的描述,都會以common clock framework的核心程式碼為基礎,其它的,就不再涉及了。
下面是clk_register的實現:
/**
* clk_register - allocate a new clock, register it and return an opaque cookie
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
* clk_register is the primary interface for populating the clock tree with new
* clock nodes. It returns a pointer to the newly allocated struct clk which
* cannot be dereferenced by driver code but may be used in conjuction with the
* rest of the clock API. In the event of an error clk_register will return an
* error code; drivers must test for an error code after calling clk_register.
*/
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
int i, ret;
struct clk *clk;
clk = kzalloc(sizeof(*clk), GFP_KERNEL); /* 申請一個clk */
if (!clk) {
pr_err("%s: could not allocate clk\n", __func__);
ret = -ENOMEM;
goto fail_out;
}
clk->name = kstrdup(hw->init->name, GFP_KERNEL); /* 把hw裡面的name重新拷貝一份給clk的name */
if (!clk->name) {
pr_err("%s: could not allocate clk->name\n", __func__);
ret = -ENOMEM;
goto fail_name;
}
clk->ops = hw->init->ops; /* clk繫結ops */
if (dev && dev->driver)
clk->owner = dev->driver->owner;
clk->hw = hw; /* clk繫結hw */
clk->flags = hw->init->flags;
clk->num_parents = hw->init->num_parents; /* clk繫結mux的所有partnt */
hw->clk = clk; /* hw繫結clk */
/* 給num個parent申請num個指標,用來儲存所有的paerent */
/* allocate local copy in case parent_names is __initdata */
clk->parent_names = kcalloc(clk->num_parents, sizeof(char *),
GFP_KERNEL);
if (!clk->parent_names) {
pr_err("%s: could not allocate clk->parent_names\n", __func__);
ret = -ENOMEM;
goto fail_parent_names;
}
/* 把hw中所有的parent的name都重新拷貝一份繫結到新申請的clk上 */
/* copy each string name in case parent_names is __initdata */
for (i = 0; i < clk->num_parents; i++) {
clk->parent_names[i] = kstrdup(hw->init->parent_names[i],
GFP_KERNEL);
if (!clk->parent_names[i]) {
pr_err("%s: could not copy parent_names\n", __func__);
ret = -ENOMEM;
goto fail_parent_names_copy;
}
}
ret = __clk_init(dev, clk); /* 校驗fops,執行init函式 */
if (!ret)
return clk;
fail_parent_names_copy:
while (--i >= 0)
kfree(clk->parent_names[i]);
kfree(clk->parent_names);
fail_parent_names:
kfree(clk->name);
fail_name:
kfree(clk);
fail_out:
return ERR_PTR(ret);
}
該介面接受一個struct clk_hw指標,該指標包含了將要註冊的clock的資訊(具體可參考上一節),在內部分配一個struct clk變數後,將clock資訊儲存在變數中,並返回給呼叫者。實現邏輯如下:
分配struct clk空間;
根據struct clk_hw指標提供的資訊,初始化clk的name、ops、hw、flags、num_parents、parents_names等變數;
呼叫內部介面__clk_init,執行後續的初始化操作。這個介面包含了clk_regitser的主要邏輯,具體如下。
/**
* __clk_init - initialize the data structures in a struct clk
* @dev: device initializing this clk, placeholder for now
* @clk: clk being initialized
*
* Initializes the lists in struct clk, queries the hardware for the
* parent and rate and sets them both.
*/
int __clk_init(struct device *dev, struct clk *clk)
{
int i, ret = 0;
struct clk *orphan;
struct hlist_node *tmp2;
if (!clk)
return -EINVAL;
clk_prepare_lock(); //獲取鎖
/* check to see if a clock with this name is already registered */
/*校驗當前註冊的時鐘是否已經註冊過了*/
if (__clk_lookup(clk->name)) {
pr_debug("%s: clk %s already initialized\n",
__func__, clk->name);
ret = -EEXIST;
goto out;
}
/* check that clk_ops are sane. See Documentation/clk.txt */
/* 檢查ops裡面是否有相應clk的計算函式 */
if (clk->ops->set_rate &&
!((clk->ops->round_rate || clk->ops->determine_rate) &&
clk->ops->recalc_rate)) {
pr_warning("%s: %s must implement .round_rate or .determine_rate in addition to .recalc_rate\n",
__func__, clk->name);
ret = -EINVAL;
goto out;
}
/* 校驗該clk的ops裡面是否由設定和得到當前時鐘的介面 */
if (clk->ops->set_parent && !clk->ops->get_parent) {
pr_warning("%s: %s must implement .get_parent & .set_parent\n",
__func__, clk->name);
ret = -EINVAL;
goto out;
}
/* 校驗如果有set_rate_and_parent ,則set_parent和set_rate也必須有,因為裡面呼叫這兩個了 */
if (clk->ops->set_rate_and_parent &&
!(clk->ops->set_parent && clk->ops->set_rate)) {
pr_warn("%s: %s must implement .set_parent & .set_rate\n",
__func__, clk->name);
ret = -EINVAL;
goto out;
}
/* throw a WARN if any entries in parent_names are NULL */
/* 檢查parent不是NULL */
for (i = 0; i < clk->num_parents; i++)
WARN(!clk->parent_names[i],
"%s: invalid NULL in %s's .parent_names\n",
__func__, clk->name);
/*
* Allocate an array of struct clk *'s to avoid unnecessary string
* look-ups of clk's possible parents. This can fail for clocks passed
* in to clk_init during early boot; thus any access to clk->parents[]
* must always check for a NULL pointer and try to populate it if
* necessary.
*
* If clk->parents is not NULL we skip this entire block. This allows
* for clock drivers to statically initialize clk->parents.
*/
/* 如果父時鐘個數大於1個,且目前沒繫結好paernt,則查詢到並繫結到clk的parents中 */
if (clk->num_parents > 1 && !clk->parents) {
clk->parents = kcalloc(clk->num_parents, sizeof(struct clk *),
GFP_KERNEL);
/*
* __clk_lookup returns NULL for parents that have not been
* clk_init'd; thus any access to clk->parents[] must check
* for a NULL pointer. We can always perform lazy lookups for
* missing parents later on.
*/
/* 從clk tree中根據parent_names中的每個name查詢parent,並繫結到clk->parents陣列中 */
if (clk->parents)
for (i = 0; i < clk->num_parents; i++)
clk->parents[i] =
__clk_lookup(clk->parent_names[i]);
}
clk->parent = __clk_init_parent(clk); /* 得到parent */
/*
* Populate clk->parent if parent has already been __clk_init'd. If
* parent has not yet been __clk_init'd then place clk in the orphan
* list. If clk has set the CLK_IS_ROOT flag then place it in the root
* clk list.
*
* Every time a new clk is clk_init'd then we walk the list of orphan
* clocks and re-parent any that are children of the clock currently
* being clk_init'd.
*/
/*根據時鐘型別的不同,註冊到不同的連結串列中*/
if (clk->parent)
hlist_add_head(&clk->child_node,
&clk->parent->children);
else if (clk->flags & CLK_IS_ROOT)
hlist_add_head(&clk->child_node, &clk_root_list);
else
hlist_add_head(&clk->child_node, &clk_orphan_list);
/*
* Set clk's accuracy. The preferred method is to use
* .recalc_accuracy. For simple clocks and lazy developers the default
* fallback is to use the parent's accuracy. If a clock doesn't have a
* parent (or is orphaned) then accuracy is set to zero (perfect
* clock).
*/
//設定clk的準確性
if (clk->ops->recalc_accuracy)
clk->accuracy = clk->ops->recalc_accuracy(clk->hw,
__clk_get_accuracy(clk->parent));
else if (clk->parent)
clk->accuracy = clk->parent->accuracy;
else
clk->accuracy = 0;
/*
* Set clk's rate. The preferred method is to use .recalc_rate. For
* simple clocks and lazy developers the default fallback is to use the
* parent's rate. If a clock doesn't have a parent (or is orphaned)
* then rate is set to zero.
*/
//重新計算時鐘頻率
if (clk->ops->recalc_rate)
clk->rate = clk->ops->recalc_rate(clk->hw,
__clk_get_rate(clk->parent));
else if (clk->parent)
clk->rate = clk->parent->rate;
else
clk->rate = 0;
clk_debug_register(clk);
/*
* walk the list of orphan clocks and reparent any that are children of
* this clock
*/
//遍歷孤兒時鐘列表並重新顯示此時鐘的任何孩子
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
if (orphan->num_parents && orphan->ops->get_parent) {
i = orphan->ops->get_parent(orphan->hw);
if (!strcmp(clk->name, orphan->parent_names[i]))
__clk_reparent(orphan, clk);
continue;
}
for (i = 0; i < orphan->num_parents; i++)
if (!strcmp(clk->name, orphan->parent_names[i])) {
__clk_reparent(orphan, clk);
break;
}
}
/*
* optional platform-specific magic
*
* The .init callback is not used by any of the basic clock types, but
* exists for weird hardware that must perform initialization magic.
* Please consider other ways of solving initialization problems before
* using this callback, as its use is discouraged.
*/
//如果clock ops提供了init介面,執行之(由註釋可知,kernel不建議提供init介面)。
if (clk->ops->init)
clk->ops->init(clk->hw);
kref_init(&clk->ref);
out:
clk_prepare_unlock();
return ret;
}
__clk_init介面的實現相當繁瑣,做的事情包括:
21~27行,以clock name為引數,呼叫__clk_lookup介面,查詢是否已有相同name的clock註冊,如果有,則返回錯誤。由此可以看出,clock framework以name唯一識別一個clock,因此不能有同名的clock存在;
29~55行,檢查clk ops的完整性,例如:如果提供了set_rate介面,就必須提供round_rate和recalc_rate介面;如果提供了set_parent,就必須提供get_parent。這些邏輯背後的含義,會在後面相應的地方詳細描述;
57~89行,分配一個struct clk *型別的陣列,快取該clock的parents clock。具體方法是根據parents_name,查詢相應的struct clk指標;
91行,獲取當前的parent clock,並將其儲存在parent指標中。具體可參考下面“說明2”;
93~110行,根據該clock的特性,將它新增到clk_root_list、clk_orphan_list或者parent->children三個連結串列中的一個,具體請參考下面“說明1”;
112~126行,計算時鐘的準確性
118~141行,計算clock的初始rate,具體請參考下面“說明3”;
148~162行,嘗試reparent當前所有的孤兒(orphan)clock,具體請參考下面“說明4”;
164~174行,如果clock ops提供了init介面,執行之(由註釋可知,kernel不建議提供init介面)。
上面的clock init流程,有下面4點補充說明:
說明1:clock的管理和查詢
static HLIST_HEAD(clk_root_list);
static HLIST_HEAD(clk_orphan_list);
static LIST_HEAD(clk_notifier_list);
clock framework有2條全域性的連結串列:clk_root_list和clk_orphan_list。所有設定了CLK_IS_ROOT屬性的clock都會掛在clk_root_list中。其它clock,如果有valid的parent ,則會掛到parent的“children”連結串列中,如果沒有valid的parent,則會掛到clk_orphan_list中。
查詢時(__clk_lookup介面做的事情),依次搜尋:clk_root_list-->root_clk-->children-->child's children,clk_orphan_list-->orphan_clk-->children-->child's children,即可。
說明2:當前parent clock的選擇(__clk_init_parent)
對於沒有parent,或者只有1個parent 的clock來說,比較簡單,設定為NULL,或者根據parent name獲得parent的struct clk指標接。
對於有多個parent的clock,就必須提供.get_parent ops,該ops要根據當前硬體的配置情況,例如暫存器值,返回當前所有使用的parent的index(即第幾個parent)。然後根據index,取出對應parent clock的struct clk指標,作為當前的parent。
說明3:clock的初始rate計算
對於提供.recalc_rate ops的clock來說,優先使用該ops獲取初始的rate。如果沒有提供,退而求其次,直接使用parent clock的rate。最後,如果該clock沒有parent,則初始的rate只能選擇為0。
.recalc_rate ops的功能,是以parent clock的rate為輸入引數,根據當前硬體的配置情況,如暫存器值,計算獲得自身的rate值。
說明4:orphan clocks的reparen
有些情況下,child clock會先於parent clock註冊,此時該child就會成為orphan clock,被收養在clk_orphan_list中。
而每當新的clock註冊時,kernel都會檢查這個clock是否是某個orphan的parent,如果是,就把這個orphan從clk_orphan_list中移除,放到新註冊的clock的懷抱。這就是reparent的功能,它的處理邏輯是:
a) 遍歷orphan list,如果orphan提供了.get_parent ops,則通過該ops得到當前parent的index,並從parent_names中取出該parent的name,然後和新註冊的clock name比較,如果相同,呵呵,找到parent了,執行__clk_reparent,進行後續的操作。
b) 如果沒有提供.get_parent ops,只能遍歷自己的parent_names,檢查是否有和新註冊clock匹配的,如果有,執行__clk_reparent,進行後續的操作。
c) __clk_reparent會把這個orphan從clk_orphan_list中移除,並掛到新註冊的clock上。然後呼叫__clk_recalc_rates,重新計算自己以及自己所有children的rate。計算過程和上面的clock rate設定類似。
3.2 clk_unregister/devm_clk_register/devm_clk_unregister
clock的regitser和init,幾乎佔了clock framework大部分的實現邏輯。clk_unregister是regitser介面的反操作。而devm_clk_register/devm_clk_unregister則是clk_register/clk_unregister的device resource manager版本。
/**
* clk_unregister - unregister a currently registered clock
* @clk: clock to unregister
*/
void clk_unregister(struct clk *clk)
{
unsigned long flags;
if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
return;
clk_prepare_lock();
if (clk->ops == &clk_nodrv_ops) { /* 檢查是否已經解除安裝的 */
pr_err("%s: unregistered clock: %s\n", __func__, clk->name);
goto out;
}
/*
* Assign empty clock ops for consumers that might still hold
* a reference to this clock.
*/
flags = clk_enable_lock();
clk->ops = &clk_nodrv_ops; /* 已經解除安裝的會用空的drv來標記 */
clk_enable_unlock(flags);
if (!hlist_empty(&clk->children)) { /* 如果有子clk,則該clk解除安裝後,它的所有子clk都要設定成孤兒 */
struct clk *child;
struct hlist_node *t;
/* Reparent all children to the orphan list. */
hlist_for_each_entry_safe(child, t, &clk->children, child_node)
clk_set_parent(child, NULL);
}
clk_debug_unregister(clk);
hlist_del_init(&clk->child_node); /* 刪除 */
if (clk->prepare_count)
pr_warn("%s: unregistering prepared clock: %s\n",
__func__, clk->name);
kref_put(&clk->ref, __clk_release);
out:
clk_prepare_unlock();
}
/**
* devm_clk_register - resource managed clk_register()
* @dev: device that is registering this clock
* @hw: link to hardware-specific clock data
*
* Managed clk_register(). Clocks returned from this function are
* automatically clk_unregister()ed on driver detach. See clk_register() for
* more information.
*/
struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw)
{
struct clk *clk;
struct clk **clkp;
clkp = devres_alloc(devm_clk_release, sizeof(*clkp), GFP_KERNEL);
if (!clkp)
return ERR_PTR(-ENOMEM);
clk = clk_register(dev, hw);
if (!IS_ERR(clk)) {
*clkp = clk;
devres_add(dev, clkp);
} else {
devres_free(clkp);
}
return clk;
}
/**
* devm_clk_unregister - resource managed clk_unregister()
* @clk: clock to unregister
*
* Deallocate a clock allocated with devm_clk_register(). Normally
* this function will not need to be called and the resource management
* code will ensure that the resource is freed.
*/
void devm_clk_unregister(struct device *dev, struct clk *clk)
{
WARN_ON(devres_release(dev, devm_clk_release, devm_clk_match, clk));
}
static void devm_clk_release(struct device *dev, void *res)
{
clk_unregister(*(struct clk **)res);
}
3.3 fixed rate clock的註冊
上一節中已經對fixed rate clock有過詳細的介紹,這種型別的clock有兩種註冊方式,通過API註冊和通過DTS註冊,具體的實現位於“drivers/clk/clk-fixed-rate.c”中,介紹如下。
/**
* clk_register_fixed_rate - register fixed-rate clock with the clock framework
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @flags: framework-specific flags
* @fixed_rate: non-adjustable clock rate
*/
//註冊固定時鐘頻率
struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate)
{
return clk_register_fixed_rate_with_accuracy(dev, name, parent_name,
flags, fixed_rate, 0);
}
/**
* clk_register_fixed_rate_with_accuracy - register fixed-rate clock with the
* clock framework
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @flags: framework-specific flags
* @fixed_rate: non-adjustable clock rate
* @fixed_accuracy: non-adjustable clock rate
*/
struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev,
const char *name, const char *parent_name, unsigned long flags,
unsigned long fixed_rate, unsigned long fixed_accuracy)
{
struct clk_fixed_rate *fixed;
struct clk *clk;
struct clk_init_data init;
/* allocate fixed-rate clock */
fixed = kzalloc(sizeof(struct clk_fixed_rate), GFP_KERNEL); //分配一個固定係數的時鐘
if (!fixed) {
pr_err("%s: could not allocate fixed clk\n", __func__);
return ERR_PTR(-ENOMEM);
}
init.name = name;
init.ops = &clk_fixed_rate_ops; /* 繫結固定時鐘的ops,見下面 */
init.flags = flags | CLK_IS_BASIC;
init.parent_names = (parent_name ? &parent_name: NULL); /* 這種只有一個parent或沒有 */
init.num_parents = (parent_name ? 1 : 0);
/* struct clk_fixed_rate assignments */
fixed->fixed_rate = fixed_rate; /* 固定速率 */
fixed->fixed_accuracy = fixed_accuracy; /* 固定精度 */
fixed->hw.init = &init;
/* register the clock */
clk = clk_register(dev, &fixed->hw); /* 註冊固定評論的clk */
if (IS_ERR(clk))
kfree(fixed);
return clk;
}
說明1:struct clk_init_data型別的變數
struct clk_init_data型別的變數(init),是一個區域性變數,傳遞給clk_regitser使用時,用的是它的指標,說明了什麼?說明該變數不會再後面使用了。再回憶一下clk_regitser的實現邏輯,會把所有的資訊copy一遍,這裡就好理解了。後面其它型別的clock註冊時,道理相同。
說明2:fixed rate clock的實現思路
私有資料結構的定義如下:
/**
* struct clk_fixed_rate - fixed-rate clock
* @hw: handle between common and hardware-specific interfaces
* @fixed_rate: constant frequency of clock
*/
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
unsigned long fixed_accuracy;
u8 flags;
};
包含一個struct clk_hw變數,用於clk_regitser。另外三個變數,則為該型別clock特有的屬性。私有資料結構變數(fixed)是通過kzalloc分配的,說明後續還需要使用。那怎麼用呢?
由clk_regitser的實現可知,fixed rate clock註冊時hw);>,把fixed指標中hw變數的地址儲存在了struct clk指標中了。因此,在任何時候,通過struct clk指標(clock的代表),就可以找到所對應clock的struct clk_hw指標,從而可以找到相應的私有變數(fixed)的指標以及其中的私有資料。
基於此,fixed rate ops的實現就順利成章了:
/*
* DOC: basic fixed-rate clock that cannot gate
*
* Traits of this clock:
* prepare - clk_(un)prepare only ensures parents are prepared
* enable - clk_enable only ensures parents are enabled
* rate - rate is always a fixed value. No clk_set_rate support
* parent - fixed parent. No clk_set_parent support
*/
#define to_clk_fixed_rate(_hw) container_of(_hw, struct clk_fixed_rate, hw)
static unsigned long clk_fixed_rate_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return to_clk_fixed_rate(hw)->fixed_rate; /* 固定時鐘頻率 */
}
static unsigned long clk_fixed_rate_recalc_accuracy(struct clk_hw *hw,
unsigned long parent_accuracy)
{
return to_clk_fixed_rate(hw)->fixed_accuracy; /* 固定時鐘精度 */
}
const struct clk_ops clk_fixed_rate_ops = {
.recalc_rate = clk_fixed_rate_recalc_rate,
.recalc_accuracy = clk_fixed_rate_recalc_accuracy,
};
2)通過DTS註冊
fixed rate clock是非常簡單的一種clock,因而可以直接通過DTS的形式註冊,clock framework負責解析DTS,並呼叫API註冊clock,如下:
#ifdef CONFIG_OF
/**
* of_fixed_clk_setup() - Setup function for simple fixed rate clock
*/
void of_fixed_clk_setup(struct device_node *node)
{
struct clk *clk;
const char *clk_name = node->name;
u32 rate;
u32 accuracy = 0;
if (of_property_read_u32(node, "clock-frequency", &rate))
return;
of_property_read_u32(node, "clock-accuracy", &accuracy);
of_property_read_string(node, "clock-output-names", &clk_name);
clk = clk_register_fixed_rate_with_accuracy(NULL, clk_name, NULL,
CLK_IS_ROOT, rate,
accuracy);
if (!IS_ERR(clk))
of_clk_add_provider(node, of_clk_src_simple_get, clk);
}
EXPORT_SYMBOL_GPL(of_fixed_clk_setup);
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
#endif
#define CLK_OF_DECLARE(name, compat, fn) OF_DECLARE_1(clk, name, compat, fn)
#define OF_DECLARE_1(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
#ifdef CONFIG_OF
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#else
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__attribute__((unused)) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#endif
首先看一下CLK_OF_DECLARE巨集,它的定義位於“include/linux/clk-provider.h”中,負責在指定的section中(以__clk_of_table開始的位置),定義struct of_device_id型別的變數,並由of_clk_init介面解析、匹配,如果匹配成功,則執行相應的回撥函式(這裡為of_fixed_clk_setup);
初始化的時候,device tree負責讀取DTS,並和這些變數的名字(這裡為"fixed-clock")匹配,如果匹配成功,則執行相應的回撥函式(這裡為of_fixed_clk_setup);
of_fixed_clk_setup會解析兩個DTS欄位"clock-frequency"和"clock-output-names",然後呼叫clk_register_fixed_rate,註冊clock。注意,註冊時的flags為CLK_IS_ROOT,說明目前只支援ROOT型別的clock通過DTS註冊;
最後,呼叫of_clk_add_provider介面,將該clock新增到provider list中,方便後續的查詢使用。該介面會在後面再詳細介紹。
of_clk_init負責從DTS中掃描並初始化clock provider,如下:
/**
* of_clk_init() - Scan and init clock providers from the DT
* @matches: array of compatible values and init functions for providers.
*
* This function scans the device tree for matching clock providers
* and calls their initialization functions. It also does it by trying
* to follow the dependencies.
*/
void __init of_clk_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np;
struct clock_provider *clk_provider, *next;
bool is_init_done;
bool force = false;
if (!matches)
matches = &__clk_of_table;
/* First prepare the list of the clocks providers */
for_each_matching_node_and_match(np, matches, &match) {
struct clock_provider *parent =
kzalloc(sizeof(struct clock_provider), GFP_KERNEL);
parent->clk_init_cb = match->data;
parent->np = np;
list_add_tail(&parent->node, &clk_provider_list);
}
while (!list_empty(&clk_provider_list)) {
is_init_done = false;
list_for_each_entry_safe(clk_provider, next,
&clk_provider_list, node) {
if (force || parent_ready(clk_provider->np)) {
clk_provider->clk_init_cb(clk_provider->np);
list_del(&clk_provider->node);
kfree(clk_provider);
is_init_done = true;
}
}
/*
* We didn't manage to initialize any of the
* remaining providers during the last loop, so now we
* initialize all the remaining ones unconditionally
* in case the clock parent was not mandatory
*/
if (!is_init_done)
force = true;
}
}
該介面有一個輸入引數,用於指定需要掃描的OF id,如果留空,則會掃描__clk_of_table,就是通過CLK_OF_DECLARE巨集指定的fixed rate等型別的clock。
在最新的kernel中,會在初始化程式碼(time_init)中以NULL為引數呼叫一次of_clk_init,以便自動匹配並初始化DTS中的描述的類似fixed rate的clock。
這裡使用大量篇幅描述一個簡單的fixed rate clock的註冊方式,主要目的是給大家介紹一種通用的實現方式,或者說通用思路。後面其它型別的clock,包括我們自定義的型別,實現方法都是一樣的。這裡就不羅嗦了,大家看程式碼就可以了。
3.4 gate、devider、mux、fixed factor、composite以及自定義型別clock的註冊
這裡先放有一個時鐘分頻器(devider)的驅動底層實現
/**
* clk_register_divider - register a divider clock with the clock framework
* @dev: device registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @flags: framework-specific flags
* @reg: register address to adjust divider
* @shift: number of bits to shift the bitfield
* @width: width of the bitfield
* @clk_divider_flags: divider-specific flags for this clock
* @lock: shared register lock for this clock
*/
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)
{
return _register_divider(dev, name, parent_name, flags, reg, shift,
width, clk_divider_flags, NULL, lock);
}
EXPORT_SYMBOL_GPL(clk_register_divider);
/**
* clk_register_divider_table - register a table based divider clock with
* the clock framework
* @dev: device registering this clock
* @name: name of this clock
* @parent_name: name of clock's parent
* @flags: framework-specific flags
* @reg: register address to adjust divider
* @shift: number of bits to shift the bitfield
* @width: width of the bitfield
* @clk_divider_flags: divider-specific flags for this clock
* @table: array of divider/value pairs ending with a div set to 0
* @lock: shared register lock for this clock
*/
struct clk *clk_register_divider_table(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 shift, u8 width,
u8 clk_divider_flags, const struct clk_div_table *table,
spinlock_t *lock)
{
return _register_divider(dev, name, parent_name, flags, reg, shift,
width, clk_divider_flags, table, lock);
}
static struct 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, const struct clk_div_table *table,
spinlock_t *lock)
{
struct clk_divider *div;
struct clk *clk;
struct clk_init_data init;
if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {
if (width + shift > 16) {
pr_warn("divider value exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the divider */
div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL);
if (!div) {
pr_err("%s: could not allocate divider clk\n", __func__);
return ERR_PTR(-ENOMEM);
}
init.name = name;
init.ops = &clk_divider_ops; /* 分頻器操作介面 */
init.flags = flags | CLK_IS_BASIC;
init.parent_names = (parent_name ? &parent_name: NULL);
init.num_parents = (parent_name ? 1 : 0);
/* struct clk_divider assignments */
div->reg = reg; /* 分頻器暫存器地址 */
div->shift = shift; /* 分頻器從那一位開始 */
div->width = width; /* 分頻器位寬 */
div->flags = clk_divider_flags;
div->lock = lock;
div->hw.init = &init;
div->table = table;
/* register the clock */
clk = clk_register(dev, &div->hw);
if (IS_ERR(clk))
kfree(div);
return clk;
}
因為分頻器分頻可能涉及到不止2分頻,還可能1、2、4...很多分頻的情況,註冊時會註冊對應值以及分頻比,同時也會有讀寫暫存器,以及按需求設定最接近要求的頻率的計算和設定,需要設定某個頻率,最終是通過查表來確認分頻值,所以分頻器屬於比較複雜的時鐘,程式碼會比較多,但思路和固定頻率的完全一樣。
/*
* DOC: basic adjustable divider clock that cannot gate
*
* Traits of this clock:
* prepare - clk_prepare only ensures that parents are prepared
* enable - clk_enable only ensures that parents are enabled
* rate - rate is adjustable. clk->rate = DIV_ROUND_UP(parent->rate / divisor)
* parent - fixed parent. No clk_set_parent support
*/
#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)
#define div_mask(d) ((1 << ((d)->width)) - 1)
static unsigned int _get_table_maxdiv(const struct clk_div_table *table)
{
unsigned int maxdiv = 0;
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
if (clkt->div > maxdiv)
maxdiv = clkt->div;
return maxdiv;
}
static unsigned int _get_table_mindiv(const struct clk_div_table *table)
{
unsigned int mindiv = UINT_MAX;
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
if (clkt->div < mindiv)
mindiv = clkt->div;
return mindiv;
}
static unsigned int _get_maxdiv(struct clk_divider *divider)
{
if (divider->flags & CLK_DIVIDER_ONE_BASED)
return div_mask(divider);
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << div_mask(divider);
if (divider->table)
return _get_table_maxdiv(divider->table);
return div_mask(divider) + 1;
}
static unsigned int _get_table_div(const struct clk_div_table *table,
unsigned int val)
{
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
if (clkt->val == val)
return clkt->div;
return 0;
}
static unsigned int _get_div(struct clk_divider *divider, unsigned int val)
{
if (divider->flags & CLK_DIVIDER_ONE_BASED)
return val;
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
return 1 << val;
if (divider->table)
return _get_table_div(divider->table, val);
return val + 1;
}
static unsigned int _get_table_val(const struct clk_div_table *table,
unsigned int div)
{
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
if (clkt->div == div)
return clkt->val;
return 0;
}
static unsigned int _get_val(struct clk_divider *divider, unsigned int div)
{
if (divider->flags & CLK_DIVIDER_ONE_BASED)
return div;
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
return __ffs(div);
if (divider->table)
return _get_table_val(divider->table, div);
return div - 1;
}
static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_divider *divider = to_clk_divider(hw);
unsigned int div, val;
val = clk_readl(divider->reg) >> divider->shift;
val &= div_mask(divider);
div = _get_div(divider, val);
if (!div) {
WARN(!(divider->flags & CLK_DIVIDER_ALLOW_ZERO),
"%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n",
__clk_get_name(hw->clk));
return parent_rate;
}
return DIV_ROUND_UP(parent_rate, div);
}
static bool _is_valid_table_div(const struct clk_div_table *table,
unsigned int div)
{
const struct clk_div_table *clkt;
for (clkt = table; clkt->div; clkt++)
if (clkt->div == div)
return true;
return false;
}
static bool _is_valid_div(struct clk_divider *divider, unsigned int div)
{
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
return is_power_of_2(div);
if (divider->table)
return _is_valid_table_div(divider->table, div);
return true;
}
static int _round_up_table(const struct clk_div_table *table, int div)
{
const struct clk_div_table *clkt;
int up = INT_MAX;
for (clkt = table; clkt->div; clkt++) {
if (clkt->div == div)
return clkt->div;
else if (clkt->div < div)
continue;
if ((clkt->div - div) < (up - div))
up = clkt->div;
}
return up;
}
static int _round_down_table(const struct clk_div_table *table, int div)
{
const struct clk_div_table *clkt;
int down = _get_table_mindiv(table);
for (clkt = table; clkt->div; clkt++) {
if (clkt->div == div)
return clkt->div;
else if (clkt->div > div)
continue;
if ((div - clkt->div) < (div - down))
down = clkt->div;
}
return down;
}
static int _div_round_up(struct clk_divider *divider,
unsigned long parent_rate, unsigned long rate)
{
int div = DIV_ROUND_UP(parent_rate, rate);
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
div = __roundup_pow_of_two(div);
if (divider->table)
div = _round_up_table(divider->table, div);
return div;
}
static int _div_round_closest(struct clk_divider *divider,
unsigned long parent_rate, unsigned long rate)
{
int up, down, div;
unsigned long up_rate, down_rate;
up = down = div = DIV_ROUND_CLOSEST(parent_rate, rate);
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO) {
up = __roundup_pow_of_two(div);
down = __rounddown_pow_of_two(div);
} else if (divider->table) {
up = _round_up_table(divider->table, div);
down = _round_down_table(divider->table, div);
}
up_rate = DIV_ROUND_UP(parent_rate, up);
down_rate = DIV_ROUND_UP(parent_rate, down);
return (rate - up_rate) <= (down_rate - rate) ? up : down;
}
static int _div_round(struct clk_divider *divider, unsigned long parent_rate,
unsigned long rate)
{
if (divider->flags & CLK_DIVIDER_ROUND_CLOSEST)
return _div_round_closest(divider, parent_rate, rate);
return _div_round_up(divider, parent_rate, rate);
}
static bool _is_best_div(struct clk_divider *divider,
unsigned long rate, unsigned long now, unsigned long best)
{
if (divider->flags & CLK_DIVIDER_ROUND_CLOSEST)
return abs(rate - now) < abs(rate - best);
return now <= rate && now > best;
}
static int _next_div(struct clk_divider *divider, int div)
{
div++;
if (divider->flags & CLK_DIVIDER_POWER_OF_TWO)
return __roundup_pow_of_two(div);
if (divider->table)
return _round_up_table(divider->table, div);
return div;
}
static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
unsigned long *best_parent_rate)
{
struct clk_divider *divider = to_clk_divider(hw);
int i, bestdiv = 0;
unsigned long parent_rate, best = 0, now, maxdiv;
unsigned long parent_rate_saved = *best_parent_rate;
if (!rate)
rate = 1;
/* if read only, just return current value */
if (divider->flags & CLK_DIVIDER_READ_ONLY) {
bestdiv = clk_readl(divider->reg) >> divider->shift;
bestdiv &= div_mask(divider);
bestdiv = _get_div(divider, bestdiv);
return bestdiv;
}
maxdiv = _get_maxdiv(divider);
if (!(__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT)) {
parent_rate = *best_parent_rate;
bestdiv = _div_round(divider, parent_rate, rate);
bestdiv = bestdiv == 0 ? 1 : bestdiv;
bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
return bestdiv;
}
/*
* The maximum divider we can use without overflowing
* unsigned long in rate * i below
*/
maxdiv = min(ULONG_MAX / rate, maxdiv);
for (i = 1; i <= maxdiv; i = _next_div(divider, i)) {
if (!_is_valid_div(divider, i))
continue;
if (rate * i == parent_rate_saved) {
/*
* It's the most ideal case if the requested rate can be
* divided from parent clock without needing to change
* parent rate, so return the divider immediately.
*/
*best_parent_rate = parent_rate_saved;
return i;
}
parent_rate = __clk_round_rate(__clk_get_parent(hw->clk),
rate * i);
now = DIV_ROUND_UP(parent_rate, i);
if (_is_best_div(divider, rate, now, best)) {
bestdiv = i;
best = now;
*best_parent_rate = parent_rate;
}
}
if (!bestdiv) {
bestdiv = _get_maxdiv(divider);
*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1);
}
return bestdiv;
}
static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
int div;
div = clk_divider_bestdiv(hw, rate, prate);
return DIV_ROUND_UP(*prate, div);
}
static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_divider *divider = to_clk_divider(hw);
unsigned int div, value;
unsigned long flags = 0;
u32 val;
div = DIV_ROUND_UP(parent_rate, rate);
if (!_is_valid_div(divider, div))
return -EINVAL;
value = _get_val(divider, div);
if (value > div_mask(divider))
value = div_mask(divider);
if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
val = div_mask(divider) << (divider->shift + 16);
} else {
val = clk_readl(divider->reg);
val &= ~(div_mask(divider) << divider->shift);
}
val |= value << divider->shift;
clk_writel(val, divider->reg);
if (divider->lock)
spin_unlock_irqrestore(divider->lock, flags);
return 0;
}
const struct clk_ops clk_divider_ops = {
.recalc_rate = clk_divider_recalc_rate, /* 根據父時鐘計算頻率 */
.round_rate = clk_divider_round_rate, /* 計算最接近要求頻率的設定 */
.set_rate = clk_divider_set_rate, /* 讀寫暫存器,設定頻率 */
};
其他幾個的和這個類似,就不再這裡說明了。
4.時鐘查詢表的註冊
從前面分析,clk本來是以連結串列的形式組成樹的形式的(下節給圖)。但樹的形式對查詢來說就不是很方便了,所以核心中,又對五種形式的時鐘,以clk_lookup_alloc組織的連結串列的形式管理了起來。
下面是管理連結串列的定義
static LIST_HEAD(clocks);
static DEFINE_MUTEX(clocks_mutex);
下面看一下是怎麼組織的,後面章節講使用。
/**
* clk_register_clkdev - register one clock lookup for a struct clk
* @clk: struct clk to associate with all clk_lookups
* @con_id: connection ID string on device
* @dev_id: format string describing device name
*
* con_id or dev_id may be NULL as a wildcard, just as in the rest of
* clkdev.
*
* To make things easier for mass registration, we detect error clks
* from a previous clk_register() call, and return the error code for
* those. This is to permit this function to be called immediately
* after clk_register().
*/
int clk_register_clkdev(struct clk *clk, const char *con_id,
const char *dev_fmt, ...)
{
struct clk_lookup *cl;
va_list ap;
if (IS_ERR(clk))
return PTR_ERR(clk);
va_start(ap, dev_fmt);
cl = vclkdev_alloc(clk, con_id, dev_fmt, ap); /* clk繫結到clk_lookup,同時把con_id和引數dev_fmt繫結好 */
va_end(ap);
if (!cl)
return -ENOMEM;
clkdev_add(cl); /* 把cl以加入clock連結串列中 */
return 0;
}
struct clk_lookup_alloc {
struct clk_lookup cl;
char dev_id[MAX_DEV_ID];
char con_id[MAX_CON_ID];
};
struct clk_lookup {
struct list_head node;
const char *dev_id;
const char *con_id;
struct clk *clk;
};
static inline struct clk_lookup_alloc *__clkdev_alloc(size_t size)
{
return kzalloc(size, GFP_KERNEL);
}
static struct clk_lookup * __init_refok
vclkdev_alloc(struct clk *clk, const char *con_id, const char *dev_fmt,
va_list ap)
{
struct clk_lookup_alloc *cla;
cla = __clkdev_alloc(sizeof(*cla)); /* 申請clk_lookup_alloc空間 */
if (!cla)
return NULL;
cla->cl.clk = clk; /* clk繫結到clk_lookup_alloc 裡面的clk_lookup上 */
if (con_id) {
strlcpy(cla->con_id, con_id, sizeof(cla->con_id)); /* 把使用者傳的con_id繫結到clk_lookup_alloc的con_id裡 */
cla->cl.con_id = cla->con_id; /* 同時clk_lookup 也繫結相同的 */
}
if (dev_fmt) {
/* 同上,繫結dev_id */
vscnprintf(cla->dev_id, sizeof(cla->dev_id), dev_fmt, ap);
cla->cl.dev_id = cla->dev_id;
}
return &cla->cl; /* 返回clk_lookup,後面的使用也是用的clk_lookup,不用clk_lookup_alloc */
}
/* 繫結clk_lookup 到clock連結串列上 */
void clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_add_tail(&cl->node, &clocks);
mutex_unlock(&clocks_mutex);
}
當然有註冊也必須有刪除的,但是這個是沒有用的,時鐘屬於cpu中的硬體,可以關掉,不能刪除。
/*
* clkdev_drop - remove a clock dynamically allocated
*/
void clkdev_drop(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_del(&cl->node);
mutex_unlock(&clocks_mutex);
kfree(cl);
}
前面時把時鐘註冊到clock連結串列中。
下面就是使用了。
先看一下使用方式。
現在核心都是和裝置資源管理一塊來使用的,但核心的函式都是一樣的。
struct clk *devm_clk_get(struct device *dev, const char *id)
{
struct clk **ptr, *clk;
ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL); /* 申請繫結自動釋放裝置 */
if (!ptr)
return ERR_PTR(-ENOMEM);
clk = clk_get(dev, id); /* 得到時鐘 */
if (!IS_ERR(clk)) {
*ptr = clk;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return clk;
}
起始前面第一節已經說過了,這裡再分析一遍。
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, con_id); /* 從裝置樹節點得到clk */
if (!IS_ERR(clk)) /* 注意這裡對錯誤判斷取反了,即沒錯的話會直接返回clk */
return clk;
if (PTR_ERR(clk) == -EPROBE_DEFER) /* 重新獲取 */
return clk;
}
return clk_get_sys(dev_id, con_id); /* 裝置樹節點沒找到,從系統註冊連結串列中搜索得到 */
}
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
struct clk_lookup *cl;
mutex_lock(&clocks_mutex);
cl = clk_find(dev_id, con_id); /* 根據裝置名或連結名在clock連結串列中查詢到對應的cl*/
if (cl && !__clk_get(cl->clk)) /* 這裡對找到的時鐘的引用計數+1 */
cl = NULL;
mutex_unlock(&clocks_mutex);
return cl ? cl->clk : ERR_PTR(-ENOENT);
}
/*
* Find the correct struct clk for the device and connection ID.
* We do slightly fuzzy matching here:
* An entry with a NULL ID is assumed to be a wildcard.
* If an entry has a device ID, it must match
* If an entry has a connection ID, it must match
* Then we take the most specific entry - with the following
* order of precedence: dev+con > dev only > con only. 這是重點,即匹配優先順序
*/
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) /* 連線名稱(可以是pclk,uart_clk,phy,pci,總是一般都是匯流排的時鐘名稱) */
best_possible += 1;
list_for_each_entry(p, &clocks, node) { /* clocks連結串列中查詢,根據dev+con > dev only > con優先順序來匹配 */
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;
}
可以看到最終就是根據con_id或dev_id來從clock連結串列中搜索匹配,找到最恰當的那個clk。
最後說一下,新的4.9.92的核心中,對資料結構做了一些調整。
下面列出不同點3.16.57
/**
* struct clk_hw - handle for traversing from a struct clk to its corresponding
* hardware-specific structure. struct clk_hw should be declared within struct
* clk_foo and then referenced by the struct clk instance that uses struct
* clk_foo's clk_ops
*
* @clk: pointer to the struct clk instance that points back to this struct
* clk_hw instance
*
* @init: pointer to struct clk_init_data that contains the init data shared
* with the common clock framework.
*/
struct clk_hw {
struct clk *clk;
const struct clk_init_data *init;
};
struct clk {
const char *name;
const struct clk_ops *ops;
struct clk_hw *hw;
struct module *owner;
struct clk *parent;
const char **parent_names;
struct clk **parents;
u8 num_parents;
u8 new_parent_index;
unsigned long rate;
unsigned long new_rate;
struct clk *new_parent;
struct clk *new_child;
unsigned long flags;
unsigned int enable_count;
unsigned int prepare_count;
unsigned long accuracy;
struct hlist_head children;
struct hlist_node child_node;
unsigned int notifier_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
#endif
struct kref ref;
};
4.9.92中把
/**
* struct clk_hw - handle for traversing from a struct clk to its corresponding
* hardware-specific structure. struct clk_hw should be declared within struct
* clk_foo and then referenced by the struct clk instance that uses struct
* clk_foo's clk_ops
*
* @core: pointer to the struct clk_core instance that points back to this
* struct clk_hw instance
*
* @clk: pointer to the per-user struct clk instance that can be used to call
* into the clk API
*
* @init: pointer to struct clk_init_data that contains the init data shared
* with the common clock framework.
*/
struct clk_hw {
struct clk_core *core;
struct clk *clk;
const struct clk_init_data *init;
};
/*** private data structures ***/
struct clk_core {
const char *name;
const struct clk_ops *ops;
struct clk_hw *hw;
struct module *owner;
struct clk_core *parent;
const char **parent_names;
struct clk_core **parents;
u8 num_parents;
u8 new_parent_index;
unsigned long rate;
unsigned long req_rate;
unsigned long new_rate;
struct clk_core *new_parent;
struct clk_core *new_child;
unsigned long flags;
bool orphan;
unsigned int enable_count;
unsigned int prepare_count;
unsigned long min_rate;
unsigned long max_rate;
unsigned long accuracy;
int phase;
struct hlist_head children;
struct hlist_node child_node;
struct hlist_head clks;
unsigned int notifier_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct hlist_node debug_node;
#endif
struct kref ref;
};
struct clk {
struct clk_core *core;
const char *dev_id;
const char *con_id;
unsigned long min_rate;
unsigned long max_rate;
struct hlist_node clks_node;
};
對比可以看到,把之前clk中一些私有的資料,全部都重新分裝了一個結構體。稱作clk_core。
同時也增加來時鐘速率的最大最小分為限制。
同時clk_hw連結來一個clk的所有有效資訊,3.16.57核心還有clk做內部中轉,而4.9.98用clk_hw來作為來一個核心中轉戰。
下面列出4.9.98的核心程式碼
/**
* clk_register_clkdev - register one clock lookup for a struct clk
* @clk: struct clk to associate with all clk_lookups
* @con_id: connection ID string on device
* @dev_id: string describing device name
*
* con_id or dev_id may be NULL as a wildcard, just as in the rest of
* clkdev.
*
* To make things easier for mass registration, we detect error clks
* from a previous clk_register() call, and return the error code for
* those. This is to permit this function to be called immediately
* after clk_register().
*/
int clk_register_clkdev(struct clk *clk, const char *con_id,
const char *dev_id)
{
struct clk_lookup *cl;
if (IS_ERR(clk))
return PTR_ERR(clk);
/*
* Since dev_id can be NULL, and NULL is handled specially, we must
* pass it as either a NULL format string, or with "%s".
*/
/* 下面就是差異點,先從clk中拿到clk_hw再使用 */
if (dev_id)
cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, "%s",
dev_id);
else
cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, NULL);
return cl ? 0 : -ENOMEM;
}
static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw,
const char *con_id,
const char *dev_id, ...)
{
struct clk_lookup *cl;
va_list ap;
va_start(ap, dev_id);
cl = vclkdev_create(hw, con_id, dev_id, ap); /* 名字做來調整 */
va_end(ap);
return cl;
}
static struct clk_lookup *
vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt,
va_list ap)
{
struct clk_lookup *cl;
cl = vclkdev_alloc(hw, con_id, dev_fmt, ap);
if (cl)
__clkdev_add(cl);
return cl;
}
static struct clk_lookup * __ref
vclkdev_alloc(struct clk_hw *hw, const char *con_id, const char *dev_fmt,
va_list ap)
{
struct clk_lookup_alloc *cla;
cla = __clkdev_alloc(sizeof(*cla));
if (!cla)
return NULL;
cla->cl.clk_hw = hw;
if (con_id) {
strlcpy(cla->con_id, con_id, sizeof(cla->con_id));
cla->cl.con_id = cla->con_id;
}
if (dev_fmt) {
vscnprintf(cla->dev_id, sizeof(cla->dev_id), dev_fmt, ap);
cla->cl.dev_id = cla->dev_id;
}
return &cla->cl;
}
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);
}
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); /* 相比前面增加clk申請來管理 */
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);
}
/*
每個clock由一個struct clk_core描述,其與struct clk_hw是一一對應的關係,
但是struct clk可能有很多個,其他驅動需要操作clock時,都需要先分配一個
struct clk 型別的指標,因此其與struct clk_core是一對多的關係,
也可以說clk是clk_core的例項 */
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 ERR_CAST(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;
}