kernel clk framework浅析

最近为情所困~hhh,文章耽误了,上一篇文章本人介绍了kernel下的power domain framework,这篇文章介绍下kernel的clk framework。

soc的时钟结构都是非常复杂的往往拥有多个时钟源,通过PLL进行分频与倍频,可以产生不同频率的时钟供各个模块使用,那么kernel是如何管理soc的时钟的呢,本篇文章将梳理一下kernel实现的时钟框架~

老规矩,从使用clk framework开始,先会用,再去分析~

如何使用clk framework?

和power domain类似,想要使用平台中某个时钟,只需在dts中引用该时钟节点,在驱动代码中调用devm_clk_get()函数获取时钟即可,来举个例子(三星平台)更加的清晰:

	//provider,提供时钟源,这个由芯片厂家完成
	clocks: clock-controller@4c000000 {
		compatible = "samsung,s3c2440-clock";
		reg = <0x4c000000 0x20>;
		#clock-cells = <1>; //clock-cell表示引用该时钟时需要传进的参数个数
	};

	//cousumer,引用时钟源,即时钟使用者
	serial@50000000 {
		compatible = "samsung,s3c2440-uart";
		clock-names = "uart";
		clocks = <&clocks PCLK_UART0>;
		pinctrl-names = "default";
		pinctrl-0 = <&uart0_data>;
	};

	//linux-4.9\drivers\tty\serial\samsung.c
	s3c24xx_serial_init_port
        ourport->clk = clk_get(&platdev->dev, "uart");  //驱动代码里调用clk_get函数获取时钟

上面的代码里贴出了如何使用clk framework,作为使用者只需在dts里引用厂家提供的时钟节点,然后在驱动代码里调用clk_get函数即可,非常的简单~,较其它平台,三星的代码里,只提供了一个时钟源节点,这里有点奇怪,后面分析代码时,会解答这个疑惑

那么为何只需引用该节点,调用clk_get函数,即完成了时钟的初始化等操作呢?这里面发生了什么?接下来就需要看看厂家是如何注册一个clk_framework的了

如何注册一个clk framework?

还是老规矩,选择一个平台,去看看人家的bsp工程师是如何注册一个自家平台的clk framework,以三星平台为例吧,各个平台注册的clk framework在\linux-4.9\drivers\clk路径下

对于芯片厂商,要做的就是按照kernel提供的框架填充数据结构就完了,所以从硬件的角度出发,可以猜测注册一个clk framework要做的事情就是描述clk的级联关系,提供clk的分频、倍频等跟时钟相关的操作函数。

分析下代码,看看是否是这么操作~

和power domain一样,kernel对外提供of_clk_add_provider()函数向内核注册一个clk provider,由该函数的名称可知,该函数与dts相关,先暂时搁置一下dts,来看看samsung如何使用该函数的,samsung使用samsung_clk_of_add_provider(np, ctx)包装了一下of_clk_add_provider(),这样的话分析起来就简单点啦,重点看samsung函数的入参就可以啦。

void __init samsung_clk_of_add_provider(struct device_node *np,
				struct samsung_clk_provider *ctx) //可以看到三星传入了 ctx这个结构体,这个结构体就是data指针
{
	if (np) {
		if (of_clk_add_provider(np, of_clk_src_onecell_get,
					&ctx->clk_data))
			panic("could not register clk provider\n");
	}
}

/**
 * of_clk_add_provider() - Register a clock provider for a node
 * @np: Device node pointer associated with clock provider
 * @clk_src_get: callback for decoding clock
 * @data: context pointer for @clk_src_get callback.
 */
int of_clk_add_provider(struct device_node *np,
			struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
						   void *data),
						void *data)
    
    
 /**
 * struct samsung_clk_provider: information about clock provider
 * @reg_base: virtual address for the register base.
 * @clk_data: holds clock related data like clk* and number of clocks.
 * @lock: maintains exclusion between callbacks for a given clock-provider.
 */
struct samsung_clk_provider {
	void __iomem *reg_base;
	struct clk_onecell_data clk_data;
	spinlock_t lock;
};

