diff options
Diffstat (limited to 'drivers/clk/qcom/common.c')
-rw-r--r-- | drivers/clk/qcom/common.c | 149 |
1 files changed, 135 insertions, 14 deletions
diff --git a/drivers/clk/qcom/common.c b/drivers/clk/qcom/common.c index 8fa477293ae0..8ecb207a97e5 100644 --- a/drivers/clk/qcom/common.c +++ b/drivers/clk/qcom/common.c @@ -1,5 +1,7 @@ /* - * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2013-2014, 2016-2017, + * + * 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 @@ -11,12 +13,14 @@ * GNU General Public License for more details. */ +#include <linux/clk.h> #include <linux/export.h> #include <linux/module.h> #include <linux/regmap.h> #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 +92,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 +201,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 +251,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); |