From 7e535993fa4f671dbbd7fd95c93ce90181ddd9e1 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Tue, 12 Jun 2018 14:47:03 +0530 Subject: OPP: Separate out custom OPP handler specific code Create a separate routine to take care of custom set_opp() handler specific stuff. Reviewed-by: Ulf Hansson Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 67 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 27 deletions(-) (limited to 'drivers/opp/core.c') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 2c2df4e4fc14..ebb3b648e0fd 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -635,6 +635,34 @@ restore_voltage: return ret; } +static int _set_opp_custom(const struct opp_table *opp_table, + struct device *dev, unsigned long old_freq, + unsigned long freq, + struct dev_pm_opp_supply *old_supply, + struct dev_pm_opp_supply *new_supply) +{ + struct dev_pm_set_opp_data *data; + int size; + + data = opp_table->set_opp_data; + data->regulators = opp_table->regulators; + data->regulator_count = opp_table->regulator_count; + data->clk = opp_table->clk; + data->dev = dev; + + data->old_opp.rate = old_freq; + size = sizeof(*old_supply) * opp_table->regulator_count; + if (IS_ERR(old_supply)) + memset(data->old_opp.supplies, 0, size); + else + memcpy(data->old_opp.supplies, old_supply, size); + + data->new_opp.rate = freq; + memcpy(data->new_opp.supplies, new_supply, size); + + return opp_table->set_opp(data); +} + /** * dev_pm_opp_set_rate() - Configure new OPP based on frequency * @dev: device for which we do this operation @@ -649,7 +677,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) unsigned long freq, old_freq; struct dev_pm_opp *old_opp, *opp; struct clk *clk; - int ret, size; + int ret; if (unlikely(!target_freq)) { dev_err(dev, "%s: Invalid target frequency %lu\n", __func__, @@ -702,8 +730,17 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, old_freq, freq); - /* Only frequency scaling */ - if (!opp_table->regulators) { + if (opp_table->set_opp) { + ret = _set_opp_custom(opp_table, dev, old_freq, freq, + IS_ERR(old_opp) ? NULL : old_opp->supplies, + opp->supplies); + } else if (opp_table->regulators) { + ret = _generic_set_opp_regulator(opp_table, dev, old_freq, freq, + IS_ERR(old_opp) ? NULL : old_opp->supplies, + opp->supplies); + } else { + /* Only frequency scaling */ + /* * We don't support devices with both regulator and * domain performance-state for now. @@ -714,30 +751,6 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) opp->pstate); else ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq); - } else if (!opp_table->set_opp) { - ret = _generic_set_opp_regulator(opp_table, dev, old_freq, freq, - IS_ERR(old_opp) ? NULL : old_opp->supplies, - opp->supplies); - } else { - struct dev_pm_set_opp_data *data; - - data = opp_table->set_opp_data; - data->regulators = opp_table->regulators; - data->regulator_count = opp_table->regulator_count; - data->clk = clk; - data->dev = dev; - - data->old_opp.rate = old_freq; - size = sizeof(*opp->supplies) * opp_table->regulator_count; - if (IS_ERR(old_opp)) - memset(data->old_opp.supplies, 0, size); - else - memcpy(data->old_opp.supplies, old_opp->supplies, size); - - data->new_opp.rate = freq; - memcpy(data->new_opp.supplies, opp->supplies, size); - - ret = opp_table->set_opp(data); } dev_pm_opp_put(opp); -- cgit v1.2.3 From 5d6d106fa45501ea6494a8653adbf2bee4a6f803 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 7 Jun 2018 14:50:43 +0530 Subject: OPP: Populate required opp tables from "required-opps" property The current implementation works only for the case where a single phandle is present in the "required-opps" property, while DT allows multiple phandles to be present there. This patch adds new infrastructure to parse all the phandles present in "required-opps" property and save pointers of the required OPP's OPP tables. These will be used by later commits. Reviewed-by: Ulf Hansson Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/opp/core.c') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index ebb3b648e0fd..85174a5c4850 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -901,6 +901,8 @@ static void _opp_table_kref_release(struct kref *kref) struct opp_table *opp_table = container_of(kref, struct opp_table, kref); struct opp_device *opp_dev, *temp; + _of_clear_opp_table(opp_table); + /* Release clk */ if (!IS_ERR(opp_table->clk)) clk_put(opp_table->clk); -- cgit v1.2.3 From da544b61eb5541db8827af0beab618daead88a34 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 7 Jun 2018 14:50:43 +0530 Subject: OPP: Populate OPPs from "required-opps" property An earlier commit populated the OPP tables from the "required-opps" property, this commit populates the individual OPPs. This is repeated for each OPP in the OPP table and these populated OPPs will be used by later commits. Reviewed-by: Ulf Hansson Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/opp/core.c') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 85174a5c4850..02a69a62dac8 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -976,6 +976,7 @@ static void _opp_kref_release(struct kref *kref) * frequency/voltage list. */ blocking_notifier_call_chain(&opp_table->head, OPP_EVENT_REMOVE, opp); + _of_opp_free_required_opps(opp_table, opp); opp_debug_remove_one(opp); list_del(&opp->node); kfree(opp); -- cgit v1.2.3 From 4f018bc0e1cfdec2e25072db9fecc1f363ba79ea Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Tue, 26 Jun 2018 16:29:34 +0530 Subject: OPP: Add dev_pm_opp_{set|put}_genpd_virt_dev() helper Multiple generic power domains for a consumer device are supported with the help of virtual devices, which are created for each consumer device - genpd pair. These are the device structures which are attached to the power domain and are required by the OPP core to set the performance state of the genpd. The helpers added by this commit are required to be called once for each of these virtual devices. These are required only if multiple domains are available for a device, otherwise the actual device structure will be used instead by the OPP core. The new helpers also support the complex cases where the consumer device wouldn't always require all the domains. For example, a camera may require only one power domain during normal operations but two during high resolution operations. The consumer driver can call dev_pm_opp_put_genpd_virt_dev(high_resolution_genpd_virt_dev) if it is currently operating in the normal mode and doesn't have any performance requirements from the genpd which manages high resolution power requirements. The consumer driver can later call dev_pm_opp_set_genpd_virt_dev(high_resolution_genpd_virt_dev) once it switches back to the high resolution mode. The new helpers differ from other OPP set/put helpers as the new ones can be called with OPPs initialized for the table as we may need to call them on the fly because of the complex case explained above. For this reason it is possible that the genpd virt_dev structure may be used in parallel while the new helpers are running and a new mutex is added to protect against that. We didn't use the existing opp_table->lock mutex as that is widely used in the OPP core and we will need this lock in the dev_pm_opp_set_rate() helper while changing OPP and we need to make sure there is not much contention while doing that as that's the hotpath. Reviewed-by: Ulf Hansson Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) (limited to 'drivers/opp/core.c') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 02a69a62dac8..cef2ccda355d 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -823,6 +823,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index) return NULL; mutex_init(&opp_table->lock); + mutex_init(&opp_table->genpd_virt_dev_lock); INIT_LIST_HEAD(&opp_table->dev_list); opp_dev = _add_opp_dev(dev, opp_table); @@ -920,6 +921,7 @@ static void _opp_table_kref_release(struct kref *kref) _remove_opp_dev(opp_dev, opp_table); } + mutex_destroy(&opp_table->genpd_virt_dev_lock); mutex_destroy(&opp_table->lock); list_del(&opp_table->node); kfree(opp_table); @@ -1602,6 +1604,92 @@ void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table) } EXPORT_SYMBOL_GPL(dev_pm_opp_unregister_set_opp_helper); +/** + * dev_pm_opp_set_genpd_virt_dev - Set virtual genpd device for an index + * @dev: Consumer device for which the genpd device is getting set. + * @virt_dev: virtual genpd device. + * @index: index. + * + * Multiple generic power domains for a device are supported with the help of + * virtual genpd devices, which are created for each consumer device - genpd + * pair. These are the device structures which are attached to the power domain + * and are required by the OPP core to set the performance state of the genpd. + * + * This helper will normally be called by the consumer driver of the device + * "dev", as only that has details of the genpd devices. + * + * This helper needs to be called once for each of those virtual devices, but + * only if multiple domains are available for a device. Otherwise the original + * device structure will be used instead by the OPP core. + */ +struct opp_table *dev_pm_opp_set_genpd_virt_dev(struct device *dev, + struct device *virt_dev, + int index) +{ + struct opp_table *opp_table; + + opp_table = dev_pm_opp_get_opp_table(dev); + if (!opp_table) + return ERR_PTR(-ENOMEM); + + mutex_lock(&opp_table->genpd_virt_dev_lock); + + if (unlikely(!opp_table->genpd_virt_devs || + index >= opp_table->required_opp_count || + opp_table->genpd_virt_devs[index])) { + + dev_err(dev, "Invalid request to set required device\n"); + dev_pm_opp_put_opp_table(opp_table); + mutex_unlock(&opp_table->genpd_virt_dev_lock); + + return ERR_PTR(-EINVAL); + } + + opp_table->genpd_virt_devs[index] = virt_dev; + mutex_unlock(&opp_table->genpd_virt_dev_lock); + + return opp_table; +} + +/** + * dev_pm_opp_put_genpd_virt_dev() - Releases resources blocked for genpd device. + * @opp_table: OPP table returned by dev_pm_opp_set_genpd_virt_dev(). + * @virt_dev: virtual genpd device. + * + * This releases the resource previously acquired with a call to + * dev_pm_opp_set_genpd_virt_dev(). The consumer driver shall call this helper + * if it doesn't want OPP core to update performance state of a power domain + * anymore. + */ +void dev_pm_opp_put_genpd_virt_dev(struct opp_table *opp_table, + struct device *virt_dev) +{ + int i; + + /* + * Acquire genpd_virt_dev_lock to make sure virt_dev isn't getting + * used in parallel. + */ + mutex_lock(&opp_table->genpd_virt_dev_lock); + + for (i = 0; i < opp_table->required_opp_count; i++) { + if (opp_table->genpd_virt_devs[i] != virt_dev) + continue; + + opp_table->genpd_virt_devs[i] = NULL; + dev_pm_opp_put_opp_table(opp_table); + + /* Drop the vote */ + dev_pm_genpd_set_performance_state(virt_dev, 0); + break; + } + + mutex_unlock(&opp_table->genpd_virt_dev_lock); + + if (unlikely(i == opp_table->required_opp_count)) + dev_err(virt_dev, "Failed to find required device entry\n"); +} + /** * dev_pm_opp_add() - Add an OPP table from a table definitions * @dev: device for which we do this operation -- cgit v1.2.3 From ca1b5d77b1c69df7d7b92860c61daa82c8bfde34 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 14 Jun 2018 10:03:26 +0530 Subject: OPP: Configure all required OPPs Now that all the infrastructure is in place to support multiple required OPPs, lets switch over to using it. A new internal routine _set_required_opps() takes care of updating performance state for all the required OPPs. With this the performance state updates are supported even when the end device needs to configure regulators as well, that wasn't the case earlier. The pstates were earlier stored in the end device's OPP structures, that also changes now as those values are stored in the genpd's OPP structures. And so we switch over to using pm_genpd_opp_to_performance_state() instead of of_genpd_opp_to_performance_state() to get performance state for the genpd OPPs. The routine _generic_set_opp_domain() is not required anymore and is removed. On errors we don't try to recover by reverting to old settings as things are really complex now and the calls here should never really fail unless there is a bug. There is no point increasing the complexity, for code which will never be executed. Reviewed-by: Ulf Hansson Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 113 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 48 deletions(-) (limited to 'drivers/opp/core.c') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index cef2ccda355d..0eaa954b3f6c 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -548,44 +548,6 @@ _generic_set_opp_clk_only(struct device *dev, struct clk *clk, return ret; } -static inline int -_generic_set_opp_domain(struct device *dev, struct clk *clk, - unsigned long old_freq, unsigned long freq, - unsigned int old_pstate, unsigned int new_pstate) -{ - int ret; - - /* Scaling up? Scale domain performance state before frequency */ - if (freq > old_freq) { - ret = dev_pm_genpd_set_performance_state(dev, new_pstate); - if (ret) - return ret; - } - - ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq); - if (ret) - goto restore_domain_state; - - /* Scaling down? Scale domain performance state after frequency */ - if (freq < old_freq) { - ret = dev_pm_genpd_set_performance_state(dev, new_pstate); - if (ret) - goto restore_freq; - } - - return 0; - -restore_freq: - if (_generic_set_opp_clk_only(dev, clk, freq, old_freq)) - dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", - __func__, old_freq); -restore_domain_state: - if (freq > old_freq) - dev_pm_genpd_set_performance_state(dev, old_pstate); - - return ret; -} - static int _generic_set_opp_regulator(const struct opp_table *opp_table, struct device *dev, unsigned long old_freq, @@ -663,6 +625,56 @@ static int _set_opp_custom(const struct opp_table *opp_table, return opp_table->set_opp(data); } +/* This is only called for PM domain for now */ +static int _set_required_opps(struct device *dev, + struct opp_table *opp_table, + struct dev_pm_opp *opp) +{ + struct opp_table **required_opp_tables = opp_table->required_opp_tables; + struct device **genpd_virt_devs = opp_table->genpd_virt_devs; + unsigned int pstate; + int i, ret = 0; + + if (!required_opp_tables) + return 0; + + /* Single genpd case */ + if (!genpd_virt_devs) { + pstate = opp->required_opps[0]->pstate; + ret = dev_pm_genpd_set_performance_state(dev, pstate); + if (ret) { + dev_err(dev, "Failed to set performance state of %s: %d (%d)\n", + dev_name(dev), pstate, ret); + } + return ret; + } + + /* Multiple genpd case */ + + /* + * Acquire genpd_virt_dev_lock to make sure we don't use a genpd_dev + * after it is freed from another thread. + */ + mutex_lock(&opp_table->genpd_virt_dev_lock); + + for (i = 0; i < opp_table->required_opp_count; i++) { + pstate = opp->required_opps[i]->pstate; + + if (!genpd_virt_devs[i]) + continue; + + ret = dev_pm_genpd_set_performance_state(genpd_virt_devs[i], pstate); + if (ret) { + dev_err(dev, "Failed to set performance rate of %s: %d (%d)\n", + dev_name(genpd_virt_devs[i]), pstate, ret); + break; + } + } + mutex_unlock(&opp_table->genpd_virt_dev_lock); + + return ret; +} + /** * dev_pm_opp_set_rate() - Configure new OPP based on frequency * @dev: device for which we do this operation @@ -730,6 +742,13 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, old_freq, freq); + /* Scaling up? Configure required OPPs before frequency */ + if (freq > old_freq) { + ret = _set_required_opps(dev, opp_table, opp); + if (ret) + goto put_opp; + } + if (opp_table->set_opp) { ret = _set_opp_custom(opp_table, dev, old_freq, freq, IS_ERR(old_opp) ? NULL : old_opp->supplies, @@ -740,19 +759,17 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) opp->supplies); } else { /* Only frequency scaling */ + ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq); + } - /* - * We don't support devices with both regulator and - * domain performance-state for now. - */ - if (opp_table->genpd_performance_state) - ret = _generic_set_opp_domain(dev, clk, old_freq, freq, - IS_ERR(old_opp) ? 0 : old_opp->pstate, - opp->pstate); - else - ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq); + /* Scaling down? Configure required OPPs after frequency */ + if (!ret && freq < old_freq) { + ret = _set_required_opps(dev, opp_table, opp); + if (ret) + dev_err(dev, "Failed to set required opps: %d\n", ret); } +put_opp: dev_pm_opp_put(opp); put_old_opp: if (!IS_ERR(old_opp)) -- cgit v1.2.3 From c8a59103e22b191e363fc0a90e08515a915b278d Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 2 Nov 2018 14:36:42 +0530 Subject: OPP: Add dev_pm_opp_xlate_performance_state() helper dev_pm_genpd_set_performance_state() needs to handle performance state propagation going forward. Currently this routine only gets the required performance state of the device's genpd as an argument, but it doesn't know how to translate that to master genpd(s) of the device's genpd. Introduce a new helper dev_pm_opp_xlate_performance_state() which will be used to translate from performance state of a device (or genpd sub-domain) to another device (or master genpd). Normally the src_table (of genpd sub-domain) will have the "required_opps" property set to point to one of the OPPs in the dst_table (of master genpd), but in some cases the genpd and its master have one to one mapping of performance states and so none of them have the "required-opps" property set. Return the performance state of the src_table as it is in such cases. Tested-by: Rajendra Nayak Reviewed-by: Ulf Hansson Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) (limited to 'drivers/opp/core.c') diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 0eaa954b3f6c..eec1b60d7781 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1707,6 +1707,69 @@ void dev_pm_opp_put_genpd_virt_dev(struct opp_table *opp_table, dev_err(virt_dev, "Failed to find required device entry\n"); } +/** + * dev_pm_opp_xlate_performance_state() - Find required OPP's pstate for src_table. + * @src_table: OPP table which has dst_table as one of its required OPP table. + * @dst_table: Required OPP table of the src_table. + * @pstate: Current performance state of the src_table. + * + * This Returns pstate of the OPP (present in @dst_table) pointed out by the + * "required-opps" property of the OPP (present in @src_table) which has + * performance state set to @pstate. + * + * Return: Zero or positive performance state on success, otherwise negative + * value on errors. + */ +int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, + struct opp_table *dst_table, + unsigned int pstate) +{ + struct dev_pm_opp *opp; + int dest_pstate = -EINVAL; + int i; + + if (!pstate) + return 0; + + /* + * Normally the src_table will have the "required_opps" property set to + * point to one of the OPPs in the dst_table, but in some cases the + * genpd and its master have one to one mapping of performance states + * and so none of them have the "required-opps" property set. Return the + * pstate of the src_table as it is in such cases. + */ + if (!src_table->required_opp_count) + return pstate; + + for (i = 0; i < src_table->required_opp_count; i++) { + if (src_table->required_opp_tables[i]->np == dst_table->np) + break; + } + + if (unlikely(i == src_table->required_opp_count)) { + pr_err("%s: Couldn't find matching OPP table (%p: %p)\n", + __func__, src_table, dst_table); + return -EINVAL; + } + + mutex_lock(&src_table->lock); + + list_for_each_entry(opp, &src_table->opp_list, node) { + if (opp->pstate == pstate) { + dest_pstate = opp->required_opps[i]->pstate; + goto unlock; + } + } + + pr_err("%s: Couldn't find matching OPP (%p: %p)\n", __func__, src_table, + dst_table); + +unlock: + mutex_unlock(&src_table->lock); + + return dest_pstate; +} + /** * dev_pm_opp_add() - Add an OPP table from a table definitions * @dev: device for which we do this operation -- cgit v1.2.3