There could be single power domain or multiple power domains associated with a clock controller. Add powerdomain_class support which would help vote/unvote for any power domain performance state for a clock frequency to the genpd framework. A clock frequency request from a consumer would look for the corresponding performance corner and thus would aggregate and request the desired performance state to genpd. Signed-off-by: Taniya Das <tdas@xxxxxxxxxxxxxx> --- drivers/clk/qcom/Makefile | 1 + drivers/clk/qcom/clk-pd.c | 193 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/clk/qcom/clk-pd.h | 55 +++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 drivers/clk/qcom/clk-pd.c create mode 100644 drivers/clk/qcom/clk-pd.h diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 599ab91..336d4da 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -12,6 +12,7 @@ clk-qcom-y += clk-regmap-divider.o clk-qcom-y += clk-regmap-mux.o clk-qcom-y += clk-regmap-mux-div.o clk-qcom-y += reset.o +clk-qcom-y += clk-pd.o clk-qcom-$(CONFIG_QCOM_GDSC) += gdsc.o # Keep alphabetically sorted by config diff --git a/drivers/clk/qcom/clk-pd.c b/drivers/clk/qcom/clk-pd.c new file mode 100644 index 0000000..d1f9df3 --- /dev/null +++ b/drivers/clk/qcom/clk-pd.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> + +#include "clk-pd.h" +#include "clk-regmap.h" + +struct clk_powerdomain { + struct list_head list; + struct clk_powerdomain_class *pd; +}; + +static LIST_HEAD(clk_pd_list); + +/* Find the corner required for a given clock rate */ +static int find_rate_to_corner(struct clk_regmap *rclk, unsigned long rate) +{ + int corner; + + for (corner = 0; corner < rclk->pd->num_corners; corner++) + if (rate <= rclk->rate_max[corner]) + break; + + if (corner == rclk->pd->num_corners) { + pr_debug("Rate %lu for %s is > than highest Fmax\n", rate, + rclk->hw.init->name); + return -EINVAL; + } + + return corner; +} + +static int pd_update_corner_state(struct clk_powerdomain_class *pd) +{ + int corner, ret, *state = pd->corner, i; + int cur_corner = pd->cur_corner, max_corner = pd->num_corners - 1; + + /* Aggregate the corner */ + for (corner = max_corner; corner > 0; corner--) { + if (pd->corner_votes[corner]) + break; + } + + if (corner == cur_corner) + return 0; + + pr_debug("Set performance state to genpd(%s) for state %d, cur_corner %d, num_corner %d\n", + pd->pd_name, state[corner], cur_corner, pd->num_corners); + + for (i = 0; i < pd->num_pd; i++) { + ret = dev_pm_genpd_set_performance_state(pd->powerdomain_dev[i], + state[corner]); + if (ret) + return ret; + + if (cur_corner == 0 || cur_corner == pd->num_corners) { + pd->links[i] = device_link_add(pd->dev, + pd->powerdomain_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!pd->links[i]) + pr_err("Links for %d not created\n", i); + } + + if (corner == 0) + device_link_del(pd->links[i]); + } + + pd->cur_corner = corner; + + return 0; +} + +/* call from prepare & set rate */ +int clk_power_domain_vote_rate(struct clk_regmap *rclk, + unsigned long rate) +{ + int corner; + + if (!rclk->pd) + return 0; + + corner = find_rate_to_corner(rclk, rate); + if (corner < 0) + return corner; + + mutex_lock(&rclk->pd->lock); + + rclk->pd->corner_votes[corner]++; + + /* update the corner to power domain */ + if (pd_update_corner_state(rclk->pd) < 0) + rclk->pd->corner_votes[corner]--; + + pr_debug("pd(%s) prepare corner_votes_count %d, corner %d\n", + rclk->pd->pd_name, rclk->pd->corner_votes[corner], + corner); + + mutex_unlock(&rclk->pd->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(clk_power_domain_vote_rate); + +/* call from unprepare & set rate */ +void clk_power_domain_unvote_rate(struct clk_regmap *rclk, + unsigned long rate) +{ + int corner; + + if (!rclk->pd) + return; + + corner = find_rate_to_corner(rclk, rate); + if (corner < 0) + return; + + if (WARN(!rclk->pd->corner_votes[corner], + "Reference counts are incorrect for %s corner %d\n", + rclk->pd->pd_name, corner)) + return; + + mutex_lock(&rclk->pd->lock); + + rclk->pd->corner_votes[corner]--; + + if (pd_update_corner_state(rclk->pd) < 0) + rclk->pd->corner_votes[corner]++; + + pr_debug("pd(%s) unprepare corner_votes_count %d, corner %d\n", + rclk->pd->pd_name, rclk->pd->corner_votes[corner], + corner); + + mutex_unlock(&rclk->pd->lock); +} +EXPORT_SYMBOL_GPL(clk_power_domain_unvote_rate); + +int clk_power_domain_class_init(struct device *dev, + struct clk_powerdomain_class *pd) +{ + struct clk_powerdomain *pwrd; + int i, num_domains; + + if (!pd) { + pr_debug("pd not defined\n"); + return 0; + } + + /* Deal only with devices using multiple PM domains. */ + num_domains = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + + list_for_each_entry(pwrd, &clk_pd_list, list) { + if (pwrd->pd == pd) + return 0; + } + + pr_debug("Voting for pd_class %s\n", pd->pd_name); + + if (num_domains == 1) { + pd->powerdomain_dev[0] = dev; + } else { + for (i = 0; i < pd->num_pd; i++) { + int index = pd->pd_index[i]; + + pd->powerdomain_dev[i] = genpd_dev_pm_attach_by_id(dev, + index); + } + } + + pwrd = kmalloc(sizeof(*pwrd), GFP_KERNEL); + if (!pwrd) + return -ENOMEM; + + pwrd->pd = pd; + list_add_tail(&pwrd->list, &clk_pd_list); + + pd->dev = dev; + + return 0; +} +EXPORT_SYMBOL_GPL(clk_power_domain_class_init); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/qcom/clk-pd.h b/drivers/clk/qcom/clk-pd.h new file mode 100644 index 0000000..addde4f --- /dev/null +++ b/drivers/clk/qcom/clk-pd.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */ + +struct clk_regmap; + +/** + * struct clk_powerdomain_class - Power Domain scaling class + * @pd_name: name of the power domain class + * @dev: clock controller device + * @powerdomain_dev: array of power domain devices + * @links: array of power domain devices linked + * @num_pd: size of power domain devices array + * @pd_index: array of power domain index which would + * be used to attach the power domain using + * genpd_dev_pm_attach_by_id(dev, index) + * @corner: sorted 2D array of legal corner settings, + indexed by the corner + * @corner_votes: array of votes for each corner + * @num_corner: specifies the size of corner_votes array + * @cur_corner: current set power domain corner + * @lock: lock to protect this struct + */ +struct clk_powerdomain_class { + const char *pd_name; + struct device *dev; + struct device **powerdomain_dev; + struct device_link **links; + int num_pd; + int *pd_index; + int *corner; + int *corner_votes; + int num_corners; + unsigned int cur_corner; + /* Protect this struct */ + struct mutex lock; +}; + +#define CLK_POWERDOMAIN_INIT(_name, _num_corners, _num_pd, _corner) \ + struct clk_powerdomain_class _name = { \ + .pd_name = #_name, \ + .powerdomain_dev = (struct device *([_num_pd])) {}, \ + .links = (struct device_link *([_num_pd])) {}, \ + .num_pd = _num_pd, \ + .pd_index = (int [_num_pd]) {}, \ + .corner_votes = (int [_num_corners]) {}, \ + .num_corners = _num_corners, \ + .corner = _corner, \ + .cur_corner = _num_corners, \ + .lock = __MUTEX_INITIALIZER(_name.lock) \ + } + +int clk_power_domain_class_init(struct device *dev, + struct clk_powerdomain_class *pd); +int clk_power_domain_vote_rate(struct clk_regmap *rclk, unsigned long rate); +void clk_power_domain_unvote_rate(struct clk_regmap *rclk, unsigned long rate); -- Qualcomm INDIA, on behalf of Qualcomm Innovation Center, Inc.is a member of the Code Aurora Forum, hosted by the Linux Foundation. -- To unsubscribe from this list: send the line "unsubscribe linux-soc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html