Linux common clock framework(3)_实现逻辑分析

1. 前言

前面两篇clock framework的分析文章,分别从clock consumer和clock provider的角度,介绍了Linux kernel怎么管理系统的clock资源,以及device driver怎么使用clock资源。本文将深入到clock framework的内部,分析相关的实现逻辑。

2. struct clk结构

到目前为止,我们还没有仔细介绍过struct clk这个代表了一个clock的数据结构呢。对consumer和provider来说,可以不关心,但对内部实现逻辑来说,就不得不提了:

drivers/clk/clk.c 

struct clk {
	struct clk_core	*core;
	struct device *dev;
	const char *dev_id;
	const char *con_id;
	unsigned long min_rate;
	unsigned long max_rate;
	unsigned int exclusive_count;
	struct hlist_node clks_node;
};

struct clk_core {
	const char		*name;
	const struct clk_ops	*ops;
	struct clk_hw		*hw;
	struct module		*owner;
	struct device		*dev;
	struct device_node	*of_node;
	struct clk_core		*parent;
	struct clk_parent_map	*parents;
	u8			num_parents;
	u8			new_parent_index;
	unsigned long		rate;
	unsigned long		req_rate;
	unsigned long		new_rate;
	struct clk_core		*new_parent;
	struct clk_core		*new_child;
	unsigned long		flags;
	bool			orphan;
	bool			rpm_enabled;
	bool			need_sync;
	bool			boot_enabled;
	unsigned int		enable_count;
	unsigned int		prepare_count;
	unsigned int		protect_count;
	unsigned long		min_rate;
	unsigned long		max_rate;
	unsigned long		accuracy;
	int			phase;
	struct clk_duty		duty;
	struct hlist_head	children;
	struct hlist_node	child_node;
	struct hlist_head	clks;
	unsigned int		notifier_count;
#ifdef CONFIG_DEBUG_FS
	struct dentry		*dentry;
	struct hlist_node	debug_node;
#endif
	struct kref		ref;
};
第一个结构体struct clk代表一个具体的时钟节点。其成员变量包括:

core: 指向struct clk_core类型的指针,表示这个时钟节点的核心属性;
dev, dev_id, con_id: 分别表示这个时钟节点所属的设备、设备ID和连接ID,用于标识和区分不同的时钟节点;
min_rate, max_rate: 表示这个时钟节点的最小和最大工作频率;
exclusive_count: 表示当前时钟节点的独占计数,用于控制时钟共享和独占的使用;
clks_node: 一个链表节点,用于将多个时钟节点组织成一个列表。
第二个结构体struct clk_core表示一个时钟节点的核心属性。其成员变量包括:

name: 时钟节点的名称,用于标识和区分不同的时钟节点;
ops: 指向struct clk_ops类型的指针,表示这个时钟节点的操作集合,如启动、停止和设置频率等;
hw: 指向struct clk_hw类型的指针,表示这个时钟节点的硬件实现;
parent: 指向struct clk_core类型的指针,表示这个时钟节点的父时钟节点;
parents: 一个数组,表示这个时钟节点可能的所有父时钟节点及其对应的索引;
num_parents: 表示这个时钟节点总共有多少个可能的父时钟节点;
rate, req_rate, new_rate: 分别表示这个时钟节点的当前频率、请求的频率和新的频率;
min_rate, max_rate: 表示这个时钟节点的最小和最大工作频率;
accuracy: 表示这个时钟节点的频率精度;
phase: 表示这个时钟节点的相位;
children: 一个链表头,用于将所有子时钟节点组织成一个列表。
这两个结构体在Linux内核中被广泛用于描述、管理和控制各种时钟节点,从而为系统提供了灵活且高效的时钟管理服务。

3. clock regitser/unregister

在“Linux common clock framework(2)_clock provider”中已经讲过,clock provider需要将系统的clock以tree的形式组织起来,分门别类,并在系统初始化时,通过provider的初始化接口,或者clock framework core的DTS接口,将所有的clock注册到kernel。

clock的注册,统一由clk_regitser接口实现,但基于该接口,kernel也提供了其它更为便利注册接口,下面将会一一描述。

3.1 clk_hw_register

clk_register是所有register接口的共同实现,负责将clock注册到kernel,并返回代表该clock的struct clk指针。分析该接口之前,我们先看一下下面的内容:

drivers/clk/clk.c

/**
 * clk_hw_register - register a clk_hw and return an error code
 * @dev: device that is registering this clock
 * @hw: link to hardware-specific clock data
 *
 * clk_hw_register is the primary interface for populating the clock tree with
 * new clock nodes. It returns an integer equal to zero indicating success or
 * less than zero indicating failure. Drivers must test for an error code after
 * calling clk_hw_register().
 */
int clk_hw_register(struct device *dev, struct clk_hw *hw)
{
	return PTR_ERR_OR_ZERO(__clk_register(dev, dev_or_parent_of_node(dev),
			       hw));
}
EXPORT_SYMBOL_GPL(clk_hw_register);

