目录
1. clk_register
1.1 __clk_create_clk
1.2 __clk_core_init
(1)检查clk是否已注册
(2)检查clk ops的完整性
(3)检查其父时钟情况
(4)将该时钟添加到响应全局链表中
(5)计算clock的初始rate
(6)对于CLK_IS_CRITICAL时钟,直接打开
(7)尝试reparent当前所有的孤儿(orphan)clock
2. divider时钟注册——clk_register_divider
2.1 struct clk_divider
2.2 clk_register_divider
2.3 操作集clk_divider_ops
3. gate时钟注册
3.1 struct clk_gate
3.2 clk_hw_register_gate
3.3 操作集clk_gate_ops
4. composite时钟注册
4.1 struct clk_composite
4.2 clk_hw_register_composite
5. clk_register_clkdev
从前文中我们知道,ccf根据不同时钟的特点,clock framework 将 clock 分为 Fixed rate、gate、Divider、Mux、Fixed factor、composite六类,Linux 内核将上面六类设备特点抽象出不同的结构图,我们看一下这些类的注册函数。
结合上文介绍的API接口,和这些接口在kernel中的调用关系,得到如下的函数调用关系图。
从图上看,clk_register是所有register接口的共同实现,负责将clock注册到kernel,并返回代表该clock的struct clk指针。
1. clk_register
/*** clk_register - allocate a new clock, register it and return an opaque cookie* @dev: device that is registering this clock* @hw: link to hardware-specific clock data** clk_register is the primary interface for populating the clock tree with new* clock nodes. It returns a pointer to the newly allocated struct clk which* cannot be dereferenced by driver code but may be used in conjunction with the* rest of the clock API. In the event of an error clk_register will return an* error code; drivers must test for an error code after calling clk_register.*/
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{int i, ret;struct clk_core *core;core = kzalloc(sizeof(*core), GFP_KERNEL);if (!core) {ret = -ENOMEM;goto fail_out;}core->name = kstrdup_const(hw->init->name, GFP_KERNEL);if (!core->name) {ret = -ENOMEM;goto fail_name;}core->ops = hw->init->ops;if (dev && dev->driver)core->owner = dev->driver->owner;core->hw = hw;core->flags = hw->init->flags;core->num_parents = hw->init->num_parents;core->min_rate = 0;core->max_rate = ULONG_MAX;hw->core = core;/* allocate local copy in case parent_names is __initdata */core->parent_names = kcalloc(core->num_parents, sizeof(char *),GFP_KERNEL);if (!core->parent_names) {ret = -ENOMEM;goto fail_parent_names;}/* copy each string name in case parent_names is __initdata */for (i = 0; i < core->num_parents; i++) {core->parent_names[i] = kstrdup_const(hw->init->parent_names[i],GFP_KERNEL);if (!core->parent_names[i]) {ret = -ENOMEM;goto fail_parent_names_copy;}}/* avoid unnecessary string look-ups of clk_core's possible parents. */core->parents = kcalloc(core->num_parents, sizeof(*core->parents),GFP_KERNEL);if (!core->parents) {ret = -ENOMEM;goto fail_parents;};INIT_HLIST_HEAD(&core->clks);hw->clk = __clk_create_clk(hw, NULL, NULL);if (IS_ERR(hw->clk)) {ret = PTR_ERR(hw->clk);goto fail_parents;}ret = __clk_core_init(core);if (!ret)return hw->clk;__clk_free_clk(hw->clk);hw->clk = NULL;fail_parents:kfree(core->parents);
fail_parent_names_copy:while (--i >= 0)kfree_const(core->parent_names[i]);kfree(core->parent_names);
fail_parent_names:kfree_const(core->name);
fail_name:kfree(core);
fail_out:return ERR_PTR(ret);
}
该接口接受一个struct clk_hw指针,该指针包含了将要注册的clock的信息,在内部分配一个struct clk变量后,将clock信息保存在变量中,并返回给调用者。实现逻辑如下:
(1)分配struct clk空间;
(2)根据struct clk_hw指针提供的信息,初始化clk的name、ops、hw、flags、num_parents、parents_names等变量;
(3)初始化list头INIT_HLIST_HEAD(&core->clks);
(4)create一个clk,hw->clk = __clk_create_clk(hw, NULL, NULL);
(5)调用内部接口ret = __clk_core_init(core),执行后续的初始化操作。这个接口包含了clk_regitser的主要逻辑,具体如下。
1.1 __clk_create_clk
struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,const char *con_id)
{struct clk *clk;/* This is to allow this function to be chained to others */if (IS_ERR_OR_NULL(hw))return ERR_CAST(hw);clk = kzalloc(sizeof(*clk), GFP_KERNEL);if (!clk)return ERR_PTR(-ENOMEM);clk->core = hw->core;clk->dev_id = dev_id;clk->con_id = kstrdup_const(con_id, GFP_KERNEL);clk->max_rate = ULONG_MAX;clk_prepare_lock();hlist_add_head(&clk->clks_node, &hw->core->clks);clk_prepare_unlock();return clk;
}
创建一个struct clk,初始化其相关参数。
1.2 __clk_core_init
/*** __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 i, ret = 0;struct clk_core *orphan;struct hlist_node *tmp2;unsigned long rate;if (!core)return -EINVAL;clk_prepare_lock();/* 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/clk.txt */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;}/* throw a WARN if any entries in parent_names are NULL */for (i = 0; i < core->num_parents; i++)WARN(!core->parent_names[i],"%s: invalid NULL in %s's .parent_names\n",__func__, core->name);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 (core->parent) {hlist_add_head(&core->child_node,&core->parent->children);core->orphan = core->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_get_accuracy(core->parent));else if (core->parent)core->accuracy = core->parent->accuracy;elsecore->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 (core->ops->get_phase)core->phase = core->ops->get_phase(core->hw);elsecore->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 (core->ops->recalc_rate)rate = core->ops->recalc_rate(core->hw,clk_core_get_rate_nolock(core->parent));else if (core->parent)rate = core->parent->rate;elserate = 0;core->rate = core->req_rate = rate;/** 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) {unsigned long flags;ret = clk_core_prepare(core);if (ret)goto out;flags = clk_enable_lock();ret = clk_core_enable(core);clk_enable_unlock(flags);if (ret) {clk_core_unprepare(core);goto out;}}/** walk the list of orphan clocks and reparent any that newly finds a* parent.*/hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {struct clk_core *parent = __clk_init_parent(orphan);/** We need to use __clk_set_parent_before() and _after() to* to properly migrate any prepare/enable count of the orphan* clock. This is important for CLK_IS_CRITICAL clocks, which* are enabled during init but might not have a parent yet.*/if (parent) {/* update the clk tree topology */__clk_set_parent_before(orphan, parent);__clk_set_parent_after(orphan, parent, NULL);__clk_recalc_accuracies(orphan);__clk_recalc_rates(orphan, 0);}}/** 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.* Please consider other ways of solving initialization problems before* using this callback, as its use is discouraged.*/if (core->ops->init)core->ops->init(core->hw);kref_init(&core->ref);
out:clk_prepare_unlock();if (!ret)clk_debug_register(core);return ret;
}
(1)检查clk是否已注册
以clock name为参数,调用clk_core_lookup接口,查找是否已有相同name的clock注册,如果有,则返回错误。由此可以看出,clock framework以name唯一识别一个clock,因此不能有同名的clock存在;
(2)检查clk ops的完整性
检查clk ops的完整性,例如:如果提供了set_rate接口,就必须提供round_rate和recalc_rate接口;如果提供了set_parent,就必须提供get_parent。这些逻辑背后的含义,会在后面相应的地方详细描述;
(3)检查其父时钟情况
分配一个struct clk *类型的数组,缓存该clock的parents clock。具体方法是根据parents_name,查找相应的struct clk指针;
对于没有parent,或者只有1个parent 的clock来说,比较简单,设置为NULL,或者根据parent name获得parent的struct clk指针接。
对于有多个parent的clock,就必须提供.get_parent ops,该ops要根据当前硬件的配置情况,例如寄存器值,返回当前所有使用的parent的index(即第几个parent)。然后根据index,取出对应parent clock的struct clk指针,作为当前的parent。
static struct clk_core *__clk_init_parent(struct clk_core *core)
{u8 index = 0;if (core->num_parents > 1 && core->ops->get_parent)index = core->ops->get_parent(core->hw);return clk_core_get_parent_by_index(core, index);
}
a) 遍历orphan list,如果orphan提供了.get_parent ops,则通过该ops得到当前parent的index,并从parent_names中取出该parent的name,然后和新注册的clock name比较,如果相同,呵呵,找到parent了,进行后续的操作。
b) 如果没有提供.get_parent ops,只能遍历自己的parent_names,检查是否有和新注册clock匹配的,如果有,进行后续的操作。
(4)将该时钟添加到响应全局链表中
根据该clock的特性,将它添加到clk_root_list、clk_orphan_list或者parent->children三个链表中的一个。
/** 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 (core->parent) {hlist_add_head(&core->child_node,&core->parent->children);core->orphan = core->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;}
clock framework有2条全局的链表:clk_root_list和clk_orphan_list。所有设置了CLK_IS_ROOT属性的clock都会挂在clk_root_list中。其它clock,如果有valid的parent ,则会挂到parent的“children”链表中,如果没有valid的parent,则会挂到clk_orphan_list中。
查询时(__clk_lookup接口做的事情),依次搜索:clk_root_list-->root_clk-->children-->child's children,clk_orphan_list-->orphan_clk-->children-->child's children,即可。
(5)计算clock的初始rate
对于提供.recalc_rate ops的clock来说,优先使用该ops获取初始的rate。如果没有提供,退而求其次,直接使用parent clock的rate。最后,如果该clock没有parent,则初始的rate只能选择为0。
.recalc_rate ops的功能,是以parent clock的rate为输入参数,根据当前硬件的配置情况,如寄存器值,计算获得自身的rate值。
/** 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(core->parent));else if (core->parent)rate = core->parent->rate;elserate = 0;core->rate = core->req_rate = rate;
(6)对于CLK_IS_CRITICAL时钟,直接打开
/** 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) {unsigned long flags;ret = clk_core_prepare(core);if (ret)goto out;flags = clk_enable_lock();ret = clk_core_enable(core);clk_enable_unlock(flags);if (ret) {clk_core_unprepare(core);goto out;}}
(7)尝试reparent当前所有的孤儿(orphan)clock
/** walk the list of orphan clocks and reparent any that newly finds a* parent.*/hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {struct clk_core *parent = __clk_init_parent(orphan);/** We need to use __clk_set_parent_before() and _after() to* to properly migrate any prepare/enable count of the orphan* clock. This is important for CLK_IS_CRITICAL clocks, which* are enabled during init but might not have a parent yet.*/if (parent) {/* update the clk tree topology */__clk_set_parent_before(orphan, parent);__clk_set_parent_after(orphan, parent, NULL);__clk_recalc_accuracies(orphan);__clk_recalc_rates(orphan, 0);}}
有些情况下,child clock会先于parent clock注册,此时该child就会成为orphan clock,被收养在clk_orphan_list中。
而每当新的clock注册时,kernel都会检查这个clock是否是某个orphan的parent,如果是,就把这个orphan从clk_orphan_list中移除,放到新注册的clock的怀抱。这就是reparent的功能,它的处理逻辑是:
a) 遍历orphan list,如果orphan提供了.get_parent ops,则通过该ops得到当前parent的index,并从parent_names中取出该parent的name,然后和新注册的clock name比较,如果相同,呵呵,找到parent了,进行后续的操作。
b) 如果没有提供.get_parent ops,只能遍历自己的parent_names,检查是否有和新注册clock匹配的,如果有,进行后续的操作。
c)把这个orphan从clk_orphan_list中移除,并挂到新注册的clock上。然后调用__clk_recalc_rates,重新计算自己以及自己所有children的rate。计算过程和上面的clock rate设置类似。
2. divider时钟注册——clk_register_divider
2.1 struct clk_divider
struct clk_divider {struct clk_hw hw;void __iomem *reg;u8 shift;u8 width;
#ifdef CONFIG_ARCH_TSu8 we; //write_enable shiftu8 sync; //generate update sync event shift
#endifu16 flags;const struct clk_div_table *table;spinlock_t *lock;
};
reg:控制该clock开关的寄存器地址(虚拟地址)
shift:控制clock开关的bit位
width,控制分频比的bit位数,默认情况下,实际的divider值是寄存器值加1(如寄存器值=1,实际分频=2)。
flags:clk_divider_flags:ivider clock特有的flag,包括:
CLK_DIVIDER_ONE_BASED:实际的divider值就是寄存器值(0是无效的,除非设置CLK_DIVIDER_ALLOW_ZERO flag)
CLK_DIVIDER_POWER_OF_TWO:实际的divider值是寄存器值的2次方
CLK_DIVIDER_ALLOW_ZERO:divider值可以为0(不改变,视硬件支持而定)
2.2 clk_register_divider
static struct clk_hw *_register_divider(struct device *dev, const char *name,const char *parent_name, unsigned long flags,void __iomem *reg, u8 shift, u8 width,u8 clk_divider_flags, const struct clk_div_table *table,spinlock_t *lock)
{struct clk_divider *div;struct clk_hw *hw;struct clk_init_data init;int ret;if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) {if (width + shift > 16) {pr_warn("divider value exceeds LOWORD field\n");return ERR_PTR(-EINVAL);}}/* allocate the divider */div = kzalloc(sizeof(*div), GFP_KERNEL);if (!div)return ERR_PTR(-ENOMEM);init.name = name;if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)init.ops = &clk_divider_ro_ops;elseinit.ops = &clk_divider_ops;init.flags = flags | CLK_IS_BASIC;init.parent_names = (parent_name ? &parent_name : NULL);init.num_parents = (parent_name ? 1 : 0);/* struct clk_divider assignments */div->reg = reg;div->shift = shift;div->width = width;div->flags = clk_divider_flags;div->lock = lock;div->hw.init = &init;div->table = table;/* register the clock */hw = &div->hw;ret = clk_hw_register(dev, hw);if (ret) {kfree(div);hw = ERR_PTR(ret);}return hw;
}/*** clk_register_divider - register a divider clock with the clock framework* @dev: device registering this clock* @name: name of this clock* @parent_name: name of clock's parent* @flags: framework-specific flags* @reg: register address to adjust divider* @shift: number of bits to shift the bitfield* @width: width of the bitfield* @clk_divider_flags: divider-specific flags for this clock* @lock: shared register lock for this clock*/
struct clk *clk_register_divider(struct device *dev, const char *name,const char *parent_name, unsigned long flags,void __iomem *reg, u8 shift, u8 width,u8 clk_divider_flags, spinlock_t *lock)
{struct clk_hw *hw;hw = _register_divider(dev, name, parent_name, flags, reg, shift,width, clk_divider_flags, NULL, lock);if (IS_ERR(hw))return ERR_CAST(hw);return hw->clk;
}
EXPORT_SYMBOL_GPL(clk_register_divider);
(1)赋值struct clk_init_data结构体
(2)赋值struct clk_divider结构体
(3)注册clk_hw_register
2.3 操作集clk_divider_ops
const struct clk_ops clk_divider_ops = {.recalc_rate = clk_divider_recalc_rate,.round_rate = clk_divider_round_rate,.set_rate = clk_divider_set_rate,
};
EXPORT_SYMBOL_GPL(clk_divider_ops);const struct clk_ops clk_divider_ro_ops = {.recalc_rate = clk_divider_recalc_rate,.round_rate = clk_divider_round_rate,
};
EXPORT_SYMBOL_GPL(clk_divider_ro_ops);
3. gate时钟注册
3.1 struct clk_gate
struct clk_gate {struct clk_hw hw;void __iomem *reg;u8 bit_idx;u8 flags;spinlock_t *lock;
};
3.2 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;/* 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;
}
基本上和divider差不多,这里不做介绍了。
3.3 操作集clk_gate_ops
const struct clk_ops clk_gate_ops = {.enable = clk_gate_enable,.disable = clk_gate_disable,.is_enabled = clk_gate_is_enabled,
};
4. composite时钟注册
4.1 struct clk_composite
/**** struct clk_composite - aggregate clock of mux, divider and gate clocks** @hw: handle between common and hardware-specific interfaces* @mux_hw: handle between composite and hardware-specific mux clock* @rate_hw: handle between composite and hardware-specific rate clock* @gate_hw: handle between composite and hardware-specific gate clock* @mux_ops: clock ops for mux* @rate_ops: clock ops for rate* @gate_ops: clock ops for gate*/
struct clk_composite {struct clk_hw hw;struct clk_ops ops;struct clk_hw *mux_hw;struct clk_hw *rate_hw;struct clk_hw *gate_hw;const struct clk_ops *mux_ops;const struct clk_ops *rate_ops;const struct clk_ops *gate_ops;
};
clk_composite时钟比较特殊,它是多种时钟类型的组合,因此其结构体复杂一点,包含多个时钟类型的相关结构图。
4.2 clk_hw_register_composite
struct clk_hw *clk_hw_register_composite(struct device *dev, const char *name,const char * const *parent_names, int num_parents,struct clk_hw *mux_hw, const struct clk_ops *mux_ops,struct clk_hw *rate_hw, const struct clk_ops *rate_ops,struct clk_hw *gate_hw, const struct clk_ops *gate_ops,unsigned long flags)
{struct clk_hw *hw;struct clk_init_data init;struct clk_composite *composite;struct clk_ops *clk_composite_ops;int ret;composite = kzalloc(sizeof(*composite), GFP_KERNEL);if (!composite)return ERR_PTR(-ENOMEM);init.name = name;init.flags = flags | CLK_IS_BASIC;init.parent_names = parent_names;init.num_parents = num_parents;hw = &composite->hw;clk_composite_ops = &composite->ops;if (mux_hw && mux_ops) {if (!mux_ops->get_parent) {hw = ERR_PTR(-EINVAL);goto err;}composite->mux_hw = mux_hw;composite->mux_ops = mux_ops;clk_composite_ops->get_parent = clk_composite_get_parent;if (mux_ops->set_parent)clk_composite_ops->set_parent = clk_composite_set_parent;if (mux_ops->determine_rate)clk_composite_ops->determine_rate = clk_composite_determine_rate;}if (rate_hw && rate_ops) {if (!rate_ops->recalc_rate) {hw = ERR_PTR(-EINVAL);goto err;}clk_composite_ops->recalc_rate = clk_composite_recalc_rate;if (rate_ops->determine_rate)clk_composite_ops->determine_rate =clk_composite_determine_rate;else if (rate_ops->round_rate)clk_composite_ops->round_rate =clk_composite_round_rate;/* .set_rate requires either .round_rate or .determine_rate */if (rate_ops->set_rate) {if (rate_ops->determine_rate || rate_ops->round_rate)clk_composite_ops->set_rate =clk_composite_set_rate;elseWARN(1, "%s: missing round_rate op is required\n",__func__);}composite->rate_hw = rate_hw;composite->rate_ops = rate_ops;}if (mux_hw && mux_ops && rate_hw && rate_ops) {if (mux_ops->set_parent && rate_ops->set_rate)clk_composite_ops->set_rate_and_parent =clk_composite_set_rate_and_parent;}if (gate_hw && gate_ops) {if (!gate_ops->is_enabled || !gate_ops->enable ||!gate_ops->disable) {hw = ERR_PTR(-EINVAL);goto err;}composite->gate_hw = gate_hw;composite->gate_ops = gate_ops;clk_composite_ops->is_enabled = clk_composite_is_enabled;clk_composite_ops->enable = clk_composite_enable;clk_composite_ops->disable = clk_composite_disable;}init.ops = clk_composite_ops;composite->hw.init = &init;ret = clk_hw_register(dev, hw);if (ret) {hw = ERR_PTR(ret);goto err;}if (composite->mux_hw)composite->mux_hw->clk = hw->clk;if (composite->rate_hw)composite->rate_hw->clk = hw->clk;if (composite->gate_hw)composite->gate_hw->clk = hw->clk;return hw;err:kfree(composite);return hw;
}
基本上和divider时钟注册差不多,这里就不多介绍了。
5. clk_register_clkdev
/*** clk_register_clkdev - register one clock lookup for a struct clk* @clk: struct clk to associate with all clk_lookups* @con_id: connection ID string on device* @dev_id: string describing device name** con_id or dev_id may be NULL as a wildcard, just as in the rest of* clkdev.** To make things easier for mass registration, we detect error clks* from a previous clk_register() call, and return the error code for* those. This is to permit this function to be called immediately* after clk_register().*/
int clk_register_clkdev(struct clk *clk, const char *con_id,const char *dev_id)
{struct clk_lookup *cl;if (IS_ERR(clk))return PTR_ERR(clk);/** Since dev_id can be NULL, and NULL is handled specially, we must* pass it as either a NULL format string, or with "%s".*/if (dev_id)cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, "%s",dev_id);elsecl = __clk_register_clkdev(__clk_get_hw(clk), con_id, NULL);return cl ? 0 : -ENOMEM;
}
向时钟系统注册dev。