diff options
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/clk.c | 262 |
1 files changed, 200 insertions, 62 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index ffa63ddcd408..ffd33b63c37e 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -39,6 +39,13 @@ static LIST_HEAD(clk_notifier_list); /*** private data structures ***/ +struct clk_parent_map { + const struct clk_hw *hw; + struct clk_core *core; + const char *fw_name; + const char *name; +}; + struct clk_core { const char *name; const struct clk_ops *ops; @@ -47,8 +54,7 @@ struct clk_core { struct device *dev; struct device_node *of_node; struct clk_core *parent; - const char **parent_names; - struct clk_core **parents; + struct clk_parent_map *parents; u8 num_parents; u8 new_parent_index; unsigned long rate; @@ -317,17 +323,92 @@ static struct clk_core *clk_core_lookup(const char *name) return NULL; } +/** + * clk_core_get - Find the parent of a clk using a clock specifier in DT + * @core: clk to find parent of + * @name: name to search for in 'clock-names' of device providing clk + * + * This is the preferred method for clk providers to find the parent of a + * clk when that parent is external to the clk controller. The parent_names + * array is indexed and treated as a local name matching a string in the device + * node's 'clock-names' property. This allows clk providers to use their own + * namespace instead of looking for a globally unique parent string. + * + * For example the following DT snippet would allow a clock registered by the + * clock-controller@c001 that has a clk_init_data::parent_data array + * with 'xtal' in the 'name' member to find the clock provided by the + * clock-controller@f00abcd without needing to get the globally unique name of + * the xtal clk. + * + * parent: clock-controller@f00abcd { + * reg = <0xf00abcd 0xabcd>; + * #clock-cells = <0>; + * }; + * + * clock-controller@c001 { + * reg = <0xc001 0xf00d>; + * clocks = <&parent>; + * clock-names = "xtal"; + * #clock-cells = <1>; + * }; + * + * Returns: -ENOENT when the provider can't be found or the clk doesn't + * exist in the provider. -EINVAL when the name can't be found. NULL when the + * provider knows about the clk but it isn't provided on this system. + * A valid clk_core pointer when the clk can be found in the provider. + */ +static struct clk_core *clk_core_get(struct clk_core *core, const char *name) +{ + struct clk_hw *hw; + struct device_node *np = core->of_node; + + if (!np) + return ERR_PTR(-ENOENT); + + /* TODO: Support clkdev clk_lookups */ + hw = of_clk_get_hw(np, -1, name); + if (IS_ERR_OR_NULL(hw)) + return ERR_CAST(hw); + + return hw->core; +} + +static void clk_core_fill_parent_index(struct clk_core *core, u8 index) +{ + struct clk_parent_map *entry = &core->parents[index]; + struct clk_core *parent = ERR_PTR(-ENOENT); + + if (entry->hw) { + parent = entry->hw->core; + /* + * We have a direct reference but it isn't registered yet? + * Orphan it and let clk_reparent() update the orphan status + * when the parent is registered. + */ + if (!parent) + parent = ERR_PTR(-EPROBE_DEFER); + } else { + if (entry->fw_name) + parent = clk_core_get(core, entry->fw_name); + if (IS_ERR(parent) && PTR_ERR(parent) == -ENOENT) + parent = clk_core_lookup(entry->name); + } + + /* Only cache it if it's not an error */ + if (!IS_ERR(parent)) + entry->core = parent; +} + static struct clk_core *clk_core_get_parent_by_index(struct clk_core *core, u8 index) { - if (!core || index >= core->num_parents) + if (!core || index >= core->num_parents || !core->parents) return NULL; - if (!core->parents[index]) - core->parents[index] = - clk_core_lookup(core->parent_names[index]); + if (!core->parents[index].core) + clk_core_fill_parent_index(core, index); - return core->parents[index]; + return core->parents[index].core; } struct clk_hw * @@ -1520,15 +1601,15 @@ static int clk_fetch_parent_index(struct clk_core *core, return -EINVAL; for (i = 0; i < core->num_parents; i++) { - if (core->parents[i] == parent) + if (core->parents[i].core == parent) return i; - if (core->parents[i]) + if (core->parents[i].core) continue; /* Fallback to comparing globally unique names */ - if (!strcmp(parent->name, core->parent_names[i])) { - core->parents[i] = parent; + if (!strcmp(parent->name, core->parents[i].name)) { + core->parents[i].core = parent; return i; } } @@ -2294,6 +2375,7 @@ void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent) bool clk_has_parent(struct clk *clk, struct clk *parent) { struct clk_core *core, *parent_core; + int i; /* NULL clocks should be nops, so return success if either is NULL. */ if (!clk || !parent) @@ -2306,8 +2388,11 @@ bool clk_has_parent(struct clk *clk, struct clk *parent) if (core->parent == parent_core) return true; - return match_string(core->parent_names, core->num_parents, - parent_core->name) >= 0; + for (i = 0; i < core->num_parents; i++) + if (!strcmp(core->parents[i].name, parent_core->name)) + return true; + + return false; } EXPORT_SYMBOL_GPL(clk_has_parent); @@ -2890,9 +2975,9 @@ static int possible_parents_show(struct seq_file *s, void *data) int i; for (i = 0; i < core->num_parents - 1; i++) - seq_printf(s, "%s ", core->parent_names[i]); + seq_printf(s, "%s ", core->parents[i].name); - seq_printf(s, "%s\n", core->parent_names[i]); + seq_printf(s, "%s\n", core->parents[i].name); return 0; } @@ -3026,7 +3111,7 @@ static inline void clk_debug_unregister(struct clk_core *core) */ static int __clk_core_init(struct clk_core *core) { - int i, ret; + int ret; struct clk_core *orphan; struct hlist_node *tmp2; unsigned long rate; @@ -3080,12 +3165,6 @@ static int __clk_core_init(struct clk_core *core) 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); /* @@ -3314,10 +3393,102 @@ struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw, return clk; } +static int clk_cpy_name(const char **dst_p, const char *src, bool must_exist) +{ + const char *dst; + + if (!src) { + if (must_exist) + return -EINVAL; + return 0; + } + + *dst_p = dst = kstrdup_const(src, GFP_KERNEL); + if (!dst) + return -ENOMEM; + + return 0; +} + +static int clk_core_populate_parent_map(struct clk_core *core) +{ + const struct clk_init_data *init = core->hw->init; + u8 num_parents = init->num_parents; + const char * const *parent_names = init->parent_names; + const struct clk_hw **parent_hws = init->parent_hws; + const struct clk_parent_data *parent_data = init->parent_data; + int i, ret = 0; + struct clk_parent_map *parents, *parent; + + if (!num_parents) + return 0; + + /* + * Avoid unnecessary string look-ups of clk_core's possible parents by + * having a cache of names/clk_hw pointers to clk_core pointers. + */ + parents = kcalloc(num_parents, sizeof(*parents), GFP_KERNEL); + core->parents = parents; + if (!parents) + return -ENOMEM; + + /* Copy everything over because it might be __initdata */ + for (i = 0, parent = parents; i < num_parents; i++, parent++) { + if (parent_names) { + /* throw a WARN if any entries are NULL */ + WARN(!parent_names[i], + "%s: invalid NULL in %s's .parent_names\n", + __func__, core->name); + ret = clk_cpy_name(&parent->name, parent_names[i], + true); + } else if (parent_data) { + parent->hw = parent_data[i].hw; + ret = clk_cpy_name(&parent->fw_name, + parent_data[i].fw_name, false); + if (!ret) + ret = clk_cpy_name(&parent->name, + parent_data[i].name, + false); + } else if (parent_hws) { + parent->hw = parent_hws[i]; + } else { + ret = -EINVAL; + WARN(1, "Must specify parents if num_parents > 0\n"); + } + + if (ret) { + do { + kfree_const(parents[i].name); + kfree_const(parents[i].fw_name); + } while (--i >= 0); + kfree(parents); + + return ret; + } + } + + return 0; +} + +static void clk_core_free_parent_map(struct clk_core *core) +{ + int i = core->num_parents; + + if (!core->num_parents) + return; + + while (--i >= 0) { + kfree_const(core->parents[i].name); + kfree_const(core->parents[i].fw_name); + } + + kfree(core->parents); +} + static struct clk * __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw) { - int i, ret; + int ret; struct clk_core *core; core = kzalloc(sizeof(*core), GFP_KERNEL); @@ -3351,33 +3522,9 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw) 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; + ret = clk_core_populate_parent_map(core); + if (ret) goto fail_parents; - }; INIT_HLIST_HEAD(&core->clks); @@ -3388,7 +3535,7 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw) hw->clk = alloc_clk(core, NULL, NULL); if (IS_ERR(hw->clk)) { ret = PTR_ERR(hw->clk); - goto fail_parents; + goto fail_create_clk; } clk_core_link_consumer(hw->core, hw->clk); @@ -3404,13 +3551,9 @@ __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw) free_clk(hw->clk); hw->clk = NULL; +fail_create_clk: + clk_core_free_parent_map(core); 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: fail_ops: kfree_const(core->name); fail_name: @@ -3473,15 +3616,10 @@ EXPORT_SYMBOL_GPL(of_clk_hw_register); static void __clk_release(struct kref *ref) { struct clk_core *core = container_of(ref, struct clk_core, ref); - int i = core->num_parents; lockdep_assert_held(&prepare_lock); - kfree(core->parents); - while (--i >= 0) - kfree_const(core->parent_names[i]); - - kfree(core->parent_names); + clk_core_free_parent_map(core); kfree_const(core->name); kfree(core); } |