From dc2598a2fe7ff8e316dcd303119a88c532ae5881 Mon Sep 17 00:00:00 2001 From: David Dai Date: Wed, 28 Aug 2019 20:21:53 -0700 Subject: [PATCH] clk: qcom: regmap: Add regmap support for voltage voting Add support for prepare, unprepare, pre and post rate change regmap clk_ops callback functions for regmap clocks to handle regulator requirements. Change-Id: If4657adcc047e1ca005d3d4f7ab88146dba85ee0 Signed-off-by: David Dai --- drivers/clk/qcom/clk-regmap.c | 148 +++++++++++++++++++++++++++++++++- drivers/clk/qcom/clk-regmap.h | 13 ++- 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/drivers/clk/qcom/clk-regmap.c b/drivers/clk/qcom/clk-regmap.c index 92ac4e0d7dbe..c133ee8b327c 100644 --- a/drivers/clk/qcom/clk-regmap.c +++ b/drivers/clk/qcom/clk-regmap.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (c) 2014, 2019 The Linux Foundation. All rights reserved. */ #include @@ -84,6 +84,152 @@ void clk_disable_regmap(struct clk_hw *hw) } EXPORT_SYMBOL_GPL(clk_disable_regmap); +/** + * clk_pre_change_regmap() - standard pre_change call back for regmap clks + * + * @hw: clk to operate on + * @cur_rate: current rate of the clk + * @new_rate: new rate about to be set for the clk + * + * Finds the new vdd level corresponding to the new rate and swap out the + * existing vdd vote if the new_vdd_level is greater than the vdd_level and + * the clock is prepared. + * + * Returns 0 on success, -EERROR otherwise. + */ +int clk_pre_change_regmap(struct clk_hw *hw, unsigned long cur_rate, + unsigned long new_rate) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + int vdd_level = rclk->vdd_data.vdd_level; + int new_vdd_level; + int ret = 0; + + if (!rclk->vdd_data.vdd_class) + return 0; + + if (!clk_hw_is_prepared(hw)) + return 0; + + new_vdd_level = clk_find_vdd_level(hw, &rclk->vdd_data, new_rate); + if (new_vdd_level < 0) + return new_vdd_level; + + if (new_vdd_level <= vdd_level) + return 0; + + ret = clk_vote_vdd_level(&rclk->vdd_data, new_vdd_level); + if (ret) + return ret; + + clk_unvote_vdd_level(&rclk->vdd_data, vdd_level); + rclk->vdd_data.vdd_level = new_vdd_level; + + return 0; +} +EXPORT_SYMBOL(clk_pre_change_regmap); + +/** + * clk_post_change_regmap() - standard post_change call back for regmap clks + * + * @hw: clk to operate on + * @old_rate: previous rate of the clk + * @cur_rate: current rate of the recently changed clk + * + * Finds the vdd level corresponding to the cur rate and swap out the + * existing vdd vote if the cur_vdd_level is less than the vdd_level and the + * clock is prepared. + * + * Returns 0 on success, -EERROR otherwise. + */ +int clk_post_change_regmap(struct clk_hw *hw, unsigned long old_rate, + unsigned long cur_rate) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + int vdd_level = rclk->vdd_data.vdd_level; + int cur_vdd_level; + int ret = 0; + + if (!rclk->vdd_data.vdd_class) + return 0; + + if (!clk_hw_is_prepared(hw)) + return 0; + + cur_vdd_level = clk_find_vdd_level(hw, &rclk->vdd_data, cur_rate); + if (cur_vdd_level < 0) + return cur_vdd_level; + + if (cur_vdd_level >= vdd_level) + return 0; + + ret = clk_vote_vdd_level(&rclk->vdd_data, cur_vdd_level); + if (ret) + return ret; + + clk_unvote_vdd_level(&rclk->vdd_data, vdd_level); + rclk->vdd_data.vdd_level = cur_vdd_level; + + return 0; +} +EXPORT_SYMBOL(clk_post_change_regmap); + +/** + * clk_prepare_regmap() - standard prepare call back for regmap clks + * + * @hw: clk to operate on + * + * Prepare the clock by updating the vdd_level to level required by + * the current rate of the clock if it hasn't been initialized before. + * Vdd_level and level required by current clock rate mismatches can + * occur due to error cases and upon initial clock registration + * if the clock becomes an orphan and is later reparented. + * + * Returns 0 on success, -EERROR otherwise. + */ +int clk_prepare_regmap(struct clk_hw *hw) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + int rate = clk_hw_get_rate(hw); + int vdd_level; + + if (!rclk->vdd_data.vdd_class) + return 0; + + vdd_level = clk_find_vdd_level(hw, &rclk->vdd_data, rate); + if (vdd_level < 0) + return vdd_level; + + if (rclk->vdd_data.vdd_level == 0) + rclk->vdd_data.vdd_level = vdd_level; + + WARN(vdd_level > rclk->vdd_data.vdd_level, + "%s level:%d > vdd_level:%d\n", clk_hw_get_name(hw), + vdd_level, rclk->vdd_data.vdd_level); + + return clk_vote_vdd_level(&rclk->vdd_data, rclk->vdd_data.vdd_level); +} +EXPORT_SYMBOL(clk_prepare_regmap); + +/** + * clk_prepare_regmap() - standard prepare call back for regmap clks + * + * @hw: clk to operate on + * + * Unprepare the clock by removing the outstanding vdd_level vote. + * + */ +void clk_unprepare_regmap(struct clk_hw *hw) +{ + struct clk_regmap *rclk = to_clk_regmap(hw); + + if (!rclk->vdd_data.vdd_class) + return; + + clk_unvote_vdd_level(&rclk->vdd_data, rclk->vdd_data.vdd_level); +} +EXPORT_SYMBOL(clk_unprepare_regmap); + /** * devm_clk_register_regmap - register a clk_regmap clock * diff --git a/drivers/clk/qcom/clk-regmap.h b/drivers/clk/qcom/clk-regmap.h index 14ec659a3a77..b6f8b0854ee9 100644 --- a/drivers/clk/qcom/clk-regmap.h +++ b/drivers/clk/qcom/clk-regmap.h @@ -1,10 +1,11 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Copyright (c) 2014, The Linux Foundation. All rights reserved. */ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2014, 2019, The Linux Foundation. All rights reserved. */ #ifndef __QCOM_CLK_REGMAP_H__ #define __QCOM_CLK_REGMAP_H__ #include +#include "vdd-class.h" struct regmap; @@ -16,6 +17,7 @@ struct regmap; * @enable_mask: mask when using regmap enable/disable ops * @enable_is_inverted: flag to indicate set enable_mask bits to disable * when using clock_enable_regmap and friends APIs. + * @vdd_data: struct containing vdd-class data for this clock */ struct clk_regmap { struct clk_hw hw; @@ -23,6 +25,7 @@ struct clk_regmap { unsigned int enable_reg; unsigned int enable_mask; bool enable_is_inverted; + struct clk_vdd_class_data vdd_data; }; static inline struct clk_regmap *to_clk_regmap(struct clk_hw *hw) @@ -33,6 +36,12 @@ static inline struct clk_regmap *to_clk_regmap(struct clk_hw *hw) int clk_is_enabled_regmap(struct clk_hw *hw); int clk_enable_regmap(struct clk_hw *hw); void clk_disable_regmap(struct clk_hw *hw); +int clk_prepare_regmap(struct clk_hw *hw); +void clk_unprepare_regmap(struct clk_hw *hw); +int clk_pre_change_regmap(struct clk_hw *hw, unsigned long cur_rate, + unsigned long new_rate); +int clk_post_change_regmap(struct clk_hw *hw, unsigned long old_rate, + unsigned long cur_rate); int devm_clk_register_regmap(struct device *dev, struct clk_regmap *rclk); #endif