summaryrefslogtreecommitdiffstats
path: root/drivers/clk/clk.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/clk.c')
-rw-r--r--drivers/clk/clk.c252
1 files changed, 196 insertions, 56 deletions
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index fff1e21144..d3f5d5e838 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -211,6 +211,47 @@ int clk_hw_set_rate(struct clk_hw *hw, unsigned long rate)
return clk_set_rate(&hw->clk, rate);
}
+static int clk_fetch_parent_index(struct clk *clk,
+ struct clk *parent)
+{
+ int i;
+
+ if (!parent)
+ return -EINVAL;
+
+ for (i = 0; i < clk->num_parents; i++) {
+ if (IS_ERR_OR_NULL(clk->parents[i]))
+ clk->parents[i] = clk_lookup(clk->parent_names[i]);
+
+ if (!IS_ERR_OR_NULL(clk->parents[i]))
+ if (clk->parents[i] == parent)
+ break;
+ }
+
+ if (i == clk->num_parents)
+ return -EINVAL;
+
+ return i;
+}
+
+/**
+ * clk_hw_get_parent_index - return the index of the parent clock
+ * @hw: clk_hw associated with the clk being consumed
+ *
+ * Fetches and returns the index of parent clock. Returns -EINVAL if the given
+ * clock does not have a current parent.
+ */
+int clk_hw_get_parent_index(struct clk_hw *hw)
+{
+ struct clk_hw *parent = clk_hw_get_parent(hw);
+
+ if (WARN_ON(parent == NULL))
+ return -EINVAL;
+
+ return clk_fetch_parent_index(clk_hw_to_clk(hw), clk_hw_to_clk(parent));
+}
+EXPORT_SYMBOL_GPL(clk_hw_get_parent_index);
+
struct clk *clk_lookup(const char *name)
{
struct clk *c;
@@ -232,6 +273,9 @@ int clk_set_parent(struct clk *clk, struct clk *newparent)
int i, ret;
struct clk *curparent = clk_get_parent(clk);
+ if (!clk || !newparent)
+ return 0;
+
if (IS_ERR(clk))
return PTR_ERR(clk);
if (IS_ERR(newparent))
@@ -242,17 +286,9 @@ int clk_set_parent(struct clk *clk, struct clk *newparent)
if (!clk->ops->set_parent)
return -EINVAL;
- for (i = 0; i < clk->num_parents; i++) {
- if (IS_ERR_OR_NULL(clk->parents[i]))
- clk->parents[i] = clk_lookup(clk->parent_names[i]);
-
- if (!IS_ERR_OR_NULL(clk->parents[i]))
- if (clk->parents[i] == newparent)
- break;
- }
-
- if (i == clk->num_parents)
- return -EINVAL;
+ i = clk_fetch_parent_index(clk, newparent);
+ if (i < 0)
+ return i;
if (clk->enable_count)
clk_enable(newparent);
@@ -282,12 +318,32 @@ int clk_hw_set_parent(struct clk_hw *hw, struct clk_hw *newparent)
return clk_set_parent(&hw->clk, &newparent->clk);
}
+static struct clk *clk_get_parent_by_index(struct clk *clk, u8 idx)
+{
+ if (IS_ERR_OR_NULL(clk->parents[idx]))
+ clk->parents[idx] = clk_lookup(clk->parent_names[idx]);
+
+ return clk->parents[idx];
+}
+
+struct clk_hw *
+clk_hw_get_parent_by_index(const struct clk_hw *hw, unsigned int idx)
+{
+ struct clk *clk = clk_hw_to_clk(hw);
+
+ if (!clk || idx >= clk->num_parents || !clk->parents)
+ return NULL;
+
+ return clk_to_clk_hw(clk_get_parent_by_index(clk, idx));
+}
+EXPORT_SYMBOL_GPL(clk_hw_get_parent_by_index);
+
struct clk *clk_get_parent(struct clk *clk)
{
struct clk_hw *hw;
int idx;
- if (IS_ERR(clk))
+ if (IS_ERR_OR_NULL(clk))
return clk;
if (!clk->num_parents)
@@ -307,10 +363,7 @@ struct clk *clk_get_parent(struct clk *clk)
idx = 0;
}
- if (IS_ERR_OR_NULL(clk->parents[idx]))
- clk->parents[idx] = clk_lookup(clk->parent_names[idx]);
-
- return clk->parents[idx];
+ return clk_get_parent_by_index(clk, idx);
}
struct clk_hw *clk_hw_get_parent(struct clk_hw *hw)
@@ -378,7 +431,7 @@ int clk_get_phase(struct clk *clk)
return ret;
}
-int bclk_register(struct clk *clk)
+static int __bclk_register(struct clk *clk)
{
struct clk_hw *hw = clk_to_clk_hw(clk);
struct clk *c;
@@ -392,8 +445,6 @@ int bclk_register(struct clk *clk)
}
}
- clk->parents = xzalloc(sizeof(struct clk *) * clk->num_parents);
-
list_add_tail(&clk->list, &clks);
if (clk->ops->init) {
@@ -408,16 +459,28 @@ int bclk_register(struct clk *clk)
return 0;
out:
list_del(&clk->list);
- free(clk->parents);
return ret;
}
-struct clk *clk_register(struct device_d *dev, struct clk_hw *hw)
+int bclk_register(struct clk *clk)
+{
+ int ret;
+
+ clk->parents = xzalloc(sizeof(struct clk *) * clk->num_parents);
+
+ ret = __bclk_register(clk);
+ if (ret)
+ free(clk->parents);
+
+ return ret;
+}
+
+struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
struct clk *clk;
const struct clk_init_data *init = hw->init;
- char **parent_names;
+ char **parent_names = NULL;
int i, ret;
if (!hw->init)
@@ -430,20 +493,32 @@ struct clk *clk_register(struct device_d *dev, struct clk_hw *hw)
clk->name = xstrdup(init->name);
clk->ops = init->ops;
clk->num_parents = init->num_parents;
- parent_names = xzalloc(init->num_parents * sizeof(char *));
- for (i = 0; i < init->num_parents; i++)
- parent_names[i] = xstrdup(init->parent_names[i]);
+ clk->parents = xzalloc(sizeof(struct clk *) * clk->num_parents);
+
+ if (init->parent_names) {
+ parent_names = xzalloc(init->num_parents * sizeof(char *));
- clk->parent_names = (const char *const*)parent_names;
+ for (i = 0; i < init->num_parents; i++)
+ parent_names[i] = xstrdup(init->parent_names[i]);
+
+ clk->parent_names = (const char *const*)parent_names;
+
+ } else {
+ for (i = 0; i < init->num_parents; i++)
+ clk->parents[i] = clk_hw_to_clk(init->parent_hws[i]);
+ }
clk->flags = init->flags;
- ret = bclk_register(clk);
+ ret = __bclk_register(clk);
if (ret) {
- for (i = 0; i < init->num_parents; i++)
- free(parent_names[i]);
- free(parent_names);
+ if (parent_names) {
+ for (i = 0; i < init->num_parents; i++)
+ free(parent_names[i]);
+ free(parent_names);
+ }
+ free(clk->parents);
return ERR_PTR(ret);
}
@@ -561,12 +636,12 @@ struct of_clk_provider {
struct device_node *node;
struct clk *(*get)(struct of_phandle_args *clkspec, void *data);
+ struct clk_hw *(*get_hw)(struct of_phandle_args *clkspec, void *data);
void *data;
};
extern struct of_device_id __clk_of_table_start[];
-const struct of_device_id __clk_of_table_sentinel
- __attribute__ ((unused,section (".__clk_of_table_end")));
+const struct of_device_id __clk_of_table_sentinel __ll_elem(.__clk_of_table_end);
static LIST_HEAD(of_clk_providers);
@@ -577,6 +652,12 @@ struct clk *of_clk_src_simple_get(struct of_phandle_args *clkspec,
}
EXPORT_SYMBOL_GPL(of_clk_src_simple_get);
+struct clk_hw *of_clk_hw_simple_get(struct of_phandle_args *clkspec, void *data)
+{
+ return data;
+}
+EXPORT_SYMBOL_GPL(of_clk_hw_simple_get);
+
struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data)
{
struct clk_onecell_data *clk_data = data;
@@ -591,15 +672,27 @@ struct clk *of_clk_src_onecell_get(struct of_phandle_args *clkspec, void *data)
}
EXPORT_SYMBOL_GPL(of_clk_src_onecell_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_hw *
+of_clk_hw_onecell_get(struct of_phandle_args *clkspec, void *data)
+{
+ struct clk_hw_onecell_data *hw_data = data;
+ unsigned int idx = clkspec->args[0];
+
+ if (idx >= hw_data->num) {
+ pr_err("%s: invalid index %u\n", __func__, idx);
+ return ERR_PTR(-EINVAL);
+ }
+
+ return hw_data->hws[idx];
+}
+EXPORT_SYMBOL_GPL(of_clk_hw_onecell_get);
+
+
+static int __of_clk_add_provider(struct device_node *np,
struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,
void *data),
+ struct clk_hw *(*clk_hw_src_get)(struct of_phandle_args *clkspec,
+ void *data),
void *data)
{
struct of_clk_provider *cp;
@@ -611,16 +704,40 @@ int of_clk_add_provider(struct device_node *np,
cp->node = np;
cp->data = data;
cp->get = clk_src_get;
+ cp->get_hw = clk_hw_src_get;
list_add(&cp->link, &of_clk_providers);
- pr_debug("Added clock from %s\n", np ? np->full_name : "<none>");
+ pr_debug("Added clock from %pOF\n", np);
of_clk_set_defaults(np, true);
return 0;
}
+
+/**
+ * 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)
+{
+ return __of_clk_add_provider(np, clk_src_get, NULL, data);
+}
EXPORT_SYMBOL_GPL(of_clk_add_provider);
+int of_clk_add_hw_provider(struct device_node *np,
+ struct clk_hw *(*clk_hw_src_get)(struct of_phandle_args *clkspec,
+ void *data),
+ void *data)
+{
+ return __of_clk_add_provider(np, NULL, clk_hw_src_get, data);
+}
+EXPORT_SYMBOL_GPL(of_clk_add_hw_provider);
+
/**
* of_clk_del_provider() - Remove a previously registered clock provider
* @np: Device node pointer associated with clock provider
@@ -643,16 +760,18 @@ struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
{
struct of_clk_provider *provider;
struct clk *clk = ERR_PTR(-EPROBE_DEFER);
- int ret;
- ret = of_device_ensure_probed(clkspec->np);
- if (ret)
- return ERR_PTR(ret);
+ /* Ignore error, as CLK_OF_DECLARE clocks have no proper driver. */
+ of_device_ensure_probed(clkspec->np);
/* Check if we have such a provider in our array */
list_for_each_entry(provider, &of_clk_providers, link) {
- if (provider->node == clkspec->np)
- clk = provider->get(clkspec, provider->data);
+ if (provider->node == clkspec->np) {
+ if (provider->get)
+ clk = provider->get(clkspec, provider->data);
+ else
+ clk = clk_hw_to_clk(provider->get_hw(clkspec, provider->data));
+ }
if (!IS_ERR(clk))
break;
}
@@ -799,18 +918,30 @@ static int parent_ready(struct device_node *np)
}
}
+static LIST_HEAD(probed_clks);
+
+static bool of_clk_probed(struct device_node *np)
+{
+ struct clock_provider *clk_provider;
+
+ list_for_each_entry(clk_provider, &probed_clks, node)
+ if (clk_provider->np == np)
+ return true;
+ return false;
+}
+
/**
* of_clk_init() - Scan and init clock providers from the DT
- * @root: parent of the first level to probe or NULL for the root of the tree
- * @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
*
* Returns 0 on success, < 0 on failure.
*/
-int of_clk_init(struct device_node *root, const struct of_device_id *matches)
+int of_clk_init(void)
{
+ struct device_node *root = of_get_root_node();
+ const struct of_device_id *matches = __clk_of_table_start;
struct clock_provider *clk_provider, *next;
bool is_init_done;
bool force = false;
@@ -818,11 +949,7 @@ int of_clk_init(struct device_node *root, const struct of_device_id *matches)
const struct of_device_id *match;
if (!root)
- root = of_find_node_by_path("/");
- if (!root)
return -EINVAL;
- if (!matches)
- matches = __clk_of_table_start;
/* First prepare the list of the clocks providers */
for_each_matching_node_and_match(root, matches, &match) {
@@ -831,6 +958,11 @@ int of_clk_init(struct device_node *root, const struct of_device_id *matches)
if (!of_device_is_available(root))
continue;
+ if (of_clk_probed(root)) {
+ pr_debug("%s: already probed: %pOF\n", __func__, root);
+ continue;
+ }
+
parent = xzalloc(sizeof(*parent));
parent->clk_init_cb = match->data;
@@ -850,8 +982,7 @@ int of_clk_init(struct device_node *root, const struct of_device_id *matches)
clk_provider->clk_init_cb(np);
of_clk_set_defaults(np, true);
- list_del(&clk_provider->node);
- free(clk_provider);
+ list_move_tail(&clk_provider->node, &probed_clks);
is_init_done = true;
}
}
@@ -887,6 +1018,15 @@ static const char *clk_hw_stat(struct clk *clk)
return "unknown";
}
+static const char *clk_parent_name_by_index(struct clk *clk, u8 idx)
+{
+ if (clk->parent_names)
+ return clk->parent_names[idx];
+ if (clk->parents[idx])
+ return clk->parents[idx]->name;
+ return "unknown";
+}
+
static void dump_one(struct clk *clk, int verbose, int indent)
{
int enabled = clk_is_enabled(clk);
@@ -911,7 +1051,7 @@ static void dump_one(struct clk *clk, int verbose, int indent)
int i;
printf("%*s`---- possible parents: ", indent * 4, "");
for (i = 0; i < clk->num_parents; i++)
- printf("%s ", clk->parent_names[i]);
+ printf("%s ", clk_parent_name_by_index(clk, i));
printf("\n");
}
}