static struct clk *
__clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)
{
	int ret;
	struct clk_core *core;
	const struct clk_init_data *init = hw->init;

	/*
	 * The init data is not supposed to be used outside of registration path.
	 * Set it to NULL so that provider drivers can't use it either and so that
	 * we catch use of hw->init early on in the core.
	 */
	hw->init = NULL;

	core = kzalloc(sizeof(*core), GFP_KERNEL);
	if (!core) {
		ret = -ENOMEM;
		goto fail_out;
	}

	core->name = kstrdup_const(init->name, GFP_KERNEL);
	if (!core->name) {
		ret = -ENOMEM;
		goto fail_name;
	}

	if (WARN_ON(!init->ops)) {
		ret = -EINVAL;
		goto fail_ops;
	}
	core->ops = init->ops;

	if (dev && pm_runtime_enabled(dev))
		core->rpm_enabled = true;
	core->dev = dev;
	core->of_node = np;
	if (dev && dev->driver)
		core->owner = dev->driver->owner;
	core->hw = hw;
	core->flags = init->flags;
	core->num_parents = init->num_parents;
	core->min_rate = 0;
	core->max_rate = ULONG_MAX;

	ret = clk_core_populate_parent_map(core, init);
	if (ret)
		goto fail_parents;

	INIT_HLIST_HEAD(&core->clks);

	/*
	 * Don't call clk_hw_create_clk() here because that would pin the
	 * provider module to itself and prevent it from ever being removed.
	 */
	hw->clk = alloc_clk(core, NULL, NULL);
	if (IS_ERR(hw->clk)) {
		ret = PTR_ERR(hw->clk);
		goto fail_create_clk;
	}

	clk_core_link_consumer(core, hw->clk);

	ret = __clk_core_init(core);
	if (!ret)
		return hw->clk;

	clk_prepare_lock();
	clk_core_unlink_consumer(hw->clk);
	clk_prepare_unlock();

	free_clk(hw->clk);
	hw->clk = NULL;

fail_create_clk:
	clk_core_free_parent_map(core);
fail_parents:
fail_ops:
	kfree_const(core->name);
fail_name:
	kfree(core);
fail_out:
	return ERR_PTR(ret);
}
int clk_hw_register(struct device *dev, struct clk_hw *hw)

这是用于注册 struct clk_hw 的主要接口。它返回一个整数,表示注册操作的结果。具体来说:

    如果返回值等于零,表示注册成功。
    如果返回值小于零,表示注册失败,具体的错误代码可以通过 PTR_ERR() 获取。

这个函数是用于注册硬件特定的时钟数据(struct clk_hw)的接口,不返回 struct clk 指针。它主要用于底层的硬件时钟注册

static struct clk *__clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw)

这是clk_register的底层实现函数,负责真正的时钟注册工作。函数的主要步骤如下:

    初始化时钟核心结构体 core: 通过kzalloc为时钟核心结构体分配内存,并对其进行初始化,包括设置名称、操作集、设备等信息。

    检查并设置RPM(Runtime Power Management)状态: 如果设备启用了RPM,则设置core->rpm_enabled为true。

    初始化时钟核心的父时钟映射 core->parents: 通过调用clk_core_populate_parent_map函数,初始化时钟核心的父时钟映射。

    初始化时钟核心的哈希链表 core->clks: 通过INIT_HLIST_HEAD宏,初始化时钟核心的哈希链表。

    分配时钟 hw->clk: 通过调用alloc_clk函数,为时钟核心分配时钟。这里不调用clk_hw_create_clk,以避免将提供者模块与自身绑定,防止提供者模块无法被移除。

    将时钟链接到消费者 hw->clk: 通过clk_core_link_consumer函数,将时钟链接到其消费者。

    初始化时钟核心 __clk_core_init: 通过调用__clk_core_init函数,对时钟核心进行初始化。

    如果一切正常,返回时钟指针 hw->clk;否则,进行错误处理。

    错误处理: 如果注册过程中发生错误,会进行相应的错误处理,包括释放内存、解除时钟与消费者的关联等。

这两个函数的作用是通过时钟框架注册一个新的时钟,并返回一个不可直接解引用的指针。其中,__clk_register是底层实现,而clk_hw_register是对外的接口,调用者应该使用后者。时钟的注册涉及到内存分配、参数初始化、时钟关系的建立等一系列操作,确保时钟能够在系统中正确注册和使用。

/**
 * __clk_core_init - initialize the data structures in a struct clk_core
 * @core:	clk_core being initialized
 *
 * Initializes the lists in struct clk_core, queries the hardware for the
 * parent and rate and sets them both.
 */
