diff options
Diffstat (limited to 'drivers/clk/clkdev.c')
| -rw-r--r-- | drivers/clk/clkdev.c | 299 |
1 files changed, 122 insertions, 177 deletions
diff --git a/drivers/clk/clkdev.c b/drivers/clk/clkdev.c index 9ab3db8b3988..e0bede6350e1 100644 --- a/drivers/clk/clkdev.c +++ b/drivers/clk/clkdev.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * drivers/clk/clkdev.c * * Copyright (C) 2008 Russell King. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * * Helper for the clk API to assist looking up a struct clk. */ #include <linux/module.h> @@ -27,99 +24,6 @@ static LIST_HEAD(clocks); static DEFINE_MUTEX(clocks_mutex); -#if defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) -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; - - 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); - of_node_put(clkspec.np); - - return clk; -} - -struct clk *of_clk_get(struct device_node *np, int index) -{ - return __of_clk_get(np, index, np->full_name, NULL); -} -EXPORT_SYMBOL(of_clk_get); - -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 %pOF:%s(%i)\n", - np, 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; -} - -/** - * of_clk_get_by_name() - Parse and lookup a clock referenced by a device node - * @np: pointer to clock consumer node - * @name: name of consumer's clock input, or NULL for the first clock reference - * - * This function parses the clocks and clock-names properties, - * and uses them to look up the struct clk from the registered list of clock - * providers. - */ -struct clk *of_clk_get_by_name(struct device_node *np, const char *name) -{ - if (!np) - return ERR_PTR(-ENOENT); - - return __of_clk_get_by_name(np, np->full_name, name); -} -EXPORT_SYMBOL(of_clk_get_by_name); - -#else /* defined(CONFIG_OF) && defined(CONFIG_COMMON_CLK) */ - -static struct clk *__of_clk_get_by_name(struct device_node *np, - const char *dev_id, - const char *name) -{ - return ERR_PTR(-ENOENT); -} -#endif - /* * Find the correct struct clk for the device and connection ID. * We do slightly fuzzy matching here: @@ -139,6 +43,8 @@ static struct clk_lookup *clk_find(const char *dev_id, const char *con_id) if (con_id) best_possible += 1; + lockdep_assert_held(&clocks_mutex); + list_for_each_entry(p, &clocks, node) { match = 0; if (p->dev_id) { @@ -163,46 +69,46 @@ static struct clk_lookup *clk_find(const char *dev_id, const char *con_id) return cl; } -struct clk *clk_get_sys(const char *dev_id, const char *con_id) +struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id) { struct clk_lookup *cl; - struct clk *clk = NULL; + struct clk_hw *hw = ERR_PTR(-ENOENT); mutex_lock(&clocks_mutex); - cl = clk_find(dev_id, con_id); - if (!cl) - goto out; + if (cl) + hw = cl->clk_hw; + mutex_unlock(&clocks_mutex); - clk = __clk_create_clk(cl->clk_hw, dev_id, con_id); - if (IS_ERR(clk)) - goto out; + return hw; +} - if (!__clk_get(clk)) { - __clk_free_clk(clk); - cl = NULL; - goto out; - } +static struct clk *__clk_get_sys(struct device *dev, const char *dev_id, + const char *con_id) +{ + struct clk_hw *hw = clk_find_hw(dev_id, con_id); -out: - mutex_unlock(&clocks_mutex); + return clk_hw_create_clk(dev, hw, dev_id, con_id); +} - return cl ? clk : ERR_PTR(-ENOENT); +struct clk *clk_get_sys(const char *dev_id, const char *con_id) +{ + return __clk_get_sys(NULL, dev_id, con_id); } EXPORT_SYMBOL(clk_get_sys); struct clk *clk_get(struct device *dev, const char *con_id) { const char *dev_id = dev ? dev_name(dev) : NULL; - struct clk *clk; + struct clk_hw *hw; if (dev && dev->of_node) { - clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id); - if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER) - return clk; + hw = of_clk_get_hw(dev->of_node, 0, con_id); + if (!IS_ERR(hw) || PTR_ERR(hw) == -EPROBE_DEFER) + return clk_hw_create_clk(dev, hw, dev_id, con_id); } - return clk_get_sys(dev_id, con_id); + return __clk_get_sys(dev, dev_id, con_id); } EXPORT_SYMBOL(clk_get); @@ -238,7 +144,7 @@ void clkdev_add_table(struct clk_lookup *cl, size_t num) mutex_unlock(&clocks_mutex); } -#define MAX_DEV_ID 20 +#define MAX_DEV_ID 24 #define MAX_CON_ID 16 struct clk_lookup_alloc { @@ -247,31 +153,69 @@ struct clk_lookup_alloc { char con_id[MAX_CON_ID]; }; -static struct clk_lookup * __ref +static __printf(3, 0) struct clk_lookup * __ref vclkdev_alloc(struct clk_hw *hw, const char *con_id, const char *dev_fmt, va_list ap) { struct clk_lookup_alloc *cla; + struct va_format vaf; + const char *failure; + va_list ap_copy; + size_t max_size; + ssize_t res; cla = kzalloc(sizeof(*cla), GFP_KERNEL); if (!cla) return NULL; + va_copy(ap_copy, ap); + cla->cl.clk_hw = hw; if (con_id) { - strlcpy(cla->con_id, con_id, sizeof(cla->con_id)); + res = strscpy(cla->con_id, con_id, sizeof(cla->con_id)); + if (res < 0) { + max_size = sizeof(cla->con_id); + failure = "connection"; + goto fail; + } cla->cl.con_id = cla->con_id; } if (dev_fmt) { - vscnprintf(cla->dev_id, sizeof(cla->dev_id), dev_fmt, ap); + res = vsnprintf(cla->dev_id, sizeof(cla->dev_id), dev_fmt, ap); + if (res >= sizeof(cla->dev_id)) { + max_size = sizeof(cla->dev_id); + failure = "device"; + goto fail; + } cla->cl.dev_id = cla->dev_id; } + va_end(ap_copy); + + return &cla->cl; + +fail: + if (dev_fmt) + vaf.fmt = dev_fmt; + else + vaf.fmt = "null-device"; + vaf.va = &ap_copy; + pr_err("%pV:%s: %s ID is greater than %zu\n", + &vaf, con_id, failure, max_size); + va_end(ap_copy); + + /* + * Don't fail in this case, but as the entry won't ever match just + * fill it with something that also won't match. + */ + strscpy(cla->con_id, "bad", sizeof(cla->con_id)); + strscpy(cla->dev_id, "bad", sizeof(cla->dev_id)); + return &cla->cl; } -static struct clk_lookup * +static __printf(3, 0) struct clk_lookup * vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt, va_list ap) { @@ -284,34 +228,6 @@ vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt, return cl; } -struct clk_lookup * __ref -clkdev_alloc(struct clk *clk, const char *con_id, const char *dev_fmt, ...) -{ - struct clk_lookup *cl; - va_list ap; - - va_start(ap, dev_fmt); - cl = vclkdev_alloc(__clk_get_hw(clk), con_id, dev_fmt, ap); - va_end(ap); - - return cl; -} -EXPORT_SYMBOL(clkdev_alloc); - -struct clk_lookup * -clkdev_hw_alloc(struct clk_hw *hw, const char *con_id, const char *dev_fmt, ...) -{ - struct clk_lookup *cl; - va_list ap; - - va_start(ap, dev_fmt); - cl = vclkdev_alloc(hw, con_id, dev_fmt, ap); - va_end(ap); - - return cl; -} -EXPORT_SYMBOL(clkdev_hw_alloc); - /** * clkdev_create - allocate and add a clkdev lookup structure * @clk: struct clk to associate with all clk_lookups @@ -387,9 +303,8 @@ void clkdev_drop(struct clk_lookup *cl) } EXPORT_SYMBOL(clkdev_drop); -static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw, - const char *con_id, - const char *dev_id, ...) +static __printf(3, 4) struct clk_lookup * +__clk_register_clkdev(struct clk_hw *hw, const char *con_id, const char *dev_id, ...) { struct clk_lookup *cl; va_list ap; @@ -401,6 +316,23 @@ static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw, return cl; } +static int do_clk_register_clkdev(struct clk_hw *hw, + struct clk_lookup **cl, const char *con_id, const char *dev_id) +{ + if (IS_ERR(hw)) + return PTR_ERR(hw); + /* + * 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(hw, con_id, "%s", dev_id); + else + *cl = __clk_register_clkdev(hw, con_id, NULL); + + return *cl ? 0 : -ENOMEM; +} + /** * clk_register_clkdev - register one clock lookup for a struct clk * @clk: struct clk to associate with all clk_lookups @@ -423,17 +355,8 @@ int clk_register_clkdev(struct clk *clk, const char *con_id, 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; + return do_clk_register_clkdev(__clk_get_hw(clk), &cl, con_id, + dev_id); } EXPORT_SYMBOL(clk_register_clkdev); @@ -456,18 +379,40 @@ int clk_hw_register_clkdev(struct clk_hw *hw, const char *con_id, { struct clk_lookup *cl; - if (IS_ERR(hw)) - return PTR_ERR(hw); + return do_clk_register_clkdev(hw, &cl, con_id, dev_id); +} +EXPORT_SYMBOL(clk_hw_register_clkdev); - /* - * 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(hw, con_id, "%s", dev_id); - else - cl = __clk_register_clkdev(hw, con_id, NULL); +static void devm_clkdev_release(void *res) +{ + clkdev_drop(res); +} + +/** + * devm_clk_hw_register_clkdev - managed clk lookup registration for clk_hw + * @dev: device this lookup is bound + * @hw: struct clk_hw to associate with all clk_lookups + * @con_id: connection ID string on device + * @dev_id: format 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 clk_hws + * from a previous clk_hw_register_*() call, and return the error code for + * those. This is to permit this function to be called immediately + * after clk_hw_register_*(). + */ +int devm_clk_hw_register_clkdev(struct device *dev, struct clk_hw *hw, + const char *con_id, const char *dev_id) +{ + struct clk_lookup *cl; + int rval; + + rval = do_clk_register_clkdev(hw, &cl, con_id, dev_id); + if (rval) + return rval; - return cl ? 0 : -ENOMEM; + return devm_add_action_or_reset(dev, devm_clkdev_release, cl); } -EXPORT_SYMBOL(clk_hw_register_clkdev); +EXPORT_SYMBOL(devm_clk_hw_register_clkdev); |
