目录
(7)尝试reparent当前所有的孤儿(orphan)clock
2. divider时钟注册——clk_register_divider
从前文中我们知道,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;
else
core->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);
else
core->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;
else
rate = 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;
else
rate = 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_TS
u8 we; //write_enable shift
u8 sync; //generate update sync event shift
#endif
u16 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;
else
init.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;
else
WARN(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);
else
cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, NULL);
return cl ? 0 : -ENOMEM;
}
向时钟系统注册dev。