static int __clk_core_init(struct clk_core *core)
{
	int ret;
	struct clk_core *parent;
	unsigned long rate;
	int phase;

	clk_prepare_lock();

	/*
	 * Set hw->core after grabbing the prepare_lock to synchronize with
	 * callers of clk_core_fill_parent_index() where we treat hw->core
	 * being NULL as the clk not being registered yet. This is crucial so
	 * that clks aren't parented until their parent is fully registered.
	 */
	core->hw->core = core;

	ret = clk_pm_runtime_get(core);
	if (ret)
		goto unlock;

	/* check to see if a clock with this name is already registered */
	if (clk_core_lookup(core->name)) {
		pr_debug("%s: clk %s already initialized\n",
				__func__, core->name);
		ret = -EEXIST;
		goto out;
	}

	/* check that clk_ops are sane.  See Documentation/driver-api/clk.rst */
	if (core->ops->set_rate &&
	    !((core->ops->round_rate || core->ops->determine_rate) &&
	      core->ops->recalc_rate)) {
		pr_err("%s: %s must implement .round_rate or .determine_rate in addition to .recalc_rate\n",
		       __func__, core->name);
		ret = -EINVAL;
		goto out;
	}

	if (core->ops->set_parent && !core->ops->get_parent) {
		pr_err("%s: %s must implement .get_parent & .set_parent\n",
		       __func__, core->name);
		ret = -EINVAL;
		goto out;
	}

	if (core->num_parents > 1 && !core->ops->get_parent) {
		pr_err("%s: %s must implement .get_parent as it has multi parents\n",
		       __func__, core->name);
		ret = -EINVAL;
		goto out;
	}

	if (core->ops->set_rate_and_parent &&
			!(core->ops->set_parent && core->ops->set_rate)) {
		pr_err("%s: %s must implement .set_parent & .set_rate\n",
				__func__, core->name);
		ret = -EINVAL;
		goto out;
	}

	/*
	 * optional platform-specific magic
	 *
	 * The .init callback is not used by any of the basic clock types, but
	 * exists for weird hardware that must perform initialization magic for
	 * CCF to get an accurate view of clock for any other callbacks. It may
	 * also be used needs to perform dynamic allocations. Such allocation
	 * must be freed in the terminate() callback.
	 * This callback shall not be used to initialize the parameters state,
	 * such as rate, parent, etc ...
	 *
	 * If it exist, this callback should called before any other callback of
	 * the clock
	 */
	if (core->ops->init) {
		ret = core->ops->init(core->hw);
		if (ret)
			goto out;
	}

	parent = core->parent = __clk_init_parent(core);

	/*
	 * Populate core->parent if parent has already been clk_core_init'd. If
	 * parent has not yet been clk_core_init'd then place clk in the orphan
	 * list.  If clk doesn't have any parents then place it in the root
	 * clk list.
	 *
	 * Every time a new clk is clk_init'd then we walk the list of orphan
	 * clocks and re-parent any that are children of the clock currently
	 * being clk_init'd.
	 */
	if (parent) {
		hlist_add_head(&core->child_node, &parent->children);
		core->orphan = parent->orphan;
	} else if (!core->num_parents) {
		hlist_add_head(&core->child_node, &clk_root_list);
		core->orphan = false;
	} else {
		hlist_add_head(&core->child_node, &clk_orphan_list);
		core->orphan = true;
	}

	/*
	 * 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 (core->ops->recalc_accuracy)
		core->accuracy = core->ops->recalc_accuracy(core->hw,
					clk_core_get_accuracy_no_lock(parent));
	else if (parent)
		core->accuracy = parent->accuracy;
	else
		core->accuracy = 0;

	/*
	 * Set clk's phase by clk_core_get_phase() caching the phase.
	 * Since a phase is by definition relative to its parent, just
	 * query the current clock phase, or just assume it's in phase.
	 */
	phase = clk_core_get_phase(core);
	if (phase < 0) {
		ret = phase;
		pr_warn("%s: Failed to get phase for clk '%s'\n", __func__,
			core->name);
		goto out;
	}

	/*
	 * Set clk's duty cycle.
	 */
	clk_core_update_duty_cycle_nolock(core);

	/*
	 * 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 (core->ops->recalc_rate)
		rate = core->ops->recalc_rate(core->hw,
				clk_core_get_rate_nolock(parent));
	else if (parent)
		rate = parent->rate;
	else
		rate = 0;
	core->rate = core->req_rate = rate;

	core->boot_enabled = clk_core_is_enabled(core);

	/*
	 * Enable CLK_IS_CRITICAL clocks so newly added critical clocks
	 * don't get accidentally disabled when walking the orphan tree and
	 * reparenting clocks
	 */
	if (core->flags & CLK_IS_CRITICAL) {
		ret = clk_core_prepare(core);
		if (ret) {
			pr_warn("%s: critical clk '%s' failed to prepare\n",
			       __func__, core->name);
			goto out;
		}

		ret = clk_core_enable_lock(core);
		if (ret) {
			pr_warn("%s: critical clk '%s' failed to enable\n",
			       __func__, core->name);
			clk_core_unprepare(core);
			goto out;
		}
	}

	clk_core_hold_state(core);
	clk_core_reparent_orphans_nolock();

	kref_init(&core->ref);
out:
	clk_pm_runtime_put(core);
unlock:
	if (ret) {
		hlist_del_init(&core->child_node);
		core->hw->core = NULL;
	}

	clk_prepare_unlock();

	if (!ret)
		clk_debug_register(core);

	return ret;
}

这段代码实现了__clk_core_init函数,该函数用于初始化struct clk_core中的数据结构,包括查询硬件的父时钟和频率并设置它们

1.获取锁:通过调用 clk_prepare_lock 获取时钟准备锁,确保在初始化过程中时钟状态的一致性。

2.设置hw->core:将时钟硬件结构中的 core 成员设置为当前时钟核心,以便在调用 clk_core_fill_parent_index() 时同步。

3.尝试获取运行时 PM 引用:尝试获取时钟核心的运行时 PM 引用,以确保在
操作时钟之前处于运行状态。

4.检查时钟是否已经注册:检查时钟名称是否已经在时钟列表中注册,防止重复注册

5.检查时钟操作函数是否实现了必要的回调函数,如果没有,返回错误码。

