前面三节参考蜗窝大神的文章分析了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);
即最终,一般情况下下