diff options
Diffstat (limited to 'drivers/opp/core.c')
-rw-r--r-- | drivers/opp/core.c | 261 |
1 files changed, 191 insertions, 70 deletions
diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 2c2df4e4fc14..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, @@ -635,6 +597,84 @@ 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); +} + +/* 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 @@ -649,7 +689,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,44 +742,34 @@ 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) { - /* - * 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); - } else if (!opp_table->set_opp) { + /* 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, + 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 { - 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); + /* Only frequency scaling */ + ret = _generic_set_opp_clk_only(dev, clk, old_freq, freq); + } - ret = opp_table->set_opp(data); + /* 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)) @@ -810,6 +840,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); @@ -888,6 +919,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); @@ -905,6 +938,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); @@ -961,6 +995,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); @@ -1587,6 +1622,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 * @freq: Frequency in Hz for this OPP |