6.检查时钟父时钟设置是否合理:检查时钟设置父时钟的操作是否实现了必要的回调函数,如果没有,返回错误码。
7.检查多父时钟设置是否合理:检查时钟有多个父时钟时,获取父时钟操作是否实现,如果没有,返回错误码。
8.检查设置父时钟和频率的操作是否合理:检查设置父时钟和频率的操作是否实现,如果没有,返回错误码。
9.调用可选的平台特定初始化回调函数:如果存在初始化回调函数,调用它来执行平台特定的初始化操作。
10.初始化时钟父时钟:调用 __clk_init_parent 函数初始化时钟的父时钟,并将其设置为时钟核心的父时钟。
11.将时钟添加到相应的链表中:将时钟核心添加到相应的链表中,如果有父时钟,则添加到父时钟的子时钟链表,否则添加到根时钟链表或孤立时钟链表。
12.设置时钟的准确性(accuracy):如果实现了 .recalc_accuracy 回调,则通过它来设置时钟的准确性;否则,使用父时钟的准确性作为默认值。如果没有父时钟,则准确性设置为0。
13.设置时钟的相位(phase):调用 clk_core_get_phase 函数获取时钟的相位,如果获取失败,则打印警告信息。
14.更新时钟的占空比(duty cycle):调用 clk_core_update_duty_cycle_nolock 函数更新时钟的占空比。
15.设置时钟的频率(rate):如果实现了 .recalc_rate 回调,则通过它来设置时钟的频率;否则,使用父时钟的频率作为默认值。如果没有父时钟,则频率设置为0。
16.设置时钟的一些状态信息:设置时钟的当前频率、请求频率和启动状态。
17.启用 CLK_IS_CRITICAL 类型的时钟:如果时钟标记为 CLK_IS_CRITICAL,则尝试启用它。
18.持有时钟的状态:持有时钟的状态,确保在时钟重新注册过程中,新添加的关键时钟不会被意外禁用。
19.重新父时钟树上的孤立时钟:在时钟重新注册过程中,重新父时钟树上的孤立时钟。
20.初始化时钟核心的引用计数:
21.清理和解锁: 根据操作结果,进行相应的清理工作,并释放锁。
22.注册调试信息:如果初始化成功,注册调试信息。

__clk_core_init 函数的主要作用是初始化时钟核心的数据结构,包括设置父时钟、频率、准确性、相位等信息,然后将时钟核心添加到相应的链表中。函数的实现考虑了多种情况,包括错误处理、检查操作的合理性等。这是时钟框架在内核中重要的一部分,确保时钟能够在系统中正确注册和初始化。

3.2clk_unregister/devm_clk_register/devm_clk_unregister

clock的regitser和init,几乎占了clock framework大部分的实现逻辑。clk_unregister是regitser接口的反操作,不过当前没有实现(不需要)。而devm_clk_register/devm_clk_unregister则是clk_register/clk_unregister的device resource manager版本。

3.3 fixed rate clock的注册

“Linux common clock framework(2)_clock provider”中已经对fixed rate clock有过详细的介绍,这种类型的clock有两种注册方式,通过API注册和通过DTS注册,具体的实现位于“drivers/clk/clk-fixed-rate.c”中,介绍如下。

1)通过API注册

struct clk_hw *__clk_hw_register_fixed_rate(struct device *dev,
		struct device_node *np, const char *name,
		const char *parent_name, const struct clk_hw *parent_hw,
		const struct clk_parent_data *parent_data, unsigned long flags,
		unsigned long fixed_rate, unsigned long fixed_accuracy,
		unsigned long clk_fixed_flags, bool devm)
{
	struct clk_fixed_rate *fixed;
	struct clk_hw *hw;
	struct clk_init_data init = {};
	int ret = -EINVAL;

	/* allocate fixed-rate clock */
	if (devm)
		fixed = devres_alloc(devm_clk_hw_register_fixed_rate_release,
				     sizeof(*fixed), GFP_KERNEL);
	else
		fixed = kzalloc(sizeof(*fixed), GFP_KERNEL);
	if (!fixed)
		return ERR_PTR(-ENOMEM);

	init.name = name;
	init.ops = &clk_fixed_rate_ops;
	init.flags = flags;
	init.parent_names = parent_name ? &parent_name : NULL;
	init.parent_hws = parent_hw ? &parent_hw : NULL;
	init.parent_data = parent_data;
	if (parent_name || parent_hw || parent_data)
		init.num_parents = 1;
	else
		init.num_parents = 0;

	/* struct clk_fixed_rate assignments */
	fixed->flags = clk_fixed_flags;
	fixed->fixed_rate = fixed_rate;
	fixed->fixed_accuracy = fixed_accuracy;
	fixed->hw.init = &init;

	/* register the clock */
	hw = &fixed->hw;
	if (dev || !np)
		ret = clk_hw_register(dev, hw);
	else
		ret = of_clk_hw_register(np, hw);
	if (ret) {
		if (devm)
			devres_free(fixed);
		else
			kfree(fixed);
		hw = ERR_PTR(ret);
	} else if (devm)
		devres_add(dev, fixed);

	return hw;
}
EXPORT_SYMBOL_GPL(__clk_hw_register_fixed_rate);

struct clk *clk_register_fixed_rate(struct device *dev, const char *name,
		const char *parent_name, unsigned long flags,
		unsigned long fixed_rate)
{
	struct clk_hw *hw;

	hw = clk_hw_register_fixed_rate_with_accuracy(dev, name, parent_name,
						      flags, fixed_rate, 0);
	if (IS_ERR(hw))
		return ERR_CAST(hw);
	return hw->clk;
}
EXPORT_SYMBOL_GPL(clk_register_fixed_rate);

这段代码涉及到 Linux 内核中时钟框架(Clock Framework)中的固定频率时钟的注册和初始化。这两个函数是用于注册固定频率时钟的接口。

