diff options
Diffstat (limited to 'drivers/clk/clk.c')
-rw-r--r-- | drivers/clk/clk.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c index 46317cbc088f..c87fdd710560 100644 --- a/drivers/clk/clk.c +++ b/drivers/clk/clk.c @@ -16,6 +16,7 @@ #include <linux/err.h> #include <linux/list.h> #include <linux/slab.h> +#include <linux/of.h> static DEFINE_SPINLOCK(enable_lock); static DEFINE_MUTEX(prepare_lock); @@ -1550,3 +1551,142 @@ int clk_notifier_unregister(struct clk *clk, struct notifier_block *nb) return ret; } EXPORT_SYMBOL_GPL(clk_notifier_unregister); + +#ifdef CONFIG_OF +/** + * struct of_clk_provider - Clock provider registration structure + * @link: Entry in global list of clock providers + * @node: Pointer to device tree node of clock provider + * @get: Get clock callback. Returns NULL or a struct clk for the + * given clock specifier + * @data: context pointer to be passed into @get callback + */ +struct of_clk_provider { + struct list_head link; + + struct device_node *node; + struct clk *(*get)(struct of_phandle_args *clkspec, void *data); + void *data; +}; + +static LIST_HEAD(of_clk_providers); +static DEFINE_MUTEX(of_clk_lock); + +struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec, + void *data) +{ + return data; +} +EXPORT_SYMBOL_GPL(of_clk_src_simple_get); + +/** + * 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; + + cp = kzalloc(sizeof(struct of_clk_provider), GFP_KERNEL); + if (!cp) + return -ENOMEM; + + cp->node = of_node_get(np); + cp->data = data; + cp->get = clk_src_get; + + mutex_lock(&of_clk_lock); + list_add(&cp->link, &of_clk_providers); + mutex_unlock(&of_clk_lock); + pr_debug("Added clock from %s\n", np->full_name); + + return 0; +} +EXPORT_SYMBOL_GPL(of_clk_add_provider); + +/** + * of_clk_del_provider() - Remove a previously registered clock provider + * @np: Device node pointer associated with clock provider + */ +void of_clk_del_provider(struct device_node *np) +{ + struct of_clk_provider *cp; + + mutex_lock(&of_clk_lock); + list_for_each_entry(cp, &of_clk_providers, link) { + if (cp->node == np) { + list_del(&cp->link); + of_node_put(cp->node); + kfree(cp); + break; + } + } + mutex_unlock(&of_clk_lock); +} +EXPORT_SYMBOL_GPL(of_clk_del_provider); + +struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec) +{ + struct of_clk_provider *provider; + struct clk *clk = ERR_PTR(-ENOENT); + + /* Check if we have such a provider in our array */ + mutex_lock(&of_clk_lock); + list_for_each_entry(provider, &of_clk_providers, link) { + if (provider->node == clkspec->np) + clk = provider->get(clkspec, provider->data); + if (!IS_ERR(clk)) + break; + } + mutex_unlock(&of_clk_lock); + + return clk; +} + +const char *of_clk_get_parent_name(struct device_node *np, int index) +{ + struct of_phandle_args clkspec; + const char *clk_name; + int rc; + + if (index < 0) + return NULL; + + rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, + &clkspec); + if (rc) + return NULL; + + if (of_property_read_string_index(clkspec.np, "clock-output-names", + clkspec.args_count ? clkspec.args[0] : 0, + &clk_name) < 0) + clk_name = clkspec.np->name; + + of_node_put(clkspec.np); + return clk_name; +} +EXPORT_SYMBOL_GPL(of_clk_get_parent_name); + +/** + * of_clk_init() - Scan and init clock providers from the DT + * @matches: array of compatible values and init functions for providers. + * + * This function scans the device tree for matching clock providers and + * calls their initialization functions + */ +void __init of_clk_init(const struct of_device_id *matches) +{ + struct device_node *np; + + for_each_matching_node(np, matches) { + const struct of_device_id *match = of_match_node(matches, np); + of_clk_init_cb_t clk_init_cb = match->data; + clk_init_cb(np); + } +} +#endif |