从代码层面分析,可以看出samsung调用 of_clk_add_provider函数向内核注册一个clk framework,而samsung实现的一个结构体就是struct clk_onecell_data,那么是不是可以推论出struct clk_onecell_data就对应一个clk provider,接下来深入分析一下struct clk_onecell_data结构体,是不是如我们推测的这样~

 struct clk_onecell_data {
  	struct clk **clks;
  	unsigned int clk_num;
  };
  
  struct clk {
  	struct clk_core	*core;
  	const char *dev_id;
  	const char *con_id;
  	unsigned long min_rate;
  	unsigned long max_rate;
  	struct hlist_node clks_node;
  };
  
  struct clk_core {
  	const char		*name;
  	const struct clk_ops	*ops;
  	struct clk_hw		*hw;
  	struct module		*owner;
  	struct clk_core		*parent;
  	const char		**parent_names;
  	struct clk_core		**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;
  	unsigned int		enable_count;
  	unsigned int		prepare_count;
  	unsigned long		min_rate;
  	unsigned long		max_rate;
  	unsigned long		accuracy;
  	int			phase;
  	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_hw - handle for traversing from a struct clk to its corresponding
   * hardware-specific structure.  struct clk_hw should be declared within struct
   * clk_foo and then referenced by the struct clk instance that uses struct
   * clk_foo's clk_ops
   *
   * @core: pointer to the struct clk_core instance that points back to this
   * struct clk_hw instance
   *
   * @clk: pointer to the per-user struct clk instance that can be used to call
   * into the clk API
   *
   * @init: pointer to struct clk_init_data that contains the init data shared
   * with the common clock framework.
   */
  struct clk_hw {
  	struct clk_core *core;
  	struct clk *clk;
  	const struct clk_init_data *init;
  };
  
  /**
   * struct clk_init_data - holds init data that's common to all clocks and is
   * shared between the clock provider and the common clock framework.
   *
   * @name: clock name
   * @ops: operations this clock supports
   * @parent_names: array of string names for all possible parents
   * @num_parents: number of possible parents
   * @flags: framework-level hints and quirks
   */
  struct clk_init_data {
  	const char		*name;
  	const struct clk_ops	*ops;
  	const char		* const *parent_names;
  	u8			num_parents;
  	unsigned long		flags;
  };

struct clk_onecell_data结构体层层剖析,可以发现其包含了几个重要的结构体,分别是struct clkstruct clk_corestruct clk_hw以及struct clk_init_data,要分析出这些important struct,那么接下来就需要看看samsung如何构建这些结构体的实例的,接着分析代码!

s3c2410_common_clk_init
    ctx = samsung_clk_init(np, reg_base, NR_CLKS);

/* setup the essentials required to support clock lookup using ccf */
struct samsung_clk_provider *__init samsung_clk_init(struct device_node *np,
			void __iomem *base, unsigned long nr_clks)
{
	struct samsung_clk_provider *ctx;
	struct clk **clk_table;
	int i;

	ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL); //分配一个struct samsung_clk_provider结构体实例,并清0该内存
	if (!ctx)
		panic("could not allocate clock provider context.\n");

	clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
	if (!clk_table)
		panic("could not allocate clock lookup table\n");

	for (i = 0; i < nr_clks; ++i)
		clk_table[i] = ERR_PTR(-ENOENT);

	ctx->reg_base = base;
	ctx->clk_data.clks = clk_table; //初始化clk指针
	ctx->clk_data.clk_num = nr_clks; //初始化clk num
	spin_lock_init(&ctx->lock);

	return ctx;
}