这两个函数用于注册固定频率时钟,其中 __clk_hw_register_fixed_rate 是一个更通用的底层函数,可以在不同的情况下注册固定频率时钟,而 clk_register_fixed_rate 是对其的一个简化封装,更适用于一般的固定频率时钟注册场景。这些函数的目的是简化时钟注册的过程,提高代码的可读性和可维护性。

2)通过DTS注册

static struct clk_hw *_of_fixed_clk_setup(struct device_node *node)
{
	struct clk_hw *hw;
	const char *clk_name = node->name;
	u32 rate;
	u32 accuracy = 0;
	int ret;

	if (of_property_read_u32(node, "clock-frequency", &rate))
		return ERR_PTR(-EIO);

	of_property_read_u32(node, "clock-accuracy", &accuracy);

	of_property_read_string(node, "clock-output-names", &clk_name);

	hw = clk_hw_register_fixed_rate_with_accuracy(NULL, clk_name, NULL,
						    0, rate, accuracy);
	if (IS_ERR(hw))
		return hw;

	ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw);
	if (ret) {
		clk_hw_unregister_fixed_rate(hw);
		return ERR_PTR(ret);
	}

	return hw;
}

/**
 * of_fixed_clk_setup() - Setup function for simple fixed rate clock
 * @node:	device node for the clock
 */
void __init of_fixed_clk_setup(struct device_node *node)
{
	_of_fixed_clk_setup(node);
}
CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);

这段代码涉及 Linux 内核时钟框架中的固定频率时钟的初始化和设置。

_of_fixed_clk_setup 函数:

3.4 gate、devider、mux、fixed factor、composite以及自定义类型clock的注册

和fixed rate类似,不再一一说明。

4. 通用API的实现

4.1 clock 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中)。

1)clk_get

/**
 * clk_get - 获取指定设备上的时钟
 * @dev: 目标设备的指针
 * @con_id: 时钟消费者标识符
 *
 * 该函数用于从目标设备上获取指定标识符的时钟。它首先尝试从设备树中获取时钟硬件(通过of_clk_get_hw函数),
 * 如果成功或者返回-EPROBE_DEFER(延迟探测)时,将创建并返回一个表示时钟的struct clk结构体指针。
 * 如果在设备树中未找到时钟硬件,将调用__clk_get_sys函数尝试从系统时钟表中获取时钟。
 *
 * 参数:
 * @dev: 目标设备的指针,可以为NULL。
 * @con_id: 时钟消费者标识符,用于匹配设备树中的时钟配置。
 *
 * 返回:
 * 成功时,返回表示时钟的struct clk结构体指针;失败时返回错误码或NULL。
 */
struct clk *clk_get(struct device *dev, const char *con_id)
{
	const char *dev_id = dev ? dev_name(dev) : NULL; // 获取设备名称,用于时钟的标识
	struct clk_hw *hw;

	if (dev && dev->of_node) {
		// 尝试从设备树中获取时钟硬件
		hw = of_clk_get_hw(dev->of_node, 0, con_id);
		if (!IS_ERR(hw) || PTR_ERR(hw) == -EPROBE_DEFER)
			// 如果成功获取时钟硬件,或者返回-EPROBE_DEFER(延迟探测)时,创建并返回表示时钟的struct clk结构体指针
			return clk_hw_create_clk(dev, hw, dev_id, con_id);
	}

	// 如果在设备树中未找到时钟硬件,尝试从系统时钟表中获取时钟
	return __clk_get_sys(dev, dev_id, con_id);
}
EXPORT_SYMBOL(clk_get);

函数clk_get 的作用是在给定的设备上获取指定标识符的时钟。它首先尝试从设备树中获取时钟硬件,如果成功或者返回-EPROBE_DEFER(延迟探测)时,将创建并返回一个表示时钟的 struct clk 结构体指针。如果在设备树中未找到时钟硬件,将调用 __clk_get_sys 函数尝试从系统时钟表中获取时钟。

/**
 * of_clk_get_hw - 从设备树中获取时钟硬件
 * @np: 设备树节点的指针
 * @index: 时钟在节点中的索引
 * @con_id: 时钟消费者标识符
 *
 * 该函数通过设备树中的节点 @np、时钟索引 @index 和时钟消费者标识符 @con_id,
 * 解析出时钟硬件的信息,并返回表示该时钟硬件的 struct clk_hw 结构体指针。
 *
 * 参数:
 * @np: 设备树节点的指针,描述时钟的节点
 * @index: 时钟在节点中的索引,用于区分节点中包含的多个时钟
 * @con_id: 时钟消费者标识符,用于匹配时钟配置
 *
 * 返回:
 * 成功时,返回表示时钟硬件的 struct clk_hw 结构体指针;失败时返回错误码或错误指针。
 */
struct clk_hw *of_clk_get_hw(struct device_node *np, int index,
			     const char *con_id)
{
	int ret;
	struct clk_hw *hw;
	struct of_phandle_args clkspec;

	// 从设备树中解析时钟规格(配置)信息
	ret = of_parse_clkspec(np, index, con_id, &clkspec);
	if (ret)
		return ERR_PTR(ret);

	// 根据解析得到的时钟规格信息获取时钟硬件
	hw = of_clk_get_hw_from_clkspec(&clkspec);

	// 释放用于解析时钟规格的设备树节点
	of_node_put(clkspec.np);

	// 返回表示时钟硬件的 struct clk_hw 结构体指针
	return hw;
}

