從零開始之驅動發開、linux驅動(三十九、Linux common clock framework(4)_總結)
前面三節參考蝸窩大神的文章分析了Linux common clock framework的主要實現細節,本篇則是對前三篇從全域性的一個整合說明。
common clock framework主要維護著四條連結串列
static HLIST_HEAD(clk_root_list);
static HLIST_HEAD(clk_orphan_list);
static LIST_HEAD(clk_notifier_list);
static LIST_HEAD(clocks);
其中clock連結串列我們在上一節的末尾已經說明,這個主要就是為了方便查詢。
clk_root_list是存放根時鐘的,一個連結串列,通常根時鐘都是固定頻率的時鐘。(fix)
clk_orphan_list是存放孤兒節點的時鐘,即一般而言沒有父時鐘,也不是根時鐘,這種時鐘通常就是孤兒時鐘。
clk_notifier_list是通知連結串列,即某個父時鐘跟新了頻率,其也要通知子時鐘更新它的頻率計算。
註冊時的位置如下
clk_register
__clk_init
正常情況下,絕大多數時鐘都是有父時鐘的。一般都是root連結串列上某個根時鐘的子時鐘。(比如下圖的藍色框標註的)
當然,上面的各個root的子時鐘,下面有可以繼續級聯子時鐘。
但是對於某些特殊情況下就會出現孤兒時鐘:
即註冊時,其父節點還沒註冊的非根時鐘都是孤兒時鐘。orphan時鐘和root時鐘的管理是一致的,孤兒時鐘下面,也是可以掛接子時鐘的。
一般在註冊時,如果子時鐘先於父時鐘註冊,這時就會掛接到clk_orphan_list時鐘連結串列上,這種情況下,待父時鐘註冊好後,子時鐘又要從orphan時鐘連結串列上移到它的父時鐘下面(位於root連結串列下)。
函式同樣位於註冊時鐘的函式中。
clk_register
__clk_init
最後,再次列出註冊用的哪幾種函式原型。
開關型別的時鐘
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_fixed_rate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
unsigned long fixed_rate);
多選一型別的時鐘
void __init samsung_clk_register_mux(struct samsung_clk_provider *ctx,
struct samsung_mux_clock *list,
unsigned int nr_clk);
分頻型別的時鐘
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);
綜合類型的時鐘(上面幾個的結合,但一般用的不多)
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)
通過上面可以發現,倍頻的時鐘註冊是沒有的。
一般都是用的鎖相環PLL(phase locked loop)來實現的。
因為上面幾個標準的時鐘註冊函式是沒有這個的(因為這個各廠商的差異太大),所以這個的ops等都是廠商單獨來實現的。
已三星為例,三星是根據鎖相環電路(晶片)的不通過來實現自己平臺下的各種處理器的pll註冊的。
static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_clk,
void __iomem *base)
{
struct samsung_clk_pll *pll;
struct clk *clk;
struct clk_init_data init;
int ret, len;
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll) {
pr_err("%s: could not allocate pll clk %s\n",
__func__, pll_clk->name);
return;
}
init.name = pll_clk->name;
init.flags = pll_clk->flags;
init.parent_names = &pll_clk->parent_name;
init.num_parents = 1;
if (pll_clk->rate_table) {
/* find count of rates in rate_table */
for (len = 0; pll_clk->rate_table[len].rate != 0; )
len++;
pll->rate_count = len;
pll->rate_table = kmemdup(pll_clk->rate_table,
pll->rate_count *
sizeof(struct samsung_pll_rate_table),
GFP_KERNEL);
WARN(!pll->rate_table,
"%s: could not allocate rate table for %s\n",
__func__, pll_clk->name);
}
switch (pll_clk->type) {
case pll_2126:
init.ops = &samsung_pll2126_clk_ops;
break;
case pll_3000:
init.ops = &samsung_pll3000_clk_ops;
break;
/* clk_ops for 35xx and 2550 are similar */
case pll_35xx:
case pll_2550:
case pll_1450x:
case pll_1451x:
case pll_1452x:
if (!pll->rate_table)
init.ops = &samsung_pll35xx_clk_min_ops;
else
init.ops = &samsung_pll35xx_clk_ops;
break;
case pll_4500:
init.ops = &samsung_pll45xx_clk_min_ops;
break;
case pll_4502:
case pll_4508:
if (!pll->rate_table)
init.ops = &samsung_pll45xx_clk_min_ops;
else
init.ops = &samsung_pll45xx_clk_ops;
break;
/* clk_ops for 36xx and 2650 are similar */
case pll_36xx:
case pll_2650:
if (!pll->rate_table)
init.ops = &samsung_pll36xx_clk_min_ops;
else
init.ops = &samsung_pll36xx_clk_ops;
break;
case pll_6552:
case pll_6552_s3c2416:
init.ops = &samsung_pll6552_clk_ops;
break;
case pll_6553:
init.ops = &samsung_pll6553_clk_ops;
break;
case pll_4600:
case pll_4650:
case pll_4650c:
case pll_1460x:
if (!pll->rate_table)
init.ops = &samsung_pll46xx_clk_min_ops;
else
init.ops = &samsung_pll46xx_clk_ops;
break;
case pll_s3c2410_mpll:
if (!pll->rate_table)
init.ops = &samsung_s3c2410_mpll_clk_min_ops;
else
init.ops = &samsung_s3c2410_mpll_clk_ops;
break;
case pll_s3c2410_upll:
if (!pll->rate_table)
init.ops = &samsung_s3c2410_upll_clk_min_ops;
else
init.ops = &samsung_s3c2410_upll_clk_ops;
break;
case pll_s3c2440_mpll:
if (!pll->rate_table)
init.ops = &samsung_s3c2440_mpll_clk_min_ops;
else
init.ops = &samsung_s3c2440_mpll_clk_ops;
break;
case pll_2550x:
init.ops = &samsung_pll2550x_clk_ops;
break;
case pll_2550xx:
if (!pll->rate_table)
init.ops = &samsung_pll2550xx_clk_min_ops;
else
init.ops = &samsung_pll2550xx_clk_ops;
break;
case pll_2650x:
if (!pll->rate_table)
init.ops = &samsung_pll2650x_clk_min_ops;
else
init.ops = &samsung_pll2650x_clk_ops;
break;
case pll_2650xx:
if (!pll->rate_table)
init.ops = &samsung_pll2650xx_clk_min_ops;
else
init.ops = &samsung_pll2650xx_clk_ops;
break;
default:
pr_warn("%s: Unknown pll type for pll clk %s\n",
__func__, pll_clk->name);
}
pll->hw.init = &init;
pll->type = pll_clk->type;
pll->lock_reg = base + pll_clk->lock_offset;
pll->con_reg = base + pll_clk->con_offset;
clk = clk_register(NULL, &pll->hw);
if (IS_ERR(clk)) {
pr_err("%s: failed to register pll clock %s : %ld\n",
__func__, pll_clk->name, PTR_ERR(clk));
kfree(pll);
return;
}
samsung_clk_add_lookup(ctx, clk, pll_clk->id);
if (!pll_clk->alias)
return;
ret = clk_register_clkdev(clk, pll_clk->alias, pll_clk->dev_name);
if (ret)
pr_err("%s: failed to register lookup for %s : %d",
__func__, pll_clk->name, ret);
}
void __init samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_list,
unsigned int nr_pll, void __iomem *base)
{
int cnt;
for (cnt = 0; cnt < nr_pll; cnt++)
_samsung_clk_register_pll(ctx, &pll_list[cnt], base);
}
下面列出三星目前有的鎖相環型別
enum samsung_pll_type {
pll_2126,
pll_3000,
pll_35xx,
pll_36xx,
pll_2550,
pll_2650,
pll_4500,
pll_4502,
pll_4508,
pll_4600,
pll_4650,
pll_4650c,
pll_6552,
pll_6552_s3c2416,
pll_6553,
pll_s3c2410_mpll,
pll_s3c2410_upll,
pll_s3c2440_mpll,
pll_2550x,
pll_2550xx,
pll_2650x,
pll_2650xx,
pll_1450x,
pll_1451x,
pll_1452x,
pll_1460x,
};
因為每種鎖相環支援的操作不一樣,所以三星對鎖相環的有多種ops介面。
最後就是每種時鐘註冊時為了方便查詢,都統一繫結在clock連結串列的註冊介面:
int clk_register_clkdev(struct clk *clk, const char *con_id,
const char *dev_id);
即最終,一般情況下下