由上面的代码中可以看出samsung_clk_init函数仅仅是分配了相关结构体的内存,并没有进行实际的初始化,比如仅仅给结构体struct clk_onecell_data的成员做了指针的赋值操作,那么实际的赋值操作在哪里进行的呢?接着看代码…

	
	s3c2410_common_clk_init
       
	if (current_soc == S3C2440 || current_soc == S3C2442) {
		samsung_clk_register_div(ctx, s3c244x_common_dividers,
				ARRAY_SIZE(s3c244x_common_dividers));
		samsung_clk_register_gate(ctx, s3c244x_common_gates,
				ARRAY_SIZE(s3c244x_common_gates));
		samsung_clk_register_mux(ctx, s3c244x_common_muxes,
				ARRAY_SIZE(s3c244x_common_muxes));
		samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor,
				ARRAY_SIZE(s3c244x_common_ffactor));
	}

//在s3c2410_common_clk_init函数中,有很多register函数,我们选择其中之一进行分析...

/* register a list of gate clocks */
void __init samsung_clk_register_gate(struct samsung_clk_provider *ctx,
				const struct samsung_gate_clock *list,
				unsigned int nr_clk)
{
	struct clk *clk;
	unsigned int idx, ret;

	for (idx = 0; idx < nr_clk; idx++, list++) {
		clk = clk_register_gate(NULL, list->name, list->parent_name,
				list->flags, ctx->reg_base + list->offset,
				list->bit_idx, list->gate_flags, &ctx->lock); //实际的注册gate函数
		if (IS_ERR(clk)) {
			pr_err("%s: failed to register clock %s\n", __func__,
				list->name);
			continue;
		}

		/* register a clock lookup only if a clock alias is specified */
		if (list->alias) {
			ret = clk_register_clkdev(clk, list->alias,
							list->dev_name);
			if (ret)
				pr_err("%s: failed to register lookup %s\n",
					__func__, list->alias);
		}

		samsung_clk_add_lookup(ctx, clk, list->id);
	}
}

//来看看clk_register_gate函数
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_hw *hw;

	hw = clk_hw_register_gate(dev, name, parent_name, flags, reg,
				  bit_idx, clk_gate_flags, lock);
	if (IS_ERR(hw))
		return ERR_CAST(hw);
	return hw->clk;
}

//来看看clk_hw_register_gate函数
/**
 * clk_hw_register_gate - register a gate clock with the clock framework
 * @dev: device that is registering this clock
 * @name: name of this clock
 * @parent_name: name of this clock's parent
 * @flags: framework-specific flags for this clock
 * @reg: register address to control gating of this clock
 * @bit_idx: which bit in the register controls gating of this clock
 * @clk_gate_flags: gate-specific flags for this clock
 * @lock: shared register lock for this clock
 */
struct clk_hw *clk_hw_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_gate *gate;
	struct clk_hw *hw;
	struct clk_init_data init;
	int ret;

	if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
		if (bit_idx > 15) {
			pr_err("gate bit exceeds LOWORD field\n");
			return ERR_PTR(-EINVAL);
		}
	}

	/* allocate the gate */
	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
	if (!gate)
		return ERR_PTR(-ENOMEM);

	init.name = name;
	init.ops = &clk_gate_ops; //最重要的地方,初始化驱动提供的操作函数
	init.flags = flags | CLK_IS_BASIC;
	init.parent_names = (parent_name ? &parent_name: NULL);
	init.num_parents = (parent_name ? 1 : 0);

	/* struct clk_gate assignments */
	gate->reg = reg;
	gate->bit_idx = bit_idx;
	gate->flags = clk_gate_flags;
	gate->lock = lock;
	gate->hw.init = &init;

	hw = &gate->hw; //结构体进行关联
	ret = clk_hw_register(dev, hw);
	if (ret) {
		kfree(gate);
		hw = ERR_PTR(ret);
	}

	return hw;
}

//在来看看samsung_clk_add_lookup函数
/* add a clock instance to the clock lookup table used for dt based lookup */
void samsung_clk_add_lookup(struct samsung_clk_provider *ctx, struct clk *clk,
				unsigned int id)
{
	if (ctx->clk_data.clks && id)
		ctx->clk_data.clks[id] = clk;
}