函数of_clk_get_hw 的作用是从设备树中解析时钟硬件的信息。通过给定的设备树节点指针 np、时钟索引 index 和时钟消费者标识符 con_id,解析出时钟硬件的配置信息,并返回表示该时钟硬件的 struct clk_hw 结构体指针。

3)clk_get_sys

clk_get_sys接口是在调用者没有提供struct device指针或者通过of_clk_get_xxx获取clock失败时,获取clock指针的另一种手段。基于kernel大力推行device tree的现状,蜗蜗不建议使用这种过时的手段,就不分析了。

4.2 clk_prepare/clk_unprepare

prepare和unprepare的的代码位于drivers/clk/clk.c中,分别由内部接口__clk_prepare和__clk_unprepare实现具体动作,如下:

/**
 * clk_unprepare - 取消对时钟源的准备
 * @clk: 要取消准备的时钟结构体指针
 *
 * clk_unprepare 可能会睡眠,这与 clk_disable 不同。在简单的情况下,如果操作可能会睡眠,可以使用 clk_unprepare 替代 clk_disable 来关闭时钟。一个例子是通过 I2C 访问的时钟。在复杂的情况下,时钟门操作可能需要一个快速和一个慢速部分。这就是 clk_unprepare 和 clk_disable 不是互斥的原因。实际上,必须在调用 clk_unprepare 之前调用 clk_disable。
 *
 * 参数:
 * @clk: 要取消准备的时钟结构体指针
 */
void clk_unprepare(struct clk *clk)
{
	if (IS_ERR_OR_NULL(clk))
		return;

	// 调用内部函数取消时钟准备操作
	clk_core_unprepare_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_unprepare);

/**
 * clk_core_prepare - 准备时钟源
 * @core: 时钟核心结构体指针
 *
 * clk_core_prepare 函数用于准备时钟源。它可能会睡眠,这与 clk_enable 不同。在简单的情况下,如果操作可能会睡眠,可以使用 clk_core_prepare 替代 clk_enable 来启用时钟。在复杂的情况下,时钟解锁操作可能需要一个快速和一个慢速部分。这就是 clk_core_prepare 和 clk_enable 不是互斥的原因。实际上,必须在调用 clk_core_prepare 之前调用 clk_enable。
 *
 * 参数:
 * @core: 时钟核心结构体指针
 *
 * 返回:
 * 成功时返回0,失败时返回错误码。
 */
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) {
		// 获取运行时,以防止时钟频率变更时的运行时断电
		ret = clk_pm_runtime_get(core);
		if (ret)
			return ret;

		// 递归调用 clk_core_prepare 准备时钟源的父时钟
		ret = clk_core_prepare(core->parent);
		if (ret)
			goto runtime_put;

		// 跟踪时钟准备操作
		trace_clk_prepare(core);

		// 调用时钟源操作函数的 prepare 函数
		if (core->ops->prepare)
			ret = core->ops->prepare(core->hw);

		// 跟踪时钟准备完成
		trace_clk_prepare_complete(core);

		if (ret)
			goto unprepare;
	}

	// 增加时钟准备计数
	core->prepare_count++;

	/*
	 * CLK_SET_RATE_GATE 是时钟保护的一种特殊情况
	 * 它不是消费者声明独占速率控制,而是提供者阻止任何消费者在时钟准备期间进行任何可能导致频率变化或频率闪烁的操作。
	 */
	if (core->flags & CLK_SET_RATE_GATE)
		clk_core_rate_protect(core);

	return 0;

unprepare:
	// 如果准备失败,需要取消准备父时钟
	clk_core_unprepare(core->parent);
runtime_put:
	// 减少运行时引用计数
	clk_pm_runtime_put(core);
	return ret;
}

/**
 * clk_core_prepare_lock - 获取时钟准备锁,准备时钟源
 * @core: 时钟核心结构体指针
 *
 * clk_core_prepare_lock 函数获取时钟准备锁,然后调用 clk_core_prepare 准备时钟源。该函数是 clk_prepare 的一个辅助函数。
 *
 * 参数:
 * @core: 时钟核心结构体指针
 *
 * 返回:
 * 成功时返回0,失败时返回错误码。
 */
static int clk_core_prepare_lock(struct clk_core *core)
{
	int ret;

	// 获取时钟准备锁
	clk_prepare_lock();
	// 调用内部函数准备时钟源
	ret = clk_core_prepare(core);
	// 释放时钟准备锁
	clk_prepare_unlock();

	return ret;
}

/**
 * clk_prepare - 准备时钟源
 * @clk: 要准备的时钟结构体指针
 *
 * clk_prepare 可能会睡眠,这与 clk_enable 不同。在简单的情况下,如果操作可能会睡眠,可以使用 clk_prepare 替代 clk_enable 来启用时钟。一个例子是通过 I2C 访问的时钟。在复杂的情况下,时钟解锁操作可能需要一个快速和一个慢速部分。这就是 clk_prepare 和 clk_enable 不是互斥的原因。实际上,必须在调用 clk_prepare 之前调用 clk_enable。
 *
 * 参数:
 * @clk: 要准备的时钟结构体指针
 *
 * 返回:
 * 成功时返回0,失败时返回错误码。
 */
int clk_prepare(struct clk *clk)
{
	if (!clk)
		return 0;

	// 调用内部函数获取时钟准备锁,并准备时钟源
	return clk_core_prepare_lock(clk->core);
}
EXPORT_SYMBOL_GPL(clk_prepare);

