diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2022-08-03 17:49:38 +0200 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2022-08-03 17:49:38 +0200 |
commit | f6e0b468dab97a9214c9713c298ed9e2dd62f97f (patch) | |
tree | 4ddebb6700fa629c1c6bc549b376dd539b5ca4ad /drivers/opp | |
parent | a771ea6413c00cf4af0570745f2e27084d7e2376 (diff) | |
parent | 1d95af02f23031c2e1cca7607c514b86ce85bc6e (diff) |
Merge tag 'opp-updates-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm
Pull operating performance points (OPP) updates for 5.20-rc1 from Viresh
Kumar:
"- Make dev_pm_opp_set_regulators() accept NULL terminated list (Viresh
Kumar).
- Add dev_pm_opp_set_config() and friends and migrate other
users/helpers to using them (Viresh Kumar).
- Add support for multiple clocks for a device (Viresh Kumar and
Krzysztof Kozlowski).
- Configure resources before adding OPP table for Venus (Stanimir
Varbanov).
- Keep reference count up for opp->np and opp_table->np while they are
still in use (Liang He).
- Minor cleanups (Viresh Kumar and Yang Li)."
* tag 'opp-updates-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm: (43 commits)
venus: pm_helpers: Fix warning in OPP during probe
OPP: Don't drop opp->np reference while it is still in use
OPP: Don't drop opp_table->np reference while it is still in use
OPP: Remove dev{m}_pm_opp_of_add_table_noclk()
PM / devfreq: tegra30: Register config_clks helper
OPP: Allow config_clks helper for single clk case
OPP: Provide a simple implementation to configure multiple clocks
OPP: Assert clk_count == 1 for single clk helpers
OPP: Add key specific assert() method to key finding helpers
OPP: Compare bandwidths for all paths in _opp_compare_key()
OPP: Allow multiple clocks for a device
dt-bindings: opp: accept array of frequencies
OPP: Make dev_pm_opp_set_opp() independent of frequency
OPP: Reuse _opp_compare_key() in _opp_add_static_v2()
OPP: Remove rate_not_available parameter to _opp_add()
OPP: Use consistent names for OPP table instances
OPP: Use generic key finding helpers for bandwidth key
OPP: Use generic key finding helpers for level key
OPP: Add generic key finding helpers and use them for freq APIs
OPP: Remove dev_pm_opp_find_freq_ceil_by_volt()
...
Diffstat (limited to 'drivers/opp')
-rw-r--r-- | drivers/opp/core.c | 1571 | ||||
-rw-r--r-- | drivers/opp/cpu.c | 12 | ||||
-rw-r--r-- | drivers/opp/debugfs.c | 27 | ||||
-rw-r--r-- | drivers/opp/of.c | 150 | ||||
-rw-r--r-- | drivers/opp/opp.h | 56 | ||||
-rw-r--r-- | drivers/opp/ti-opp-supply.c | 77 |
6 files changed, 971 insertions, 922 deletions
diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 84063eaebb91..77d1ba3a4154 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -13,11 +13,12 @@ #include <linux/clk.h> #include <linux/errno.h> #include <linux/err.h> -#include <linux/slab.h> #include <linux/device.h> #include <linux/export.h> #include <linux/pm_domain.h> #include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/xarray.h> #include "opp.h" @@ -36,6 +37,9 @@ DEFINE_MUTEX(opp_table_lock); /* Flag indicating that opp_tables list is being updated at the moment */ static bool opp_tables_busy; +/* OPP ID allocator */ +static DEFINE_XARRAY_ALLOC1(opp_configs); + static bool _find_opp_dev(const struct device *dev, struct opp_table *opp_table) { struct opp_device *opp_dev; @@ -93,6 +97,18 @@ struct opp_table *_find_opp_table(struct device *dev) return opp_table; } +/* + * Returns true if multiple clocks aren't there, else returns false with WARN. + * + * We don't force clk_count == 1 here as there are users who don't have a clock + * representation in the OPP table and manage the clock configuration themselves + * in an platform specific way. + */ +static bool assert_single_clk(struct opp_table *opp_table) +{ + return !WARN_ON(opp_table->clk_count > 1); +} + /** * dev_pm_opp_get_voltage() - Gets the voltage corresponding to an opp * @opp: opp for which voltage has to be returned for @@ -114,6 +130,31 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp) EXPORT_SYMBOL_GPL(dev_pm_opp_get_voltage); /** + * dev_pm_opp_get_supplies() - Gets the supply information corresponding to an opp + * @opp: opp for which voltage has to be returned for + * @supplies: Placeholder for copying the supply information. + * + * Return: negative error number on failure, 0 otherwise on success after + * setting @supplies. + * + * This can be used for devices with any number of power supplies. The caller + * must ensure the @supplies array must contain space for each regulator. + */ +int dev_pm_opp_get_supplies(struct dev_pm_opp *opp, + struct dev_pm_opp_supply *supplies) +{ + if (IS_ERR_OR_NULL(opp) || !supplies) { + pr_err("%s: Invalid parameters\n", __func__); + return -EINVAL; + } + + memcpy(supplies, opp->supplies, + sizeof(*supplies) * opp->opp_table->regulator_count); + return 0; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_get_supplies); + +/** * dev_pm_opp_get_power() - Gets the power corresponding to an opp * @opp: opp for which power has to be returned for * @@ -152,7 +193,10 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) return 0; } - return opp->rate; + if (!assert_single_clk(opp->opp_table)) + return 0; + + return opp->rates[0]; } EXPORT_SYMBOL_GPL(dev_pm_opp_get_freq); @@ -398,6 +442,154 @@ int dev_pm_opp_get_opp_count(struct device *dev) } EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_count); +/* Helpers to read keys */ +static unsigned long _read_freq(struct dev_pm_opp *opp, int index) +{ + return opp->rates[0]; +} + +static unsigned long _read_level(struct dev_pm_opp *opp, int index) +{ + return opp->level; +} + +static unsigned long _read_bw(struct dev_pm_opp *opp, int index) +{ + return opp->bandwidth[index].peak; +} + +/* Generic comparison helpers */ +static bool _compare_exact(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp, + unsigned long opp_key, unsigned long key) +{ + if (opp_key == key) { + *opp = temp_opp; + return true; + } + + return false; +} + +static bool _compare_ceil(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp, + unsigned long opp_key, unsigned long key) +{ + if (opp_key >= key) { + *opp = temp_opp; + return true; + } + + return false; +} + +static bool _compare_floor(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp, + unsigned long opp_key, unsigned long key) +{ + if (opp_key > key) + return true; + + *opp = temp_opp; + return false; +} + +/* Generic key finding helpers */ +static struct dev_pm_opp *_opp_table_find_key(struct opp_table *opp_table, + unsigned long *key, int index, bool available, + unsigned long (*read)(struct dev_pm_opp *opp, int index), + bool (*compare)(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp, + unsigned long opp_key, unsigned long key), + bool (*assert)(struct opp_table *opp_table)) +{ + struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); + + /* Assert that the requirement is met */ + if (assert && !assert(opp_table)) + return ERR_PTR(-EINVAL); + + mutex_lock(&opp_table->lock); + + list_for_each_entry(temp_opp, &opp_table->opp_list, node) { + if (temp_opp->available == available) { + if (compare(&opp, temp_opp, read(temp_opp, index), *key)) + break; + } + } + + /* Increment the reference count of OPP */ + if (!IS_ERR(opp)) { + *key = read(opp, index); + dev_pm_opp_get(opp); + } + + mutex_unlock(&opp_table->lock); + + return opp; +} + +static struct dev_pm_opp * +_find_key(struct device *dev, unsigned long *key, int index, bool available, + unsigned long (*read)(struct dev_pm_opp *opp, int index), + bool (*compare)(struct dev_pm_opp **opp, struct dev_pm_opp *temp_opp, + unsigned long opp_key, unsigned long key), + bool (*assert)(struct opp_table *opp_table)) +{ + struct opp_table *opp_table; + struct dev_pm_opp *opp; + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + dev_err(dev, "%s: OPP table not found (%ld)\n", __func__, + PTR_ERR(opp_table)); + return ERR_CAST(opp_table); + } + + opp = _opp_table_find_key(opp_table, key, index, available, read, + compare, assert); + + dev_pm_opp_put_opp_table(opp_table); + + return opp; +} + +static struct dev_pm_opp *_find_key_exact(struct device *dev, + unsigned long key, int index, bool available, + unsigned long (*read)(struct dev_pm_opp *opp, int index), + bool (*assert)(struct opp_table *opp_table)) +{ + /* + * The value of key will be updated here, but will be ignored as the + * caller doesn't need it. + */ + return _find_key(dev, &key, index, available, read, _compare_exact, + assert); +} + +static struct dev_pm_opp *_opp_table_find_key_ceil(struct opp_table *opp_table, + unsigned long *key, int index, bool available, + unsigned long (*read)(struct dev_pm_opp *opp, int index), + bool (*assert)(struct opp_table *opp_table)) +{ + return _opp_table_find_key(opp_table, key, index, available, read, + _compare_ceil, assert); +} + +static struct dev_pm_opp *_find_key_ceil(struct device *dev, unsigned long *key, + int index, bool available, + unsigned long (*read)(struct dev_pm_opp *opp, int index), + bool (*assert)(struct opp_table *opp_table)) +{ + return _find_key(dev, key, index, available, read, _compare_ceil, + assert); +} + +static struct dev_pm_opp *_find_key_floor(struct device *dev, + unsigned long *key, int index, bool available, + unsigned long (*read)(struct dev_pm_opp *opp, int index), + bool (*assert)(struct opp_table *opp_table)) +{ + return _find_key(dev, key, index, available, read, _compare_floor, + assert); +} + /** * dev_pm_opp_find_freq_exact() - search for an exact frequency * @dev: device for which we do this operation @@ -422,61 +614,18 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_count); * use. */ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, - unsigned long freq, - bool available) + unsigned long freq, bool available) { - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) { - int r = PTR_ERR(opp_table); - - dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r); - return ERR_PTR(r); - } - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available == available && - temp_opp->rate == freq) { - opp = temp_opp; - - /* Increment the reference count of OPP */ - dev_pm_opp_get(opp); - break; - } - } - - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); - - return opp; + return _find_key_exact(dev, freq, 0, available, _read_freq, + assert_single_clk); } EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_exact); static noinline struct dev_pm_opp *_find_freq_ceil(struct opp_table *opp_table, unsigned long *freq) { - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available && temp_opp->rate >= *freq) { - opp = temp_opp; - *freq = opp->rate; - - /* Increment the reference count of OPP */ - dev_pm_opp_get(opp); - break; - } - } - - mutex_unlock(&opp_table->lock); - - return opp; + return _opp_table_find_key_ceil(opp_table, freq, 0, true, _read_freq, + assert_single_clk); } /** @@ -500,23 +649,7 @@ static noinline struct dev_pm_opp *_find_freq_ceil(struct opp_table *opp_table, struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev, unsigned long *freq) { - struct opp_table *opp_table; - struct dev_pm_opp *opp; - - if (!dev || !freq) { - dev_err(dev, "%s: Invalid argument freq=%p\n", __func__, freq); - return ERR_PTR(-EINVAL); - } - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) - return ERR_CAST(opp_table); - - opp = _find_freq_ceil(opp_table, freq); - - dev_pm_opp_put_opp_table(opp_table); - - return opp; + return _find_key_ceil(dev, freq, 0, true, _read_freq, assert_single_clk); } EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_ceil); @@ -541,98 +674,11 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_ceil); struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, unsigned long *freq) { - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - if (!dev || !freq) { - dev_err(dev, "%s: Invalid argument freq=%p\n", __func__, freq); - return ERR_PTR(-EINVAL); - } - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) - return ERR_CAST(opp_table); - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available) { - /* go to the next node, before choosing prev */ - if (temp_opp->rate > *freq) - break; - else - opp = temp_opp; - } - } - - /* Increment the reference count of OPP */ - if (!IS_ERR(opp)) - dev_pm_opp_get(opp); - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); - - if (!IS_ERR(opp)) - *freq = opp->rate; - - return opp; + return _find_key_floor(dev, freq, 0, true, _read_freq, assert_single_clk); } EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor); /** - * dev_pm_opp_find_freq_ceil_by_volt() - Find OPP with highest frequency for - * target voltage. - * @dev: Device for which we do this operation. - * @u_volt: Target voltage. - * - * Search for OPP with highest (ceil) frequency and has voltage <= u_volt. - * - * Return: matching *opp, else returns ERR_PTR in case of error which should be - * handled using IS_ERR. - * - * Error return values can be: - * EINVAL: bad parameters - * - * The callers are required to call dev_pm_opp_put() for the returned OPP after - * use. - */ -struct dev_pm_opp *dev_pm_opp_find_freq_ceil_by_volt(struct device *dev, - unsigned long u_volt) -{ - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - if (!dev || !u_volt) { - dev_err(dev, "%s: Invalid argument volt=%lu\n", __func__, - u_volt); - return ERR_PTR(-EINVAL); - } - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) - return ERR_CAST(opp_table); - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available) { - if (temp_opp->supplies[0].u_volt > u_volt) - break; - opp = temp_opp; - } - } - - /* Increment the reference count of OPP */ - if (!IS_ERR(opp)) - dev_pm_opp_get(opp); - - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); - - return opp; -} -EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_ceil_by_volt); - -/** * dev_pm_opp_find_level_exact() - search for an exact level * @dev: device for which we do this operation * @level: level to search for @@ -650,33 +696,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_ceil_by_volt); struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, unsigned int level) { - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) { - int r = PTR_ERR(opp_table); - - dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r); - return ERR_PTR(r); - } - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->level == level) { - opp = temp_opp; - - /* Increment the reference count of OPP */ - dev_pm_opp_get(opp); - break; - } - } - - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); - - return opp; + return _find_key_exact(dev, level, 0, true, _read_level, NULL); } EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_exact); @@ -698,33 +718,11 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_exact); struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev, unsigned int *level) { - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) { - int r = PTR_ERR(opp_table); - - dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r); - return ERR_PTR(r); - } - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available && temp_opp->level >= *level) { - opp = temp_opp; - *level = opp->level; - - /* Increment the reference count of OPP */ - dev_pm_opp_get(opp); - break; - } - } - - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); + unsigned long temp = *level; + struct dev_pm_opp *opp; + opp = _find_key_ceil(dev, &temp, 0, true, _read_level, NULL); + *level = temp; return opp; } EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_ceil); @@ -732,7 +730,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_ceil); /** * dev_pm_opp_find_bw_ceil() - Search for a rounded ceil bandwidth * @dev: device for which we do this operation - * @freq: start bandwidth + * @bw: start bandwidth * @index: which bandwidth to compare, in case of OPPs with several values * * Search for the matching floor *available* OPP from a starting bandwidth @@ -748,42 +746,14 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_ceil); * The callers are required to call dev_pm_opp_put() for the returned OPP after * use. */ -struct dev_pm_opp *dev_pm_opp_find_bw_ceil(struct device *dev, - unsigned int *bw, int index) +struct dev_pm_opp *dev_pm_opp_find_bw_ceil(struct device *dev, unsigned int *bw, + int index) { - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - if (!dev || !bw) { - dev_err(dev, "%s: Invalid argument bw=%p\n", __func__, bw); - return ERR_PTR(-EINVAL); - } - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) - return ERR_CAST(opp_table); - - if (index >= opp_table->path_count) - return ERR_PTR(-EINVAL); - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available && temp_opp->bandwidth) { - if (temp_opp->bandwidth[index].peak >= *bw) { - opp = temp_opp; - *bw = opp->bandwidth[index].peak; - - /* Increment the reference count of OPP */ - dev_pm_opp_get(opp); - break; - } - } - } - - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); + unsigned long temp = *bw; + struct dev_pm_opp *opp; + opp = _find_key_ceil(dev, &temp, index, true, _read_bw, NULL); + *bw = temp; return opp; } EXPORT_SYMBOL_GPL(dev_pm_opp_find_bw_ceil); @@ -791,7 +761,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_bw_ceil); /** * dev_pm_opp_find_bw_floor() - Search for a rounded floor bandwidth * @dev: device for which we do this operation - * @freq: start bandwidth + * @bw: start bandwidth * @index: which bandwidth to compare, in case of OPPs with several values * * Search for the matching floor *available* OPP from a starting bandwidth @@ -810,41 +780,11 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_bw_ceil); struct dev_pm_opp *dev_pm_opp_find_bw_floor(struct device *dev, unsigned int *bw, int index) { - struct opp_table *opp_table; - struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE); - - if (!dev || !bw) { - dev_err(dev, "%s: Invalid argument bw=%p\n", __func__, bw); - return ERR_PTR(-EINVAL); - } - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) - return ERR_CAST(opp_table); - - if (index >= opp_table->path_count) - return ERR_PTR(-EINVAL); - - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available && temp_opp->bandwidth) { - /* go to the next node, before choosing prev */ - if (temp_opp->bandwidth[index].peak > *bw) - break; - opp = temp_opp; - } - } - - /* Increment the reference count of OPP */ - if (!IS_ERR(opp)) - dev_pm_opp_get(opp); - mutex_unlock(&opp_table->lock); - dev_pm_opp_put_opp_table(opp_table); - - if (!IS_ERR(opp)) - *bw = opp->bandwidth[index].peak; + unsigned long temp = *bw; + struct dev_pm_opp *opp; + opp = _find_key_floor(dev, &temp, index, true, _read_bw, NULL); + *bw = temp; return opp; } EXPORT_SYMBOL_GPL(dev_pm_opp_find_bw_floor); @@ -874,80 +814,97 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg, return ret; } -static inline int _generic_set_opp_clk_only(struct device *dev, struct clk *clk, - unsigned long freq) +static int +_opp_config_clk_single(struct device *dev, struct opp_table *opp_table, + struct dev_pm_opp *opp, void *data, bool scaling_down) { + unsigned long *target = data; + unsigned long freq; int ret; - /* We may reach here for devices which don't change frequency */ - if (IS_ERR(clk)) - return 0; + /* One of target and opp must be available */ + if (target) { + freq = *target; + } else if (opp) { + freq = opp->rates[0]; + } else { + WARN_ON(1); + return -EINVAL; + } - ret = clk_set_rate(clk, freq); + ret = clk_set_rate(opp_table->clk, freq); if (ret) { dev_err(dev, "%s: failed to set clock rate: %d\n", __func__, ret); + } else { + opp_table->rate_clk_single = freq; } return ret; } -static int _generic_set_opp_regulator(struct opp_table *opp_table, - struct device *dev, - struct dev_pm_opp *opp, - unsigned long freq, - int scaling_down) +/* + * Simple implementation for configuring multiple clocks. Configure clocks in + * the order in which they are present in the array while scaling up. + */ +int dev_pm_opp_config_clks_simple(struct device *dev, + struct opp_table *opp_table, struct dev_pm_opp *opp, void *data, + bool scaling_down) { - struct regulator *reg = opp_table->regulators[0]; - struct dev_pm_opp *old_opp = opp_table->current_opp; + int ret, i; + + if (scaling_down) { + for (i = opp_table->clk_count - 1; i >= 0; i--) { + ret = clk_set_rate(opp_table->clks[i], opp->rates[i]); + if (ret) { + dev_err(dev, "%s: failed to set clock rate: %d\n", __func__, + ret); + return ret; + } + } + } else { + for (i = 0; i < opp_table->clk_count; i++) { + ret = clk_set_rate(opp_table->clks[i], opp->rates[i]); + if (ret) { + dev_err(dev, "%s: failed to set clock rate: %d\n", __func__, + ret); + return ret; + } + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_config_clks_simple); + +static int _opp_config_regulator_single(struct device *dev, + struct dev_pm_opp *old_opp, struct dev_pm_opp *new_opp, + struct regulator **regulators, unsigned int count) +{ + struct regulator *reg = regulators[0]; int ret; /* This function only supports single regulator per device */ - if (WARN_ON(opp_table->regulator_count > 1)) { + if (WARN_ON(count > 1)) { dev_err(dev, "multiple regulators are not supported\n"); return -EINVAL; } - /* Scaling up? Scale voltage before frequency */ - if (!scaling_down) { - ret = _set_opp_voltage(dev, reg, opp->supplies); - if (ret) - goto restore_voltage; - } - - /* Change frequency */ - ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); + ret = _set_opp_voltage(dev, reg, new_opp->supplies); if (ret) - goto restore_voltage; - - /* Scaling down? Scale voltage after frequency */ - if (scaling_down) { - ret = _set_opp_voltage(dev, reg, opp->supplies); - if (ret) - goto restore_freq; - } + return ret; /* * Enable the regulator after setting its voltages, otherwise it breaks * some boot-enabled regulators. */ - if (unlikely(!opp_table->enabled)) { + if (unlikely(!new_opp->opp_table->enabled)) { ret = regulator_enable(reg); if (ret < 0) dev_warn(dev, "Failed to enable regulator: %d", ret); } return 0; - -restore_freq: - if (_generic_set_opp_clk_only(dev, opp_table->clk, old_opp->rate)) - dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", - __func__, old_opp->rate); -restore_voltage: - /* This shouldn't harm even if the voltages weren't updated earlier */ - _set_opp_voltage(dev, reg, old_opp->supplies); - - return ret; } static int _set_opp_bw(const struct opp_table *opp_table, @@ -978,36 +935,6 @@ static int _set_opp_bw(const struct opp_table *opp_table, return 0; } -static int _set_opp_custom(const struct opp_table *opp_table, - struct device *dev, struct dev_pm_opp *opp, - unsigned long freq) -{ - struct dev_pm_set_opp_data *data = opp_table->set_opp_data; - struct dev_pm_opp *old_opp = opp_table->current_opp; - int size; - - /* - * We support this only if dev_pm_opp_set_regulators() was called - * earlier. - */ - if (opp_table->sod_supplies) { - size = sizeof(*old_opp->supplies) * opp_table->regulator_count; - memcpy(data->old_opp.supplies, old_opp->supplies, size); - memcpy(data->new_opp.supplies, opp->supplies, size); - data->regulator_count = opp_table->regulator_count; - } else { - data->regulator_count = 0; - } - - data->regulators = opp_table->regulators; - data->clk = opp_table->clk; - data->dev = dev; - data->old_opp.rate = old_opp->rate; - data->new_opp.rate = freq; - - return opp_table->set_opp(data); -} - static int _set_required_opp(struct device *dev, struct device *pd_dev, struct dev_pm_opp *opp, int i) { @@ -1019,7 +946,7 @@ static int _set_required_opp(struct device *dev, struct device *pd_dev, ret = dev_pm_genpd_set_performance_state(pd_dev, pstate); if (ret) { - dev_err(dev, "Failed to set performance rate of %s: %d (%d)\n", + dev_err(dev, "Failed to set performance state of %s: %d (%d)\n", dev_name(pd_dev), pstate, ret); } @@ -1138,7 +1065,7 @@ static int _disable_opp_table(struct device *dev, struct opp_table *opp_table) } static int _set_opp(struct device *dev, struct opp_table *opp_table, - struct dev_pm_opp *opp, unsigned long freq) + struct dev_pm_opp *opp, void *clk_data, bool forced) { struct dev_pm_opp *old_opp; int scaling_down, ret; @@ -1153,18 +1080,17 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, old_opp = opp_table->current_opp; /* Return early if nothing to do */ - if (old_opp == opp && opp_table->current_rate == freq && - opp_table->enabled) { + if (!forced && old_opp == opp && opp_table->enabled) { dev_dbg(dev, "%s: OPPs are same, nothing to do\n", __func__); return 0; } dev_dbg(dev, "%s: switching OPP: Freq %lu -> %lu Hz, Level %u -> %u, Bw %u -> %u\n", - __func__, opp_table->current_rate, freq, old_opp->level, + __func__, old_opp->rates[0], opp->rates[0], old_opp->level, opp->level, old_opp->bandwidth ? old_opp->bandwidth[0].peak : 0, opp->bandwidth ? opp->bandwidth[0].peak : 0); - scaling_down = _opp_compare_key(old_opp, opp); + scaling_down = _opp_compare_key(opp_table, old_opp, opp); if (scaling_down == -1) scaling_down = 0; @@ -1181,23 +1107,38 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, dev_err(dev, "Failed to set bw: %d\n", ret); return ret; } - } - if (opp_table->set_opp) { - ret = _set_opp_custom(opp_table, dev, opp, freq); - } else if (opp_table->regulators) { - ret = _generic_set_opp_regulator(opp_table, dev, opp, freq, - scaling_down); - } else { - /* Only frequency scaling */ - ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); + if (opp_table->config_regulators) { + ret = opp_table->config_regulators(dev, old_opp, opp, + opp_table->regulators, + opp_table->regulator_count); + if (ret) { + dev_err(dev, "Failed to set regulator voltages: %d\n", + ret); + return ret; + } + } } - if (ret) - return ret; + if (opp_table->config_clks) { + ret = opp_table->config_clks(dev, opp_table, opp, clk_data, scaling_down); + if (ret) + return ret; + } /* Scaling down? Configure required OPPs after frequency */ if (scaling_down) { + if (opp_table->config_regulators) { + ret = opp_table->config_regulators(dev, old_opp, opp, + opp_table->regulators, + opp_table->regulator_count); + if (ret) { + dev_err(dev, "Failed to set regulator voltages: %d\n", + ret); + return ret; + } + } + ret = _set_opp_bw(opp_table, opp, dev); if (ret) { dev_err(dev, "Failed to set bw: %d\n", ret); @@ -1217,7 +1158,6 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, /* Make sure current_opp doesn't get freed */ dev_pm_opp_get(opp); opp_table->current_opp = opp; - opp_table->current_rate = freq; return ret; } @@ -1238,6 +1178,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) struct opp_table *opp_table; unsigned long freq = 0, temp_freq; struct dev_pm_opp *opp = NULL; + bool forced = false; int ret; opp_table = _find_opp_table(dev); @@ -1255,7 +1196,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) * equivalent to a clk_set_rate() */ if (!_get_opp_count(opp_table)) { - ret = _generic_set_opp_clk_only(dev, opp_table->clk, target_freq); + ret = opp_table->config_clks(dev, opp_table, NULL, + &target_freq, false); goto put_opp_table; } @@ -1276,12 +1218,22 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) __func__, freq, ret); goto put_opp_table; } + + /* + * An OPP entry specifies the highest frequency at which other + * properties of the OPP entry apply. Even if the new OPP is + * same as the old one, we may still reach here for a different + * value of the frequency. In such a case, do not abort but + * configure the hardware to the desired frequency forcefully. + */ + forced = opp_table->rate_clk_single != target_freq; } - ret = _set_opp(dev, opp_table, opp, freq); + ret = _set_opp(dev, opp_table, opp, &target_freq, forced); if (target_freq) dev_pm_opp_put(opp); + put_opp_table: dev_pm_opp_put_opp_table(opp_table); return ret; @@ -1309,7 +1261,7 @@ int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp) return PTR_ERR(opp_table); } - ret = _set_opp(dev, opp_table, opp, opp ? opp->rate : 0); + ret = _set_opp(dev, opp_table, opp, NULL, false); dev_pm_opp_put_opp_table(opp_table); return ret; @@ -1366,6 +1318,8 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index) INIT_LIST_HEAD(&opp_table->dev_list); INIT_LIST_HEAD(&opp_table->lazy); + opp_table->clk = ERR_PTR(-ENODEV); + /* Mark regulator count uninitialized */ opp_table->regulator_count = -1; @@ -1412,20 +1366,38 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, int ret; /* - * Return early if we don't need to get clk or we have already tried it + * Return early if we don't need to get clk or we have already done it * earlier. */ - if (!getclk || IS_ERR(opp_table) || opp_table->clk) + if (!getclk || IS_ERR(opp_table) || !IS_ERR(opp_table->clk) || + opp_table->clks) return opp_table; /* Find clk for the device */ opp_table->clk = clk_get(dev, NULL); ret = PTR_ERR_OR_ZERO(opp_table->clk); - if (!ret) + if (!ret) { + opp_table->config_clks = _opp_config_clk_single; + opp_table->clk_count = 1; return opp_table; + } if (ret == -ENOENT) { + /* + * There are few platforms which don't want the OPP core to + * manage device's clock settings. In such cases neither the + * platform provides the clks explicitly to us, nor the DT + * contains a valid clk entry. The OPP nodes in DT may still + * contain "opp-hz" property though, which we need to parse and + * allow the platform to find an OPP based on freq later on. + * + * This is a simple solution to take care of such corner cases, + * i.e. make the clk_count 1, which lets us allocate space for + * frequency in opp->rates and also parse the entries in DT. + */ + opp_table->clk_count = 1; + dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); return opp_table; } @@ -1528,7 +1500,7 @@ static void _opp_table_kref_release(struct kref *kref) _of_clear_opp_table(opp_table); - /* Release clk */ + /* Release automatically acquired single clk */ if (!IS_ERR(opp_table->clk)) clk_put(opp_table->clk); @@ -1581,7 +1553,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); + _of_clear_opp(opp_table, opp); opp_debug_remove_one(opp); kfree(opp); } @@ -1613,10 +1585,13 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) if (IS_ERR(opp_table)) return; + if (!assert_single_clk(opp_table)) + goto put_table; + mutex_lock(&opp_table->lock); list_for_each_entry(iter, &opp_table->opp_list, node) { - if (iter->rate == freq) { + if (iter->rates[0] == freq) { opp = iter; break; } @@ -1634,6 +1609,7 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) __func__, freq); } +put_table: /* Drop the reference taken by _find_opp_table() */ dev_pm_opp_put_opp_table(opp_table); } @@ -1720,26 +1696,31 @@ void dev_pm_opp_remove_all_dynamic(struct device *dev) } EXPORT_SYMBOL_GPL(dev_pm_opp_remove_all_dynamic); -struct dev_pm_opp *_opp_allocate(struct opp_table *table) +struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table) { struct dev_pm_opp *opp; - int supply_count, supply_size, icc_size; + int supply_count, supply_size, icc_size, clk_size; /* Allocate space for at least one supply */ - supply_count = table->regulator_count > 0 ? table->regulator_count : 1; + supply_count = opp_table->regulator_count > 0 ? + opp_table->regulator_count : 1; supply_size = sizeof(*opp->supplies) * supply_count; - icc_size = sizeof(*opp->bandwidth) * table->path_count; + clk_size = sizeof(*opp->rates) * opp_table->clk_count; + icc_size = sizeof(*opp->bandwidth) * opp_table->path_count; /* allocate new OPP node and supplies structures */ - opp = kzalloc(sizeof(*opp) + supply_size + icc_size, GFP_KERNEL); - + opp = kzalloc(sizeof(*opp) + supply_size + clk_size + icc_size, GFP_KERNEL); if (!opp) return NULL; - /* Put the supplies at the end of the OPP structure as an empty array */ + /* Put the supplies, bw and clock at the end of the OPP structure */ opp->supplies = (struct dev_pm_opp_supply *)(opp + 1); + + opp->rates = (unsigned long *)(opp->supplies + supply_count); + if (icc_size) - opp->bandwidth = (struct dev_pm_opp_icc_bw *)(opp->supplies + supply_count); + opp->bandwidth = (struct dev_pm_opp_icc_bw *)(opp->rates + opp_table->clk_count); + INIT_LIST_HEAD(&opp->node); return opp; @@ -1770,15 +1751,57 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp, return true; } -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2) +static int _opp_compare_rate(struct opp_table *opp_table, + struct dev_pm_opp *opp1, struct dev_pm_opp *opp2) +{ + int i; + + for (i = 0; i < opp_table->clk_count; i++) { + if (opp1->rates[i] != opp2->rates[i]) + return opp1->rates[i] < opp2->rates[i] ? -1 : 1; + } + + /* Same rates for both OPPs */ + return 0; +} + +static int _opp_compare_bw(struct opp_table *opp_table, struct dev_pm_opp *opp1, + struct dev_pm_opp *opp2) { - if (opp1->rate != opp2->rate) - return opp1->rate < opp2->rate ? -1 : 1; - if (opp1->bandwidth && opp2->bandwidth && - opp1->bandwidth[0].peak != opp2->bandwidth[0].peak) - return opp1->bandwidth[0].peak < opp2->bandwidth[0].peak ? -1 : 1; + int i; + + for (i = 0; i < opp_table->path_count; i++) { + if (opp1->bandwidth[i].peak != opp2->bandwidth[i].peak) + return opp1->bandwidth[i].peak < opp2->bandwidth[i].peak ? -1 : 1; + } + + /* Same bw for both OPPs */ + return 0; +} + +/* + * Returns + * 0: opp1 == opp2 + * 1: opp1 > opp2 + * -1: opp1 < opp2 + */ +int _opp_compare_key(struct opp_table *opp_table, struct dev_pm_opp *opp1, + struct dev_pm_opp *opp2) +{ + int ret; + + ret = _opp_compare_rate(opp_table, opp1, opp2); + if (ret) + return ret; + + ret = _opp_compare_bw(opp_table, opp1, opp2); + if (ret) + return ret; + if (opp1->level != opp2->level) return opp1->level < opp2->level ? -1 : 1; + + /* Duplicate OPPs */ return 0; } @@ -1798,7 +1821,7 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp, * loop. */ list_for_each_entry(opp, &opp_table->opp_list, node) { - opp_cmp = _opp_compare_key(new_opp, opp); + opp_cmp = _opp_compare_key(opp_table, new_opp, opp); if (opp_cmp > 0) { *head = &opp->node; continue; @@ -1809,8 +1832,8 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp, /* Duplicate OPPs */ dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", - __func__, opp->rate, opp->supplies[0].u_volt, - opp->available, new_opp->rate, + __func__, opp->rates[0], opp->supplies[0].u_volt, + opp->available, new_opp->rates[0], new_opp->supplies[0].u_volt, new_opp->available); /* Should we compare voltages for all regulators here ? */ @@ -1831,7 +1854,7 @@ void _required_opps_available(struct dev_pm_opp *opp, int count) opp->available = false; pr_warn("%s: OPP not supported by required OPP %pOF (%lu)\n", - __func__, opp->required_opps[i]->np, opp->rate); + __func__, opp->required_opps[i]->np, opp->rates[0]); return; } } @@ -1847,7 +1870,7 @@ void _required_opps_available(struct dev_pm_opp *opp, int count) * should be considered an error by the callers of _opp_add(). */ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, - struct opp_table *opp_table, bool rate_not_available) + struct opp_table *opp_table) { struct list_head *head; int ret; @@ -1872,7 +1895,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, if (!_opp_supported_by_regulators(new_opp, opp_table)) { new_opp->available = false; dev_warn(dev, "%s: OPP not supported by regulators (%lu)\n", - __func__, new_opp->rate); + __func__, new_opp->rates[0]); } /* required-opps not fully initialized yet */ @@ -1913,12 +1936,15 @@ int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long tol; int ret; + if (!assert_single_clk(opp_table)) + return -EINVAL; + new_opp = _opp_allocate(opp_table); if (!new_opp) return -ENOMEM; /* populate the opp table */ - new_opp->rate = freq; + new_opp->rates[0] = freq; tol = u_volt * opp_table->voltage_tolerance_v1 / 100; new_opp->supplies[0].u_volt = u_volt; new_opp->supplies[0].u_volt_min = u_volt - tol; @@ -1926,7 +1952,7 @@ int _opp_add_v1(struct opp_table *opp_table, struct device *dev, new_opp->available = true; new_opp->dynamic = dynamic; - ret = _opp_add(dev, new_opp, opp_table, false); + ret = _opp_add(dev, new_opp, opp_table); if (ret) { /* Don't return error for duplicate OPPs */ if (ret == -EBUSY) @@ -1948,7 +1974,7 @@ free_opp: } /** - * dev_pm_opp_set_supported_hw() - Set supported platforms + * _opp_set_supported_hw() - Set supported platforms * @dev: Device for which supported-hw has to be set. * @versions: Array of hierarchy of versions to match. * @count: Number of elements in the array. @@ -1958,87 +1984,42 @@ free_opp: * OPPs, which are available for those versions, based on its 'opp-supported-hw' * property. */ -struct opp_table *dev_pm_opp_set_supported_hw(struct device *dev, - const u32 *versions, unsigned int count) +static int _opp_set_supported_hw(struct opp_table *opp_table, + const u32 *versions, unsigned int count) { - struct opp_table *opp_table; - - opp_table = _add_opp_table(dev, false); - if (IS_ERR(opp_table)) - return opp_table; - - /* Make sure there are no concurrent readers while updating opp_table */ - WARN_ON(!list_empty(&opp_table->opp_list)); - /* Another CPU that shares the OPP table has set the property ? */ if (opp_table->supported_hw) - return opp_table; + return 0; opp_table->supported_hw = kmemdup(versions, count * sizeof(*versions), GFP_KERNEL); - if (!opp_table->supported_hw) { - dev_pm_opp_put_opp_table(opp_table); - return ERR_PTR(-ENOMEM); - } + if (!opp_table->supported_hw) + return -ENOMEM; opp_table->supported_hw_count = count; - return opp_table; + return 0; } -EXPORT_SYMBOL_GPL(dev_pm_opp_set_supported_hw); /** - * dev_pm_opp_put_supported_hw() - Releases resources blocked for supported hw - * @opp_table: OPP table returned by dev_pm_opp_set_supported_hw(). + * _opp_put_supported_hw() - Releases resources blocked for supported hw + * @opp_table: OPP table returned by _opp_set_supported_hw(). * * This is required only for the V2 bindings, and is called for a matching - * dev_pm_opp_set_supported_hw(). Until this is called, the opp_table structure + * _opp_set_supported_hw(). Until this is called, the opp_table structure * will not be freed. */ -void dev_pm_opp_put_supported_hw(struct opp_table *opp_table) -{ - if (unlikely(!opp_table)) - return; - - kfree(opp_table->supported_hw); - opp_table->supported_hw = NULL; - opp_table->supported_hw_count = 0; - - dev_pm_opp_put_opp_table(opp_table); -} -EXPORT_SYMBOL_GPL(dev_pm_opp_put_supported_hw); - -static void devm_pm_opp_supported_hw_release(void *data) +static void _opp_put_supported_hw(struct opp_table *opp_table) { - dev_pm_opp_put_supported_hw(data); -} - -/** - * devm_pm_opp_set_supported_hw() - Set supported platforms - * @dev: Device for which supported-hw has to be set. - * @versions: Array of hierarchy of versions to match. - * @count: Number of elements in the array. - * - * This is a resource-managed variant of dev_pm_opp_set_supported_hw(). - * - * Return: 0 on success and errorno otherwise. - */ -int devm_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, - unsigned int count) -{ - struct opp_table *opp_table; - - opp_table = dev_pm_opp_set_supported_hw(dev, versions, count); - if (IS_ERR(opp_table)) - return PTR_ERR(opp_table); - - return devm_add_action_or_reset(dev, devm_pm_opp_supported_hw_release, - opp_table); + if (opp_table->supported_hw) { + kfree(opp_table->supported_hw); + opp_table->supported_hw = NULL; + opp_table->supported_hw_count = 0; + } } -EXPORT_SYMBOL_GPL(devm_pm_opp_set_supported_hw); /** - * dev_pm_opp_set_prop_name() - Set prop-extn name + * _opp_set_prop_name() - Set prop-extn name * @dev: Device for which the prop-name has to be set. * @name: name to postfix to properties. * @@ -2047,53 +2028,36 @@ EXPORT_SYMBOL_GPL(devm_pm_opp_set_supported_hw); * which the extension will apply are opp-microvolt and opp-microamp. OPP core * should postfix the property name with -<name> while looking for them. */ -struct opp_table *dev_pm_opp_set_prop_name(struct device *dev, const char *name) +static int _opp_set_prop_name(struct opp_table *opp_table, const char *name) { - struct opp_table *opp_table; - - opp_table = _add_opp_table(dev, false); - if (IS_ERR(opp_table)) - return opp_table; - - /* Make sure there are no concurrent readers while updating opp_table */ - WARN_ON(!list_empty(&opp_table->opp_list)); - /* Another CPU that shares the OPP table has set the property ? */ - if (opp_table->prop_name) - return opp_table; - - opp_table->prop_name = kstrdup(name, GFP_KERNEL); if (!opp_table->prop_name) { - dev_pm_opp_put_opp_table(opp_table); - return ERR_PTR(-ENOMEM); + opp_table->prop_name = kstrdup(name, GFP_KERNEL); + if (!opp_table->prop_name) + return -ENOMEM; } - return opp_table; + return 0; } -EXPORT_SYMBOL_GPL(dev_pm_opp_set_prop_name); /** - * dev_pm_opp_put_prop_name() - Releases resources blocked for prop-name - * @opp_table: OPP table returned by dev_pm_opp_set_prop_name(). + * _opp_put_prop_name() - Releases resources blocked for prop-name + * @opp_table: OPP table returned by _opp_set_prop_name(). * * This is required only for the V2 bindings, and is called for a matching - * dev_pm_opp_set_prop_name(). Until this is called, the opp_table structure + * _opp_set_prop_name(). Until this is called, the opp_table structure * will not be freed. */ -void dev_pm_opp_put_prop_name(struct opp_table *opp_table) +static void _opp_put_prop_name(struct opp_table *opp_table) { - if (unlikely(!opp_table)) - return; - - kfree(opp_table->prop_name); - opp_table->prop_name = NULL; - - dev_pm_opp_put_opp_table(opp_table); + if (opp_table->prop_name) { + kfree(opp_table->prop_name); + opp_table->prop_name = NULL; + } } -EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name); /** - * dev_pm_opp_set_regulators() - Set regulator names for the device + * _opp_set_regulators() - Set regulator names for the device * @dev: Device for which regulator name is being set. * @names: Array of pointers to the names of the regulator. * @count: Number of regulators. @@ -2104,36 +2068,29 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name); * * This must be called before any OPPs are initialized for the device. */ -struct opp_table *dev_pm_opp_set_regulators(struct device *dev, - const char * const names[], - unsigned int count) +static int _opp_set_regulators(struct opp_table *opp_table, struct device *dev, + const char * const names[]) { - struct dev_pm_opp_supply *supplies; - struct opp_table *opp_table; + const char * const *temp = names; struct regulator *reg; - int ret, i; + int count = 0, ret, i; - opp_table = _add_opp_table(dev, false); - if (IS_ERR(opp_table)) - return opp_table; + /* Count number of regulators */ + while (*temp++) + count++; - /* This should be called before OPPs are initialized */ - if (WARN_ON(!list_empty(&opp_table->opp_list))) { - ret = -EBUSY; - goto err; - } + if (!count) + return -EINVAL; /* Another CPU that shares the OPP table has set the regulators ? */ if (opp_table->regulators) - return opp_table; + return 0; opp_table->regulators = kmalloc_array(count, sizeof(*opp_table->regulators), GFP_KERNEL); - if (!opp_table->regulators) { - ret = -ENOMEM; - goto err; - } + if (!opp_table->regulators) + return -ENOMEM; for (i = 0; i < count; i++) { reg = regulator_get_optional(dev, names[i]); @@ -2149,21 +2106,11 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev, opp_table->regulator_count = count; - supplies = kmalloc_array(count * 2, sizeof(*supplies), GFP_KERNEL); - if (!supplies) { - ret = -ENOMEM; - goto free_regulators; - } - - mutex_lock(&opp_table->lock); - opp_table->sod_supplies = supplies; - if (opp_table->set_opp_data) { - opp_table->set_opp_data->old_opp.supplies = supplies; - opp_table->set_opp_data->new_opp.supplies = supplies + count; - } - mutex_unlock(&opp_table->lock); + /* Set generic config_regulators() for single regulators here */ + if (count == 1) + opp_table->config_regulators = _opp_config_regulator_single; - return opp_table; + return 0; free_regulators: while (i != 0) @@ -2172,26 +2119,20 @@ free_regulators: kfree(opp_table->regulators); opp_table->regulators = NULL; opp_table->regulator_count = -1; -err: - dev_pm_opp_put_opp_table(opp_table); - return ERR_PTR(ret); + return ret; } -EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulators); /** - * dev_pm_opp_put_regulators() - Releases resources blocked for regulator - * @opp_table: OPP table returned from dev_pm_opp_set_regulators(). + * _opp_put_regulators() - Releases resources blocked for regulator + * @opp_table: OPP table returned from _opp_set_regulators(). */ -void dev_pm_opp_put_regulators(struct opp_table *opp_table) +static void _opp_put_regulators(struct opp_table *opp_table) { int i; - if (unlikely(!opp_table)) - return; - if (!opp_table->regulators) - goto put_opp_table; + return; if (opp_table->enabled) { for (i = opp_table->regulator_count - 1; i >= 0; i--) @@ -2201,252 +2142,158 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table) for (i = opp_table->regulator_count - 1; i >= 0; i--) regulator_put(opp_table->regulators[i]); - mutex_lock(&opp_table->lock); - if (opp_table->set_opp_data) { - opp_table->set_opp_data->old_opp.supplies = NULL; - opp_table->set_opp_data->new_opp.supplies = NULL; - } - - kfree(opp_table->sod_supplies); - opp_table->sod_supplies = NULL; - mutex_unlock(&opp_table->lock); - kfree(opp_table->regulators); opp_table->regulators = NULL; opp_table->regulator_count = -1; - -put_opp_table: - dev_pm_opp_put_opp_table(opp_table); } -EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators); -static void devm_pm_opp_regulators_release(void *data) +static void _put_clks(struct opp_table *opp_table, int count) { - dev_pm_opp_put_regulators(data); -} - -/** - * devm_pm_opp_set_regulators() - Set regulator names for the device - * @dev: Device for which regulator name is being set. - * @names: Array of pointers to the names of the regulator. - * @count: Number of regulators. - * - * This is a resource-managed variant of dev_pm_opp_set_regulators(). - * - * Return: 0 on success and errorno otherwise. - */ -int devm_pm_opp_set_regulators(struct device *dev, - const char * const names[], - unsigned int count) -{ - struct opp_table *opp_table; + int i; - opp_table = dev_pm_opp_set_regulators(dev, names, count); - if (IS_ERR(opp_table)) - return PTR_ERR(opp_table); + for (i = count - 1; i >= 0; i--) + clk_put(opp_table->clks[i]); - return devm_add_action_or_reset(dev, devm_pm_opp_regulators_release, - opp_table); + kfree(opp_table->clks); + opp_table->clks = NULL; } -EXPORT_SYMBOL_GPL(devm_pm_opp_set_regulators); /** - * dev_pm_opp_set_clkname() - Set clk name for the device - * @dev: Device for which clk name is being set. - * @name: Clk name. - * - * In order to support OPP switching, OPP layer needs to get pointer to the - * clock for the device. Simple cases work fine without using this routine (i.e. - * by passing connection-id as NULL), but for a device with multiple clocks - * available, the OPP core needs to know the exact name of the clk to use. + * _opp_set_clknames() - Set clk names for the device + * @dev: Device for which clk names is being set. + * @names: Clk names. + * + * In order to support OPP switching, OPP layer needs to get pointers to the + * clocks for the device. Simple cases work fine without using this routine + * (i.e. by passing connection-id as NULL), but for a device with multiple + * clocks available, the OPP core needs to know the exact names of the clks to + * use. * * This must be called before any OPPs are initialized for the device. */ -struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name) +static int _opp_set_clknames(struct opp_table *opp_table, struct device *dev, + const char * const names[], + config_clks_t config_clks) { - struct opp_table *opp_table; - int ret; + const char * const *temp = names; + int count = 0, ret, i; + struct clk *clk; - opp_table = _add_opp_table(dev, false); - if (IS_ERR(opp_table)) - return opp_table; + /* Count number of clks */ + while (*temp++) + count++; - /* This should be called before OPPs are initialized */ - if (WARN_ON(!list_empty(&opp_table->opp_list))) { - ret = -EBUSY; - goto err; - } + /* + * This is a special case where we have a single clock, whose connection + * id name is NULL, i.e. first two entries are NULL in the array. + */ + if (!count && !names[1]) + count = 1; - /* clk shouldn't be initialized at this point */ - if (WARN_ON(opp_table->clk)) { - ret = -EBUSY; - goto err; - } + /* Fail early for invalid configurations */ + if (!count || (!config_clks && count > 1)) + return -EINVAL; - /* Find clk for the device */ - opp_table->clk = clk_get(dev, name); - if (IS_ERR(opp_table->clk)) { - ret = dev_err_probe(dev, PTR_ERR(opp_table->clk), - "%s: Couldn't find clock\n", __func__); - goto err; - } + /* Another CPU that shares the OPP table has set the clkname ? */ + if (opp_table->clks) + return 0; - return opp_table; + opp_table->clks = kmalloc_array(count, sizeof(*opp_table->clks), + GFP_KERNEL); + if (!opp_table->clks) + return -ENOMEM; -err: - dev_pm_opp_put_opp_table(opp_table); + /* Find clks for the device */ + for (i = 0; i < count; i++) { + clk = clk_get(dev, names[i]); + if (IS_ERR(clk)) { + ret = dev_err_probe(dev, PTR_ERR(clk), + "%s: Couldn't find clock with name: %s\n", + __func__, names[i]); + goto free_clks; + } - return ERR_PTR(ret); -} -EXPORT_SYMBOL_GPL(dev_pm_opp_set_clkname); + opp_table->clks[i] = clk; + } -/** - * dev_pm_opp_put_clkname() - Releases resources blocked for clk. - * @opp_table: OPP table returned from dev_pm_opp_set_clkname(). - */ -void dev_pm_opp_put_clkname(struct opp_table *opp_table) -{ - if (unlikely(!opp_table)) - return; + opp_table->clk_count = count; + opp_table->config_clks = config_clks; - clk_put(opp_table->clk); - opp_table->clk = ERR_PTR(-EINVAL); + /* Set generic single clk set here */ + if (count == 1) { + if (!opp_table->config_clks) + opp_table->config_clks = _opp_config_clk_single; - dev_pm_opp_put_opp_table(opp_table); -} -EXPORT_SYMBOL_GPL(dev_pm_opp_put_clkname); + /* + * We could have just dropped the "clk" field and used "clks" + * everywhere. Instead we kept the "clk" field around for + * following reasons: + * + * - avoiding clks[0] everywhere else. + * - not running single clk helpers for multiple clk usecase by + * mistake. + * + * Since this is single-clk case, just update the clk pointer + * too. + */ + opp_table->clk = opp_table->clks[0]; + } -static void devm_pm_opp_clkname_release(void *data) -{ - dev_pm_opp_put_clkname(data); + return 0; + +free_clks: + _put_clks(opp_table, i); + return ret; } /** - * devm_pm_opp_set_clkname() - Set clk name for the device - * @dev: Device for which clk name is being set. - * @name: Clk name. - * - * This is a resource-managed variant of dev_pm_opp_set_clkname(). - * - * Return: 0 on success and errorno otherwise. + * _opp_put_clknames() - Releases resources blocked for clks. + * @opp_table: OPP table returned from _opp_set_clknames(). */ -int devm_pm_opp_set_clkname(struct device *dev, const char *name) +static void _opp_put_clknames(struct opp_table *opp_table) { - struct opp_table *opp_table; + if (!opp_table->clks) + return; - opp_table = dev_pm_opp_set_clkname(dev, name); - if (IS_ERR(opp_table)) - return PTR_ERR(opp_table); + opp_table->config_clks = NULL; + opp_table->clk = ERR_PTR(-ENODEV); - return devm_add_action_or_reset(dev, devm_pm_opp_clkname_release, - opp_table); + _put_clks(opp_table, opp_table->clk_count); } -EXPORT_SYMBOL_GPL(devm_pm_opp_set_clkname); /** - * dev_pm_opp_register_set_opp_helper() - Register custom set OPP helper + * _opp_set_config_regulators_helper() - Register custom set regulator helper. * @dev: Device for which the helper is getting registered. - * @set_opp: Custom set OPP helper. + * @config_regulators: Custom set regulator helper. * - * This is useful to support complex platforms (like platforms with multiple - * regulators per device), instead of the generic OPP set rate helper. + * This is useful to support platforms with multiple regulators per device. * * This must be called before any OPPs are initialized for the device. */ -struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, - int (*set_opp)(struct dev_pm_set_opp_data *data)) +static int _opp_set_config_regulators_helper(struct opp_table *opp_table, + struct device *dev, config_regulators_t config_regulators) { - struct dev_pm_set_opp_data *data; - struct opp_table *opp_table; - - if (!set_opp) - return ERR_PTR(-EINVAL); - - opp_table = _add_opp_table(dev, false); - if (IS_ERR(opp_table)) - return opp_table; - - /* This should be called before OPPs are initialized */ - if (WARN_ON(!list_empty(&opp_table->opp_list))) { - dev_pm_opp_put_opp_table(opp_table); - return ERR_PTR(-EBUSY); - } - /* Another CPU that shares the OPP table has set the helper ? */ - if (opp_table->set_opp) - return opp_table; + if (!opp_table->config_regulators) + opp_table->config_regulators = config_regulators; - data = kzalloc(sizeof(*data), GFP_KERNEL); - if (!data) - return ERR_PTR(-ENOMEM); - - mutex_lock(&opp_table->lock); - opp_table->set_opp_data = data; - if (opp_table->sod_supplies) { - data->old_opp.supplies = opp_table->sod_supplies; - data->new_opp.supplies = opp_table->sod_supplies + - opp_table->regulator_count; - } - mutex_unlock(&opp_table->lock); - - opp_table->set_opp = set_opp; - - return opp_table; -} -EXPORT_SYMBOL_GPL(dev_pm_opp_register_set_opp_helper); - -/** - * dev_pm_opp_unregister_set_opp_helper() - Releases resources blocked for - * set_opp helper - * @opp_table: OPP table returned from dev_pm_opp_register_set_opp_helper(). - * - * Release resources blocked for platform specific set_opp helper. - */ -void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table) -{ - if (unlikely(!opp_table)) - return; - - opp_table->set_opp = NULL; - - mutex_lock(&opp_table->lock); - kfree(opp_table->set_opp_data); - opp_table->set_opp_data = NULL; - mutex_unlock(&opp_table->lock); - - dev_pm_opp_put_opp_table(opp_table); -} -EXPORT_SYMBOL_GPL(dev_pm_opp_unregister_set_opp_helper); - -static void devm_pm_opp_unregister_set_opp_helper(void *data) -{ - dev_pm_opp_unregister_set_opp_helper(data); + return 0; } /** - * devm_pm_opp_register_set_opp_helper() - Register custom set OPP helper - * @dev: Device for which the helper is getting registered. - * @set_opp: Custom set OPP helper. - * - * This is a resource-managed version of dev_pm_opp_register_set_opp_helper(). + * _opp_put_config_regulators_helper() - Releases resources blocked for + * config_regulators helper. + * @opp_table: OPP table returned from _opp_set_config_regulators_helper(). * - * Return: 0 on success and errorno otherwise. + * Release resources blocked for platform specific config_regulators helper. */ -int devm_pm_opp_register_set_opp_helper(struct device *dev, - int (*set_opp)(struct dev_pm_set_opp_data *data)) +static void _opp_put_config_regulators_helper(struct opp_table *opp_table) { - struct opp_table *opp_table; - - opp_table = dev_pm_opp_register_set_opp_helper(dev, set_opp); - if (IS_ERR(opp_table)) - return PTR_ERR(opp_table); - - return devm_add_action_or_reset(dev, devm_pm_opp_unregister_set_opp_helper, - opp_table); + if (opp_table->config_regulators) + opp_table->config_regulators = NULL; } -EXPORT_SYMBOL_GPL(devm_pm_opp_register_set_opp_helper); -static void _opp_detach_genpd(struct opp_table *opp_table) +static void _detach_genpd(struct opp_table *opp_table) { int index; @@ -2466,7 +2313,7 @@ static void _opp_detach_genpd(struct opp_table *opp_table) } /** - * dev_pm_opp_attach_genpd - Attach genpd(s) for the device and save virtual device pointer + * _opp_attach_genpd - Attach genpd(s) for the device and save virtual device pointer * @dev: Consumer device for which the genpd is getting attached. * @names: Null terminated array of pointers containing names of genpd to attach. * @virt_devs: Pointer to return the array of virtual devices. @@ -2487,30 +2334,23 @@ static void _opp_detach_genpd(struct opp_table *opp_table) * The order of entries in the names array must match the order in which * "required-opps" are added in DT. */ -struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, - const char * const *names, struct device ***virt_devs) +static int _opp_attach_genpd(struct opp_table *opp_table, struct device *dev, + const char * const *names, struct device ***virt_devs) { - struct opp_table *opp_table; struct device *virt_dev; int index = 0, ret = -EINVAL; const char * const *name = names; - opp_table = _add_opp_table(dev, false); - if (IS_ERR(opp_table)) - return opp_table; - if (opp_table->genpd_virt_devs) - return opp_table; + return 0; /* * If the genpd's OPP table isn't already initialized, parsing of the * required-opps fail for dev. We should retry this after genpd's OPP * table is added. */ - if (!opp_table->required_opp_count) { - ret = -EPROBE_DEFER; - goto put_table; - } + if (!opp_table->required_opp_count) + return -EPROBE_DEFER; mutex_lock(&opp_table->genpd_virt_dev_lock); @@ -2528,8 +2368,8 @@ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, } virt_dev = dev_pm_domain_attach_by_name(dev, *name); - if (IS_ERR(virt_dev)) { - ret = PTR_ERR(virt_dev); + if (IS_ERR_OR_NULL(virt_dev)) { + ret = PTR_ERR(virt_dev) ? : -ENODEV; dev_err(dev, "Couldn't attach to pm_domain: %d\n", ret); goto err; } @@ -2543,73 +2383,230 @@ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, *virt_devs = opp_table->genpd_virt_devs; mutex_unlock(&opp_table->genpd_virt_dev_lock); - return opp_table; + return 0; err: - _opp_detach_genpd(opp_table); + _detach_genpd(opp_table); unlock: mutex_unlock(&opp_table->genpd_virt_dev_lock); + return ret; -put_table: - dev_pm_opp_put_opp_table(opp_table); - - return ERR_PTR(ret); } -EXPORT_SYMBOL_GPL(dev_pm_opp_attach_genpd); /** - * dev_pm_opp_detach_genpd() - Detach genpd(s) from the device. - * @opp_table: OPP table returned by dev_pm_opp_attach_genpd(). + * _opp_detach_genpd() - Detach genpd(s) from the device. + * @opp_table: OPP table returned by _opp_attach_genpd(). * * This detaches the genpd(s), resets the virtual device pointers, and puts the * OPP table. */ -void dev_pm_opp_detach_genpd(struct opp_table *opp_table) +static void _opp_detach_genpd(struct opp_table *opp_table) { - if (unlikely(!opp_table)) - return; - /* * Acquire genpd_virt_dev_lock to make sure virt_dev isn't getting * used in parallel. */ mutex_lock(&opp_table->genpd_virt_dev_lock); - _opp_detach_genpd(opp_table); + _detach_genpd(opp_table); mutex_unlock(&opp_table->genpd_virt_dev_lock); - - dev_pm_opp_put_opp_table(opp_table); } -EXPORT_SYMBOL_GPL(dev_pm_opp_detach_genpd); -static void devm_pm_opp_detach_genpd(void *data) +static void _opp_clear_config(struct opp_config_data *data) { - dev_pm_opp_detach_genpd(data); + if (data->flags & OPP_CONFIG_GENPD) + _opp_detach_genpd(data->opp_table); + if (data->flags & OPP_CONFIG_REGULATOR) + _opp_put_regulators(data->opp_table); + if (data->flags & OPP_CONFIG_SUPPORTED_HW) + _opp_put_supported_hw(data->opp_table); + if (data->flags & OPP_CONFIG_REGULATOR_HELPER) + _opp_put_config_regulators_helper(data->opp_table); + if (data->flags & OPP_CONFIG_PROP_NAME) + _opp_put_prop_name(data->opp_table); + if (data->flags & OPP_CONFIG_CLK) + _opp_put_clknames(data->opp_table); + + dev_pm_opp_put_opp_table(data->opp_table); + kfree(data); } /** - * devm_pm_opp_attach_genpd - Attach genpd(s) for the device and save virtual - * device pointer - * @dev: Consumer device for which the genpd is getting attached. - * @names: Null terminated array of pointers containing names of genpd to attach. - * @virt_devs: Pointer to return the array of virtual devices. + * dev_pm_opp_set_config() - Set OPP configuration for the device. + * @dev: Device for which configuration is being set. + * @config: OPP configuration. * - * This is a resource-managed version of dev_pm_opp_attach_genpd(). + * This allows all device OPP configurations to be performed at once. * - * Return: 0 on success and errorno otherwise. + * This must be called before any OPPs are initialized for the device. This may + * be called multiple times for the same OPP table, for example once for each + * CPU that share the same table. This must be balanced by the same number of + * calls to dev_pm_opp_clear_config() in order to free the OPP table properly. + * + * This returns a token to the caller, which must be passed to + * dev_pm_opp_clear_config() to free the resources later. The value of the + * returned token will be >= 1 for success and negative for errors. The minimum + * value of 1 is chosen here to make it easy for callers to manage the resource. */ -int devm_pm_opp_attach_genpd(struct device *dev, const char * const *names, - struct device ***virt_devs) +int dev_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config) { struct opp_table *opp_table; + struct opp_config_data *data; + unsigned int id; + int ret; - opp_table = dev_pm_opp_attach_genpd(dev, names, virt_devs); - if (IS_ERR(opp_table)) + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + opp_table = _add_opp_table(dev, false); + if (IS_ERR(opp_table)) { + kfree(data); return PTR_ERR(opp_table); + } + + data->opp_table = opp_table; + data->flags = 0; + + /* This should be called before OPPs are initialized */ + if (WARN_ON(!list_empty(&opp_table->opp_list))) { + ret = -EBUSY; + goto err; + } + + /* Configure clocks */ + if (config->clk_names) { + ret = _opp_set_clknames(opp_table, dev, config->clk_names, + config->config_clks); + if (ret) + goto err; + + data->flags |= OPP_CONFIG_CLK; + } else if (config->config_clks) { + /* Don't allow config callback without clocks */ + ret = -EINVAL; + goto err; + } + + /* Configure property names */ + if (config->prop_name) { + ret = _opp_set_prop_name(opp_table, config->prop_name); + if (ret) + goto err; + + data->flags |= OPP_CONFIG_PROP_NAME; + } + + /* Configure config_regulators helper */ + if (config->config_regulators) { + ret = _opp_set_config_regulators_helper(opp_table, dev, + config->config_regulators); + if (ret) + goto err; + + data->flags |= OPP_CONFIG_REGULATOR_HELPER; + } - return devm_add_action_or_reset(dev, devm_pm_opp_detach_genpd, - opp_table); + /* Configure supported hardware */ + if (config->supported_hw) { + ret = _opp_set_supported_hw(opp_table, config->supported_hw, + config->supported_hw_count); + if (ret) + goto err; + + data->flags |= OPP_CONFIG_SUPPORTED_HW; + } + + /* Configure supplies */ + if (config->regulator_names) { + ret = _opp_set_regulators(opp_table, dev, + config->regulator_names); + if (ret) + goto err; + + data->flags |= OPP_CONFIG_REGULATOR; + } + + /* Attach genpds */ + if (config->genpd_names) { + ret = _opp_attach_genpd(opp_table, dev, config->genpd_names, + config->virt_devs); + if (ret) + goto err; + + data->flags |= OPP_CONFIG_GENPD; + } + + ret = xa_alloc(&opp_configs, &id, data, XA_LIMIT(1, INT_MAX), + GFP_KERNEL); + if (ret) + goto err; + + return id; + +err: + _opp_clear_config(data); + return ret; } -EXPORT_SYMBOL_GPL(devm_pm_opp_attach_genpd); +EXPORT_SYMBOL_GPL(dev_pm_opp_set_config); + +/** + * dev_pm_opp_clear_config() - Releases resources blocked for OPP configuration. + * @opp_table: OPP table returned from dev_pm_opp_set_config(). + * + * This allows all device OPP configurations to be cleared at once. This must be + * called once for each call made to dev_pm_opp_set_config(), in order to free + * the OPPs properly. + * + * Currently the first call itself ends up freeing all the OPP configurations, + * while the later ones only drop the OPP table reference. This works well for + * now as we would never want to use an half initialized OPP table and want to + * remove the configurations together. + */ +void dev_pm_opp_clear_config(int token) +{ + struct opp_config_data *data; + + /* + * This lets the callers call this unconditionally and keep their code + * simple. + */ + if (unlikely(token <= 0)) + return; + + data = xa_erase(&opp_configs, token); + if (WARN_ON(!data)) + return; + + _opp_clear_config(data); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_clear_config); + +static void devm_pm_opp_config_release(void *token) +{ + dev_pm_opp_clear_config((unsigned long)token); +} + +/** + * devm_pm_opp_set_config() - Set OPP configuration for the device. + * @dev: Device for which configuration is being set. + * @config: OPP configuration. + * + * This allows all device OPP configurations to be performed at once. + * This is a resource-managed variant of dev_pm_opp_set_config(). + * + * Return: 0 on success and errorno otherwise. + */ +int devm_pm_opp_set_config(struct device *dev, struct dev_pm_opp_config *config) +{ + int token = dev_pm_opp_set_config(dev, config); + + if (token < 0) + return token; + + return devm_add_action_or_reset(dev, devm_pm_opp_config_release, + (void *) ((unsigned long) token)); +} +EXPORT_SYMBOL_GPL(devm_pm_opp_set_config); /** * dev_pm_opp_xlate_required_opp() - Find required OPP for @src_table OPP. @@ -2795,11 +2792,16 @@ static int _opp_set_availability(struct device *dev, unsigned long freq, return r; } + if (!assert_single_clk(opp_table)) { + r = -EINVAL; + goto put_table; + } + mutex_lock(&opp_table->lock); /* Do we have the frequency? */ list_for_each_entry(tmp_opp, &opp_table->opp_list, node) { - if (tmp_opp->rate == freq) { + if (tmp_opp->rates[0] == freq) { opp = tmp_opp; break; } @@ -2866,11 +2868,16 @@ int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq, return r; } + if (!assert_single_clk(opp_table)) { + r = -EINVAL; + goto put_table; + } + mutex_lock(&opp_table->lock); /* Do we have the frequency? */ list_for_each_entry(tmp_opp, &opp_table->opp_list, node) { - if (tmp_opp->rate == freq) { + if (tmp_opp->rates[0] == freq) { opp = tmp_opp; break; } @@ -2897,11 +2904,11 @@ int dev_pm_opp_adjust_voltage(struct device *dev, unsigned long freq, opp); dev_pm_opp_put(opp); - goto adjust_put_table; + goto put_table; adjust_unlock: mutex_unlock(&opp_table->lock); -adjust_put_table: +put_table: dev_pm_opp_put_opp_table(opp_table); return r; } diff --git a/drivers/opp/cpu.c b/drivers/opp/cpu.c index 5004335cf0de..3c3506021501 100644 --- a/drivers/opp/cpu.c +++ b/drivers/opp/cpu.c @@ -41,7 +41,7 @@ * the table if any of the mentioned functions have been invoked in the interim. */ int dev_pm_opp_init_cpufreq_table(struct device *dev, - struct cpufreq_frequency_table **table) + struct cpufreq_frequency_table **opp_table) { struct dev_pm_opp *opp; struct cpufreq_frequency_table *freq_table = NULL; @@ -76,7 +76,7 @@ int dev_pm_opp_init_cpufreq_table(struct device *dev, freq_table[i].driver_data = i; freq_table[i].frequency = CPUFREQ_TABLE_END; - *table = &freq_table[0]; + *opp_table = &freq_table[0]; out: if (ret) @@ -94,13 +94,13 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_init_cpufreq_table); * Free up the table allocated by dev_pm_opp_init_cpufreq_table */ void dev_pm_opp_free_cpufreq_table(struct device *dev, - struct cpufreq_frequency_table **table) + struct cpufreq_frequency_table **opp_table) { - if (!table) + if (!opp_table) return; - kfree(*table); - *table = NULL; + kfree(*opp_table); + *opp_table = NULL; } EXPORT_SYMBOL_GPL(dev_pm_opp_free_cpufreq_table); #endif /* CONFIG_CPU_FREQ */ diff --git a/drivers/opp/debugfs.c b/drivers/opp/debugfs.c index 1b6e5c55c3ed..96a30a032c5f 100644 --- a/drivers/opp/debugfs.c +++ b/drivers/opp/debugfs.c @@ -74,6 +74,24 @@ static void opp_debug_create_bw(struct dev_pm_opp *opp, } } +static void opp_debug_create_clks(struct dev_pm_opp *opp, + struct opp_table *opp_table, + struct dentry *pdentry) +{ + char name[12]; + int i; + + if (opp_table->clk_count == 1) { + debugfs_create_ulong("rate_hz", S_IRUGO, pdentry, &opp->rates[0]); + return; + } + + for (i = 0; i < opp_table->clk_count; i++) { + snprintf(name, sizeof(name), "rate_hz_%d", i); + debugfs_create_ulong(name, S_IRUGO, pdentry, &opp->rates[i]); + } +} + static void opp_debug_create_supplies(struct dev_pm_opp *opp, struct opp_table *opp_table, struct dentry *pdentry) @@ -117,10 +135,11 @@ void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) * Get directory name for OPP. * * - Normally rate is unique to each OPP, use it to get unique opp-name. - * - For some devices rate isn't available, use index instead. + * - For some devices rate isn't available or there are multiple, use + * index instead for them. */ - if (likely(opp->rate)) - id = opp->rate; + if (likely(opp_table->clk_count == 1 && opp->rates[0])) + id = opp->rates[0]; else id = _get_opp_count(opp_table); @@ -134,7 +153,6 @@ void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) debugfs_create_bool("turbo", S_IRUGO, d, &opp->turbo); debugfs_create_bool("suspend", S_IRUGO, d, &opp->suspend); debugfs_create_u32("performance_state", S_IRUGO, d, &opp->pstate); - debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate); debugfs_create_u32("level", S_IRUGO, d, &opp->level); debugfs_create_ulong("clock_latency_ns", S_IRUGO, d, &opp->clock_latency_ns); @@ -142,6 +160,7 @@ void opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) opp->of_name = of_node_full_name(opp->np); debugfs_create_str("of_name", S_IRUGO, d, (char **)&opp->of_name); + opp_debug_create_clks(opp, opp_table, d); opp_debug_create_supplies(opp, opp_table, d); opp_debug_create_bw(opp, opp_table, d); diff --git a/drivers/opp/of.c b/drivers/opp/of.c index eb89c9a75985..605d68673f92 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -242,20 +242,20 @@ void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, opp_table->np = opp_np; _opp_table_alloc_required_tables(opp_table, dev, opp_np); - of_node_put(opp_np); } void _of_clear_opp_table(struct opp_table *opp_table) { _opp_table_free_required_tables(opp_table); + of_node_put(opp_table->np); } /* * Release all resources previously acquired with a call to * _of_opp_alloc_required_opps(). */ -void _of_opp_free_required_opps(struct opp_table *opp_table, - struct dev_pm_opp *opp) +static void _of_opp_free_required_opps(struct opp_table *opp_table, + struct dev_pm_opp *opp) { struct dev_pm_opp **required_opps = opp->required_opps; int i; @@ -275,6 +275,12 @@ void _of_opp_free_required_opps(struct opp_table *opp_table, kfree(required_opps); } +void _of_clear_opp(struct opp_table *opp_table, struct dev_pm_opp *opp) +{ + _of_opp_free_required_opps(opp_table, opp); + of_node_put(opp->np); +} + /* Populate all required OPPs which are part of "required-opps" list */ static int _of_opp_alloc_required_opps(struct opp_table *opp_table, struct dev_pm_opp *opp) @@ -767,7 +773,51 @@ void dev_pm_opp_of_remove_table(struct device *dev) } EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table); -static int _read_bw(struct dev_pm_opp *new_opp, struct opp_table *table, +static int _read_rate(struct dev_pm_opp *new_opp, struct opp_table *opp_table, + struct device_node *np) +{ + struct property *prop; + int i, count, ret; + u64 *rates; + + prop = of_find_property(np, "opp-hz", NULL); + if (!prop) + return -ENODEV; + + count = prop->length / sizeof(u64); + if (opp_table->clk_count != count) { + pr_err("%s: Count mismatch between opp-hz and clk_count (%d %d)\n", + __func__, count, opp_table->clk_count); + return -EINVAL; + } + + rates = kmalloc_array(count, sizeof(*rates), GFP_KERNEL); + if (!rates) + return -ENOMEM; + + ret = of_property_read_u64_array(np, "opp-hz", rates, count); + if (ret) { + pr_err("%s: Error parsing opp-hz: %d\n", __func__, ret); + } else { + /* + * Rate is defined as an unsigned long in clk API, and so + * casting explicitly to its type. Must be fixed once rate is 64 + * bit guaranteed in clk API. + */ + for (i = 0; i < count; i++) { + new_opp->rates[i] = (unsigned long)rates[i]; + + /* This will happen for frequencies > 4.29 GHz */ + WARN_ON(new_opp->rates[i] != rates[i]); + } + } + + kfree(rates); + + return ret; +} + +static int _read_bw(struct dev_pm_opp *new_opp, struct opp_table *opp_table, struct device_node *np, bool peak) { const char *name = peak ? "opp-peak-kBps" : "opp-avg-kBps"; @@ -780,9 +830,9 @@ static int _read_bw(struct dev_pm_opp *new_opp, struct opp_table *table, return -ENODEV; count = prop->length / sizeof(u32); - if (table->path_count != count) { + if (opp_table->path_count != count) { pr_err("%s: Mismatch between %s and paths (%d %d)\n", - __func__, name, count, table->path_count); + __func__, name, count, opp_table->path_count); return -EINVAL; } @@ -808,34 +858,27 @@ out: return ret; } -static int _read_opp_key(struct dev_pm_opp *new_opp, struct opp_table *table, - struct device_node *np, bool *rate_not_available) +static int _read_opp_key(struct dev_pm_opp *new_opp, + struct opp_table *opp_table, struct device_node *np) { bool found = false; - u64 rate; int ret; - ret = of_property_read_u64(np, "opp-hz", &rate); - if (!ret) { - /* - * Rate is defined as an unsigned long in clk API, and so - * casting explicitly to its type. Must be fixed once rate is 64 - * bit guaranteed in clk API. - */ - new_opp->rate = (unsigned long)rate; + ret = _read_rate(new_opp, opp_table, np); + if (!ret) found = true; - } - *rate_not_available = !!ret; + else if (ret != -ENODEV) + return ret; /* * Bandwidth consists of peak and average (optional) values: * opp-peak-kBps = <path1_value path2_value>; * opp-avg-kBps = <path1_value path2_value>; */ - ret = _read_bw(new_opp, table, np, true); + ret = _read_bw(new_opp, opp_table, np, true); if (!ret) { found = true; - ret = _read_bw(new_opp, table, np, false); + ret = _read_bw(new_opp, opp_table, np, false); } /* The properties were found but we failed to parse them */ @@ -881,13 +924,12 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, struct dev_pm_opp *new_opp; u32 val; int ret; - bool rate_not_available = false; new_opp = _opp_allocate(opp_table); if (!new_opp) return ERR_PTR(-ENOMEM); - ret = _read_opp_key(new_opp, opp_table, np, &rate_not_available); + ret = _read_opp_key(new_opp, opp_table, np); if (ret < 0) { dev_err(dev, "%s: opp key field not found\n", __func__); goto free_opp; @@ -895,14 +937,14 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, /* Check if the OPP supports hardware's hierarchy of versions or not */ if (!_opp_is_supported(dev, opp_table, np)) { - dev_dbg(dev, "OPP not supported by hardware: %lu\n", - new_opp->rate); + dev_dbg(dev, "OPP not supported by hardware: %s\n", + of_node_full_name(np)); goto free_opp; } new_opp->turbo = of_property_read_bool(np, "turbo-mode"); - new_opp->np = np; + new_opp->np = of_node_get(np); new_opp->dynamic = false; new_opp->available = true; @@ -920,7 +962,7 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, if (opp_table->is_genpd) new_opp->pstate = pm_genpd_opp_to_performance_state(dev, new_opp); - ret = _opp_add(dev, new_opp, opp_table, rate_not_available); + ret = _opp_add(dev, new_opp, opp_table); if (ret) { /* Don't return error for duplicate OPPs */ if (ret == -EBUSY) @@ -931,8 +973,8 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, /* OPP to select on device suspend */ if (of_property_read_bool(np, "opp-suspend")) { if (opp_table->suspend_opp) { - /* Pick the OPP with higher rate as suspend OPP */ - if (new_opp->rate > opp_table->suspend_opp->rate) { + /* Pick the OPP with higher rate/bw/level as suspend OPP */ + if (_opp_compare_key(opp_table, new_opp, opp_table->suspend_opp) == 1) { opp_table->suspend_opp->suspend = false; new_opp->suspend = true; opp_table->suspend_opp = new_opp; @@ -947,7 +989,7 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, opp_table->clock_latency_ns_max = new_opp->clock_latency_ns; pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu level:%u\n", - __func__, new_opp->turbo, new_opp->rate, + __func__, new_opp->turbo, new_opp->rates[0], new_opp->supplies[0].u_volt, new_opp->supplies[0].u_volt_min, new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns, new_opp->level); @@ -1084,7 +1126,7 @@ remove_static_opp: return ret; } -static int _of_add_table_indexed(struct device *dev, int index, bool getclk) +static int _of_add_table_indexed(struct device *dev, int index) { struct opp_table *opp_table; int ret, count; @@ -1100,7 +1142,7 @@ static int _of_add_table_indexed(struct device *dev, int index, bool getclk) index = 0; } - opp_table = _add_opp_table_indexed(dev, index, getclk); + opp_table = _add_opp_table_indexed(dev, index, true); if (IS_ERR(opp_table)) return PTR_ERR(opp_table); @@ -1124,11 +1166,11 @@ static void devm_pm_opp_of_table_release(void *data) dev_pm_opp_of_remove_table(data); } -static int _devm_of_add_table_indexed(struct device *dev, int index, bool getclk) +static int _devm_of_add_table_indexed(struct device *dev, int index) { int ret; - ret = _of_add_table_indexed(dev, index, getclk); + ret = _of_add_table_indexed(dev, index); if (ret) return ret; @@ -1156,7 +1198,7 @@ static int _devm_of_add_table_indexed(struct device *dev, int index, bool getclk */ int devm_pm_opp_of_add_table(struct device *dev) { - return _devm_of_add_table_indexed(dev, 0, true); + return _devm_of_add_table_indexed(dev, 0); } EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table); @@ -1179,7 +1221,7 @@ EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table); */ int dev_pm_opp_of_add_table(struct device *dev) { - return _of_add_table_indexed(dev, 0, true); + return _of_add_table_indexed(dev, 0); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); @@ -1195,7 +1237,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); */ int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) { - return _of_add_table_indexed(dev, index, true); + return _of_add_table_indexed(dev, index); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed); @@ -1208,42 +1250,10 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed); */ int devm_pm_opp_of_add_table_indexed(struct device *dev, int index) { - return _devm_of_add_table_indexed(dev, index, true); + return _devm_of_add_table_indexed(dev, index); } EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table_indexed); -/** - * dev_pm_opp_of_add_table_noclk() - Initialize indexed opp table from device - * tree without getting clk for device. - * @dev: device pointer used to lookup OPP table. - * @index: Index number. - * - * Register the initial OPP table with the OPP library for given device only - * using the "operating-points-v2" property. Do not try to get the clk for the - * device. - * - * Return: Refer to dev_pm_opp_of_add_table() for return values. - */ -int dev_pm_opp_of_add_table_noclk(struct device *dev, int index) -{ - return _of_add_table_indexed(dev, index, false); -} -EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_noclk); - -/** - * devm_pm_opp_of_add_table_noclk() - Initialize indexed opp table from device - * tree without getting clk for device. - * @dev: device pointer used to lookup OPP table. - * @index: Index number. - * - * This is a resource-managed variant of dev_pm_opp_of_add_table_noclk(). - */ -int devm_pm_opp_of_add_table_noclk(struct device *dev, int index) -{ - return _devm_of_add_table_indexed(dev, index, false); -} -EXPORT_SYMBOL_GPL(devm_pm_opp_of_add_table_noclk); - /* CPU device specific helpers */ /** diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 45e3a55239a1..3a6e077df386 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -28,6 +28,27 @@ extern struct mutex opp_table_lock; extern struct list_head opp_tables, lazy_opp_tables; +/* OPP Config flags */ +#define OPP_CONFIG_CLK BIT(0) +#define OPP_CONFIG_REGULATOR BIT(1) +#define OPP_CONFIG_REGULATOR_HELPER BIT(2) +#define OPP_CONFIG_PROP_NAME BIT(3) +#define OPP_CONFIG_SUPPORTED_HW BIT(4) +#define OPP_CONFIG_GENPD BIT(5) + +/** + * struct opp_config_data - data for set config operations + * @opp_table: OPP table + * @flags: OPP config flags + * + * This structure stores the OPP config information for each OPP table + * configuration by the callers. + */ +struct opp_config_data { + struct opp_table *opp_table; + unsigned int flags; +}; + /* * Internal data structure organization with the OPP layer library is as * follows: @@ -58,7 +79,7 @@ extern struct list_head opp_tables, lazy_opp_tables; * @suspend: true if suspend OPP * @removed: flag indicating that OPP's reference is dropped by OPP core. * @pstate: Device's power domain's performance state. - * @rate: Frequency in hertz + * @rates: Frequencies in hertz * @level: Performance level * @supplies: Power supplies voltage/current values * @bandwidth: Interconnect bandwidth values @@ -81,7 +102,7 @@ struct dev_pm_opp { bool suspend; bool removed; unsigned int pstate; - unsigned long rate; + unsigned long *rates; unsigned int level; struct dev_pm_opp_supply *supplies; @@ -138,7 +159,7 @@ enum opp_table_access { * @clock_latency_ns_max: Max clock latency in nanoseconds. * @parsed_static_opps: Count of devices for which OPPs are initialized from DT. * @shared_opp: OPP is shared between multiple devices. - * @current_rate: Currently configured frequency. + * @rate_clk_single: Currently configured frequency for single clk. * @current_opp: Currently configured OPP for the table. * @suspend_opp: Pointer to OPP to be used during device suspend. * @genpd_virt_dev_lock: Mutex protecting the genpd virtual device pointers. @@ -149,7 +170,11 @@ enum opp_table_access { * @supported_hw: Array of version number to support. * @supported_hw_count: Number of elements in supported_hw array. * @prop_name: A name to postfix to many DT properties, while parsing them. - * @clk: Device's clock handle + * @config_clks: Platform specific config_clks() callback. + * @clks: Device's clock handles, for multiple clocks. + * @clk: Device's clock handle, for single clock. + * @clk_count: Number of clocks. + * @config_regulators: Platform specific config_regulators() callback. * @regulators: Supply regulators * @regulator_count: Number of power supply regulators. Its value can be -1 * (uninitialized), 0 (no opp-microvolt property) or > 0 (has opp-microvolt @@ -159,9 +184,6 @@ enum opp_table_access { * @enabled: Set to true if the device's resources are enabled/configured. * @genpd_performance_state: Device's power domain support performance state. * @is_genpd: Marks if the OPP table belongs to a genpd. - * @set_opp: Platform specific set_opp callback - * @sod_supplies: Set opp data supplies - * @set_opp_data: Data to be passed to set_opp callback * @dentry: debugfs dentry pointer of the real device directory (not links). * @dentry_name: Name of the real dentry. * @@ -188,7 +210,7 @@ struct opp_table { unsigned int parsed_static_opps; enum opp_table_access shared_opp; - unsigned long current_rate; + unsigned long rate_clk_single; struct dev_pm_opp *current_opp; struct dev_pm_opp *suspend_opp; @@ -200,7 +222,11 @@ struct opp_table { unsigned int *supported_hw; unsigned int supported_hw_count; const char *prop_name; + config_clks_t config_clks; + struct clk **clks; struct clk *clk; + int clk_count; + config_regulators_t config_regulators; struct regulator **regulators; int regulator_count; struct icc_path **paths; @@ -209,10 +235,6 @@ struct opp_table { bool genpd_performance_state; bool is_genpd; - int (*set_opp)(struct dev_pm_set_opp_data *data); - struct dev_pm_opp_supply *sod_supplies; - struct dev_pm_set_opp_data *set_opp_data; - #ifdef CONFIG_DEBUG_FS struct dentry *dentry; char dentry_name[NAME_MAX]; @@ -228,8 +250,8 @@ struct opp_table *_find_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table); void _opp_free(struct dev_pm_opp *opp); -int _opp_compare_key(struct dev_pm_opp *opp1, struct dev_pm_opp *opp2); -int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available); +int _opp_compare_key(struct opp_table *opp_table, struct dev_pm_opp *opp1, struct dev_pm_opp *opp2); +int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table); int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic); void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, int last_cpu); struct opp_table *_add_opp_table_indexed(struct device *dev, int index, bool getclk); @@ -245,14 +267,12 @@ static inline bool lazy_linking_pending(struct opp_table *opp_table) void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index); void _of_clear_opp_table(struct opp_table *opp_table); struct opp_table *_managed_opp(struct device *dev, int index); -void _of_opp_free_required_opps(struct opp_table *opp_table, - struct dev_pm_opp *opp); +void _of_clear_opp(struct opp_table *opp_table, struct dev_pm_opp *opp); #else static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) {} static inline void _of_clear_opp_table(struct opp_table *opp_table) {} static inline struct opp_table *_managed_opp(struct device *dev, int index) { return NULL; } -static inline void _of_opp_free_required_opps(struct opp_table *opp_table, - struct dev_pm_opp *opp) {} +static inline void _of_clear_opp(struct opp_table *opp_table, struct dev_pm_opp *opp) {} #endif #ifdef CONFIG_DEBUG_FS diff --git a/drivers/opp/ti-opp-supply.c b/drivers/opp/ti-opp-supply.c index bd4771f388ab..8f3f13fbbb25 100644 --- a/drivers/opp/ti-opp-supply.c +++ b/drivers/opp/ti-opp-supply.c @@ -36,11 +36,15 @@ struct ti_opp_supply_optimum_voltage_table { * @vdd_table: Optimized voltage mapping table * @num_vdd_table: number of entries in vdd_table * @vdd_absolute_max_voltage_uv: absolute maximum voltage in UV for the supply + * @old_supplies: Placeholder for supplies information for old OPP. + * @new_supplies: Placeholder for supplies information for new OPP. */ struct ti_opp_supply_data { struct ti_opp_supply_optimum_voltage_table *vdd_table; u32 num_vdd_table; u32 vdd_absolute_max_voltage_uv; + struct dev_pm_opp_supply old_supplies[2]; + struct dev_pm_opp_supply new_supplies[2]; }; static struct ti_opp_supply_data opp_data; @@ -266,27 +270,32 @@ static int _opp_set_voltage(struct device *dev, return 0; } -/** - * ti_opp_supply_set_opp() - do the opp supply transition - * @data: information on regulators and new and old opps provided by - * opp core to use in transition - * - * Return: If successful, 0, else appropriate error value. - */ -static int ti_opp_supply_set_opp(struct dev_pm_set_opp_data *data) +/* Do the opp supply transition */ +static int ti_opp_config_regulators(struct device *dev, + struct dev_pm_opp *old_opp, struct dev_pm_opp *new_opp, + struct regulator **regulators, unsigned int count) { - struct dev_pm_opp_supply *old_supply_vdd = &data->old_opp.supplies[0]; - struct dev_pm_opp_supply *old_supply_vbb = &data->old_opp.supplies[1]; - struct dev_pm_opp_supply *new_supply_vdd = &data->new_opp.supplies[0]; - struct dev_pm_opp_supply *new_supply_vbb = &data->new_opp.supplies[1]; - struct device *dev = data->dev; - unsigned long old_freq = data->old_opp.rate, freq = data->new_opp.rate; - struct clk *clk = data->clk; - struct regulator *vdd_reg = data->regulators[0]; - struct regulator *vbb_reg = data->regulators[1]; + struct dev_pm_opp_supply *old_supply_vdd = &opp_data.old_supplies[0]; + struct dev_pm_opp_supply *old_supply_vbb = &opp_data.old_supplies[1]; + struct dev_pm_opp_supply *new_supply_vdd = &opp_data.new_supplies[0]; + struct dev_pm_opp_supply *new_supply_vbb = &opp_data.new_supplies[1]; + struct regulator *vdd_reg = regulators[0]; + struct regulator *vbb_reg = regulators[1]; + unsigned long old_freq, freq; int vdd_uv; int ret; + /* We must have two regulators here */ + WARN_ON(count != 2); + + /* Fetch supplies and freq information from OPP core */ + ret = dev_pm_opp_get_supplies(new_opp, opp_data.new_supplies); + WARN_ON(ret); + + old_freq = dev_pm_opp_get_freq(old_opp); + freq = dev_pm_opp_get_freq(new_opp); + WARN_ON(!old_freq || !freq); + vdd_uv = _get_optimal_vdd_voltage(dev, &opp_data, new_supply_vdd->u_volt); @@ -303,39 +312,24 @@ static int ti_opp_supply_set_opp(struct dev_pm_set_opp_data *data) ret = _opp_set_voltage(dev, new_supply_vbb, 0, vbb_reg, "vbb"); if (ret) goto restore_voltage; - } - - /* Change frequency */ - dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", - __func__, old_freq, freq); - - ret = clk_set_rate(clk, freq); - if (ret) { - dev_err(dev, "%s: failed to set clock rate: %d\n", __func__, - ret); - goto restore_voltage; - } - - /* Scaling down? Scale voltage after frequency */ - if (freq < old_freq) { + } else { ret = _opp_set_voltage(dev, new_supply_vbb, 0, vbb_reg, "vbb"); if (ret) - goto restore_freq; + goto restore_voltage; ret = _opp_set_voltage(dev, new_supply_vdd, vdd_uv, vdd_reg, "vdd"); if (ret) - goto restore_freq; + goto restore_voltage; } return 0; -restore_freq: - ret = clk_set_rate(clk, old_freq); - if (ret) - dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", - __func__, old_freq); restore_voltage: + /* Fetch old supplies information only if required */ + ret = dev_pm_opp_get_supplies(old_opp, opp_data.old_supplies); + WARN_ON(ret); + /* This shouldn't harm even if the voltages weren't updated earlier */ if (old_supply_vdd->u_volt) { ret = _opp_set_voltage(dev, old_supply_vbb, 0, vbb_reg, "vbb"); @@ -405,9 +399,8 @@ static int ti_opp_supply_probe(struct platform_device *pdev) return ret; } - ret = PTR_ERR_OR_ZERO(dev_pm_opp_register_set_opp_helper(cpu_dev, - ti_opp_supply_set_opp)); - if (ret) + ret = dev_pm_opp_set_config_regulators(cpu_dev, ti_opp_config_regulators); + if (ret < 0) _free_optimized_voltages(dev, &opp_data); return ret; |