上面代码量有点大,我们简单的梳理一下,时钟的功能有很多,比如gate、mux、div等等,这里三星根据时钟的功能依次调用对应的注册函数进行注册,当然这里这些时钟的相关信息比如频率、名字等等samsung的bsp工程师都已经做成一个数组了,感兴趣的可以再看看代码,这里不再赘述,在samsung_clk_register_gate函数里,通过for循环依次注册gate clock,该函数里做了哪些事情呢,该函数分配一个struct clk_hw结构体,初始化该结构体内的成员,**最重要的就是初始化驱动提供的gate的操作函数ops!!!**同时初始化一个struct clk_gate结构体实例。最终将struct clk_gatestruct clk_hw实例进行关联,调用clk_hw_register注册一个clk_hw!所以无论是gate、mux还是其它的注册,最终都是调用clk_hw_register函数注册了一个clk_hw,因为结构体之间是有关联的。

最后通过samsung_clk_add_lookup函数,将注册成功返回的hw->clk指针记录到struct clk_onecell_data中的clk数组中,方便consumer进行查找,在这里我们也可以推测中,consumer获取的clk就是hw->clk。

provider总结

通过以上的分析,可以总结出以下信息:

  1. struct clk_onecell_data结构体可以认为是包含了一个单板的所有clk信息(当然这只是三星是这么干的,不同的厂家实现会有所区别),其两个成员变量分别是struct clk数组及数组个数,所以注册provider需要实现这个结构体,以便consumer拿到clk。
  2. struct clk_hw该结构体对应一个具体的clk,因为其包含了clk的操作函数。

浅析内核clk framework的实现

provider实现

深入of_clk_add_provider函数进行分析,通过下面的代码可以看到该函数非常的简单,分配一个provider,初始化其成员,一共就三个成员,以作了相应注释,比较重要的可能就是node,后续内核应该就是根据consumer引用了哪个node去返回provider。

/**
 * of_clk_add_provider() - Register a clock provider for a node
 * @np: Device node pointer associated with clock provider
 * @clk_src_get: callback for decoding clock
 * @data: context pointer for @clk_src_get callback.
 */
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; //分配一个provider实例
	int ret;

	cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL);
	if (!cp)
		return -ENOMEM;

	cp->node = of_node_get(np); //provider node,对应前文的clocks
	cp->data = data; //拿到私有数据
	cp->get = clk_src_get; //初始化get函数

	mutex_lock(&of_clk_mutex);
	list_add(&cp->link, &of_clk_providers); //添加到provider链表中
	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;
}

consumer实现

来分析下clk_get函数,这个函数深入分析就略微复杂点了,大家看注释吧 - - 一点点看~

这里一些dts的操作函数,还有具体的查找方式本人也还没有弄清楚,后续本人弄清楚了会继续更新本章,但是总体看下来就是根据clk name为入参,最终一步步找到provider。

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);
}

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

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);
}

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); //获取provider
	of_node_put(clkspec.np);

	return clk;
}

小结

本章本人完全是从代码上去分析clk framework,总的来说clk framework较power domain要复杂点,因为需要结合单板的clk tree 去看,但是本人在写该篇博客之前,没有去看2440的时钟树,所以导致很多结构体和思想理解的还不够透彻,所以本章如果有误,还望大家们指出,同时希望大牛们可以留言对本章内容指点一二~

最后,注意到kernel的framework与dts都强相关,所以要深入理解framework的话,需要对dts的操作函数很熟悉,所以下周本人决定总结一些出现频率较多的dts操作函数,以便后续分析代码。

前面提到使用2440单板的时钟,我们需要在dts里引用一个时钟节点即可,现在可以解答该问题了,因为samsung只注册了一个provider进去,samsung将所有的clk都包含在了一个struct clk_onecell_data结构体里,而其它bsp厂家,是按照功能分类的,比如sprd(紫光展锐),按照时钟功能进行分类,所有gate clk构建一个struct clk_onecell_data,并注册成一个provider,这样的话consumer就可以根据功能引用节点,感兴趣的可以看看sprd的dts,可以发现往往一个consumer会引用多个provider进行配置,我觉得这样会更好点~

希望身体健康,坚持运动!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值