4.3 clk_enable/clk_disable

enable/disable和prepare/unprepare的实现逻辑基本一致,需要注意的是,enable/disable时如果prepare_count为0,则会报错并返回。

4.4 clock rate有关的实现

clock rate有关的实现包括get、set和round三类,让我们依次说明。

1)clk_get_rate负责获取某个clock的当前rate,代码如下:

/**
 * __clk_recalc_rates - 重新计算时钟子树中的所有时钟的频率
 * @core: 子树的第一个时钟
 * @update_req: 是否应该使用新的频率更新 req_rate
 * @msg: 通知类型(参见 include/linux/clk.h)
 *
 * 从 clk 开始遍历时钟子树,并在遍历过程中重新计算频率。请注意,如果时钟没有实现 .recalc_rate 回调函数,
 * 则假定该时钟的频率将与其父时钟相同。
 *
 * clk_recalc_rates 还会传播 POST_RATE_CHANGE 通知(如果需要的话)。
 */
static void __clk_recalc_rates(struct clk_core *core, bool update_req,
			       unsigned long msg)
{
	unsigned long old_rate;
	unsigned long parent_rate = 0;
	struct clk_core *child;

	// 断言调用时的锁状态
	lockdep_assert_held(&prepare_lock);

	// 记录旧的时钟频率
	old_rate = core->rate;

	// 获取父时钟的频率
	if (core->parent)
		parent_rate = core->parent->rate;

	// 调用 clk_recalc 函数重新计算时钟频率
	core->rate = clk_recalc(core, parent_rate);
	if (update_req)
		core->req_rate = core->rate;

	/*
	 * 忽略 POST_RATE_CHANGE 和 ABORT_RATE_CHANGE 通知的 NOTIFY_STOP 和
	 * NOTIFY_BAD 返回值
	 */
	if (core->notifier_count && msg)
		__clk_notify(core, msg, old_rate, core->rate);

	// 递归遍历子时钟,重新计算它们的频率
	hlist_for_each_entry(child, &core->children, child_node)
		__clk_recalc_rates(child, update_req, msg);
}

/**
 * clk_core_get_rate_recalc - 获取时钟核心结构体对应时钟的频率,如果有需要则重新计算
 * @core: 时钟核心结构体指针
 *
 * 如果时钟核心结构体包含 CLK_GET_RATE_NOCACHE 标志,则重新计算整个子树中的所有时钟的频率。
 * 否则,返回缓存的频率。该函数是 clk_get_rate 的辅助函数。
 */
static unsigned long clk_core_get_rate_recalc(struct clk_core *core)
{
	// 如果时钟核心结构体包含 CLK_GET_RATE_NOCACHE 标志,则重新计算整个子树中的所有时钟的频率
	if (core && (core->flags & CLK_GET_RATE_NOCACHE))
		__clk_recalc_rates(core, false, 0);

	// 返回时钟的频率(无锁版本)
	return clk_core_get_rate_nolock(core);
}

/**
 * clk_get_rate - 返回时钟的频率
 * @clk: 要获取频率的时钟结构体指针
 *
 * 简单地返回时钟的缓存频率,除非设置了 CLK_GET_RATE_NOCACHE 标志,否则将发出 recalc_rate。
 * 可以在时钟启用状态下调用。如果 clk 为 NULL,或者发生错误,则返回 0。
 */
unsigned long clk_get_rate(struct clk *clk)
{
	unsigned long rate;

	// 如果 clk 为 NULL,则返回 0
	if (!clk)
		return 0;

	// 获取时钟准备锁
	clk_prepare_lock();
	// 调用内部函数获取时钟的频率,如果有需要则重新计算
	rate = clk_core_get_rate_recalc(clk->core);
	// 释放时钟准备锁
	clk_prepare_unlock();

	return rate;
}
EXPORT_SYMBOL_GPL(clk_get_rate);

2)clk_round_rate,返回该clock支持的,和输入rate最接近的rate值(不做任何改动),实际是由内部函数__clk_round_rate实现,代码如下:

/**
 * clk_round_rate - 将给定的频率四舍五入到时钟支持的合适频率
 * @clk: 正在进行频率四舍五入操作的时钟
 * @rate: 要四舍五入的频率
 *
 * 接受一个频率作为输入,将其四舍五入为时钟实际可用的频率,并返回结果。如果时钟不支持 round_rate 操作,
 * 则返回父时钟的频率。
 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
	struct clk_rate_request req;
	int ret;

	// 如果时钟为空,则返回 0
	if (!clk)
		return 0;

	// 获取时钟准备锁
	clk_prepare_lock();

	// 如果时钟被独占使用,取消时钟频率的保护
	if (clk->exclusive_count)
		clk_core_rate_unprotect(clk->core);

	// 初始化时钟频率请求结构体
	clk_core_init_rate_req(clk->core, &req, rate);

	// 调用内部函数进行频率四舍五入操作
	ret = clk_core_round_rate_nolock(clk->core, &req);

	// 如果时钟被独占使用,重新保护时钟频率
	if (clk->exclusive_count)
		clk_core_rate_protect(clk->core);

	// 释放时钟准备锁
	clk_prepare_unlock();

	// 如果四舍五入操作失败,则返回错误码
	if (ret)
		return ret;

	// 返回四舍五入后的频率
	return req.rate;
}
EXPORT_SYMBOL_GPL(clk_round_rate);

3)clk_set_rate
set rate的逻辑比较复杂,代码如下:

/**
 * clk_core_set_rate_nolock - 在不加锁的情况下设置时钟频率
 * @core: 要设置频率的时钟核心结构
 * @req_rate: 请求设置的频率
 *
 * 在不加锁的情况下设置时钟频率。如果请求的频率等于当前频率,则提前返回,否则进行频率设置操作。
 * 如果直接设置受保护的提供者的频率,则返回错误。函数通过计算新的频率并找到受影响的最顶层时钟,
 * 然后通过通知机制进行频率改变的通知。最后,修改频率并更新请求频率。
 */
