Linux内核4.14版本——ccf时钟子系统(5)——通用API
1. clk_get
1.1 __of_clk_get_by_name
1.2 clk_get_sys
2. clk_prepare_enable
2.1 clk_prepare
2.2 clk_enable
3. clk_set_rate
1. clk_get
clock get是通过clock名称获取struct clk指针的过程,由clk_get、devm_clk_get、clk_get_sys、of_clk_get、of_clk_get_by_name、of_clk_get_from_provider等接口负责实现,这里以clk_get为例,分析其实现过程(位于drivers/clk/clkdev.c中)。
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);
}
该函数用于从目标设备上获取指定标识符的时钟。
(1)它首先尝试从设备树中获取时钟硬件(通过of_clk_get_hw函数),
(2)如果成功或者返回-EPROBE_DEFER(延迟探测)时,将创建并返回一个表示时钟的struct clk结构体指针。
(3)如果在设备树中未找到时钟硬件,将调用clk_get_sys函数尝试从系统时钟表中获取时钟。
1.1 __of_clk_get_by_name
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 %pOF:%s(%i)\n",np, 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;
}
最终调用__of_clk_get-->__of_clk_get_from_provider,__of_clk_get_from_provider函数我们后面介绍。
1.2 clk_get_sys
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);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);
}
1.2.1 clk_find
/** 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)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;elsebreak;}}return cl;
}static LIST_HEAD(clocks);
clk_find从系统时钟链表中查找已经注册的时钟,这些时钟是通过函数clk_register_clkdev注册的。
2. clk_prepare_enable
/* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
static inline int clk_prepare_enable(struct clk *clk)
{int ret;ret = clk_prepare(clk);if (ret)return ret;ret = clk_enable(clk);if (ret)clk_unprepare(clk);return ret;
}
2.1 clk_prepare
clk_prepare--->clk_core_prepare_lock--->clk_core_prepare
static int clk_core_prepare(struct clk_core *core)
{int ret = 0;lockdep_assert_held(&prepare_lock);if (!core)return 0;if (core->prepare_count == 0) {// 递归调用 clk_core_prepare 准备时钟源的父时钟ret = clk_core_prepare(core->parent);if (ret)return ret;// 跟踪时钟准备操作trace_clk_prepare(core);// 调用时钟源操作函数的 prepare 函数if (core->ops->prepare)ret = core->ops->prepare(core->hw);// 跟踪时钟准备完成trace_clk_prepare_complete(core);if (ret) {clk_core_unprepare(core->parent);return ret;}}// 增加时钟准备计数core->prepare_count++;return 0;
}
clk_core_prepare 函数用于准备时钟源。它可能会睡眠,这与 clk_enable 不同。在简单的情况下,如果操作可能会睡眠,可以使用 clk_core_prepare 替代 clk_enable 来启用时钟。在复杂的情况下,时钟解锁操作可能需要一个快速和一个慢速部分。这就是 clk_core_prepare 和clk_enable 不是互斥的原因。实际上,必须在调用 clk_core_prepare 之前调用 clk_enable。
2.2 clk_enable
enable也是差不多的操作这里不做介绍了。
3. clk_set_rate
clk_set_rate --> clk_core_set_rate_nolock
3.1 clk_core_set_rate_nolock
static int clk_core_set_rate_nolock(struct clk_core *core,unsigned long req_rate)
{struct clk_core *top, *fail_clk;unsigned long rate = req_rate;if (!core)return 0;/* bail early if nothing to do */if (rate == clk_core_get_rate_nolock(core))return 0;if ((core->flags & CLK_SET_RATE_GATE) && core->prepare_count)return -EBUSY;/* calculate new rates and get the topmost changed clock */top = clk_calc_new_rates(core, rate);if (!top)return -EINVAL;/* notify that we are about to change rates */fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE);if (fail_clk) {pr_debug("%s: failed to set %s rate\n", __func__,fail_clk->name);clk_propagate_rate_change(top, ABORT_RATE_CHANGE);return -EBUSY;}/* change the rates */clk_change_rate(top);core->req_rate = req_rate;return 0;
}