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;
			else
				break;
		}
	}
	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;
}

3.1.1 clk_calc_new_rates

3.1.2 clk_propagate_rate_change

3.1.3 clk_change_rate

上一篇:初级数据结构——二叉搜索树-二、基本操作


下一篇:Python基于 Opencv+wxPython 的人脸识别上课考勤系统,附源码-1 引言