static int clk_core_set_rate_nolock(struct clk_core *core,
				    unsigned long req_rate)
{
	struct clk_core *top, *fail_clk;
	unsigned long rate;
	int ret;

	// 如果时钟核心为空,则直接返回
	if (!core)
		return 0;

	// 对请求频率进行四舍五入
	rate = clk_core_req_round_rate_nolock(core, req_rate);

	// 如果四舍五入后的频率等于当前频率,则提前返回
	if (rate == clk_core_get_rate_nolock(core))
		return 0;

	// 如果直接设置受保护的提供者的频率,则返回错误
	if (clk_core_rate_is_protected(core))
		return -EBUSY;

	// 计算新的频率并获取受影响的最顶层时钟
	top = clk_calc_new_rates(core, req_rate);
	if (!top)
		return -EINVAL;

	// 获取时钟 PM runtime
	ret = clk_pm_runtime_get(core);
	if (ret)
		return ret;

	// 通知即将改变频率
	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);
		ret = -EBUSY;
		goto err;
	}

	// 改变频率
	clk_change_rate(top);

	// 更新请求频率
	core->req_rate = req_rate;

err:
	// 释放时钟 PM runtime
	clk_pm_runtime_put(core);

	return ret;
}

/**
 * clk_set_rate - 指定时钟的新频率
 * @clk: 要改变频率的时钟
 * @rate: 时钟的新频率
 *
 * 在最简单的情况下,clk_set_rate 将仅调整时钟的频率。
 *
 * 如果设置了 CLK_SET_RATE_PARENT 标志,则允许频率更改操作向上传播到 clk 的父时钟;
 * 是否发生这种情况取决于 .round_rate 实现的结果。如果在调用 .round_rate 后 *parent_rate 未更改,
 * 则忽略上游父时钟的传播。如果 *parent_rate 返回 clk 的父时钟的新频率,则向上传播到 clk 的父时钟并设置其频率。
 * 向上传播将继续,直到时钟不支持 CLK_SET_RATE_PARENT 标志或 .round_rate 停止请求更改 clk 的父时钟的频率。
 *
 * 通过进行树遍历,同时重新计算时钟的频率并触发 POST_RATE_CHANGE 通知,实现频率的更改。
 *
 * 返回值:成功返回 0,否则返回错误码。
 */
int clk_set_rate(struct clk *clk, unsigned long rate)
{
	int ret;

	// 如果时钟为空,则直接返回
	if (!clk)
		return 0;

	// 防止与时钟拓扑的更新竞争
	clk_prepare_lock();

	// 如果时钟被独占使用,取消时钟频率的保护
	if (clk->exclusive_count)
		clk_core_rate_unprotect(clk->core);

	// 调用内部函数进行频率设置操作
	ret = clk_core_set_rate_nolock(clk->core, rate);

	// 如果时钟被独占使用,重新保护时钟频率
	if (clk->exclusive_count)
		clk_core_rate_protect(clk->core);

	// 释放时钟准备锁
	clk_prepare_unlock();

	return ret;
}
EXPORT_SYMBOL_GPL(clk_set_rate);

注4:clock rate set有2种场景,一是只需修改自身的配置,即可达到rate set的目的。第二种是需要同时修改parent的rate(可向上递归)才能达成目的。看似简单的逻辑,里面却包含非常复杂的系统设计的知识。大家在使用clock framework,知道有这回事即可,并尽可能的不要使用第二种场景,以保持系统的简洁性。

4.5 clock parent有关的实现

parent操作包括get parent和set parent两类。
get parent的逻辑非常简单,直接从clk->parent指针中获取即可。
set parent稍微复杂,需要执行reparent和recalc_rates动作,具体不再描述了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux中,可以使用函数clock_gettime来获取系统时间。具体的代码如下示: ```c #include <stdio.h> #include <time.h> void getNowTime() { struct timespec time; clock_gettime(CLOCK_REALTIME, &time); printf("当前时间为:%ld秒\n", time.tv_sec); } ``` 上述代码中,使用clock_gettime函数来获取相对于1970年1月1日至今的秒数,并将其存储在结构体timespec的变量time中。然后可以通过time.tv_sec获取到系统当前的秒数。 引用中的代码片段展示了如何在C语言中使用clock_gettime来获取当前时间。要注意的是,也可以使用time(NULL)来替换clock_gettime函数。 值得注意的是,clock_gettime函数的第一个参数指定了要获取的时钟类型,其中CLOCK_REALTIME表示获取系统实时时间。 总结起来,要在Linux中获取系统时间,可以使用clock_gettime函数,并指定时钟类型为CLOCK_REALTIME。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Linux下用C获取当前时间](https://download.csdn.net/download/weixin_38722184/14109306)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [linuxptp中clock_gettime的实现](https://blog.csdn.net/a459688264/article/details/126286081)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值