一、相关数据结构介绍
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;
};
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 rate,
unsigned long *parent_rate);
long (*determine_rate)(struct clk_hw *hw, unsigned long rate,
unsigned long *best_parent_rate,
struct clk **best_parent_clk);
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 rate,
unsigned long parent_rate);
int (*set_rate_and_parent)(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate, u8 index);
unsigned long (*recalc_accuracy)(struct clk_hw *hw,
unsigned long parent_accuracy);
int (*get_phase)(struct clk_hw *hw);
int (*set_phase)(struct clk_hw *hw, int degrees);
void (*init)(struct clk_hw *hw);
int (*debug_init)(struct clk_hw *hw, struct dentry *dentry);
};
// 固定速率时钟
struct clk_fixed_rate {
struct clk_hw hw;
unsigned long fixed_rate;
unsigned long fixed_accuracy;
u8 flags;
};
// 固定倍频时钟
struct clk_fixed_factor {
struct clk_hw hw;
unsigned int mult;
unsigned int div;
};
// 分频时钟
struct clk_divider {
struct clk_hw hw;
void __iomem *reg;
u8 shift;
u8 width;
u8 flags;
const struct clk_div_table *table;
spinlock_t *lock;
};
// 多选一时钟
struct clk_mux {
struct clk_hw hw;
void __iomem *reg;
u32 *table;
u32 mask;
u8 shift;
u8 flags;
spinlock_t *lock;
};
// 门时钟
struct clk_gate {
struct clk_hw hw;
void __iomem *reg;
u8 bit_idx;
u8 flags;
spinlock_t *lock;
};
二、时钟的注册
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
int i, ret;
struct clk_core *clk;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk) {
pr_err("%s: could not allocate clk\n", __func__);
ret = -ENOMEM;
goto fail_out;
}
clk->name = kstrdup_const(hw->init->name, GFP_KERNEL);
if (!clk->name) {
pr_err("%s: could not allocate clk->name\n", __func__);
ret = -ENOMEM;
goto fail_name;
}
clk->ops = hw->init->ops;
if (dev && dev->driver)
clk->owner = dev->driver->owner;
clk->hw = hw;
clk->flags = hw->init->flags;
clk->num_parents = hw->init->num_parents;
hw->core = clk;
/* 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;
}
/* copy each string name in case parent_names is __initdata */
for (i = 0; i < clk->num_parents; i++) {
clk->parent_names[i] = kstrdup_const(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;
}
}
INIT_HLIST_HEAD(&clk->clks);
hw->clk = __clk_create_clk(hw, NULL, NULL);
if (IS_ERR(hw->clk)) {
pr_err("%s: could not allocate per-user clk\n", __func__);
ret = PTR_ERR(hw->clk);
goto fail_parent_names_copy;
}
ret = __clk_init(dev, hw->clk);
if (!ret)
return hw->clk;
__clk_free_clk(hw->clk);
hw->clk = NULL;
......
}
- 创建分配clk_core结构体对象,并填充其成员变量
- 调用__clk_create_clk创建用户侧时钟结构体
- 调用__clk_init将clk_core加入到时钟树里
static int __clk_init(struct device *dev, struct clk *clk_user)
{
......
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).
*/
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 phase.
* Since a phase is by definition relative to its parent, just
* query the current clock phase, or just assume it's in phase.
*/
if (clk->ops->get_phase)
clk->phase = clk->ops->get_phase(clk->hw);
else
clk->phase = 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)
rate = clk->ops->recalc_rate(clk->hw,
clk_core_get_rate_nolock(clk->parent));
else if (clk->parent)
rate = clk->parent->rate;
else
rate = 0;
clk->rate = clk->req_rate = rate;
/*
* 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_core_reparent(orphan, clk);
continue;
}
for (i = 0; i < orphan->num_parents; i++)
if (!strcmp(clk->name, orphan->parent_names[i])) {
clk_core_reparent(orphan, clk);
break;
}
}
if (clk->ops->init)
clk->ops->init(clk->hw);
......
}
- 对clk_core进行约束性检查
- 将clk_core加入时钟树
- 对clk_core进行初始化工作,并且注册debugfs调试接口
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);
va_end(ap);
if (!cl)
return -ENOMEM;
clkdev_add(cl);
return 0;
}
- 该函数的主要功能是建立用户侧时钟clk和设备的关联,方便设备驱动获取clk
- 调用vclkdev_alloc创建clk_lookup
- 调用clkdev_add添加clk_lookup到系统中
void clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_add_tail(&cl->node, &clocks);
mutex_unlock(&clocks_mutex);
}
EXPORT_SYMBOL(clkdev_add);
int of_clk_add_provider(struct device_node *np,
struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
void *data),
void *data)
{
struct of_clk_provider *cp;
int ret;
cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL);
if (!cp)
return -ENOMEM;
cp->node = of_node_get(np);
cp->data = data;
cp->get = clk_src_get;
mutex_lock(&of_clk_mutex);
list_add(&cp->link, &of_clk_providers);
mutex_unlock(&of_clk_mutex);
pr_debug("Added clock from %s\n", np->full_name);
ret = of_clk_set_defaults(np, true);
if (ret < 0)
of_clk_del_provider(np);
return ret;
}
- 该函数为设备树节点注册一个查找用户侧时钟的方法
- 创建of_clk_provider结点体,并将该结构点添加到链表of_clk_providers
三、时钟的获取
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);
}
EXPORT_SYMBOL(clk_get);
- 如果dev存在,则首先通过设备树的方法获取clk
- 其次通过传统方法查找clk_lookup链表获取clk
static struct clk *__of_clk_get_by_name(struct device_node *np,
const char *dev_id,
const char *name)
{
struct clk *clk = ERR_PTR(-ENOENT);
/* Walk up the tree of devices looking for a clock that matches */
while (np) {
int index = 0;
/*
* For named clocks, first look up the name in the
* "clock-names" property. If it cannot be found, then
* index will be an error code, and of_clk_get() will fail.
*/
if (name)
index = of_property_match_string(np, "clock-names", name);
clk = __of_clk_get(np, index, dev_id, name);
if (!IS_ERR(clk)) {
break;
} else if (name && index >= 0) {
if (PTR_ERR(clk) != -EPROBE_DEFER)
pr_err("ERROR: could not get clock %s:%s(%i)\n",
np->full_name, name ? name : "", index);
return clk;
}
/*
* No matching clock found on this node. If the parent node
* has a "clock-ranges" property, then we can try one of its
* clocks.
*/
np = np->parent;
if (np && !of_get_property(np, "clock-ranges", NULL))
break;
}
return clk;
}
- 根据传入的name查找index
- 调用__of_clk_get获取clk
static struct clk *__of_clk_get(struct device_node *np, int index,
const char *dev_id, const char *con_id)
{
struct of_phandle_args clkspec;
struct clk *clk;
int rc;
if (index < 0)
return ERR_PTR(-EINVAL);
rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,
&clkspec);
if (rc)
return ERR_PTR(rc);
clk = __of_clk_get_from_provider(&clkspec, dev_id, con_id);
of_node_put(clkspec.np);
return clk;
}
- 调用of_parse_phandle_with_args获取时钟参数
- 调用__of_clk_get_from_provider获取clk
struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
const char *dev_id, const char *con_id)
{
struct of_clk_provider *provider;
struct clk *clk = ERR_PTR(-EPROBE_DEFER);
if (!clkspec)
return ERR_PTR(-EINVAL);
/* Check if we have such a provider in our array */
mutex_lock(&of_clk_mutex);
list_for_each_entry(provider, &of_clk_providers, link) {
if (provider->node == clkspec->np)
clk = provider->get(clkspec, provider->data);
if (!IS_ERR(clk)) {
clk = __clk_create_clk(__clk_get_hw(clk), dev_id,
con_id);
if (!IS_ERR(clk) && !__clk_get(clk)) {
__clk_free_clk(clk);
clk = ERR_PTR(-ENOENT);
}
break;
}
}
mutex_unlock(&of_clk_mutex);
return clk;
}
- 编历链表of_clk_providers
- 根据传入的clkspec查找对应的of_clk_provider
- 然后调用of_clk_provider的get函数获取时钟,并调用__clk_create_clk创建用户侧clk
// 传统方法获取时钟
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(__clk_get_hw(cl->clk), dev_id, con_id);
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);
}
- 调用clk_find函数,根据dev_id和con_id得到clk_lookup
- 然后在调用__clk_create_clk创建用户侧clk
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)
best_possible += 1;
list_for_each_entry(p, &clocks, node) {
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;
}
- 遍历链表clocks,查找最佳clk_lookup