ipq806x: update clk-qcom patches
Preparing for cpufreq driver switch to generic cpufreq-dt Signed-off-by: Pavel Kubelun <be.dissent@gmail.com>
This commit is contained in:
parent
bf7166e545
commit
8749fb7894
5 changed files with 563 additions and 6 deletions
|
@ -0,0 +1,135 @@
|
||||||
|
From ee15faffef11309219aa87a24efc86f6dd13f7cb Mon Sep 17 00:00:00 2001
|
||||||
|
From: Stephen Boyd <sboyd@codeaurora.org>
|
||||||
|
Date: Mon, 26 Oct 2015 17:11:32 -0700
|
||||||
|
Subject: clk: qcom: common: Add API to register board clocks backwards
|
||||||
|
compatibly
|
||||||
|
|
||||||
|
We want to put the XO board clocks into the dt files, but we also
|
||||||
|
need to be backwards compatible with an older dtb. Add an API to
|
||||||
|
the common code to do this. This also makes a place for us to
|
||||||
|
handle the case when the RPM clock driver is enabled and we don't
|
||||||
|
want to register the fixed factor clock.
|
||||||
|
|
||||||
|
Cc: Georgi Djakov <georgi.djakov@linaro.org>
|
||||||
|
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||||
|
---
|
||||||
|
drivers/clk/qcom/common.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
drivers/clk/qcom/common.h | 4 +++
|
||||||
|
2 files changed, 91 insertions(+)
|
||||||
|
|
||||||
|
--- a/drivers/clk/qcom/common.c
|
||||||
|
+++ b/drivers/clk/qcom/common.c
|
||||||
|
@@ -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,6 +89,92 @@ static void qcom_cc_gdsc_unregister(void
|
||||||
|
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.flags = CLK_IS_ROOT;
|
||||||
|
+ 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)
|
||||||
|
{
|
||||||
|
--- a/drivers/clk/qcom/common.h
|
||||||
|
+++ b/drivers/clk/qcom/common.h
|
||||||
|
@@ -37,6 +37,10 @@ extern const struct freq_tbl *qcom_find_
|
||||||
|
extern int qcom_find_src_index(struct clk_hw *hw, const struct parent_map *map,
|
||||||
|
u8 src);
|
||||||
|
|
||||||
|
+extern int qcom_cc_register_board_clk(struct device *dev, const char *path,
|
||||||
|
+ const char *name, unsigned long rate);
|
||||||
|
+extern int qcom_cc_register_sleep_clk(struct device *dev);
|
||||||
|
+
|
||||||
|
extern struct regmap *qcom_cc_map(struct platform_device *pdev,
|
||||||
|
const struct qcom_cc_desc *desc);
|
||||||
|
extern int qcom_cc_really_probe(struct platform_device *pdev,
|
|
@ -0,0 +1,35 @@
|
||||||
|
From add479eeb1a208a31ab913ae7c97506a81383079 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Philipp Zabel <p.zabel@pengutronix.de>
|
||||||
|
Date: Thu, 25 Feb 2016 10:45:12 +0100
|
||||||
|
Subject: clk: qcom: Make reset_control_ops const
|
||||||
|
|
||||||
|
The qcom_reset_ops structure is never modified. Make it const.
|
||||||
|
|
||||||
|
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
|
||||||
|
Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
|
||||||
|
---
|
||||||
|
drivers/clk/qcom/reset.c | 2 +-
|
||||||
|
drivers/clk/qcom/reset.h | 2 +-
|
||||||
|
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
--- a/drivers/clk/qcom/reset.c
|
||||||
|
+++ b/drivers/clk/qcom/reset.c
|
||||||
|
@@ -55,7 +55,7 @@ qcom_reset_deassert(struct reset_control
|
||||||
|
return regmap_update_bits(rst->regmap, map->reg, mask, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
-struct reset_control_ops qcom_reset_ops = {
|
||||||
|
+const struct reset_control_ops qcom_reset_ops = {
|
||||||
|
.reset = qcom_reset,
|
||||||
|
.assert = qcom_reset_assert,
|
||||||
|
.deassert = qcom_reset_deassert,
|
||||||
|
--- a/drivers/clk/qcom/reset.h
|
||||||
|
+++ b/drivers/clk/qcom/reset.h
|
||||||
|
@@ -32,6 +32,6 @@ struct qcom_reset_controller {
|
||||||
|
#define to_qcom_reset_controller(r) \
|
||||||
|
container_of(r, struct qcom_reset_controller, rcdev);
|
||||||
|
|
||||||
|
-extern struct reset_control_ops qcom_reset_ops;
|
||||||
|
+extern const struct reset_control_ops qcom_reset_ops;
|
||||||
|
|
||||||
|
#endif
|
|
@ -86,7 +86,7 @@ I'd really like to get rid of __clk_hfpll_init_once() if possible...
|
||||||
+ u32 regval = hd->user_val;
|
+ u32 regval = hd->user_val;
|
||||||
+ unsigned long rate;
|
+ unsigned long rate;
|
||||||
+
|
+
|
||||||
+ rate = clk_hw_get_rate(hw->clk);
|
+ rate = clk_hw_get_rate(hw);
|
||||||
+
|
+
|
||||||
+ /* Pick the right VCO. */
|
+ /* Pick the right VCO. */
|
||||||
+ if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
|
+ if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
|
||||||
|
|
|
@ -50,7 +50,7 @@ drivers/clk/qcom/Kconfig | 4 ++
|
||||||
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
|
clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/drivers/clk/qcom/clk-krait.c
|
+++ b/drivers/clk/qcom/clk-krait.c
|
||||||
@@ -0,0 +1,166 @@
|
@@ -0,0 +1,167 @@
|
||||||
+/*
|
+/*
|
||||||
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
|
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
|
||||||
+ *
|
+ *
|
||||||
|
@ -128,18 +128,19 @@ drivers/clk/qcom/Kconfig | 4 ++
|
||||||
+ return clk_mux_get_parent(hw, sel, mux->parent_map, 0);
|
+ return clk_mux_get_parent(hw, sel, mux->parent_map, 0);
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
+static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw)
|
+static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw,
|
||||||
|
+ unsigned long *safe_freq)
|
||||||
+{
|
+{
|
||||||
+ int i;
|
+ int i;
|
||||||
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
+ struct krait_mux_clk *mux = to_krait_mux_clk(hw);
|
||||||
+ int num_parents = clk_hw_get_num_parents(hw->clk);
|
+ int num_parents = clk_hw_get_num_parents(hw);
|
||||||
+
|
+
|
||||||
+ i = mux->safe_sel;
|
+ i = mux->safe_sel;
|
||||||
+ for (i = 0; i < num_parents; i++)
|
+ for (i = 0; i < num_parents; i++)
|
||||||
+ if (mux->safe_sel == mux->parent_map[i])
|
+ if (mux->safe_sel == mux->parent_map[i])
|
||||||
+ break;
|
+ break;
|
||||||
+
|
+
|
||||||
+ return __clk_get_hw(clk_hw_get_parent_by_index(hw->clk, i));
|
+ return clk_hw_get_parent_by_index(hw, i);
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
+static int krait_mux_enable(struct clk_hw *hw)
|
+static int krait_mux_enable(struct clk_hw *hw)
|
||||||
|
@ -172,7 +173,7 @@ drivers/clk/qcom/Kconfig | 4 ++
|
||||||
+static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate,
|
+static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
+ unsigned long *parent_rate)
|
+ unsigned long *parent_rate)
|
||||||
+{
|
+{
|
||||||
+ *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw->clk), rate * 2);
|
+ *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), rate * 2);
|
||||||
+ return DIV_ROUND_UP(*parent_rate, 2);
|
+ return DIV_ROUND_UP(*parent_rate, 2);
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
|
|
386
target/linux/ipq806x/patches-4.4/175-add-regmap-mux-div.patch
Normal file
386
target/linux/ipq806x/patches-4.4/175-add-regmap-mux-div.patch
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
From 7727b1cae43e9abac746ef016c3dbf50ee81a6d6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Georgi Djakov <georgi.djakov@linaro.org>
|
||||||
|
Date: Wed, 18 Mar 2015 17:23:29 +0200
|
||||||
|
Subject: clk: qcom: Add support for regmap mux-div clocks
|
||||||
|
|
||||||
|
Add support for hardware that support switching both parent clocks and the
|
||||||
|
divider at the same time. This avoids generating intermediate frequencies
|
||||||
|
from either the old parent clock and new divider or new parent clock and
|
||||||
|
old divider combinations.
|
||||||
|
|
||||||
|
Signed-off-by: Georgi Djakov <georgi.djakov@linaro.org>
|
||||||
|
---
|
||||||
|
drivers/clk/qcom/Makefile | 1 +
|
||||||
|
drivers/clk/qcom/clk-regmap-mux-div.c | 288 ++++++++++++++++++++++++++++++++++
|
||||||
|
drivers/clk/qcom/clk-regmap-mux-div.h | 63 ++++++++
|
||||||
|
3 files changed, 352 insertions(+)
|
||||||
|
create mode 100644 drivers/clk/qcom/clk-regmap-mux-div.c
|
||||||
|
create mode 100644 drivers/clk/qcom/clk-regmap-mux-div.h
|
||||||
|
|
||||||
|
--- a/drivers/clk/qcom/Makefile
|
||||||
|
+++ b/drivers/clk/qcom/Makefile
|
||||||
|
@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o
|
||||||
|
clk-qcom-y += clk-branch.o
|
||||||
|
clk-qcom-y += clk-regmap-divider.o
|
||||||
|
clk-qcom-y += clk-regmap-mux.o
|
||||||
|
+clk-qcom-y += clk-regmap-mux-div.o
|
||||||
|
clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o
|
||||||
|
obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o
|
||||||
|
clk-qcom-y += clk-hfpll.o
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/clk/qcom/clk-regmap-mux-div.c
|
||||||
|
@@ -0,0 +1,288 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (c) 2015, Linaro Limited
|
||||||
|
+ * Copyright (c) 2014, 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
|
||||||
|
+ * may be copied, distributed, and modified under those terms.
|
||||||
|
+ *
|
||||||
|
+ * This program is distributed in the hope that it will be useful,
|
||||||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
+ * GNU General Public License for more details.
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+#include <linux/bitops.h>
|
||||||
|
+#include <linux/clk.h>
|
||||||
|
+#include <linux/delay.h>
|
||||||
|
+#include <linux/export.h>
|
||||||
|
+#include <linux/kernel.h>
|
||||||
|
+#include <linux/regmap.h>
|
||||||
|
+
|
||||||
|
+#include "clk-regmap-mux-div.h"
|
||||||
|
+
|
||||||
|
+#define CMD_RCGR 0x0
|
||||||
|
+#define CMD_RCGR_UPDATE BIT(0)
|
||||||
|
+#define CMD_RCGR_DIRTY_CFG BIT(4)
|
||||||
|
+#define CMD_RCGR_ROOT_OFF BIT(31)
|
||||||
|
+#define CFG_RCGR 0x4
|
||||||
|
+
|
||||||
|
+static int __mux_div_update_config(struct clk_regmap_mux_div *md)
|
||||||
|
+{
|
||||||
|
+ int ret;
|
||||||
|
+ u32 val, count;
|
||||||
|
+ const char *name = clk_hw_get_name(&md->clkr.hw);
|
||||||
|
+
|
||||||
|
+ ret = regmap_update_bits(md->clkr.regmap, CMD_RCGR + md->reg_offset,
|
||||||
|
+ CMD_RCGR_UPDATE, CMD_RCGR_UPDATE);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ /* Wait for update to take effect */
|
||||||
|
+ for (count = 500; count > 0; count--) {
|
||||||
|
+ ret = regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset,
|
||||||
|
+ &val);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+ if (!(val & CMD_RCGR_UPDATE))
|
||||||
|
+ return 0;
|
||||||
|
+ udelay(1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pr_err("%s: RCG did not update its configuration", name);
|
||||||
|
+ return -EBUSY;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int __mux_div_set_src_div(struct clk_regmap_mux_div *md, u32 src_sel,
|
||||||
|
+ u32 src_div)
|
||||||
|
+{
|
||||||
|
+ int ret;
|
||||||
|
+ u32 val, mask;
|
||||||
|
+
|
||||||
|
+ val = (src_div << md->hid_shift) | (src_sel << md->src_shift);
|
||||||
|
+ mask = ((BIT(md->hid_width) - 1) << md->hid_shift) |
|
||||||
|
+ ((BIT(md->src_width) - 1) << md->src_shift);
|
||||||
|
+
|
||||||
|
+ ret = regmap_update_bits(md->clkr.regmap, CFG_RCGR + md->reg_offset,
|
||||||
|
+ mask, val);
|
||||||
|
+ if (ret)
|
||||||
|
+ return ret;
|
||||||
|
+
|
||||||
|
+ return __mux_div_update_config(md);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void __mux_div_get_src_div(struct clk_regmap_mux_div *md, u32 *src_sel,
|
||||||
|
+ u32 *src_div)
|
||||||
|
+{
|
||||||
|
+ u32 val, div, src;
|
||||||
|
+ const char *name = clk_hw_get_name(&md->clkr.hw);
|
||||||
|
+
|
||||||
|
+ regmap_read(md->clkr.regmap, CMD_RCGR + md->reg_offset, &val);
|
||||||
|
+
|
||||||
|
+ if (val & CMD_RCGR_DIRTY_CFG) {
|
||||||
|
+ pr_err("%s: RCG configuration is pending\n", name);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ regmap_read(md->clkr.regmap, CFG_RCGR + md->reg_offset, &val);
|
||||||
|
+ src = (val >> md->src_shift);
|
||||||
|
+ src &= BIT(md->src_width) - 1;
|
||||||
|
+ *src_sel = src;
|
||||||
|
+
|
||||||
|
+ div = (val >> md->hid_shift);
|
||||||
|
+ div &= BIT(md->hid_width) - 1;
|
||||||
|
+ *src_div = div;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int mux_div_enable(struct clk_hw *hw)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+
|
||||||
|
+ return __mux_div_set_src_div(md, md->src_sel, md->div);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static inline bool is_better_rate(unsigned long req, unsigned long best,
|
||||||
|
+ unsigned long new)
|
||||||
|
+{
|
||||||
|
+ return (req <= new && new < best) || (best < req && best < new);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int mux_div_determine_rate(struct clk_hw *hw,
|
||||||
|
+ struct clk_rate_request *req)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+ unsigned int i, div, max_div;
|
||||||
|
+ unsigned long actual_rate, best_rate = 0;
|
||||||
|
+ unsigned long req_rate = req->rate;
|
||||||
|
+
|
||||||
|
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
|
||||||
|
+ struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
|
||||||
|
+ unsigned long parent_rate = clk_hw_get_rate(parent);
|
||||||
|
+
|
||||||
|
+ max_div = BIT(md->hid_width) - 1;
|
||||||
|
+ for (div = 1; div < max_div; div++) {
|
||||||
|
+ parent_rate = mult_frac(req_rate, div, 2);
|
||||||
|
+ parent_rate = clk_hw_round_rate(parent, parent_rate);
|
||||||
|
+ actual_rate = mult_frac(parent_rate, 2, div);
|
||||||
|
+
|
||||||
|
+ if (is_better_rate(req_rate, best_rate, actual_rate)) {
|
||||||
|
+ best_rate = actual_rate;
|
||||||
|
+ req->rate = best_rate;
|
||||||
|
+ req->best_parent_rate = parent_rate;
|
||||||
|
+ req->best_parent_hw = parent;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (actual_rate < req_rate || best_rate <= req_rate)
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (!best_rate)
|
||||||
|
+ return -EINVAL;
|
||||||
|
+
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int __mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
|
||||||
|
+ unsigned long prate, u32 src_sel)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+ int ret, i;
|
||||||
|
+ u32 div, max_div, best_src = 0, best_div = 0;
|
||||||
|
+ unsigned long actual_rate, best_rate = 0;
|
||||||
|
+
|
||||||
|
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
|
||||||
|
+ struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
|
||||||
|
+ unsigned long parent_rate = clk_hw_get_rate(parent);
|
||||||
|
+
|
||||||
|
+ max_div = BIT(md->hid_width) - 1;
|
||||||
|
+ for (div = 1; div < max_div; div++) {
|
||||||
|
+ parent_rate = mult_frac(rate, div, 2);
|
||||||
|
+ parent_rate = clk_hw_round_rate(parent, parent_rate);
|
||||||
|
+ actual_rate = mult_frac(parent_rate, 2, div);
|
||||||
|
+
|
||||||
|
+ if (is_better_rate(rate, best_rate, actual_rate)) {
|
||||||
|
+ best_rate = actual_rate;
|
||||||
|
+ best_src = md->parent_map[i].cfg;
|
||||||
|
+ best_div = div - 1;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (actual_rate < rate || best_rate <= rate)
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ ret = __mux_div_set_src_div(md, best_src, best_div);
|
||||||
|
+ if (!ret) {
|
||||||
|
+ md->div = best_div;
|
||||||
|
+ md->src_sel = best_src;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return ret;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static u8 mux_div_get_parent(struct clk_hw *hw)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+ const char *name = clk_hw_get_name(hw);
|
||||||
|
+ u32 i, div, src;
|
||||||
|
+
|
||||||
|
+ __mux_div_get_src_div(md, &src, &div);
|
||||||
|
+
|
||||||
|
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++)
|
||||||
|
+ if (src == md->parent_map[i].cfg)
|
||||||
|
+ return i;
|
||||||
|
+
|
||||||
|
+ pr_err("%s: Can't find parent %d\n", name, src);
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int mux_div_set_parent(struct clk_hw *hw, u8 index)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+
|
||||||
|
+ return __mux_div_set_src_div(md, md->parent_map[index].cfg, md->div);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int mux_div_set_rate(struct clk_hw *hw,
|
||||||
|
+ unsigned long rate, unsigned long prate)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+
|
||||||
|
+ return __mux_div_set_rate_and_parent(hw, rate, prate, md->src_sel);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static int mux_div_set_rate_and_parent(struct clk_hw *hw, unsigned long rate,
|
||||||
|
+ unsigned long prate, u8 index)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+
|
||||||
|
+ return __mux_div_set_rate_and_parent(hw, rate, prate,
|
||||||
|
+ md->parent_map[index].cfg);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static unsigned long mux_div_recalc_rate(struct clk_hw *hw, unsigned long prate)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+ u32 div, src;
|
||||||
|
+ int i, num_parents = clk_hw_get_num_parents(hw);
|
||||||
|
+ const char *name = clk_hw_get_name(hw);
|
||||||
|
+
|
||||||
|
+ __mux_div_get_src_div(md, &src, &div);
|
||||||
|
+ for (i = 0; i < num_parents; i++)
|
||||||
|
+ if (src == md->parent_map[i].cfg) {
|
||||||
|
+ struct clk_hw *p = clk_hw_get_parent_by_index(hw, i);
|
||||||
|
+ unsigned long parent_rate = clk_hw_get_rate(p);
|
||||||
|
+
|
||||||
|
+ return mult_frac(parent_rate, 2, div + 1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ pr_err("%s: Can't find parent %d\n", name, src);
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static struct clk_hw *mux_div_get_safe_parent(struct clk_hw *hw,
|
||||||
|
+ unsigned long *safe_freq)
|
||||||
|
+{
|
||||||
|
+ int i;
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+
|
||||||
|
+ if (md->safe_freq)
|
||||||
|
+ *safe_freq = md->safe_freq;
|
||||||
|
+
|
||||||
|
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++)
|
||||||
|
+ if (md->safe_src == md->parent_map[i].cfg)
|
||||||
|
+ break;
|
||||||
|
+
|
||||||
|
+ return clk_hw_get_parent_by_index(hw, i);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+static void mux_div_disable(struct clk_hw *hw)
|
||||||
|
+{
|
||||||
|
+ struct clk_regmap_mux_div *md = to_clk_regmap_mux_div(hw);
|
||||||
|
+ struct clk_hw *parent;
|
||||||
|
+ u32 div;
|
||||||
|
+
|
||||||
|
+ if (!md->safe_freq || !md->safe_src)
|
||||||
|
+ return;
|
||||||
|
+
|
||||||
|
+ parent = mux_div_get_safe_parent(hw, &md->safe_freq);
|
||||||
|
+ div = divider_get_val(md->safe_freq, clk_get_rate(parent->clk), NULL,
|
||||||
|
+ md->hid_width, CLK_DIVIDER_ROUND_CLOSEST);
|
||||||
|
+ div = 2 * div + 1;
|
||||||
|
+
|
||||||
|
+ __mux_div_set_src_div(md, md->safe_src, div);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+const struct clk_ops clk_regmap_mux_div_ops = {
|
||||||
|
+ .enable = mux_div_enable,
|
||||||
|
+ .disable = mux_div_disable,
|
||||||
|
+ .get_parent = mux_div_get_parent,
|
||||||
|
+ .set_parent = mux_div_set_parent,
|
||||||
|
+ .set_rate = mux_div_set_rate,
|
||||||
|
+ .set_rate_and_parent = mux_div_set_rate_and_parent,
|
||||||
|
+ .determine_rate = mux_div_determine_rate,
|
||||||
|
+ .recalc_rate = mux_div_recalc_rate,
|
||||||
|
+ .get_safe_parent = mux_div_get_safe_parent,
|
||||||
|
+};
|
||||||
|
+EXPORT_SYMBOL_GPL(clk_regmap_mux_div_ops);
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/drivers/clk/qcom/clk-regmap-mux-div.h
|
||||||
|
@@ -0,0 +1,63 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (c) 2015, Linaro Limited
|
||||||
|
+ * Copyright (c) 2014, 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
|
||||||
|
+ * may be copied, distributed, and modified under those terms.
|
||||||
|
+ *
|
||||||
|
+ * This program is distributed in the hope that it will be useful,
|
||||||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
+ * GNU General Public License for more details.
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+#ifndef __QCOM_CLK_REGMAP_MUX_DIV_H__
|
||||||
|
+#define __QCOM_CLK_REGMAP_MUX_DIV_H__
|
||||||
|
+
|
||||||
|
+#include <linux/clk-provider.h>
|
||||||
|
+#include "clk-regmap.h"
|
||||||
|
+#include "clk-rcg.h"
|
||||||
|
+
|
||||||
|
+/**
|
||||||
|
+ * struct mux_div_clk - combined mux/divider clock
|
||||||
|
+ * @reg_offset: offset of the mux/divider register
|
||||||
|
+ * @hid_width: number of bits in half integer divider
|
||||||
|
+ * @hid_shift: lowest bit of hid value field
|
||||||
|
+ * @src_width: number of bits in source select
|
||||||
|
+ * @src_shift: lowest bit of source select field
|
||||||
|
+ * @div: the divider raw configuration value
|
||||||
|
+ * @src_sel: the mux index which will be used if the clock is enabled
|
||||||
|
+ * @safe_src: the safe source mux index for this clock
|
||||||
|
+ * @safe_freq: When switching rates from A to B, the mux div clock will
|
||||||
|
+ * instead switch from A -> safe_freq -> B. This allows the
|
||||||
|
+ * mux_div clock to change rates while enabled, even if this
|
||||||
|
+ * behavior is not supported by the parent clocks.
|
||||||
|
+ * If changing the rate of parent A also causes the rate of
|
||||||
|
+ * parent B to change, then safe_freq must be defined.
|
||||||
|
+ * safe_freq is expected to have a source clock which is always
|
||||||
|
+ * on and runs at only one rate.
|
||||||
|
+ * @parent_map: pointer to parent_map struct
|
||||||
|
+ * @clkr: handle between common and hardware-specific interfaces
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+struct clk_regmap_mux_div {
|
||||||
|
+ u32 reg_offset;
|
||||||
|
+ u32 hid_width;
|
||||||
|
+ u32 hid_shift;
|
||||||
|
+ u32 src_width;
|
||||||
|
+ u32 src_shift;
|
||||||
|
+ u32 div;
|
||||||
|
+ u32 src_sel;
|
||||||
|
+ u32 safe_src;
|
||||||
|
+ unsigned long safe_freq;
|
||||||
|
+ const struct parent_map *parent_map;
|
||||||
|
+ struct clk_regmap clkr;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+#define to_clk_regmap_mux_div(_hw) \
|
||||||
|
+ container_of(to_clk_regmap(_hw), struct clk_regmap_mux_div, clkr)
|
||||||
|
+
|
||||||
|
+extern const struct clk_ops clk_regmap_mux_div_ops;
|
||||||
|
+
|
||||||
|
+#endif
|
Loading…
Reference in a new issue