From 32715be4fe95fc98762959f8dff6f9f8a39df28f Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:13 +0300 Subject: [PATCH 01/37] opp: Fix adding OPP entries in a wrong order if rate is unavailable Fix adding OPP entries in a wrong (opposite) order if OPP rate is unavailable. The OPP comparison was erroneously skipped, thus OPPs were left unsorted. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 8c905aabacc0..5793c833b86a 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1527,12 +1527,10 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, mutex_lock(&opp_table->lock); head = &opp_table->opp_list; - if (likely(!rate_not_available)) { - ret = _opp_is_duplicate(dev, new_opp, opp_table, &head); - if (ret) { - mutex_unlock(&opp_table->lock); - return ret; - } + ret = _opp_is_duplicate(dev, new_opp, opp_table, &head); + if (ret) { + mutex_unlock(&opp_table->lock); + return ret; } list_add(&new_opp->node, head); From cf65948d62c6aefd22f51c1433743f80517ee3fe Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:14 +0300 Subject: [PATCH 02/37] opp: Filter out OPPs based on availability of a required-OPP A required OPP may not be available, and thus, all OPPs which are using this required OPP should be unavailable too. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 5793c833b86a..253bc87b5695 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1522,6 +1522,7 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available) { struct list_head *head; + unsigned int i; int ret; mutex_lock(&opp_table->lock); @@ -1547,6 +1548,16 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, __func__, new_opp->rate); } + for (i = 0; i < opp_table->required_opp_count; i++) { + if (new_opp->required_opps[i]->available) + continue; + + new_opp->available = false; + dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n", + __func__, new_opp->required_opps[i]->np, new_opp->rate); + break; + } + return 0; } From d7b9d9b31a3e55dcc9b5c289abfafe31efa5b5c4 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:15 +0300 Subject: [PATCH 03/37] opp: Correct debug message in _opp_add_static_v2() The debug message always prints rate=0 instead of a proper value, fix it. Fixes: 6c591eec67cb ("OPP: Add helpers for reading the binding properties") Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko [ Viresh: Added Fixes tag ] Signed-off-by: Viresh Kumar --- drivers/opp/of.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 03cb387236c4..d0c0336be39b 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -755,7 +755,6 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, struct device *dev, struct device_node *np) { struct dev_pm_opp *new_opp; - u64 rate = 0; u32 val; int ret; bool rate_not_available = false; @@ -772,7 +771,8 @@ 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: %llu\n", rate); + dev_dbg(dev, "OPP not supported by hardware: %lu\n", + new_opp->rate); goto free_opp; } From d758eaf5f8cbdf2554e34269c75694f60c38745d Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 28 Jan 2021 11:08:47 +0530 Subject: [PATCH 04/37] opp: Staticize _add_opp_table() _add_opp_table() isn't used outside of core.c, mark it static. Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 2 +- drivers/opp/opp.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 253bc87b5695..dc7a298f3611 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1193,7 +1193,7 @@ unlock: return opp_table; } -struct opp_table *_add_opp_table(struct device *dev) +static struct opp_table *_add_opp_table(struct device *dev) { return _add_opp_table_indexed(dev, 0); } diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 4ced7ffa8158..ee2593afae0c 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -223,7 +223,6 @@ 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_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(struct device *dev); struct opp_table *_add_opp_table_indexed(struct device *dev, int index); void _put_opp_list_kref(struct opp_table *opp_table); From 8dd5cada393f6f4e825833a6ff05b1f51f36a791 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:18 +0300 Subject: [PATCH 05/37] opp: Add dev_pm_opp_find_level_ceil() Add a ceil version of the dev_pm_opp_find_level(). It's handy to have if levels don't start from 0 in OPP table and zero usually means a minimal level. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 49 ++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 8 +++++++ 2 files changed, 57 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index dc7a298f3611..b29f31146770 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -449,6 +449,55 @@ struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, } EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_exact); +/** + * dev_pm_opp_find_level_ceil() - search for an rounded up level + * @dev: device for which we do this operation + * @level: level to search for + * + * Return: Searches for rounded up match in the opp table and returns pointer + * to the matching opp if found, else returns ERR_PTR in case of error and + * should be handled using IS_ERR. Error return values can be: + * EINVAL: for bad pointer + * ERANGE: no match found for search + * ENODEV: if device not found in list of registered devices + * + * The callers are required to call dev_pm_opp_put() for the returned OPP after + * use. + */ +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); + + return opp; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_find_level_ceil); + static noinline struct dev_pm_opp *_find_freq_ceil(struct opp_table *opp_table, unsigned long *freq) { diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 1435c054016a..2b3030cb2ed2 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -111,6 +111,8 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, bool available); struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, unsigned int level); +struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev, + unsigned int *level); struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, unsigned long *freq); @@ -226,6 +228,12 @@ static inline struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, return ERR_PTR(-ENOTSUPP); } +static inline struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev, + unsigned int *level) +{ + return ERR_PTR(-ENOTSUPP); +} + static inline struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, unsigned long *freq) { From 597ff5431fd41afa888809f7936508a15c977cde Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:19 +0300 Subject: [PATCH 06/37] opp: Add dev_pm_opp_get_required_pstate() Add dev_pm_opp_get_required_pstate() which allows OPP users to retrieve required performance state of a given OPP. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 22 ++++++++++++++++++++++ include/linux/pm_opp.h | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index b29f31146770..64a95aaa90eb 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -145,6 +145,28 @@ unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp) } EXPORT_SYMBOL_GPL(dev_pm_opp_get_level); +/** + * dev_pm_opp_get_required_pstate() - Gets the required performance state + * corresponding to an available opp + * @opp: opp for which performance state has to be returned for + * @index: index of the required opp + * + * Return: performance state read from device tree corresponding to the + * required opp, else return 0. + */ +unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp, + unsigned int index) +{ + if (IS_ERR_OR_NULL(opp) || !opp->available || + index >= opp->opp_table->required_opp_count) { + pr_err("%s: Invalid parameters\n", __func__); + return 0; + } + + return opp->required_opps[index]->pstate; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_get_required_pstate); + /** * dev_pm_opp_is_turbo() - Returns if opp is turbo OPP or not * @opp: opp for which turbo mode is being verified diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 2b3030cb2ed2..8f926815bad9 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -98,6 +98,9 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp); unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp); +unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp, + unsigned int index); + bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp); int dev_pm_opp_get_opp_count(struct device *dev); @@ -186,6 +189,13 @@ static inline unsigned int dev_pm_opp_get_level(struct dev_pm_opp *opp) return 0; } +static inline +unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp, + unsigned int index) +{ + return 0; +} + static inline bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) { return false; From ce8073d83f63a2cdcfc1b86d769456726faad51d Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 21 Jan 2021 01:26:47 +0300 Subject: [PATCH 07/37] opp: Add dev_pm_opp_sync_regulators() Extend OPP API with dev_pm_opp_sync_regulators() function, which syncs voltage state of regulators. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko [ Viresh: Added unlikely() ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 41 +++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 6 ++++++ 2 files changed, 47 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 64a95aaa90eb..bf7cdab0ba64 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2584,3 +2584,44 @@ void dev_pm_opp_remove_table(struct device *dev) dev_pm_opp_put_opp_table(opp_table); } EXPORT_SYMBOL_GPL(dev_pm_opp_remove_table); + +/** + * dev_pm_opp_sync_regulators() - Sync state of voltage regulators + * @dev: device for which we do this operation + * + * Sync voltage state of the OPP table regulators. + * + * Return: 0 on success or a negative error value. + */ +int dev_pm_opp_sync_regulators(struct device *dev) +{ + struct opp_table *opp_table; + struct regulator *reg; + int i, ret = 0; + + /* Device may not have OPP table */ + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) + return 0; + + /* Regulator may not be required for the device */ + if (unlikely(!opp_table->regulators)) + goto put_table; + + /* Nothing to sync if voltage wasn't changed */ + if (!opp_table->enabled) + goto put_table; + + for (i = 0; i < opp_table->regulator_count; i++) { + reg = opp_table->regulators[i]; + ret = regulator_sync_voltage(reg); + if (ret) + break; + } +put_table: + /* Drop reference taken by _find_opp_table() */ + dev_pm_opp_put_opp_table(opp_table); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_sync_regulators); diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 8f926815bad9..979b208bc4a8 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -161,6 +161,7 @@ int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cp int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask); void dev_pm_opp_remove_table(struct device *dev); void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask); +int dev_pm_opp_sync_regulators(struct device *dev); #else static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) { @@ -384,6 +385,11 @@ static inline void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask { } +static inline int dev_pm_opp_sync_regulators(struct device *dev) +{ + return -ENOTSUPP; +} + #endif /* CONFIG_PM_OPP */ #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF) From 406e47652161d4f0d9bc4cd6237b36c51497ec75 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 27 Jan 2021 12:45:56 +0530 Subject: [PATCH 08/37] opp: Create _of_add_table_indexed() to reduce code duplication The implementation of dev_pm_opp_of_add_table() and dev_pm_opp_of_add_table_indexed() are almost identical. Create _of_add_table_indexed() to reduce code redundancy. Also remove the duplication of the doc style comments by referring to dev_pm_opp_of_add_table() from dev_pm_opp_of_add_table_indexed(). Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/of.c | 111 ++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index d0c0336be39b..c6856dcf4c34 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -956,68 +956,7 @@ remove_static_opp: return ret; } -/** - * dev_pm_opp_of_add_table() - Initialize opp table from device tree - * @dev: device pointer used to lookup OPP table. - * - * Register the initial OPP table with the OPP library for given device. - * - * Return: - * 0 On success OR - * Duplicate OPPs (both freq and volt are same) and opp->available - * -EEXIST Freq are same and volt are different OR - * Duplicate OPPs (both freq and volt are same) and !opp->available - * -ENOMEM Memory allocation failure - * -ENODEV when 'operating-points' property is not found or is invalid data - * in device node. - * -ENODATA when empty 'operating-points' property is found - * -EINVAL when invalid entries are found in opp-v2 table - */ -int dev_pm_opp_of_add_table(struct device *dev) -{ - struct opp_table *opp_table; - int ret; - - opp_table = _add_opp_table_indexed(dev, 0); - if (IS_ERR(opp_table)) - return PTR_ERR(opp_table); - - /* - * OPPs have two version of bindings now. Also try the old (v1) - * bindings for backward compatibility with older dtbs. - */ - if (opp_table->np) - ret = _of_add_opp_table_v2(dev, opp_table); - else - ret = _of_add_opp_table_v1(dev, opp_table); - - if (ret) - dev_pm_opp_put_opp_table(opp_table); - - return ret; -} -EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); - -/** - * dev_pm_opp_of_add_table_indexed() - Initialize indexed opp table from device tree - * @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. - * - * Return: - * 0 On success OR - * Duplicate OPPs (both freq and volt are same) and opp->available - * -EEXIST Freq are same and volt are different OR - * Duplicate OPPs (both freq and volt are same) and !opp->available - * -ENOMEM Memory allocation failure - * -ENODEV when 'operating-points' property is not found or is invalid data - * in device node. - * -ENODATA when empty 'operating-points' property is found - * -EINVAL when invalid entries are found in opp-v2 table - */ -int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) +static int _of_add_table_indexed(struct device *dev, int index) { struct opp_table *opp_table; int ret, count; @@ -1037,12 +976,58 @@ int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) if (IS_ERR(opp_table)) return PTR_ERR(opp_table); - ret = _of_add_opp_table_v2(dev, opp_table); + /* + * OPPs have two version of bindings now. Also try the old (v1) + * bindings for backward compatibility with older dtbs. + */ + if (opp_table->np) + ret = _of_add_opp_table_v2(dev, opp_table); + else + ret = _of_add_opp_table_v1(dev, opp_table); + if (ret) dev_pm_opp_put_opp_table(opp_table); return ret; } + +/** + * dev_pm_opp_of_add_table() - Initialize opp table from device tree + * @dev: device pointer used to lookup OPP table. + * + * Register the initial OPP table with the OPP library for given device. + * + * Return: + * 0 On success OR + * Duplicate OPPs (both freq and volt are same) and opp->available + * -EEXIST Freq are same and volt are different OR + * Duplicate OPPs (both freq and volt are same) and !opp->available + * -ENOMEM Memory allocation failure + * -ENODEV when 'operating-points' property is not found or is invalid data + * in device node. + * -ENODATA when empty 'operating-points' property is found + * -EINVAL when invalid entries are found in opp-v2 table + */ +int dev_pm_opp_of_add_table(struct device *dev) +{ + return _of_add_table_indexed(dev, 0); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); + +/** + * dev_pm_opp_of_add_table_indexed() - Initialize indexed opp table from device tree + * @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. + * + * Return: Refer to dev_pm_opp_of_add_table() for return values. + */ +int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) +{ + return _of_add_table_indexed(dev, index); +} EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed); /* CPU device specific helpers */ From 32439ac7535a8eddfa016c62ca66ce33b7df1573 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 28 Jan 2021 12:05:22 +0530 Subject: [PATCH 09/37] opp: Defer acquiring the clk until OPPs are added We acquire the clk at the time the OPP table is allocated, though it works fine, it is not the best place to do so. One of the main reason being we may need to acquire it again from dev_pm_opp_set_clkname() if the platform wants another clock to be acquired instead. There is also requirement from some of the platforms where they do not want the OPP core to manage the clock at all. This patch hence defers acquiring the clk until the time we are certain about which clk we need to acquire and if we really need to acquire one. With this commit, the clk will get acquired either from dev_pm_opp_set_clkname() or while we initialize the OPPs within the table. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 73 ++++++++++++++++++++++++++++------------------ drivers/opp/of.c | 8 ++--- drivers/opp/opp.h | 2 +- 3 files changed, 50 insertions(+), 33 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index bf7cdab0ba64..52f4a64926e6 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1158,21 +1158,11 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index) _of_init_opp_table(opp_table, dev, index); - /* Find clk for the device */ - opp_table->clk = clk_get(dev, NULL); - if (IS_ERR(opp_table->clk)) { - ret = PTR_ERR(opp_table->clk); - if (ret == -EPROBE_DEFER) - goto remove_opp_dev; - - dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); - } - /* Find interconnect path(s) for the device */ ret = dev_pm_opp_of_find_icc_paths(dev, opp_table); if (ret) { if (ret == -EPROBE_DEFER) - goto put_clk; + goto remove_opp_dev; dev_warn(dev, "%s: Error finding interconnect paths: %d\n", __func__, ret); @@ -1184,9 +1174,6 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index) return opp_table; -put_clk: - if (!IS_ERR(opp_table->clk)) - clk_put(opp_table->clk); remove_opp_dev: _remove_opp_dev(opp_dev, opp_table); err: @@ -1199,6 +1186,33 @@ void _get_opp_table_kref(struct opp_table *opp_table) kref_get(&opp_table->kref); } +static struct opp_table *_update_opp_table_clk(struct device *dev, + struct opp_table *opp_table, + bool getclk) +{ + /* + * Return early if we don't need to get clk or we have already tried it + * earlier. + */ + if (!getclk || IS_ERR(opp_table) || opp_table->clk) + return opp_table; + + /* Find clk for the device */ + opp_table->clk = clk_get(dev, NULL); + if (IS_ERR(opp_table->clk)) { + int ret = PTR_ERR(opp_table->clk); + + if (ret == -EPROBE_DEFER) { + dev_pm_opp_put_opp_table(opp_table); + return ERR_PTR(ret); + } + + dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); + } + + return opp_table; +} + /* * We need to make sure that the OPP table for a device doesn't get added twice, * if this routine gets called in parallel with the same device pointer. @@ -1214,7 +1228,8 @@ void _get_opp_table_kref(struct opp_table *opp_table) * uses the opp_tables_busy flag to indicate if another creator is in the middle * of adding an OPP table and others should wait for it to finish. */ -struct opp_table *_add_opp_table_indexed(struct device *dev, int index) +struct opp_table *_add_opp_table_indexed(struct device *dev, int index, + bool getclk) { struct opp_table *opp_table; @@ -1261,12 +1276,12 @@ again: unlock: mutex_unlock(&opp_table_lock); - return opp_table; + return _update_opp_table_clk(dev, opp_table, getclk); } -static struct opp_table *_add_opp_table(struct device *dev) +static struct opp_table *_add_opp_table(struct device *dev, bool getclk) { - return _add_opp_table_indexed(dev, 0); + return _add_opp_table_indexed(dev, 0, getclk); } struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) @@ -1711,7 +1726,7 @@ struct opp_table *dev_pm_opp_set_supported_hw(struct device *dev, { struct opp_table *opp_table; - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, false); if (IS_ERR(opp_table)) return opp_table; @@ -1773,7 +1788,7 @@ struct opp_table *dev_pm_opp_set_prop_name(struct device *dev, const char *name) { struct opp_table *opp_table; - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, false); if (IS_ERR(opp_table)) return opp_table; @@ -1869,7 +1884,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev, struct regulator *reg; int ret, i; - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, false); if (IS_ERR(opp_table)) return opp_table; @@ -1980,7 +1995,7 @@ struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name) struct opp_table *opp_table; int ret; - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, false); if (IS_ERR(opp_table)) return opp_table; @@ -1990,9 +2005,11 @@ struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name) goto err; } - /* Already have default clk set, free it */ - if (!IS_ERR(opp_table->clk)) - clk_put(opp_table->clk); + /* clk shouldn't be initialized at this point */ + if (WARN_ON(opp_table->clk)) { + ret = -EBUSY; + goto err; + } /* Find clk for the device */ opp_table->clk = clk_get(dev, name); @@ -2051,7 +2068,7 @@ struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, if (!set_opp) return ERR_PTR(-EINVAL); - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, false); if (IS_ERR(opp_table)) return opp_table; @@ -2138,7 +2155,7 @@ struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, int index = 0, ret = -EINVAL; const char **name = names; - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, false); if (IS_ERR(opp_table)) return opp_table; @@ -2306,7 +2323,7 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) struct opp_table *opp_table; int ret; - opp_table = _add_opp_table(dev); + opp_table = _add_opp_table(dev, true); if (IS_ERR(opp_table)) return PTR_ERR(opp_table); diff --git a/drivers/opp/of.c b/drivers/opp/of.c index c6856dcf4c34..d4b51b2e384f 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -956,7 +956,7 @@ remove_static_opp: return ret; } -static int _of_add_table_indexed(struct device *dev, int index) +static int _of_add_table_indexed(struct device *dev, int index, bool getclk) { struct opp_table *opp_table; int ret, count; @@ -972,7 +972,7 @@ static int _of_add_table_indexed(struct device *dev, int index) index = 0; } - opp_table = _add_opp_table_indexed(dev, index); + opp_table = _add_opp_table_indexed(dev, index, getclk); if (IS_ERR(opp_table)) return PTR_ERR(opp_table); @@ -1010,7 +1010,7 @@ static int _of_add_table_indexed(struct device *dev, int index) */ int dev_pm_opp_of_add_table(struct device *dev) { - return _of_add_table_indexed(dev, 0); + return _of_add_table_indexed(dev, 0, true); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table); @@ -1026,7 +1026,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); + return _of_add_table_indexed(dev, index, true); } EXPORT_SYMBOL_GPL(dev_pm_opp_of_add_table_indexed); diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index ee2593afae0c..6e83855ade1f 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -223,7 +223,7 @@ 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_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); +struct opp_table *_add_opp_table_indexed(struct device *dev, int index, bool getclk); void _put_opp_list_kref(struct opp_table *opp_table); #ifdef CONFIG_OF From 559fef0dfd91145b59b7c61061504f344ecf9ad8 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 27 Jan 2021 14:23:45 +0530 Subject: [PATCH 10/37] opp: Add dev_pm_opp_of_add_table_noclk() A few drivers have device's clk but they don't want the OPP core to handle that. Add a new helper for them, dev_pm_opp_of_add_table_noclk(). Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/of.c | 18 ++++++++++++++++++ include/linux/pm_opp.h | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index d4b51b2e384f..a905497c75f8 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -1030,6 +1030,24 @@ int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) } EXPORT_SYMBOL_GPL(dev_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); + /* CPU device specific helpers */ /** diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 979b208bc4a8..158158620dde 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -395,6 +395,7 @@ static inline int dev_pm_opp_sync_regulators(struct device *dev) #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF) int dev_pm_opp_of_add_table(struct device *dev); int dev_pm_opp_of_add_table_indexed(struct device *dev, int index); +int dev_pm_opp_of_add_table_noclk(struct device *dev, int index); void dev_pm_opp_of_remove_table(struct device *dev); int dev_pm_opp_of_cpumask_add_table(const struct cpumask *cpumask); void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpumask); @@ -419,6 +420,11 @@ static inline int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) return -ENOTSUPP; } +static inline int dev_pm_opp_of_add_table_noclk(struct device *dev, int index) +{ + return -ENOTSUPP; +} + static inline void dev_pm_opp_of_remove_table(struct device *dev) { } From a3c47af6942dc8e07a4328913d0263a965786895 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:20 +0300 Subject: [PATCH 11/37] opp: Add devm_pm_opp_register_set_opp_helper Add resource-managed version of dev_pm_opp_register_set_opp_helper(). Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko [ Viresh: Manually apply the patch and relocate the routines ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 52f4a64926e6..09069a564896 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2106,6 +2106,40 @@ void dev_pm_opp_unregister_set_opp_helper(struct 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); +} + +/** + * 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(). + * + * Return: pointer to 'struct opp_table' on success and errorno otherwise. + */ +struct opp_table * +devm_pm_opp_register_set_opp_helper(struct device *dev, + int (*set_opp)(struct dev_pm_set_opp_data *data)) +{ + struct opp_table *opp_table; + int err; + + opp_table = dev_pm_opp_register_set_opp_helper(dev, set_opp); + if (IS_ERR(opp_table)) + return opp_table; + + err = devm_add_action_or_reset(dev, devm_pm_opp_unregister_set_opp_helper, + opp_table); + if (err) + return ERR_PTR(err); + + return opp_table; +} +EXPORT_SYMBOL_GPL(devm_pm_opp_register_set_opp_helper); + static void _opp_detach_genpd(struct opp_table *opp_table) { int index; diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 158158620dde..473daf34160d 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -152,6 +152,7 @@ struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char * name); void dev_pm_opp_put_clkname(struct opp_table *opp_table); struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)); void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table); +struct opp_table *devm_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)); struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); void dev_pm_opp_detach_genpd(struct opp_table *opp_table); int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate); @@ -324,6 +325,13 @@ static inline struct opp_table *dev_pm_opp_register_set_opp_helper(struct device static inline void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table) {} +static inline struct opp_table * +devm_pm_opp_register_set_opp_helper(struct device *dev, + int (*set_opp)(struct dev_pm_set_opp_data *data)) +{ + return ERR_PTR(-ENOTSUPP); +} + static inline struct opp_table *dev_pm_opp_set_prop_name(struct device *dev, const char *name) { return ERR_PTR(-ENOTSUPP); From b4b9e223eccaeec6e05d927c292d4425fd18f243 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:21 +0300 Subject: [PATCH 12/37] opp: Add devm_pm_opp_attach_genpd Add resource-managed version of dev_pm_opp_attach_genpd(). Signed-off-by: Dmitry Osipenko [ Viresh: Manually apply the patch and relocate the routines ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 36 ++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 7 +++++++ 2 files changed, 43 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 09069a564896..ce0ec5bde22a 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2275,6 +2275,42 @@ void dev_pm_opp_detach_genpd(struct opp_table *opp_table) } EXPORT_SYMBOL_GPL(dev_pm_opp_detach_genpd); +static void devm_pm_opp_detach_genpd(void *data) +{ + dev_pm_opp_detach_genpd(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. + * + * This is a resource-managed version of dev_pm_opp_attach_genpd(). + * + * Return: pointer to 'struct opp_table' on success and errorno otherwise. + */ +struct opp_table * +devm_pm_opp_attach_genpd(struct device *dev, const char **names, + struct device ***virt_devs) +{ + struct opp_table *opp_table; + int err; + + opp_table = dev_pm_opp_attach_genpd(dev, names, virt_devs); + if (IS_ERR(opp_table)) + return opp_table; + + err = devm_add_action_or_reset(dev, devm_pm_opp_detach_genpd, + opp_table); + if (err) + return ERR_PTR(err); + + return opp_table; +} +EXPORT_SYMBOL_GPL(devm_pm_opp_attach_genpd); + /** * dev_pm_opp_xlate_performance_state() - Find required OPP's pstate for src_table. * @src_table: OPP table which has dst_table as one of its required OPP table. diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 473daf34160d..a2c871799603 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -155,6 +155,7 @@ void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table); struct opp_table *devm_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)); struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); void dev_pm_opp_detach_genpd(struct opp_table *opp_table); +struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate); int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq); int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp); @@ -360,6 +361,12 @@ static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, cons static inline void dev_pm_opp_detach_genpd(struct opp_table *opp_table) {} +static inline struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, + const char **names, struct device ***virt_devs) +{ + return ERR_PTR(-ENOTSUPP); +} + static inline int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate) { return -ENOTSUPP; From f2f4d2b86f432fecfd76afa5f4f60f47833121b5 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:23 +0300 Subject: [PATCH 13/37] opp: Handle missing OPP table in dev_pm_opp_xlate_performance_state() NVIDIA Tegra SoCs have a power domains topology such that child domains only clamp a power rail, while parent domain controls shared performance state of the multiple child domains. In this case child's domain doesn't need to have OPP table. Hence we want to allow children power domains to pass performance state to the parent domain if child's domain doesn't have OPP table. The dev_pm_opp_xlate_performance_state() gets src_table=NULL if a child power domain doesn't have OPP table and in this case we should pass the performance state to the parent domain. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index ce0ec5bde22a..0417cd34b805 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2339,7 +2339,7 @@ int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, * and so none of them have the "required-opps" property set. Return the * pstate of the src_table as it is in such cases. */ - if (!src_table->required_opp_count) + if (!src_table || !src_table->required_opp_count) return pstate; for (i = 0; i < src_table->required_opp_count; i++) { From b6ecd5d4f6941628d0140735d3f05eb61907141e Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Mon, 18 Jan 2021 03:55:24 +0300 Subject: [PATCH 14/37] opp: Print OPP level in debug message of _opp_add_static_v2() Print OPP level in debug message of _opp_add_static_v2(). This helps to chase GENPD bugs. Tested-by: Peter Geis Tested-by: Nicolas Chauvet Tested-by: Matt Merhar Signed-off-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/of.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/opp/of.c b/drivers/opp/of.c index a905497c75f8..20ccdaab9384 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -822,10 +822,11 @@ static struct dev_pm_opp *_opp_add_static_v2(struct opp_table *opp_table, if (new_opp->clock_latency_ns > opp_table->clock_latency_ns_max) 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\n", + 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, 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->supplies[0].u_volt_max, new_opp->clock_latency_ns, + new_opp->level); /* * Notify the changes in the availability of the operable From 38bb34393804b79eff647bdf96762db5efce392c Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Tue, 19 Jan 2021 11:58:58 +0530 Subject: [PATCH 15/37] opp: Prepare for ->set_opp() helper to work without regulators Until now the ->set_opp() helper (i.e. special implementation for setting the OPPs for platforms) was implemented only to take care of multiple regulators case, but going forward we would need that for other use cases as well. This patch prepares for that by allocating the regulator specific part from dev_pm_opp_set_regulators() and the opp helper part from dev_pm_opp_register_set_opp_helper(). Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 84 +++++++++++++++++++++++++--------------------- drivers/opp/opp.h | 2 ++ 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 0417cd34b805..f482937d72eb 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1832,38 +1832,6 @@ void dev_pm_opp_put_prop_name(struct opp_table *opp_table) } EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name); -static int _allocate_set_opp_data(struct opp_table *opp_table) -{ - struct dev_pm_set_opp_data *data; - int len, count = opp_table->regulator_count; - - if (WARN_ON(!opp_table->regulators)) - return -EINVAL; - - /* space for set_opp_data */ - len = sizeof(*data); - - /* space for old_opp.supplies and new_opp.supplies */ - len += 2 * sizeof(struct dev_pm_opp_supply) * count; - - data = kzalloc(len, GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->old_opp.supplies = (void *)(data + 1); - data->new_opp.supplies = data->old_opp.supplies + count; - - opp_table->set_opp_data = data; - - return 0; -} - -static void _free_set_opp_data(struct opp_table *opp_table) -{ - kfree(opp_table->set_opp_data); - opp_table->set_opp_data = NULL; -} - /** * dev_pm_opp_set_regulators() - Set regulator names for the device * @dev: Device for which regulator name is being set. @@ -1880,6 +1848,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count) { + struct dev_pm_opp_supply *supplies; struct opp_table *opp_table; struct regulator *reg; int ret, i; @@ -1921,10 +1890,19 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev, opp_table->regulator_count = count; - /* Allocate block only once to pass to set_opp() routines */ - ret = _allocate_set_opp_data(opp_table); - if (ret) + 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); return opp_table; @@ -1967,7 +1945,15 @@ 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]); - _free_set_opp_data(opp_table); + 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; @@ -2063,6 +2049,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_put_clkname); struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)) { + struct dev_pm_set_opp_data *data; struct opp_table *opp_table; if (!set_opp) @@ -2079,8 +2066,23 @@ struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, } /* Another CPU that shares the OPP table has set the helper ? */ - if (!opp_table->set_opp) - opp_table->set_opp = set_opp; + if (opp_table->set_opp) + return opp_table; + + 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; } @@ -2102,6 +2104,12 @@ void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table) WARN_ON(!list_empty(&opp_table->opp_list)); 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); diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 6e83855ade1f..64b9cb782a93 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -155,6 +155,7 @@ enum opp_table_access { * @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. @@ -202,6 +203,7 @@ struct opp_table { 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 From 04b447df1d098dcd7d133203a310a6d415875547 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko Date: Thu, 21 Jan 2021 01:26:49 +0300 Subject: [PATCH 16/37] opp: Make _set_opp_custom() work without regulators Check whether OPP table has regulators in _set_opp_custom() and set up dev_pm_set_opp_data accordingly. Now _set_opp_custom() works properly, i.e. it doesn't crash if OPP table doesn't have assigned regulators. Signed-off-by: Dmitry Osipenko [ Viresh: Rearrange the routine a bit ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index f482937d72eb..b4528e40ad01 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -828,24 +828,31 @@ static int _set_opp_custom(const struct opp_table *opp_table, struct dev_pm_opp_supply *old_supply, struct dev_pm_opp_supply *new_supply) { - struct dev_pm_set_opp_data *data; + struct dev_pm_set_opp_data *data = opp_table->set_opp_data; int size; - data = opp_table->set_opp_data; + /* + * We support this only if dev_pm_opp_set_regulators() was called + * earlier. + */ + if (opp_table->sod_supplies) { + size = sizeof(*old_supply) * opp_table->regulator_count; + if (!old_supply) + memset(data->old_opp.supplies, 0, size); + else + memcpy(data->old_opp.supplies, old_supply, size); + + memcpy(data->new_opp.supplies, new_supply, size); + data->regulator_count = opp_table->regulator_count; + } else { + data->regulator_count = 0; + } + 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 (!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); } From 5ad58bbacf802f7d11cadd76881311d6e4b2bce0 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 12:08:45 +0530 Subject: [PATCH 17/37] opp: Rename _opp_set_rate_zero() This routine has nothing to do with frequency, it just disables all the resources previously enabled. Rename it to match its purpose. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index b4528e40ad01..9637f2994d2e 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -950,7 +950,7 @@ int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp) } EXPORT_SYMBOL_GPL(dev_pm_opp_set_bw); -static int _opp_set_rate_zero(struct device *dev, struct opp_table *opp_table) +static int _disable_opp_table(struct device *dev, struct opp_table *opp_table) { int ret; @@ -1004,7 +1004,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) } if (unlikely(!target_freq)) { - ret = _opp_set_rate_zero(dev, opp_table); + ret = _disable_opp_table(dev, opp_table); goto put_opp_table; } From 1d3c42cabbd351e9c171e906603b5cc2ea513640 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 20 Jan 2021 15:57:21 +0530 Subject: [PATCH 18/37] opp: No need to check clk for errors Clock is not optional for users who call into dev_pm_opp_set_rate(). Remove the unnecessary checks. While at it also drop the local variable for clk and use opp_table->clk instead. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 9637f2994d2e..8ef85cd918ce 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -994,7 +994,6 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) struct opp_table *opp_table; unsigned long freq, old_freq, temp_freq; struct dev_pm_opp *old_opp, *opp; - struct clk *clk; int ret; opp_table = _find_opp_table(dev); @@ -1008,19 +1007,11 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) goto put_opp_table; } - clk = opp_table->clk; - if (IS_ERR(clk)) { - dev_err(dev, "%s: No clock available for the device\n", - __func__); - ret = PTR_ERR(clk); - goto put_opp_table; - } - - freq = clk_round_rate(clk, target_freq); + freq = clk_round_rate(opp_table->clk, target_freq); if ((long)freq <= 0) freq = target_freq; - old_freq = clk_get_rate(clk); + old_freq = clk_get_rate(opp_table->clk); /* Return early if nothing to do */ if (opp_table->enabled && old_freq == freq) { @@ -1038,7 +1029,7 @@ 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, clk, freq); + ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); goto put_opp_table; } @@ -1078,7 +1069,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) opp->supplies); } else { /* Only frequency scaling */ - ret = _generic_set_opp_clk_only(dev, clk, freq); + ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); } /* Scaling down? Configure required OPPs after frequency */ From 81c4d8a3c41488e5491142c31cd7a821ff5d71ec Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Wed, 20 Jan 2021 16:16:48 +0530 Subject: [PATCH 19/37] opp: Keep track of currently programmed OPP The dev_pm_opp_set_rate() helper needs to know the currently programmed OPP to make few decisions and currently we try to find it on every invocation of this routine. Lets start keeping track of the current_opp programmed for the devices of the opp table, that will be quite useful going forward. If we fail to find the current OPP, we pick the first one available in the list, as the list is in ascending order of frequencies, level, or bandwidth and that's the best guess we can make anyway. Note that we used to do the frequency comparison a bit early in dev_pm_opp_set_rate() previously, and now instead we check the target opp, which shall be more accurate anyway. We need to make sure that current_opp's memory doesn't get freed while it is being used and so we keep a reference of it until the time it is used. Now that current_opp will always be set, we can drop some unnecessary checks as well. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 84 +++++++++++++++++++++++++++++----------------- drivers/opp/opp.h | 2 ++ 2 files changed, 55 insertions(+), 31 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 8ef85cd918ce..c77d8ae89836 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -788,8 +788,7 @@ restore_freq: __func__, old_freq); restore_voltage: /* This shouldn't harm even if the voltages weren't updated earlier */ - if (old_supply) - _set_opp_voltage(dev, reg, old_supply); + _set_opp_voltage(dev, reg, old_supply); return ret; } @@ -837,11 +836,7 @@ static int _set_opp_custom(const struct opp_table *opp_table, */ if (opp_table->sod_supplies) { size = sizeof(*old_supply) * opp_table->regulator_count; - if (!old_supply) - memset(data->old_opp.supplies, 0, size); - else - memcpy(data->old_opp.supplies, old_supply, size); - + memcpy(data->old_opp.supplies, old_supply, size); memcpy(data->new_opp.supplies, new_supply, size); data->regulator_count = opp_table->regulator_count; } else { @@ -950,6 +945,31 @@ int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp) } EXPORT_SYMBOL_GPL(dev_pm_opp_set_bw); +static void _find_current_opp(struct device *dev, struct opp_table *opp_table) +{ + struct dev_pm_opp *opp = ERR_PTR(-ENODEV); + unsigned long freq; + + if (!IS_ERR(opp_table->clk)) { + freq = clk_get_rate(opp_table->clk); + opp = _find_freq_ceil(opp_table, &freq); + } + + /* + * Unable to find the current OPP ? Pick the first from the list since + * it is in ascending order, otherwise rest of the code will need to + * make special checks to validate current_opp. + */ + if (IS_ERR(opp)) { + mutex_lock(&opp_table->lock); + opp = list_first_entry(&opp_table->opp_list, struct dev_pm_opp, node); + dev_pm_opp_get(opp); + mutex_unlock(&opp_table->lock); + } + + opp_table->current_opp = opp; +} + static int _disable_opp_table(struct device *dev, struct opp_table *opp_table) { int ret; @@ -1011,16 +1031,6 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) if ((long)freq <= 0) freq = target_freq; - old_freq = clk_get_rate(opp_table->clk); - - /* Return early if nothing to do */ - if (opp_table->enabled && old_freq == freq) { - dev_dbg(dev, "%s: old/new frequencies (%lu Hz) are same, nothing to do\n", - __func__, freq); - ret = 0; - goto put_opp_table; - } - /* * For IO devices which require an OPP on some platforms/SoCs * while just needing to scale the clock on some others @@ -1033,12 +1043,9 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) goto put_opp_table; } - temp_freq = old_freq; - old_opp = _find_freq_ceil(opp_table, &temp_freq); - if (IS_ERR(old_opp)) { - dev_err(dev, "%s: failed to find current OPP for freq %lu (%ld)\n", - __func__, old_freq, PTR_ERR(old_opp)); - } + /* Find the currently set OPP if we don't know already */ + if (unlikely(!opp_table->current_opp)) + _find_current_opp(dev, opp_table); temp_freq = freq; opp = _find_freq_ceil(opp_table, &temp_freq); @@ -1046,7 +1053,17 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) ret = PTR_ERR(opp); dev_err(dev, "%s: failed to find OPP for freq %lu (%d)\n", __func__, freq, ret); - goto put_old_opp; + goto put_opp_table; + } + + old_opp = opp_table->current_opp; + old_freq = old_opp->rate; + + /* Return early if nothing to do */ + if (opp_table->enabled && old_opp == opp) { + dev_dbg(dev, "%s: OPPs are same, nothing to do\n", __func__); + ret = 0; + goto put_opp; } dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, @@ -1061,11 +1078,10 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) 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); + 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, + old_opp->supplies, opp->supplies); } else { /* Only frequency scaling */ @@ -1081,15 +1097,18 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) if (!ret) { ret = _set_opp_bw(opp_table, opp, dev, false); - if (!ret) + if (!ret) { opp_table->enabled = true; + dev_pm_opp_put(old_opp); + + /* Make sure current_opp doesn't get freed */ + dev_pm_opp_get(opp); + opp_table->current_opp = opp; + } } put_opp: dev_pm_opp_put(opp); -put_old_opp: - if (!IS_ERR(old_opp)) - dev_pm_opp_put(old_opp); put_opp_table: dev_pm_opp_put_opp_table(opp_table); return ret; @@ -1298,6 +1317,9 @@ static void _opp_table_kref_release(struct kref *kref) list_del(&opp_table->node); mutex_unlock(&opp_table_lock); + if (opp_table->current_opp) + dev_pm_opp_put(opp_table->current_opp); + _of_clear_opp_table(opp_table); /* Release clk */ diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 64b9cb782a93..372df68e185b 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -135,6 +135,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_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. * @genpd_virt_devs: List of virtual devices for multiple genpd support. @@ -183,6 +184,7 @@ struct opp_table { unsigned int parsed_static_opps; enum opp_table_access shared_opp; + struct dev_pm_opp *current_opp; struct dev_pm_opp *suspend_opp; struct mutex genpd_virt_dev_lock; From 386ba854d9f3163aed0119b167a874169410d8bc Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 12:12:09 +0530 Subject: [PATCH 20/37] opp: Split _set_opp() out of dev_pm_opp_set_rate() The _set_opp() helper will be used for devices which don't change their frequency (like power domains, etc.) later on, prepare for that by breaking the generic part out of dev_pm_opp_set_rate(). Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 126 +++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index c77d8ae89836..2c8939d18783 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -998,72 +998,27 @@ static int _disable_opp_table(struct device *dev, struct opp_table *opp_table) return ret; } -/** - * dev_pm_opp_set_rate() - Configure new OPP based on frequency - * @dev: device for which we do this operation - * @target_freq: frequency to achieve - * - * This configures the power-supplies to the levels specified by the OPP - * corresponding to the target_freq, and programs the clock to a value <= - * target_freq, as rounded by clk_round_rate(). Device wanting to run at fmax - * provided by the opp, should have already rounded to the target OPP's - * frequency. - */ -int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) +static int _set_opp(struct device *dev, struct opp_table *opp_table, + struct dev_pm_opp *opp, unsigned long freq) { - struct opp_table *opp_table; - unsigned long freq, old_freq, temp_freq; - struct dev_pm_opp *old_opp, *opp; + struct dev_pm_opp *old_opp; + unsigned long old_freq; int ret; - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) { - dev_err(dev, "%s: device opp doesn't exist\n", __func__); - return PTR_ERR(opp_table); - } - - if (unlikely(!target_freq)) { - ret = _disable_opp_table(dev, opp_table); - goto put_opp_table; - } - - freq = clk_round_rate(opp_table->clk, target_freq); - if ((long)freq <= 0) - freq = target_freq; - - /* - * For IO devices which require an OPP on some platforms/SoCs - * while just needing to scale the clock on some others - * we look for empty OPP tables with just a clock handle and - * scale only the clk. This makes dev_pm_opp_set_rate() - * equivalent to a clk_set_rate() - */ - if (!_get_opp_count(opp_table)) { - ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); - goto put_opp_table; - } + if (unlikely(!opp)) + return _disable_opp_table(dev, opp_table); /* Find the currently set OPP if we don't know already */ if (unlikely(!opp_table->current_opp)) _find_current_opp(dev, opp_table); - temp_freq = freq; - opp = _find_freq_ceil(opp_table, &temp_freq); - if (IS_ERR(opp)) { - ret = PTR_ERR(opp); - dev_err(dev, "%s: failed to find OPP for freq %lu (%d)\n", - __func__, freq, ret); - goto put_opp_table; - } - old_opp = opp_table->current_opp; old_freq = old_opp->rate; /* Return early if nothing to do */ if (opp_table->enabled && old_opp == opp) { dev_dbg(dev, "%s: OPPs are same, nothing to do\n", __func__); - ret = 0; - goto put_opp; + return 0; } dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, @@ -1073,7 +1028,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) if (freq >= old_freq) { ret = _set_required_opps(dev, opp_table, opp, true); if (ret) - goto put_opp; + return ret; } if (opp_table->set_opp) { @@ -1107,8 +1062,69 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) } } -put_opp: - dev_pm_opp_put(opp); + return ret; +} + +/** + * dev_pm_opp_set_rate() - Configure new OPP based on frequency + * @dev: device for which we do this operation + * @target_freq: frequency to achieve + * + * This configures the power-supplies to the levels specified by the OPP + * corresponding to the target_freq, and programs the clock to a value <= + * target_freq, as rounded by clk_round_rate(). Device wanting to run at fmax + * provided by the opp, should have already rounded to the target OPP's + * frequency. + */ +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; + int ret; + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + dev_err(dev, "%s: device's opp table doesn't exist\n", __func__); + return PTR_ERR(opp_table); + } + + if (target_freq) { + /* + * For IO devices which require an OPP on some platforms/SoCs + * while just needing to scale the clock on some others + * we look for empty OPP tables with just a clock handle and + * scale only the clk. This makes dev_pm_opp_set_rate() + * equivalent to a clk_set_rate() + */ + if (!_get_opp_count(opp_table)) { + ret = _generic_set_opp_clk_only(dev, opp_table->clk, target_freq); + goto put_opp_table; + } + + freq = clk_round_rate(opp_table->clk, target_freq); + if ((long)freq <= 0) + freq = target_freq; + + /* + * The clock driver may support finer resolution of the + * frequencies than the OPP table, don't update the frequency we + * pass to clk_set_rate() here. + */ + temp_freq = freq; + opp = _find_freq_ceil(opp_table, &temp_freq); + if (IS_ERR(opp)) { + ret = PTR_ERR(opp); + dev_err(dev, "%s: failed to find OPP for freq %lu (%d)\n", + __func__, freq, ret); + goto put_opp_table; + } + } + + ret = _set_opp(dev, opp_table, opp, freq); + + if (target_freq) + dev_pm_opp_put(opp); put_opp_table: dev_pm_opp_put_opp_table(opp_table); return ret; From f0b88fa45595254fa51427bd8ca321732e2eb73d Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 16:00:12 +0530 Subject: [PATCH 21/37] opp: Allow _set_opp() to work for non-freq devices The _set_opp() helper will be used for devices which don't change frequency (like power domains, etc.) later on, prepare for that by not relying on frequency for making decisions here. While at it, also update the debug print to contain all relevant information. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 2c8939d18783..cce1b59d7bca 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1003,7 +1003,7 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, { struct dev_pm_opp *old_opp; unsigned long old_freq; - int ret; + int scaling_down, ret; if (unlikely(!opp)) return _disable_opp_table(dev, opp_table); @@ -1021,11 +1021,17 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, return 0; } - dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, - old_freq, freq); + dev_dbg(dev, "%s: switching OPP: Freq %lu -> %lu Hz, Level %u -> %u, Bw %u -> %u\n", + __func__, old_freq, freq, 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); + if (scaling_down == -1) + scaling_down = 0; /* Scaling up? Configure required OPPs before frequency */ - if (freq >= old_freq) { + if (!scaling_down) { ret = _set_required_opps(dev, opp_table, opp, true); if (ret) return ret; @@ -1044,7 +1050,7 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, } /* Scaling down? Configure required OPPs after frequency */ - if (!ret && freq < old_freq) { + if (!ret && scaling_down) { ret = _set_required_opps(dev, opp_table, opp, false); if (ret) dev_err(dev, "Failed to set required opps: %d\n", ret); From 3f62670fcca4af3fe6492100a548603831ecc61d Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 12:38:13 +0530 Subject: [PATCH 22/37] opp: Allow _generic_set_opp_regulator() to work for non-freq devices The _generic_set_opp_regulator() helper will be used for devices which don't change frequency (like power domains, etc.) later on, prepare for that by not relying on frequency for making decisions here. While at it, update its parameters to pass only what is necessary. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index cce1b59d7bca..c078c7dab6b2 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -737,12 +737,12 @@ static inline int _generic_set_opp_clk_only(struct device *dev, struct clk *clk, static int _generic_set_opp_regulator(struct opp_table *opp_table, struct device *dev, - unsigned long old_freq, + struct dev_pm_opp *opp, unsigned long freq, - struct dev_pm_opp_supply *old_supply, - struct dev_pm_opp_supply *new_supply) + int scaling_down) { struct regulator *reg = opp_table->regulators[0]; + struct dev_pm_opp *old_opp = opp_table->current_opp; int ret; /* This function only supports single regulator per device */ @@ -752,8 +752,8 @@ static int _generic_set_opp_regulator(struct opp_table *opp_table, } /* Scaling up? Scale voltage before frequency */ - if (freq >= old_freq) { - ret = _set_opp_voltage(dev, reg, new_supply); + if (!scaling_down) { + ret = _set_opp_voltage(dev, reg, opp->supplies); if (ret) goto restore_voltage; } @@ -764,8 +764,8 @@ static int _generic_set_opp_regulator(struct opp_table *opp_table, goto restore_voltage; /* Scaling down? Scale voltage after frequency */ - if (freq < old_freq) { - ret = _set_opp_voltage(dev, reg, new_supply); + if (scaling_down) { + ret = _set_opp_voltage(dev, reg, opp->supplies); if (ret) goto restore_freq; } @@ -783,12 +783,12 @@ static int _generic_set_opp_regulator(struct opp_table *opp_table, return 0; restore_freq: - if (_generic_set_opp_clk_only(dev, opp_table->clk, old_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_freq); + __func__, old_opp->rate); restore_voltage: /* This shouldn't harm even if the voltages weren't updated earlier */ - _set_opp_voltage(dev, reg, old_supply); + _set_opp_voltage(dev, reg, old_opp->supplies); return ret; } @@ -1041,9 +1041,8 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, ret = _set_opp_custom(opp_table, dev, old_freq, freq, old_opp->supplies, opp->supplies); } else if (opp_table->regulators) { - ret = _generic_set_opp_regulator(opp_table, dev, old_freq, freq, - old_opp->supplies, - opp->supplies); + 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); From 35e74b2ee8ec64da6f8067c5b0744f16ff19915b Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 12:38:13 +0530 Subject: [PATCH 23/37] opp: Allow _generic_set_opp_clk_only() to work for non-freq devices In order to avoid conditional statements at the caller site, this patch updates _generic_set_opp_clk_only() to work for devices that don't change frequency (like power domains, etc.). Return 0 if the clk pointer passed to this routine is not valid. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index c078c7dab6b2..f21ce52a5002 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -726,6 +726,10 @@ static inline int _generic_set_opp_clk_only(struct device *dev, struct clk *clk, { int ret; + /* We may reach here for devices which don't change frequency */ + if (IS_ERR(clk)) + return 0; + ret = clk_set_rate(clk, freq); if (ret) { dev_err(dev, "%s: failed to set clock rate: %d\n", __func__, From 509e4777ca41d30808deda5ae3c1e09e3f58a33f Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 13:06:01 +0530 Subject: [PATCH 24/37] opp: Update parameters of _set_opp_custom() Drop the unnecessary parameters and follow the pattern from _generic_set_opp_regulator(). While at it, also remove the local variable old_freq. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index f21ce52a5002..2b5584ef0350 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -826,12 +826,11 @@ static int _set_opp_bw(const struct opp_table *opp_table, } 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 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; /* @@ -839,9 +838,9 @@ static int _set_opp_custom(const struct opp_table *opp_table, * earlier. */ if (opp_table->sod_supplies) { - size = sizeof(*old_supply) * opp_table->regulator_count; - memcpy(data->old_opp.supplies, old_supply, size); - memcpy(data->new_opp.supplies, new_supply, size); + 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; @@ -850,7 +849,7 @@ static int _set_opp_custom(const struct opp_table *opp_table, data->regulators = opp_table->regulators; data->clk = opp_table->clk; data->dev = dev; - data->old_opp.rate = old_freq; + data->old_opp.rate = old_opp->rate; data->new_opp.rate = freq; return opp_table->set_opp(data); @@ -1006,7 +1005,6 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, struct dev_pm_opp *opp, unsigned long freq) { struct dev_pm_opp *old_opp; - unsigned long old_freq; int scaling_down, ret; if (unlikely(!opp)) @@ -1017,7 +1015,6 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, _find_current_opp(dev, opp_table); old_opp = opp_table->current_opp; - old_freq = old_opp->rate; /* Return early if nothing to do */ if (opp_table->enabled && old_opp == opp) { @@ -1026,7 +1023,7 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, } dev_dbg(dev, "%s: switching OPP: Freq %lu -> %lu Hz, Level %u -> %u, Bw %u -> %u\n", - __func__, old_freq, freq, old_opp->level, opp->level, + __func__, old_opp->rate, freq, old_opp->level, opp->level, old_opp->bandwidth ? old_opp->bandwidth[0].peak : 0, opp->bandwidth ? opp->bandwidth[0].peak : 0); @@ -1042,8 +1039,7 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, } if (opp_table->set_opp) { - ret = _set_opp_custom(opp_table, dev, old_freq, freq, - old_opp->supplies, opp->supplies); + 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); From abbe348340c7df9e08fd7c24491c1be31ab65370 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 12:15:36 +0530 Subject: [PATCH 25/37] opp: Implement dev_pm_opp_set_opp() The new helper dev_pm_opp_set_opp() can be used for configuring the devices for a particular OPP and can be used by different type of devices, even the ones which don't change frequency (like power domains). Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 28 ++++++++++++++++++++++++++++ include/linux/pm_opp.h | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 2b5584ef0350..fac84d5a1d45 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1136,6 +1136,34 @@ put_opp_table: } EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate); +/** + * dev_pm_opp_set_opp() - Configure device for OPP + * @dev: device for which we do this operation + * @opp: OPP to set to + * + * This configures the device based on the properties of the OPP passed to this + * routine. + * + * Return: 0 on success, a negative error number otherwise. + */ +int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp) +{ + struct opp_table *opp_table; + int ret; + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) { + dev_err(dev, "%s: device opp doesn't exist\n", __func__); + return PTR_ERR(opp_table); + } + + ret = _set_opp(dev, opp_table, opp, opp ? opp->rate : 0); + dev_pm_opp_put_opp_table(opp_table); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_set_opp); + /* OPP-dev Helpers */ static void _remove_opp_dev(struct opp_device *opp_dev, struct opp_table *opp_table) diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index a2c871799603..7b1d47ab3fb3 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -158,6 +158,7 @@ void dev_pm_opp_detach_genpd(struct opp_table *opp_table); struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate); int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq); +int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp); int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp); int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask); int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask); @@ -377,6 +378,11 @@ static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_f return -ENOTSUPP; } +static inline int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp) +{ + return -ENOTSUPP; +} + static inline int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp) { return -EOPNOTSUPP; From 8d25157f738c413b40b82776b0d260cd23505266 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 15:27:55 +0530 Subject: [PATCH 26/37] cpufreq: qcom: Migrate to dev_pm_opp_set_opp() dev_pm_opp_set_bw() is getting removed and dev_pm_opp_set_opp() should be used instead. Migrate to the new API. Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/cpufreq/qcom-cpufreq-hw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/cpufreq/qcom-cpufreq-hw.c b/drivers/cpufreq/qcom-cpufreq-hw.c index 9ed5341dc515..7df18903b66c 100644 --- a/drivers/cpufreq/qcom-cpufreq-hw.c +++ b/drivers/cpufreq/qcom-cpufreq-hw.c @@ -54,7 +54,7 @@ static int qcom_cpufreq_set_bw(struct cpufreq_policy *policy, if (IS_ERR(opp)) return PTR_ERR(opp); - ret = dev_pm_opp_set_bw(dev, opp); + ret = dev_pm_opp_set_opp(dev, opp); dev_pm_opp_put(opp); return ret; } From 920b4a678099dd7429f03cb00649c5455f21cc67 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 15:27:55 +0530 Subject: [PATCH 27/37] drm: msm: Migrate to dev_pm_opp_set_opp() dev_pm_opp_set_bw() is getting removed and dev_pm_opp_set_opp() should be used instead. Migrate to the new API. Signed-off-by: Viresh Kumar --- drivers/gpu/drm/msm/adreno/a6xx_gmu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c index e6703ae98760..05e0ef58fe32 100644 --- a/drivers/gpu/drm/msm/adreno/a6xx_gmu.c +++ b/drivers/gpu/drm/msm/adreno/a6xx_gmu.c @@ -134,7 +134,7 @@ void a6xx_gmu_set_freq(struct msm_gpu *gpu, struct dev_pm_opp *opp) if (!gmu->legacy) { a6xx_hfi_set_freq(gmu, perf_index); - dev_pm_opp_set_bw(&gpu->pdev->dev, opp); + dev_pm_opp_set_opp(&gpu->pdev->dev, opp); pm_runtime_put(gmu->dev); return; } @@ -158,7 +158,7 @@ void a6xx_gmu_set_freq(struct msm_gpu *gpu, struct dev_pm_opp *opp) if (ret) dev_err(gmu->dev, "GMU set GPU frequency error: %d\n", ret); - dev_pm_opp_set_bw(&gpu->pdev->dev, opp); + dev_pm_opp_set_opp(&gpu->pdev->dev, opp); pm_runtime_put(gmu->dev); } @@ -866,7 +866,7 @@ static void a6xx_gmu_set_initial_bw(struct msm_gpu *gpu, struct a6xx_gmu *gmu) if (IS_ERR_OR_NULL(gpu_opp)) return; - dev_pm_opp_set_bw(&gpu->pdev->dev, gpu_opp); + dev_pm_opp_set_opp(&gpu->pdev->dev, gpu_opp); dev_pm_opp_put(gpu_opp); } @@ -1072,7 +1072,7 @@ int a6xx_gmu_stop(struct a6xx_gpu *a6xx_gpu) a6xx_gmu_shutdown(gmu); /* Remove the bus vote */ - dev_pm_opp_set_bw(&gpu->pdev->dev, NULL); + dev_pm_opp_set_opp(&gpu->pdev->dev, NULL); /* * Make sure the GX domain is off before turning off the GMU (CX) From c7f142190d91a7e8b3df0a6ef9fabb591fb83c71 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 15:27:55 +0530 Subject: [PATCH 28/37] devfreq: tegra30: Migrate to dev_pm_opp_set_opp() dev_pm_opp_set_bw() is getting removed and dev_pm_opp_set_opp() should be used instead. Migrate to the new API. We don't want the OPP core to manage the clk for this driver, migrate to dev_pm_opp_of_add_table_noclk() to make sure dev_pm_opp_set_opp() doesn't have any side effects. Signed-off-by: Viresh Kumar Acked-by: Chanwoo Choi Tested-by: Dmitry Osipenko --- drivers/devfreq/tegra30-devfreq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/devfreq/tegra30-devfreq.c b/drivers/devfreq/tegra30-devfreq.c index 117cad7968ab..ce83f883ca65 100644 --- a/drivers/devfreq/tegra30-devfreq.c +++ b/drivers/devfreq/tegra30-devfreq.c @@ -647,7 +647,7 @@ static int tegra_devfreq_target(struct device *dev, unsigned long *freq, return PTR_ERR(opp); } - ret = dev_pm_opp_set_bw(dev, opp); + ret = dev_pm_opp_set_opp(dev, opp); dev_pm_opp_put(opp); return ret; @@ -849,7 +849,7 @@ static int tegra_devfreq_probe(struct platform_device *pdev) return err; } - err = dev_pm_opp_of_add_table(&pdev->dev); + err = dev_pm_opp_of_add_table_noclk(&pdev->dev, 0); if (err) { dev_err(&pdev->dev, "Failed to add OPP table: %d\n", err); goto put_hw; From 240ae50e23061cd1fe1937daab195c17226ffd2e Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 21 Jan 2021 15:27:55 +0530 Subject: [PATCH 29/37] opp: Remove dev_pm_opp_set_bw() All the users have migrated to dev_pm_opp_set_opp() now, get rid of the duplicate API, dev_pm_opp_set_bw(), which only performs a part of the new API. While at it, remove the unnecessary parameter to _set_opp_bw(). Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 41 +++++------------------------------------ include/linux/pm_opp.h | 6 ------ 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index fac84d5a1d45..6958a5cd2fd8 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -798,7 +798,7 @@ restore_voltage: } static int _set_opp_bw(const struct opp_table *opp_table, - struct dev_pm_opp *opp, struct device *dev, bool remove) + struct dev_pm_opp *opp, struct device *dev) { u32 avg, peak; int i, ret; @@ -807,7 +807,7 @@ static int _set_opp_bw(const struct opp_table *opp_table, return 0; for (i = 0; i < opp_table->path_count; i++) { - if (remove) { + if (!opp) { avg = 0; peak = 0; } else { @@ -817,7 +817,7 @@ static int _set_opp_bw(const struct opp_table *opp_table, ret = icc_set_bw(opp_table->paths[i], avg, peak); if (ret) { dev_err(dev, "Failed to %s bandwidth[%d]: %d\n", - remove ? "remove" : "set", i, ret); + opp ? "set" : "remove", i, ret); return ret; } } @@ -917,37 +917,6 @@ static int _set_required_opps(struct device *dev, return ret; } -/** - * dev_pm_opp_set_bw() - sets bandwidth levels corresponding to an opp - * @dev: device for which we do this operation - * @opp: opp based on which the bandwidth levels are to be configured - * - * This configures the bandwidth to the levels specified by the OPP. However - * if the OPP specified is NULL the bandwidth levels are cleared out. - * - * Return: 0 on success or a negative error value. - */ -int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp) -{ - struct opp_table *opp_table; - int ret; - - opp_table = _find_opp_table(dev); - if (IS_ERR(opp_table)) { - dev_err(dev, "%s: device opp table doesn't exist\n", __func__); - return PTR_ERR(opp_table); - } - - if (opp) - ret = _set_opp_bw(opp_table, opp, dev, false); - else - ret = _set_opp_bw(opp_table, NULL, dev, true); - - dev_pm_opp_put_opp_table(opp_table); - return ret; -} -EXPORT_SYMBOL_GPL(dev_pm_opp_set_bw); - static void _find_current_opp(struct device *dev, struct opp_table *opp_table) { struct dev_pm_opp *opp = ERR_PTR(-ENODEV); @@ -988,7 +957,7 @@ static int _disable_opp_table(struct device *dev, struct opp_table *opp_table) if (!_get_opp_count(opp_table)) return 0; - ret = _set_opp_bw(opp_table, NULL, dev, true); + ret = _set_opp_bw(opp_table, NULL, dev); if (ret) return ret; @@ -1056,7 +1025,7 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, } if (!ret) { - ret = _set_opp_bw(opp_table, opp, dev, false); + ret = _set_opp_bw(opp_table, opp, dev); if (!ret) { opp_table->enabled = true; dev_pm_opp_put(old_opp); diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 7b1d47ab3fb3..25e47ab937b9 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -159,7 +159,6 @@ struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, const char **name int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate); int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq); int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp); -int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp); int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask); int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask); void dev_pm_opp_remove_table(struct device *dev); @@ -383,11 +382,6 @@ static inline int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp) return -ENOTSUPP; } -static inline int dev_pm_opp_set_bw(struct device *dev, struct dev_pm_opp *opp) -{ - return -EOPNOTSUPP; -} - static inline int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask) { return -ENOTSUPP; From 7eba0c7641b0009818e469dbfcdd87a0155ab9d4 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 25 Nov 2019 13:57:58 +0530 Subject: [PATCH 30/37] opp: Allow lazy-linking of required-opps The OPP core currently requires the required opp tables to be available before the dependent OPP table is added, as it needs to create links from the dependent OPP table to the required ones. This may not be convenient for all the platforms though, as this requires strict ordering for probing the drivers. This patch allows lazy-linking of the required-opps. The OPP tables for which the required-opp-tables aren't available at the time of their initialization, are added to a special list of OPP tables: lazy_opp_tables. Later on, whenever a new OPP table is registered with the OPP core, we check if it is required by an OPP table in the pending list; if yes, then we complete the linking then and there. An OPP table is marked unusable until the time all its required-opp tables are available. And if lazy-linking fails for an OPP table, the OPP core disables all of its OPPs to make sure no one can use them. Tested-by: Hsin-Yi Wang Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 45 +++++++++++++---- drivers/opp/of.c | 122 +++++++++++++++++++++++++++++++++++++++++++-- drivers/opp/opp.h | 10 +++- 3 files changed, 161 insertions(+), 16 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 6958a5cd2fd8..e03600547b98 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -27,6 +27,10 @@ * various states of availability. */ LIST_HEAD(opp_tables); + +/* OPP tables with uninitialized required OPPs */ +LIST_HEAD(lazy_opp_tables); + /* Lock to allow exclusive modification to the device and opp lists */ DEFINE_MUTEX(opp_table_lock); /* Flag indicating that opp_tables list is being updated at the moment */ @@ -163,6 +167,10 @@ unsigned int dev_pm_opp_get_required_pstate(struct dev_pm_opp *opp, return 0; } + /* required-opps not fully initialized yet */ + if (lazy_linking_pending(opp->opp_table)) + return 0; + return opp->required_opps[index]->pstate; } EXPORT_SYMBOL_GPL(dev_pm_opp_get_required_pstate); @@ -885,6 +893,10 @@ static int _set_required_opps(struct device *dev, if (!required_opp_tables) return 0; + /* required-opps not fully initialized yet */ + if (lazy_linking_pending(opp_table)) + return -EBUSY; + /* Single genpd case */ if (!genpd_virt_devs) return _set_required_opp(dev, dev, opp, 0); @@ -1181,6 +1193,7 @@ static struct opp_table *_allocate_opp_table(struct device *dev, int index) mutex_init(&opp_table->lock); mutex_init(&opp_table->genpd_virt_dev_lock); INIT_LIST_HEAD(&opp_table->dev_list); + INIT_LIST_HEAD(&opp_table->lazy); /* Mark regulator count uninitialized */ opp_table->regulator_count = -1; @@ -1632,6 +1645,21 @@ static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp, return 0; } +void _required_opps_available(struct dev_pm_opp *opp, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (opp->required_opps[i]->available) + continue; + + opp->available = false; + pr_warn("%s: OPP not supported by required OPP %pOF (%lu)\n", + __func__, opp->required_opps[i]->np, opp->rate); + return; + } +} + /* * Returns: * 0: On success. And appropriate error message for duplicate OPPs. @@ -1646,7 +1674,6 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available) { struct list_head *head; - unsigned int i; int ret; mutex_lock(&opp_table->lock); @@ -1672,15 +1699,11 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, __func__, new_opp->rate); } - for (i = 0; i < opp_table->required_opp_count; i++) { - if (new_opp->required_opps[i]->available) - continue; + /* required-opps not fully initialized yet */ + if (lazy_linking_pending(opp_table)) + return 0; - new_opp->available = false; - dev_warn(dev, "%s: OPP not supported by required OPP %pOF (%lu)\n", - __func__, new_opp->required_opps[i]->np, new_opp->rate); - break; - } + _required_opps_available(new_opp, opp_table->required_opp_count); return 0; } @@ -2388,6 +2411,10 @@ int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, if (!src_table || !src_table->required_opp_count) return pstate; + /* required-opps not fully initialized yet */ + if (lazy_linking_pending(src_table)) + return -EBUSY; + for (i = 0; i < src_table->required_opp_count; i++) { if (src_table->required_opp_tables[i]->np == dst_table->np) break; diff --git a/drivers/opp/of.c b/drivers/opp/of.c index 20ccdaab9384..f480c10e6314 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -144,7 +144,7 @@ static void _opp_table_free_required_tables(struct opp_table *opp_table) for (i = 0; i < opp_table->required_opp_count; i++) { if (IS_ERR_OR_NULL(required_opp_tables[i])) - break; + continue; dev_pm_opp_put_opp_table(required_opp_tables[i]); } @@ -153,6 +153,7 @@ static void _opp_table_free_required_tables(struct opp_table *opp_table) opp_table->required_opp_count = 0; opp_table->required_opp_tables = NULL; + list_del(&opp_table->lazy); } /* @@ -165,6 +166,7 @@ static void _opp_table_alloc_required_tables(struct opp_table *opp_table, { struct opp_table **required_opp_tables; struct device_node *required_np, *np; + bool lazy = false; int count, i; /* Traversing the first OPP node is all we need */ @@ -195,8 +197,10 @@ static void _opp_table_alloc_required_tables(struct opp_table *opp_table, required_opp_tables[i] = _find_table_of_opp_np(required_np); of_node_put(required_np); - if (IS_ERR(required_opp_tables[i])) - goto free_required_tables; + if (IS_ERR(required_opp_tables[i])) { + lazy = true; + continue; + } /* * We only support genpd's OPPs in the "required-opps" for now, @@ -210,6 +214,10 @@ static void _opp_table_alloc_required_tables(struct opp_table *opp_table, } } + /* Let's do the linking later on */ + if (lazy) + list_add(&opp_table->lazy, &lazy_opp_tables); + goto put_np; free_required_tables: @@ -278,14 +286,14 @@ void _of_opp_free_required_opps(struct opp_table *opp_table, for (i = 0; i < opp_table->required_opp_count; i++) { if (!required_opps[i]) - break; + continue; /* Put the reference back */ dev_pm_opp_put(required_opps[i]); } - kfree(required_opps); opp->required_opps = NULL; + kfree(required_opps); } /* Populate all required OPPs which are part of "required-opps" list */ @@ -309,6 +317,10 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table, for (i = 0; i < count; i++) { required_table = opp_table->required_opp_tables[i]; + /* Required table not added yet, we will link later */ + if (IS_ERR_OR_NULL(required_table)) + continue; + np = of_parse_required_opp(opp->np, i); if (unlikely(!np)) { ret = -ENODEV; @@ -334,6 +346,104 @@ free_required_opps: return ret; } +/* Link required OPPs for an individual OPP */ +static int lazy_link_required_opps(struct opp_table *opp_table, + struct opp_table *new_table, int index) +{ + struct device_node *required_np; + struct dev_pm_opp *opp; + + list_for_each_entry(opp, &opp_table->opp_list, node) { + required_np = of_parse_required_opp(opp->np, index); + if (unlikely(!required_np)) + return -ENODEV; + + opp->required_opps[index] = _find_opp_of_np(new_table, required_np); + of_node_put(required_np); + + if (!opp->required_opps[index]) { + pr_err("%s: Unable to find required OPP node: %pOF (%d)\n", + __func__, opp->np, index); + return -ENODEV; + } + } + + return 0; +} + +/* Link required OPPs for all OPPs of the newly added OPP table */ +static void lazy_link_required_opp_table(struct opp_table *new_table) +{ + struct opp_table *opp_table, *temp, **required_opp_tables; + struct device_node *required_np, *opp_np, *required_table_np; + struct dev_pm_opp *opp; + int i, ret; + + /* + * We only support genpd's OPPs in the "required-opps" for now, + * as we don't know much about other cases. + */ + if (!new_table->is_genpd) + return; + + mutex_lock(&opp_table_lock); + + list_for_each_entry_safe(opp_table, temp, &lazy_opp_tables, lazy) { + bool lazy = false; + + /* opp_np can't be invalid here */ + opp_np = of_get_next_available_child(opp_table->np, NULL); + + for (i = 0; i < opp_table->required_opp_count; i++) { + required_opp_tables = opp_table->required_opp_tables; + + /* Required opp-table is already parsed */ + if (!IS_ERR(required_opp_tables[i])) + continue; + + /* required_np can't be invalid here */ + required_np = of_parse_required_opp(opp_np, i); + required_table_np = of_get_parent(required_np); + + of_node_put(required_table_np); + of_node_put(required_np); + + /* + * Newly added table isn't the required opp-table for + * opp_table. + */ + if (required_table_np != new_table->np) { + lazy = true; + continue; + } + + required_opp_tables[i] = new_table; + _get_opp_table_kref(new_table); + + /* Link OPPs now */ + ret = lazy_link_required_opps(opp_table, new_table, i); + if (ret) { + /* The OPPs will be marked unusable */ + lazy = false; + break; + } + } + + of_node_put(opp_np); + + /* All required opp-tables found, remove from lazy list */ + if (!lazy) { + list_del(&opp_table->lazy); + INIT_LIST_HEAD(&opp_table->lazy); + + list_for_each_entry(opp, &opp_table->opp_list, node) + _required_opps_available(opp, opp_table->required_opp_count); + } + } + + mutex_unlock(&opp_table_lock); +} + static int _bandwidth_supported(struct device *dev, struct opp_table *opp_table) { struct device_node *np, *opp_np; @@ -889,6 +999,8 @@ static int _of_add_opp_table_v2(struct device *dev, struct opp_table *opp_table) } } + lazy_link_required_opp_table(opp_table); + return 0; remove_static_opp: diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 372df68e185b..9b9daf83b074 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -26,7 +26,7 @@ struct regulator; /* Lock to allow exclusive modification to the device and opp lists */ extern struct mutex opp_table_lock; -extern struct list_head opp_tables; +extern struct list_head opp_tables, lazy_opp_tables; /* * Internal data structure organization with the OPP layer library is as @@ -168,7 +168,7 @@ enum opp_table_access { * meant for book keeping and private to OPP library. */ struct opp_table { - struct list_head node; + struct list_head node, lazy; struct blocking_notifier_head head; struct list_head dev_list; @@ -229,6 +229,12 @@ int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long f 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); void _put_opp_list_kref(struct opp_table *opp_table); +void _required_opps_available(struct dev_pm_opp *opp, int count); + +static inline bool lazy_linking_pending(struct opp_table *opp_table) +{ + return unlikely(!list_empty(&opp_table->lazy)); +} #ifdef CONFIG_OF void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index); From 870d5d963972ddefa83a09a7dbe4bef01f0b35b8 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Thu, 28 Jan 2021 15:30:00 +0530 Subject: [PATCH 31/37] opp: Update bandwidth requirements based on scaling up/down The bandwidth must be scaled at a different point in the code flow based on if we are scaling up or down the frequency, otherwise this may cause undesired effects as the device will try to use more of the memory bandwidth which may be shared across several devices. Much like how regulators and required-opps are programmed. Reported-by: Dmitry Osipenko Reported-by: Akhil P Oommen Signed-off-by: Viresh Kumar Tested-by: Dmitry Osipenko --- drivers/opp/core.c | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index e03600547b98..a518173fd64a 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1015,8 +1015,16 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, /* Scaling up? Configure required OPPs before frequency */ if (!scaling_down) { ret = _set_required_opps(dev, opp_table, opp, true); - if (ret) + if (ret) { + dev_err(dev, "Failed to set required opps: %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); + return ret; + } } if (opp_table->set_opp) { @@ -1029,25 +1037,31 @@ static int _set_opp(struct device *dev, struct opp_table *opp_table, ret = _generic_set_opp_clk_only(dev, opp_table->clk, freq); } + if (ret) + return ret; + /* Scaling down? Configure required OPPs after frequency */ - if (!ret && scaling_down) { - ret = _set_required_opps(dev, opp_table, opp, false); - if (ret) - dev_err(dev, "Failed to set required opps: %d\n", ret); - } - - if (!ret) { + if (scaling_down) { ret = _set_opp_bw(opp_table, opp, dev); - if (!ret) { - opp_table->enabled = true; - dev_pm_opp_put(old_opp); + if (ret) { + dev_err(dev, "Failed to set bw: %d\n", ret); + return ret; + } - /* Make sure current_opp doesn't get freed */ - dev_pm_opp_get(opp); - opp_table->current_opp = opp; + ret = _set_required_opps(dev, opp_table, opp, false); + if (ret) { + dev_err(dev, "Failed to set required opps: %d\n", ret); + return ret; } } + opp_table->enabled = true; + dev_pm_opp_put(old_opp); + + /* Make sure current_opp doesn't get freed */ + dev_pm_opp_get(opp); + opp_table->current_opp = opp; + return ret; } From d4a4c7a41153d701f23322ea5d39c766e9ff6eee Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Fri, 29 Jan 2021 16:12:04 +0530 Subject: [PATCH 32/37] opp: Don't ignore clk_get() errors other than -ENOENT Not all devices that need to use OPP core need to have clocks, a missing clock is fine in which case -ENOENT shall be returned by clk_get(). Anything else is an error and must be handled properly. Reported-by: Dmitry Osipenko Tested-by: Dmitry Osipenko Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index a518173fd64a..dc95d29e94c1 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -1252,6 +1252,8 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, struct opp_table *opp_table, bool getclk) { + int ret; + /* * Return early if we don't need to get clk or we have already tried it * earlier. @@ -1261,18 +1263,20 @@ static struct opp_table *_update_opp_table_clk(struct device *dev, /* Find clk for the device */ opp_table->clk = clk_get(dev, NULL); - if (IS_ERR(opp_table->clk)) { - int ret = PTR_ERR(opp_table->clk); - if (ret == -EPROBE_DEFER) { - dev_pm_opp_put_opp_table(opp_table); - return ERR_PTR(ret); - } + ret = PTR_ERR_OR_ZERO(opp_table->clk); + if (!ret) + return opp_table; + if (ret == -ENOENT) { dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__, ret); + return opp_table; } - return opp_table; + dev_pm_opp_put_opp_table(opp_table); + dev_err_probe(dev, ret, "Couldn't find clock\n"); + + return ERR_PTR(ret); } /* From f3988bc5d58b768c5cf0dadf5f0e49f7176432df Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 1 Feb 2021 10:35:07 +0530 Subject: [PATCH 33/37] opp: Fix "foo * bar" should be "foo *bar" Fix checkpatch warning: ERROR: "foo * bar" should be "foo *bar". Signed-off-by: Viresh Kumar --- include/linux/pm_opp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 25e47ab937b9..c6c7d73eb015 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -148,7 +148,7 @@ struct opp_table *dev_pm_opp_set_prop_name(struct device *dev, const char *name) void dev_pm_opp_put_prop_name(struct opp_table *opp_table); struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count); void dev_pm_opp_put_regulators(struct opp_table *opp_table); -struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char * name); +struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name); void dev_pm_opp_put_clkname(struct opp_table *opp_table); struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)); void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table); @@ -347,7 +347,7 @@ static inline struct opp_table *dev_pm_opp_set_regulators(struct device *dev, co static inline void dev_pm_opp_put_regulators(struct opp_table *opp_table) {} -static inline struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char * name) +static inline struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name) { return ERR_PTR(-ENOTSUPP); } From 1d614920318b914f86c1fec2adec06ad2f7c3f55 Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Mon, 1 Feb 2021 10:48:54 +0530 Subject: [PATCH 34/37] opp: Replace ENOTSUPP with EOPNOTSUPP Checkpatch gives following warning for new patches, and the new patches normally follow the existing standards for such stuff. Lets fix it properly. WARNING: ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP. Signed-off-by: Viresh Kumar --- include/linux/pm_opp.h | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index c6c7d73eb015..ab1d15ce559d 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -167,12 +167,12 @@ int dev_pm_opp_sync_regulators(struct device *dev); #else static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct opp_table *dev_pm_opp_get_opp_table_indexed(struct device *dev, int index) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_put_opp_table(struct opp_table *opp_table) {} @@ -232,37 +232,37 @@ static inline unsigned long dev_pm_opp_get_suspend_opp_freq(struct device *dev) static inline struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, unsigned long freq, bool available) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct dev_pm_opp *dev_pm_opp_find_level_exact(struct device *dev, unsigned int level) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct dev_pm_opp *dev_pm_opp_find_level_ceil(struct device *dev, unsigned int *level) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, unsigned long *freq) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct dev_pm_opp *dev_pm_opp_find_freq_ceil_by_volt(struct device *dev, unsigned long u_volt) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev, unsigned long *freq) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_put(struct dev_pm_opp *opp) {} @@ -270,7 +270,7 @@ static inline void dev_pm_opp_put(struct dev_pm_opp *opp) {} static inline int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline void dev_pm_opp_remove(struct device *dev, unsigned long freq) @@ -301,19 +301,19 @@ static inline int dev_pm_opp_disable(struct device *dev, unsigned long freq) static inline int dev_pm_opp_register_notifier(struct device *dev, struct notifier_block *nb) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_unregister_notifier(struct device *dev, struct notifier_block *nb) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline struct opp_table *dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions, unsigned int count) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_put_supported_hw(struct opp_table *opp_table) {} @@ -321,7 +321,7 @@ static inline void dev_pm_opp_put_supported_hw(struct opp_table *opp_table) {} static inline struct opp_table *dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_unregister_set_opp_helper(struct opp_table *opp_table) {} @@ -330,33 +330,33 @@ static inline struct opp_table * devm_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data)) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline struct opp_table *dev_pm_opp_set_prop_name(struct device *dev, const char *name) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_put_prop_name(struct opp_table *opp_table) {} static inline struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_put_regulators(struct opp_table *opp_table) {} static inline struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_put_clkname(struct opp_table *opp_table) {} static inline struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline void dev_pm_opp_detach_genpd(struct opp_table *opp_table) {} @@ -364,27 +364,27 @@ static inline void dev_pm_opp_detach_genpd(struct opp_table *opp_table) {} static inline struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs) { - return ERR_PTR(-ENOTSUPP); + return ERR_PTR(-EOPNOTSUPP); } static inline int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) @@ -402,7 +402,7 @@ static inline void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask static inline int dev_pm_opp_sync_regulators(struct device *dev) { - return -ENOTSUPP; + return -EOPNOTSUPP; } #endif /* CONFIG_PM_OPP */ @@ -427,17 +427,17 @@ static inline void dev_pm_opp_of_unregister_em(struct device *dev) #else static inline int dev_pm_opp_of_add_table(struct device *dev) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_of_add_table_indexed(struct device *dev, int index) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_of_add_table_noclk(struct device *dev, int index) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline void dev_pm_opp_of_remove_table(struct device *dev) @@ -446,7 +446,7 @@ static inline void dev_pm_opp_of_remove_table(struct device *dev) static inline int dev_pm_opp_of_cpumask_add_table(const struct cpumask *cpumask) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpumask) @@ -455,7 +455,7 @@ static inline void dev_pm_opp_of_cpumask_remove_table(const struct cpumask *cpum static inline int dev_pm_opp_of_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline struct device_node *dev_pm_opp_of_get_opp_desc_node(struct device *dev) @@ -471,7 +471,7 @@ static inline struct device_node *dev_pm_opp_get_of_node(struct dev_pm_opp *opp) static inline int dev_pm_opp_of_register_em(struct device *dev, struct cpumask *cpus) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline void dev_pm_opp_of_unregister_em(struct device *dev) @@ -480,12 +480,12 @@ static inline void dev_pm_opp_of_unregister_em(struct device *dev) static inline int of_get_required_opp_performance_state(struct device_node *np, int index) { - return -ENOTSUPP; + return -EOPNOTSUPP; } static inline int dev_pm_opp_of_find_icc_paths(struct device *dev, struct opp_table *opp_table) { - return -ENOTSUPP; + return -EOPNOTSUPP; } #endif From 7d8658ef65a4f891d0cff6340fa717b378384642 Mon Sep 17 00:00:00 2001 From: Saravana Kannan Date: Thu, 4 Feb 2021 16:14:22 +0800 Subject: [PATCH 35/37] OPP: Add function to look up required OPP's for a given OPP Add a function that allows looking up required OPPs given a source OPP table, destination OPP table and the source OPP. Signed-off-by: Saravana Kannan Signed-off-by: Hsin-Yi Wang [ Viresh: Rearranged code, fixed return errors ] Signed-off-by: Viresh Kumar --- drivers/opp/core.c | 55 ++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 7 ++++++ 2 files changed, 62 insertions(+) diff --git a/drivers/opp/core.c b/drivers/opp/core.c index dc95d29e94c1..c3f3d9249cc5 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2398,6 +2398,61 @@ devm_pm_opp_attach_genpd(struct device *dev, const char **names, } EXPORT_SYMBOL_GPL(devm_pm_opp_attach_genpd); +/** + * dev_pm_opp_xlate_required_opp() - Find required OPP for @src_table OPP. + * @src_table: OPP table which has @dst_table as one of its required OPP table. + * @dst_table: Required OPP table of the @src_table. + * @src_opp: OPP from the @src_table. + * + * This function returns the OPP (present in @dst_table) pointed out by the + * "required-opps" property of the @src_opp (present in @src_table). + * + * The callers are required to call dev_pm_opp_put() for the returned OPP after + * use. + * + * Return: pointer to 'struct dev_pm_opp' on success and errorno otherwise. + */ +struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table, + struct opp_table *dst_table, + struct dev_pm_opp *src_opp) +{ + struct dev_pm_opp *opp, *dest_opp = ERR_PTR(-ENODEV); + int i; + + if (!src_table || !dst_table || !src_opp || + !src_table->required_opp_tables) + return ERR_PTR(-EINVAL); + + /* required-opps not fully initialized yet */ + if (lazy_linking_pending(src_table)) + return ERR_PTR(-EBUSY); + + for (i = 0; i < src_table->required_opp_count; i++) { + if (src_table->required_opp_tables[i] == dst_table) { + mutex_lock(&src_table->lock); + + list_for_each_entry(opp, &src_table->opp_list, node) { + if (opp == src_opp) { + dest_opp = opp->required_opps[i]; + dev_pm_opp_get(dest_opp); + break; + } + } + + mutex_unlock(&src_table->lock); + break; + } + } + + if (IS_ERR(dest_opp)) { + pr_err("%s: Couldn't find matching OPP (%p: %p)\n", __func__, + src_table, dst_table); + } + + return dest_opp; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_xlate_required_opp); + /** * dev_pm_opp_xlate_performance_state() - Find required OPP's pstate for src_table. * @src_table: OPP table which has dst_table as one of its required OPP table. diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index ab1d15ce559d..c0371efa4a0f 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -156,6 +156,7 @@ struct opp_table *devm_pm_opp_register_set_opp_helper(struct device *dev, int (* struct opp_table *dev_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); void dev_pm_opp_detach_genpd(struct opp_table *opp_table); struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, const char **names, struct device ***virt_devs); +struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table, struct opp_table *dst_table, struct dev_pm_opp *src_opp); int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate); int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq); int dev_pm_opp_set_opp(struct device *dev, struct dev_pm_opp *opp); @@ -367,6 +368,12 @@ static inline struct opp_table *devm_pm_opp_attach_genpd(struct device *dev, return ERR_PTR(-EOPNOTSUPP); } +static inline struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table, + struct opp_table *dst_table, struct dev_pm_opp *src_opp) +{ + return ERR_PTR(-EOPNOTSUPP); +} + static inline int dev_pm_opp_xlate_performance_state(struct opp_table *src_table, struct opp_table *dst_table, unsigned int pstate) { return -EOPNOTSUPP; From 26f9c7cc42a6dc036edf871544fd0e6b3a0601c1 Mon Sep 17 00:00:00 2001 From: Saravana Kannan Date: Thu, 4 Feb 2021 16:14:23 +0800 Subject: [PATCH 36/37] PM / devfreq: Cache OPP table reference in devfreq The OPP table can be used often in devfreq. Trying to get it each time can be expensive, so cache it in the devfreq struct. Signed-off-by: Saravana Kannan Acked-by: MyungJoo Ham Acked-by: Chanwoo Choi Signed-off-by: Hsin-Yi Wang [ Viresh: Added a blank line ] Signed-off-by: Viresh Kumar --- drivers/devfreq/devfreq.c | 7 +++++++ include/linux/devfreq.h | 2 ++ 2 files changed, 9 insertions(+) diff --git a/drivers/devfreq/devfreq.c b/drivers/devfreq/devfreq.c index 6aa10de792b3..cefe84a10824 100644 --- a/drivers/devfreq/devfreq.c +++ b/drivers/devfreq/devfreq.c @@ -757,6 +757,9 @@ static void devfreq_dev_release(struct device *dev) if (devfreq->profile->exit) devfreq->profile->exit(devfreq->dev.parent); + if (devfreq->opp_table) + dev_pm_opp_put_opp_table(devfreq->opp_table); + mutex_destroy(&devfreq->lock); kfree(devfreq); } @@ -844,6 +847,10 @@ struct devfreq *devfreq_add_device(struct device *dev, } devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev); + devfreq->opp_table = dev_pm_opp_get_opp_table(dev); + if (IS_ERR(devfreq->opp_table)) + devfreq->opp_table = NULL; + atomic_set(&devfreq->suspend_count, 0); dev_set_name(&devfreq->dev, "%s", dev_name(dev)); diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index b6d3bae1c74d..26ea0850be9b 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -137,6 +137,7 @@ struct devfreq_stats { * using devfreq. * @profile: device-specific devfreq profile * @governor: method how to choose frequency based on the usage. + * @opp_table: Reference to OPP table of dev.parent, if one exists. * @nb: notifier block used to notify devfreq object that it should * reevaluate operable frequencies. Devfreq users may use * devfreq.nb to the corresponding register notifier call chain. @@ -173,6 +174,7 @@ struct devfreq { struct device dev; struct devfreq_dev_profile *profile; const struct devfreq_governor *governor; + struct opp_table *opp_table; struct notifier_block nb; struct delayed_work work; From 86ad9a24f21ea7aac7deed06fe9556392568d88a Mon Sep 17 00:00:00 2001 From: Saravana Kannan Date: Thu, 4 Feb 2021 16:14:24 +0800 Subject: [PATCH 37/37] PM / devfreq: Add required OPPs support to passive governor Look at the required OPPs of the "parent" device to determine the OPP that is required from the slave device managed by the passive governor. This allows having mappings between a parent device and a slave device even when they don't have the same number of OPPs. While at it do a minor spell-fix and remove out label. Signed-off-by: Saravana Kannan Acked-by: MyungJoo Ham Acked-by: Chanwoo Choi Signed-off-by: Hsin-Yi Wang [ Viresh: Rearranged code and clean error paths ] Signed-off-by: Viresh Kumar --- drivers/devfreq/governor_passive.c | 44 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c index 63332e4a65ae..b094132bd20b 100644 --- a/drivers/devfreq/governor_passive.c +++ b/drivers/devfreq/governor_passive.c @@ -19,18 +19,16 @@ static int devfreq_passive_get_target_freq(struct devfreq *devfreq, = (struct devfreq_passive_data *)devfreq->data; struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent; unsigned long child_freq = ULONG_MAX; - struct dev_pm_opp *opp; - int i, count, ret = 0; + struct dev_pm_opp *opp, *p_opp; + int i, count; /* * If the devfreq device with passive governor has the specific method * to determine the next frequency, should use the get_target_freq() * of struct devfreq_passive_data. */ - if (p_data->get_target_freq) { - ret = p_data->get_target_freq(devfreq, freq); - goto out; - } + if (p_data->get_target_freq) + return p_data->get_target_freq(devfreq, freq); /* * If the parent and passive devfreq device uses the OPP table, @@ -56,26 +54,35 @@ static int devfreq_passive_get_target_freq(struct devfreq *devfreq, * list of parent device. Because in this case, *freq is temporary * value which is decided by ondemand governor. */ - opp = devfreq_recommended_opp(parent_devfreq->dev.parent, freq, 0); - if (IS_ERR(opp)) { - ret = PTR_ERR(opp); - goto out; + if (devfreq->opp_table && parent_devfreq->opp_table) { + p_opp = devfreq_recommended_opp(parent_devfreq->dev.parent, + freq, 0); + if (IS_ERR(p_opp)) + return PTR_ERR(p_opp); + + opp = dev_pm_opp_xlate_required_opp(parent_devfreq->opp_table, + devfreq->opp_table, p_opp); + dev_pm_opp_put(p_opp); + + if (IS_ERR(opp)) + return PTR_ERR(opp); + + *freq = dev_pm_opp_get_freq(opp); + dev_pm_opp_put(opp); + + return 0; } - dev_pm_opp_put(opp); - /* - * Get the OPP table's index of decided freqeuncy by governor + * Get the OPP table's index of decided frequency by governor * of parent device. */ for (i = 0; i < parent_devfreq->profile->max_state; i++) if (parent_devfreq->profile->freq_table[i] == *freq) break; - if (i == parent_devfreq->profile->max_state) { - ret = -EINVAL; - goto out; - } + if (i == parent_devfreq->profile->max_state) + return -EINVAL; /* Get the suitable frequency by using index of parent device. */ if (i < devfreq->profile->max_state) { @@ -88,8 +95,7 @@ static int devfreq_passive_get_target_freq(struct devfreq *devfreq, /* Return the suitable frequency for passive device. */ *freq = child_freq; -out: - return ret; + return 0; } static int devfreq_passive_notifier_call(struct notifier_block *nb,