summaryrefslogtreecommitdiff
path: root/drivers/clk/qcom/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/qcom/common.c')
-rw-r--r--drivers/clk/qcom/common.c146
1 files changed, 132 insertions, 14 deletions
diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c
index 8fa477293ae0..423e975dffee 100644
--- a/drivers/clk/qcom/common.c
+++ b/drivers/clk/qcom/common.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014, 2016, The Linux Foundation. All rights reserved.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
@@ -17,6 +17,7 @@
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/reset-controller.h>
+#include <linux/of.h>
#include "common.h"
#include "clk-rcg.h"
@@ -88,20 +89,108 @@ static void qcom_cc_gdsc_unregister(void *data)
gdsc_unregister(data);
}
+/*
+ * Backwards compatibility with old DTs. Register a pass-through factor 1/1
+ * clock to translate 'path' clk into 'name' clk and regsiter the 'path'
+ * clk as a fixed rate clock if it isn't present.
+ */
+static int _qcom_cc_register_board_clk(struct device *dev, const char *path,
+ const char *name, unsigned long rate,
+ bool add_factor)
+{
+ struct device_node *node = NULL;
+ struct device_node *clocks_node;
+ struct clk_fixed_factor *factor;
+ struct clk_fixed_rate *fixed;
+ struct clk *clk;
+ struct clk_init_data init_data = { };
+
+ clocks_node = of_find_node_by_path("/clocks");
+ if (clocks_node)
+ node = of_find_node_by_name(clocks_node, path);
+ of_node_put(clocks_node);
+
+ if (!node) {
+ fixed = devm_kzalloc(dev, sizeof(*fixed), GFP_KERNEL);
+ if (!fixed)
+ return -EINVAL;
+
+ fixed->fixed_rate = rate;
+ fixed->hw.init = &init_data;
+
+ init_data.name = path;
+ init_data.ops = &clk_fixed_rate_ops;
+
+ clk = devm_clk_register(dev, &fixed->hw);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ }
+ of_node_put(node);
+
+ if (add_factor) {
+ factor = devm_kzalloc(dev, sizeof(*factor), GFP_KERNEL);
+ if (!factor)
+ return -EINVAL;
+
+ factor->mult = factor->div = 1;
+ factor->hw.init = &init_data;
+
+ init_data.name = name;
+ init_data.parent_names = &path;
+ init_data.num_parents = 1;
+ init_data.flags = 0;
+ init_data.ops = &clk_fixed_factor_ops;
+
+ clk = devm_clk_register(dev, &factor->hw);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ }
+
+ return 0;
+}
+
+int qcom_cc_register_board_clk(struct device *dev, const char *path,
+ const char *name, unsigned long rate)
+{
+ bool add_factor = true;
+ struct device_node *node;
+
+ /* The RPM clock driver will add the factor clock if present */
+ if (IS_ENABLED(CONFIG_QCOM_RPMCC)) {
+ node = of_find_compatible_node(NULL, NULL, "qcom,rpmcc");
+ if (of_device_is_available(node))
+ add_factor = false;
+ of_node_put(node);
+ }
+
+ return _qcom_cc_register_board_clk(dev, path, name, rate, add_factor);
+}
+EXPORT_SYMBOL_GPL(qcom_cc_register_board_clk);
+
+int qcom_cc_register_sleep_clk(struct device *dev)
+{
+ return _qcom_cc_register_board_clk(dev, "sleep_clk", "sleep_clk_src",
+ 32768, true);
+}
+EXPORT_SYMBOL_GPL(qcom_cc_register_sleep_clk);
+
int qcom_cc_really_probe(struct platform_device *pdev,
const struct qcom_cc_desc *desc, struct regmap *regmap)
{
- int i, ret;
+ int i = 0, ret, j = 0;
struct device *dev = &pdev->dev;
struct clk *clk;
struct clk_onecell_data *data;
struct clk **clks;
struct qcom_reset_controller *reset;
struct qcom_cc *cc;
+ struct gdsc_desc *scd;
size_t num_clks = desc->num_clks;
struct clk_regmap **rclks = desc->clks;
+ struct clk_hw **hw_clks = desc->hwclks;
- cc = devm_kzalloc(dev, sizeof(*cc) + sizeof(*clks) * num_clks,
+ cc = devm_kzalloc(dev, sizeof(*cc) + sizeof(*clks) *
+ (num_clks + desc->num_hwclks),
GFP_KERNEL);
if (!cc)
return -ENOMEM;
@@ -109,24 +198,43 @@ int qcom_cc_really_probe(struct platform_device *pdev,
clks = cc->clks;
data = &cc->data;
data->clks = clks;
- data->clk_num = num_clks;
+ data->clk_num = num_clks + desc->num_hwclks;
- for (i = 0; i < num_clks; i++) {
- if (!rclks[i]) {
+ for (i = 0; i < desc->num_hwclks; i++) {
+ if (!hw_clks[i]) {
clks[i] = ERR_PTR(-ENOENT);
continue;
}
- clk = devm_clk_register_regmap(dev, rclks[i]);
+ clk = devm_clk_register(dev, hw_clks[i]);
if (IS_ERR(clk))
return PTR_ERR(clk);
clks[i] = clk;
+ pr_debug("Index for hw_clocks %d added %s\n", i,
+ __clk_get_name(clk));
+ }
+
+ for (j = i; j < num_clks; j++) {
+ if (!rclks[j]) {
+ clks[j] = ERR_PTR(-ENOENT);
+ continue;
+ }
+ clk = devm_clk_register_regmap(dev, rclks[j]);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ clks[j] = clk;
+ pr_debug("Index for Regmap clocks %d added %s\n", j,
+ __clk_get_name(clk));
}
ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, data);
if (ret)
return ret;
- devm_add_action(dev, qcom_cc_del_clk_provider, pdev->dev.of_node);
+ ret = devm_add_action_or_reset(dev, qcom_cc_del_clk_provider,
+ pdev->dev.of_node);
+
+ if (ret)
+ return ret;
reset = &cc->reset;
reset->rcdev.of_node = dev->of_node;
@@ -140,18 +248,28 @@ int qcom_cc_really_probe(struct platform_device *pdev,
if (ret)
return ret;
- devm_add_action(dev, qcom_cc_reset_unregister, &reset->rcdev);
+ ret = devm_add_action_or_reset(dev, qcom_cc_reset_unregister,
+ &reset->rcdev);
+
+ if (ret)
+ return ret;
if (desc->gdscs && desc->num_gdscs) {
- ret = gdsc_register(dev, desc->gdscs, desc->num_gdscs,
- &reset->rcdev, regmap);
+ scd = devm_kzalloc(dev, sizeof(*scd), GFP_KERNEL);
+ if (!scd)
+ return -ENOMEM;
+ scd->dev = dev;
+ scd->scs = desc->gdscs;
+ scd->num = desc->num_gdscs;
+ ret = gdsc_register(scd, &reset->rcdev, regmap);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, qcom_cc_gdsc_unregister,
+ scd);
if (ret)
return ret;
}
- devm_add_action(dev, qcom_cc_gdsc_unregister, dev);
-
-
return 0;
}
EXPORT_SYMBOL_GPL(qcom_cc_really_probe);