Hi Guys,
Resending this again because: - opp-hz is now 64 bit long and so that required a binding update. - There is another 4 patch series that as updating turbo OPP support, along with updates to cpufreq core and cpufreq-dt driver. - A minor bug-fix in V2 4/11, which caused false positive for duplicate OPPs in some cases.
Tested this on dual-core exynos board with the driver inbuilt as well as a module. Tried multiple insertion/removals of the module. Have tested cpufreq-dt driver with both old and new bindings.
Also tried by sending some dummy OPPs that contained opp-hz greater than what can be captured by u32, to test the u64 updates.
----------x-----------------x-------------
This adds code to support operating-points-v2 bindings. Not everything is supported yet, but most of the basic stuff is.
Pushed here as well for reference: ssh://git@git.linaro.org/people/viresh.kumar/linux.git opp/v2
V2->V3: - Update to opp-hz binding to make it pass u64 value instead of 32 bit value. This is done to support clock rates greater than 4 GHz. - Merged two inter-related series, opp-v2 and turbo mode updates. - Fixed 4/11 from opp-v2 series, which cased false positive for duplicate OPPs in some cases. - Add a separate comment in _opp_add_dynamic() (5/16), to clearly specify that we pass &dev_opp->opp_list to list_for_each_entry_rcu() and not pass 'head' which contains the same value. Because head will get modified by the code and that may result in an infinite loop. - Accumulate more RBY tags.
V1->V2: - 1/11 is a new patch which moves the bindings file into opp specific folder. - 'opp-suspend' binding got updated and is part of individual OPPs now, instead of a phandle present in the opp table. - Some more minor reformatting as suggested by Bartlomiej and Stephen, they were already posted as reply to earlier patches in V1.
Bartlomiej Zolnierkiewicz (2): PM / OPP: add dev_pm_opp_is_turbo() helper cpufreq: Update boost flag while initializing freq table from OPPs
Viresh Kumar (14): PM / OPP: Update bindings to make opp-hz a 64 bit value PM / OPP: Create a directory for opp bindings PM / OPP: Relocate few routines PM / OPP: Create _remove_device_opp() for freeing dev_opp PM / OPP: Allocate dev_opp from _add_device_opp() PM / OPP: Break _opp_add_dynamic() into smaller functions PM / OPP: Add support to parse "operating-points-v2" bindings PM / OPP: Add clock-latency-ns support PM / OPP: Add OPP sharing information to OPP library PM / OPP: Add support for opp-suspend PM / OPP: Add helpers for initializing CPU OPPs cpufreq: Allow drivers to enable boost support after registering driver cpufreq: dt: Add support for operating-points-v2 bindings cpufreq: dt: Add support for turbo/boost mode
.../devicetree/bindings/power/{ => opp}/opp.txt | 40 +- drivers/base/power/opp.c | 1084 ++++++++++++++++---- drivers/cpufreq/cpufreq-dt.c | 64 +- drivers/cpufreq/cpufreq.c | 68 +- drivers/cpufreq/cpufreq_opp.c | 4 + drivers/cpufreq/freq_table.c | 15 + include/linux/cpufreq.h | 12 + include/linux/pm_opp.h | 36 + 8 files changed, 1049 insertions(+), 274 deletions(-) rename Documentation/devicetree/bindings/power/{ => opp}/opp.txt (93%)
With a 32 bit value, the maximum frequency that the bindings can support is ~ 4 GHz. And that might fall short of what newer systems may have.
Allow opp-hz to be a 64 bit big-endian value.
Cc: Rob Herring rob.herring@linaro.org Cc: devicetree@vger.kernel.org Suggested-by: Stephen Boyd sboyd@codeaurora.org Suggested-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- Documentation/devicetree/bindings/power/opp.txt | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp.txt index 0d5e7c978121..0cb44dc21f97 100644 --- a/Documentation/devicetree/bindings/power/opp.txt +++ b/Documentation/devicetree/bindings/power/opp.txt @@ -88,7 +88,7 @@ This defines voltage-current-frequency combinations along with other related properties.
Required properties: -- opp-hz: Frequency in Hz +- opp-hz: Frequency in Hz, expressed as a 64-bit big-endian integer.
Optional properties: - opp-microvolt: voltage in micro Volts. @@ -158,20 +158,20 @@ Example 1: Single cluster Dual-core ARM cortex A9, switch DVFS states together. opp-shared;
opp00 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>; opp-microamp = <70000>; clock-latency-ns = <300000>; opp-suspend; }; opp01 { - opp-hz = <1100000000>; + opp-hz = /bits/ 64 <1100000000>; opp-microvolt = <980000 1000000 1010000>; opp-microamp = <80000>; clock-latency-ns = <310000>; }; opp02 { - opp-hz = <1200000000>; + opp-hz = /bits/ 64 <1200000000>; opp-microvolt = <1025000>; clock-latency-ns = <290000>; turbo-mode; @@ -237,20 +237,20 @@ independently. */
opp00 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>; opp-microamp = <70000>; clock-latency-ns = <300000>; opp-suspend; }; opp01 { - opp-hz = <1100000000>; + opp-hz = /bits/ 64 <1100000000>; opp-microvolt = <980000 1000000 1010000>; opp-microamp = <80000>; clock-latency-ns = <310000>; }; opp02 { - opp-hz = <1200000000>; + opp-hz = /bits/ 64 <1200000000>; opp-microvolt = <1025000>; opp-microamp = <90000; lock-latency-ns = <290000>; @@ -313,20 +313,20 @@ DVFS state together. opp-shared;
opp00 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>; opp-microamp = <70000>; clock-latency-ns = <300000>; opp-suspend; }; opp01 { - opp-hz = <1100000000>; + opp-hz = /bits/ 64 <1100000000>; opp-microvolt = <980000 1000000 1010000>; opp-microamp = <80000>; clock-latency-ns = <310000>; }; opp02 { - opp-hz = <1200000000>; + opp-hz = /bits/ 64 <1200000000>; opp-microvolt = <1025000>; opp-microamp = <90000>; clock-latency-ns = <290000>; @@ -339,20 +339,20 @@ DVFS state together. opp-shared;
opp10 { - opp-hz = <1300000000>; + opp-hz = /bits/ 64 <1300000000>; opp-microvolt = <1045000 1050000 1055000>; opp-microamp = <95000>; clock-latency-ns = <400000>; opp-suspend; }; opp11 { - opp-hz = <1400000000>; + opp-hz = /bits/ 64 <1400000000>; opp-microvolt = <1075000>; opp-microamp = <100000>; clock-latency-ns = <400000>; }; opp12 { - opp-hz = <1500000000>; + opp-hz = /bits/ 64 <1500000000>; opp-microvolt = <1010000 1100000 1110000>; opp-microamp = <95000>; clock-latency-ns = <400000>; @@ -379,7 +379,7 @@ Example 4: Handling multiple regulators opp-shared;
opp00 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000>, /* Supply 0 */ <960000>, /* Supply 1 */ <960000>; /* Supply 2 */ @@ -392,7 +392,7 @@ Example 4: Handling multiple regulators /* OR */
opp00 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>, /* Supply 0 */ <960000 965000 975000>, /* Supply 1 */ <960000 965000 975000>; /* Supply 2 */ @@ -405,7 +405,7 @@ Example 4: Handling multiple regulators /* OR */
opp00 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>, /* Supply 0 */ <960000 965000 975000>, /* Supply 1 */ <960000 965000 975000>; /* Supply 2 */ @@ -437,12 +437,12 @@ Example 5: Multiple OPP tables opp-shared;
opp00 { - opp-hz = <600000000>; + opp-hz = /bits/ 64 <600000000>; ... };
opp01 { - opp-hz = <800000000>; + opp-hz = /bits/ 64 <800000000>; ... }; }; @@ -453,12 +453,12 @@ Example 5: Multiple OPP tables opp-shared;
opp10 { - opp-hz = <1000000000>; + opp-hz = /bits/ 64 <1000000000>; ... };
opp11 { - opp-hz = <1100000000>; + opp-hz = /bits/ 64 <1100000000>; ... }; };
On Wed, Jul 29, 2015 at 5:52 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
With a 32 bit value, the maximum frequency that the bindings can support is ~ 4 GHz. And that might fall short of what newer systems may have.
4GHz should be fast enough for anyone...
Allow opp-hz to be a 64 bit big-endian value.
Cc: Rob Herring rob.herring@linaro.org
Acked-by: Rob Herring robh@kernel.org
Cc: devicetree@vger.kernel.org Suggested-by: Stephen Boyd sboyd@codeaurora.org Suggested-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Documentation/devicetree/bindings/power/opp.txt | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp.txt index 0d5e7c978121..0cb44dc21f97 100644 --- a/Documentation/devicetree/bindings/power/opp.txt +++ b/Documentation/devicetree/bindings/power/opp.txt @@ -88,7 +88,7 @@ This defines voltage-current-frequency combinations along with other related properties.
Required properties: -- opp-hz: Frequency in Hz +- opp-hz: Frequency in Hz, expressed as a 64-bit big-endian integer.
Optional properties:
- opp-microvolt: voltage in micro Volts.
@@ -158,20 +158,20 @@ Example 1: Single cluster Dual-core ARM cortex A9, switch DVFS states together. opp-shared;
opp00 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>; opp-microamp = <70000>; clock-latency-ns = <300000>; opp-suspend; }; opp01 {
opp-hz = <1100000000>;
opp-hz = /bits/ 64 <1100000000>; opp-microvolt = <980000 1000000 1010000>; opp-microamp = <80000>; clock-latency-ns = <310000>; }; opp02 {
opp-hz = <1200000000>;
opp-hz = /bits/ 64 <1200000000>; opp-microvolt = <1025000>; clock-latency-ns = <290000>; turbo-mode;
@@ -237,20 +237,20 @@ independently. */
opp00 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>; opp-microamp = <70000>; clock-latency-ns = <300000>; opp-suspend; }; opp01 {
opp-hz = <1100000000>;
opp-hz = /bits/ 64 <1100000000>; opp-microvolt = <980000 1000000 1010000>; opp-microamp = <80000>; clock-latency-ns = <310000>; }; opp02 {
opp-hz = <1200000000>;
opp-hz = /bits/ 64 <1200000000>; opp-microvolt = <1025000>; opp-microamp = <90000; lock-latency-ns = <290000>;
@@ -313,20 +313,20 @@ DVFS state together. opp-shared;
opp00 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>; opp-microamp = <70000>; clock-latency-ns = <300000>; opp-suspend; }; opp01 {
opp-hz = <1100000000>;
opp-hz = /bits/ 64 <1100000000>; opp-microvolt = <980000 1000000 1010000>; opp-microamp = <80000>; clock-latency-ns = <310000>; }; opp02 {
opp-hz = <1200000000>;
opp-hz = /bits/ 64 <1200000000>; opp-microvolt = <1025000>; opp-microamp = <90000>; clock-latency-ns = <290000>;
@@ -339,20 +339,20 @@ DVFS state together. opp-shared;
opp10 {
opp-hz = <1300000000>;
opp-hz = /bits/ 64 <1300000000>; opp-microvolt = <1045000 1050000 1055000>; opp-microamp = <95000>; clock-latency-ns = <400000>; opp-suspend; }; opp11 {
opp-hz = <1400000000>;
opp-hz = /bits/ 64 <1400000000>; opp-microvolt = <1075000>; opp-microamp = <100000>; clock-latency-ns = <400000>; }; opp12 {
opp-hz = <1500000000>;
opp-hz = /bits/ 64 <1500000000>; opp-microvolt = <1010000 1100000 1110000>; opp-microamp = <95000>; clock-latency-ns = <400000>;
@@ -379,7 +379,7 @@ Example 4: Handling multiple regulators opp-shared;
opp00 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000>, /* Supply 0 */ <960000>, /* Supply 1 */ <960000>; /* Supply 2 */
@@ -392,7 +392,7 @@ Example 4: Handling multiple regulators /* OR */
opp00 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>, /* Supply 0 */ <960000 965000 975000>, /* Supply 1 */ <960000 965000 975000>; /* Supply 2 */
@@ -405,7 +405,7 @@ Example 4: Handling multiple regulators /* OR */
opp00 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; opp-microvolt = <970000 975000 985000>, /* Supply 0 */ <960000 965000 975000>, /* Supply 1 */ <960000 965000 975000>; /* Supply 2 */
@@ -437,12 +437,12 @@ Example 5: Multiple OPP tables opp-shared;
opp00 {
opp-hz = <600000000>;
opp-hz = /bits/ 64 <600000000>; ... }; opp01 {
opp-hz = <800000000>;
opp-hz = /bits/ 64 <800000000>; ... }; };
@@ -453,12 +453,12 @@ Example 5: Multiple OPP tables opp-shared;
opp10 {
opp-hz = <1000000000>;
opp-hz = /bits/ 64 <1000000000>; ... }; opp11 {
opp-hz = <1100000000>;
opp-hz = /bits/ 64 <1100000000>; ... }; };
-- 2.4.0
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org Cc: devicetree@vger.kernel.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- Documentation/devicetree/bindings/power/{ => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/power/{ => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/power/opp/opp.txt
On Wed, Jul 29, 2015 at 5:52 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org
Use get_maintainers.pl please so you send to right people and right email.
Cc: devicetree@vger.kernel.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Documentation/devicetree/bindings/power/{ => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/power/{ => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/power/opp/opp.txt
"power" seems to be a random collection of things. I'd prefer you move this to .../bindings/opp/opp.txt.
Rob
On 30-07-15, 08:18, Rob Herring wrote:
On Wed, Jul 29, 2015 at 5:52 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org
Use get_maintainers.pl please so you send to right people and right email.
Already done, you were just cc'd as an extra guy :)
Cc: devicetree@vger.kernel.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Documentation/devicetree/bindings/power/{ => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/power/{ => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/power/opp/opp.txt
"power" seems to be a random collection of things. I'd prefer you move this to .../bindings/opp/opp.txt.
Hmm, sure. That would be better I think.
--------------8<-------------------
Message-Id: 29597667e2c8afdbf396c5fcc9269fb27794131c.1438264123.git.viresh.kumar@linaro.org From: Viresh Kumar viresh.kumar@linaro.org Date: Mon, 27 Jul 2015 10:04:21 +0530 Subject: [PATCH] PM / OPP: Create a directory for opp bindings
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org Cc: devicetree@vger.kernel.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- Documentation/devicetree/bindings/{power => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/{power => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/opp/opp.txt
On Thu, Jul 30, 2015 at 8:49 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
On 30-07-15, 08:18, Rob Herring wrote:
On Wed, Jul 29, 2015 at 5:52 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org
Use get_maintainers.pl please so you send to right people and right email.
Already done, you were just cc'd as an extra guy :)
No, because no one that is a maintainer of Documentation/devicetree/bindings is cc'ed here. You did on the other patch, but not here as neither Ian, Mark, Kumar nor Pawel are cc'ed.
Cc: devicetree@vger.kernel.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Documentation/devicetree/bindings/power/{ => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/power/{ => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/power/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/power/opp/opp.txt
"power" seems to be a random collection of things. I'd prefer you move this to .../bindings/opp/opp.txt.
Hmm, sure. That would be better I think.
--------------8<-------------------
Message-Id: 29597667e2c8afdbf396c5fcc9269fb27794131c.1438264123.git.viresh.kumar@linaro.org From: Viresh Kumar viresh.kumar@linaro.org Date: Mon, 27 Jul 2015 10:04:21 +0530 Subject: [PATCH] PM / OPP: Create a directory for opp bindings
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org
Still wrong...
Cc: devicetree@vger.kernel.org Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Documentation/devicetree/bindings/{power => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/{power => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/opp/opp.txt
On 30-07-15, 11:24, Rob Herring wrote:
On Thu, Jul 30, 2015 at 8:49 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
On 30-07-15, 08:18, Rob Herring wrote:
On Wed, Jul 29, 2015 at 5:52 AM, Viresh Kumar viresh.kumar@linaro.org wrote:
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Cc: Rob Herring rob.herring@linaro.org
Use get_maintainers.pl please so you send to right people and right email.
Already done, you were just cc'd as an extra guy :)
No, because no one that is a maintainer of Documentation/devicetree/bindings is cc'ed here. You did on the other patch, but not here as neither Ian, Mark, Kumar nor Pawel are cc'ed.
No. I sent all of them together and get_maintainers is invoked for all of them. I checked again and its a bug with get_maintainers.
@Joe: Can you please take a look at that?
Problem description: Because my patch only had this:
-----------8<-------------
--- Documentation/devicetree/bindings/{power => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/{power => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/opp/opp.txt
------------>8------------
get_maintainers reports this for the patch:
scripts/get_maintainer.pl: file '0001-PM-OPP-Create-a-directory-for-opp-bindings.patch' doesn't appear to be a patch. Add -f to options?
Cc: Rob Herring rob.herring@linaro.org
Still wrong...
Let me send the patch as reply to this one, with personally cc'ing all people you want me to.
More platform specific extended opp bindings will follow and it would be easy to manage them with a directory for opp. Lets create that and move the existing opp bindings into it.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- Resending to get cc list fixed, which isn't getting populated due to a bug with get_maintainers.
Documentation/devicetree/bindings/{power => opp}/opp.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Documentation/devicetree/bindings/{power => opp}/opp.txt (100%)
diff --git a/Documentation/devicetree/bindings/power/opp.txt b/Documentation/devicetree/bindings/opp/opp.txt similarity index 100% rename from Documentation/devicetree/bindings/power/opp.txt rename to Documentation/devicetree/bindings/opp/opp.txt
In order to prepare for the later commits, this relocates few routines towards the top as they will be used earlier in the code.
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 277 ++++++++++++++++++++++++----------------------- 1 file changed, 139 insertions(+), 138 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 677fb2843553..8c3fd57975fb 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -438,6 +438,102 @@ static struct device_opp *_add_device_opp(struct device *dev) }
/** + * _kfree_device_rcu() - Free device_opp RCU handler + * @head: RCU head + */ +static void _kfree_device_rcu(struct rcu_head *head) +{ + struct device_opp *device_opp = container_of(head, struct device_opp, rcu_head); + + kfree_rcu(device_opp, rcu_head); +} + +/** + * _kfree_opp_rcu() - Free OPP RCU handler + * @head: RCU head + */ +static void _kfree_opp_rcu(struct rcu_head *head) +{ + struct dev_pm_opp *opp = container_of(head, struct dev_pm_opp, rcu_head); + + kfree_rcu(opp, rcu_head); +} + +/** + * _opp_remove() - Remove an OPP from a table definition + * @dev_opp: points back to the device_opp struct this opp belongs to + * @opp: pointer to the OPP to remove + * + * This function removes an opp definition from the opp list. + * + * Locking: The internal device_opp and opp structures are RCU protected. + * It is assumed that the caller holds required mutex for an RCU updater + * strategy. + */ +static void _opp_remove(struct device_opp *dev_opp, + struct dev_pm_opp *opp) +{ + /* + * Notify the changes in the availability of the operable + * frequency/voltage list. + */ + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp); + list_del_rcu(&opp->node); + call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu); + + if (list_empty(&dev_opp->opp_list)) { + list_del_rcu(&dev_opp->node); + call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head, + _kfree_device_rcu); + } +} + +/** + * dev_pm_opp_remove() - Remove an OPP from OPP list + * @dev: device for which we do this operation + * @freq: OPP to remove with matching 'freq' + * + * This function removes an opp from the opp list. + * + * Locking: The internal device_opp and opp structures are RCU protected. + * Hence this function internally uses RCU updater strategy with mutex locks + * to keep the integrity of the internal data structures. Callers should ensure + * that this function is *NOT* called under RCU protection or in contexts where + * mutex cannot be locked. + */ +void dev_pm_opp_remove(struct device *dev, unsigned long freq) +{ + struct dev_pm_opp *opp; + struct device_opp *dev_opp; + bool found = false; + + /* Hold our list modification lock here */ + mutex_lock(&dev_opp_list_lock); + + dev_opp = _find_device_opp(dev); + if (IS_ERR(dev_opp)) + goto unlock; + + list_for_each_entry(opp, &dev_opp->opp_list, node) { + if (opp->rate == freq) { + found = true; + break; + } + } + + if (!found) { + dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n", + __func__, freq); + goto unlock; + } + + _opp_remove(dev_opp, opp); +unlock: + mutex_unlock(&dev_opp_list_lock); +} +EXPORT_SYMBOL_GPL(dev_pm_opp_remove); + +/** * _opp_add_dynamic() - Allocate a dynamic OPP. * @dev: device for which we do this operation * @freq: Frequency in Hz for this OPP @@ -570,102 +666,6 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) EXPORT_SYMBOL_GPL(dev_pm_opp_add);
/** - * _kfree_opp_rcu() - Free OPP RCU handler - * @head: RCU head - */ -static void _kfree_opp_rcu(struct rcu_head *head) -{ - struct dev_pm_opp *opp = container_of(head, struct dev_pm_opp, rcu_head); - - kfree_rcu(opp, rcu_head); -} - -/** - * _kfree_device_rcu() - Free device_opp RCU handler - * @head: RCU head - */ -static void _kfree_device_rcu(struct rcu_head *head) -{ - struct device_opp *device_opp = container_of(head, struct device_opp, rcu_head); - - kfree_rcu(device_opp, rcu_head); -} - -/** - * _opp_remove() - Remove an OPP from a table definition - * @dev_opp: points back to the device_opp struct this opp belongs to - * @opp: pointer to the OPP to remove - * - * This function removes an opp definition from the opp list. - * - * Locking: The internal device_opp and opp structures are RCU protected. - * It is assumed that the caller holds required mutex for an RCU updater - * strategy. - */ -static void _opp_remove(struct device_opp *dev_opp, - struct dev_pm_opp *opp) -{ - /* - * Notify the changes in the availability of the operable - * frequency/voltage list. - */ - srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp); - list_del_rcu(&opp->node); - call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu); - - if (list_empty(&dev_opp->opp_list)) { - list_del_rcu(&dev_opp->node); - call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head, - _kfree_device_rcu); - } -} - -/** - * dev_pm_opp_remove() - Remove an OPP from OPP list - * @dev: device for which we do this operation - * @freq: OPP to remove with matching 'freq' - * - * This function removes an opp from the opp list. - * - * Locking: The internal device_opp and opp structures are RCU protected. - * Hence this function internally uses RCU updater strategy with mutex locks - * to keep the integrity of the internal data structures. Callers should ensure - * that this function is *NOT* called under RCU protection or in contexts where - * mutex cannot be locked. - */ -void dev_pm_opp_remove(struct device *dev, unsigned long freq) -{ - struct dev_pm_opp *opp; - struct device_opp *dev_opp; - bool found = false; - - /* Hold our list modification lock here */ - mutex_lock(&dev_opp_list_lock); - - dev_opp = _find_device_opp(dev); - if (IS_ERR(dev_opp)) - goto unlock; - - list_for_each_entry(opp, &dev_opp->opp_list, node) { - if (opp->rate == freq) { - found = true; - break; - } - } - - if (!found) { - dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n", - __func__, freq); - goto unlock; - } - - _opp_remove(dev_opp, opp); -unlock: - mutex_unlock(&dev_opp_list_lock); -} -EXPORT_SYMBOL_GPL(dev_pm_opp_remove); - -/** * _opp_set_availability() - helper to set the availability of an opp * @dev: device for which we do this operation * @freq: OPP frequency to modify availability @@ -825,6 +825,49 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier);
#ifdef CONFIG_OF /** + * of_free_opp_table() - Free OPP table entries created from static DT entries + * @dev: device pointer used to lookup device OPPs. + * + * Free OPPs created using static entries present in DT. + * + * Locking: The internal device_opp and opp structures are RCU protected. + * Hence this function indirectly uses RCU updater strategy with mutex locks + * to keep the integrity of the internal data structures. Callers should ensure + * that this function is *NOT* called under RCU protection or in contexts where + * mutex cannot be locked. + */ +void of_free_opp_table(struct device *dev) +{ + struct device_opp *dev_opp; + struct dev_pm_opp *opp, *tmp; + + /* Check for existing list for 'dev' */ + dev_opp = _find_device_opp(dev); + if (IS_ERR(dev_opp)) { + int error = PTR_ERR(dev_opp); + + if (error != -ENODEV) + WARN(1, "%s: dev_opp: %d\n", + IS_ERR_OR_NULL(dev) ? + "Invalid device" : dev_name(dev), + error); + return; + } + + /* Hold our list modification lock here */ + mutex_lock(&dev_opp_list_lock); + + /* Free static OPPs */ + list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) { + if (!opp->dynamic) + _opp_remove(dev_opp, opp); + } + + mutex_unlock(&dev_opp_list_lock); +} +EXPORT_SYMBOL_GPL(of_free_opp_table); + +/** * of_init_opp_table() - Initialize opp table from device tree * @dev: device pointer used to lookup device OPPs. * @@ -882,46 +925,4 @@ int of_init_opp_table(struct device *dev) return 0; } EXPORT_SYMBOL_GPL(of_init_opp_table); - -/** - * of_free_opp_table() - Free OPP table entries created from static DT entries - * @dev: device pointer used to lookup device OPPs. - * - * Free OPPs created using static entries present in DT. - * - * Locking: The internal device_opp and opp structures are RCU protected. - * Hence this function indirectly uses RCU updater strategy with mutex locks - * to keep the integrity of the internal data structures. Callers should ensure - * that this function is *NOT* called under RCU protection or in contexts where - * mutex cannot be locked. - */ -void of_free_opp_table(struct device *dev) -{ - struct device_opp *dev_opp; - struct dev_pm_opp *opp, *tmp; - - /* Check for existing list for 'dev' */ - dev_opp = _find_device_opp(dev); - if (IS_ERR(dev_opp)) { - int error = PTR_ERR(dev_opp); - if (error != -ENODEV) - WARN(1, "%s: dev_opp: %d\n", - IS_ERR_OR_NULL(dev) ? - "Invalid device" : dev_name(dev), - error); - return; - } - - /* Hold our list modification lock here */ - mutex_lock(&dev_opp_list_lock); - - /* Free static OPPs */ - list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) { - if (!opp->dynamic) - _opp_remove(dev_opp, opp); - } - - mutex_unlock(&dev_opp_list_lock); -} -EXPORT_SYMBOL_GPL(of_free_opp_table); #endif
This will be used from multiple places later. Lets create a separate routine for that.
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 8c3fd57975fb..7895fdd64192 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -449,6 +449,22 @@ static void _kfree_device_rcu(struct rcu_head *head) }
/** + * _remove_device_opp() - Removes a device OPP table + * @dev_opp: device OPP table to be removed. + * + * Removes/frees device OPP table it it doesn't contain any OPPs. + */ +static void _remove_device_opp(struct device_opp *dev_opp) +{ + if (!list_empty(&dev_opp->opp_list)) + return; + + list_del_rcu(&dev_opp->node); + call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head, + _kfree_device_rcu); +} + +/** * _kfree_opp_rcu() - Free OPP RCU handler * @head: RCU head */ @@ -481,11 +497,7 @@ static void _opp_remove(struct device_opp *dev_opp, list_del_rcu(&opp->node); call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
- if (list_empty(&dev_opp->opp_list)) { - list_del_rcu(&dev_opp->node); - call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head, - _kfree_device_rcu); - } + _remove_device_opp(dev_opp); }
/**
There is no need to complicate _opp_add_dynamic() with allocation of dev_opp as well. Allocate it from _add_device_opp() instead.
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 50 ++++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 23 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 7895fdd64192..28d70c9f86ed 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -408,11 +408,11 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
/** - * _add_device_opp() - Allocate a new device OPP table + * _add_device_opp() - Find device OPP table or allocate a new one * @dev: device for which we do this operation * - * New device node which uses OPPs - used when multiple devices with OPP tables - * are maintained. + * It tries to find an existing table first, if it couldn't find one, it + * allocates a new OPP table and returns that. * * Return: valid device_opp pointer if success, else NULL. */ @@ -420,6 +420,11 @@ static struct device_opp *_add_device_opp(struct device *dev) { struct device_opp *dev_opp;
+ /* Check for existing list for 'dev' first */ + dev_opp = _find_device_opp(dev); + if (!IS_ERR(dev_opp)) + return dev_opp; + /* * Allocate a new device OPP table. In the infrequent case where a new * device is needed to be added, we pay this penalty. @@ -575,7 +580,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_remove); static int _opp_add_dynamic(struct device *dev, unsigned long freq, long u_volt, bool dynamic) { - struct device_opp *dev_opp = NULL; + struct device_opp *dev_opp; struct dev_pm_opp *opp, *new_opp; struct list_head *head; int ret; @@ -592,19 +597,11 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, new_opp->rate = freq; new_opp->u_volt = u_volt; new_opp->available = true; - new_opp->dynamic = dynamic; - - /* Check for existing list for 'dev' */ - dev_opp = _find_device_opp(dev); - if (IS_ERR(dev_opp)) { - dev_opp = _add_device_opp(dev); - if (!dev_opp) { - ret = -ENOMEM; - goto free_opp; - }
- head = &dev_opp->opp_list; - goto list_add; + dev_opp = _add_device_opp(dev); + if (!dev_opp) { + ret = -ENOMEM; + goto free_opp; }
/* @@ -612,15 +609,22 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, * and discard if already present */ head = &dev_opp->opp_list; + + /* + * Need to use &dev_opp->opp_list in the condition part of the 'for' + * loop, don't replace it with head otherwise it will become an infinite + * loop. + */ list_for_each_entry_rcu(opp, &dev_opp->opp_list, node) { - if (new_opp->rate <= opp->rate) - break; - else + if (new_opp->rate > opp->rate) { head = &opp->node; - } + continue; + } + + if (new_opp->rate < opp->rate) + break;
- /* Duplicate OPPs ? */ - if (new_opp->rate == opp->rate) { + /* Duplicate OPPs */ ret = opp->available && new_opp->u_volt == opp->u_volt ? 0 : -EEXIST;
@@ -630,7 +634,7 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, goto free_opp; }
-list_add: + new_opp->dynamic = dynamic; new_opp->dev_opp = dev_opp; list_add_rcu(&new_opp->node, head); mutex_unlock(&dev_opp_list_lock);
Later commits would add support for new OPP bindings and this would be required then. So, lets do it in a separate patch to make it easily reviewable.
Another change worth noticing is INIT_LIST_HEAD(&opp->node). We weren't doing it earlier as we never tried to delete a list node before it is added to list. But this wouldn't be the case anymore. We might try to delete a node (just to reuse the same code paths), without it being getting added to the list.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 125 ++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 49 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 28d70c9f86ed..0d8dbf21c299 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -484,6 +484,7 @@ static void _kfree_opp_rcu(struct rcu_head *head) * _opp_remove() - Remove an OPP from a table definition * @dev_opp: points back to the device_opp struct this opp belongs to * @opp: pointer to the OPP to remove + * @notify: OPP_EVENT_REMOVE notification should be sent or not * * This function removes an opp definition from the opp list. * @@ -492,13 +493,14 @@ static void _kfree_opp_rcu(struct rcu_head *head) * strategy. */ static void _opp_remove(struct device_opp *dev_opp, - struct dev_pm_opp *opp) + struct dev_pm_opp *opp, bool notify) { /* * Notify the changes in the availability of the operable * frequency/voltage list. */ - srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp); + if (notify) + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp); list_del_rcu(&opp->node); call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
@@ -544,12 +546,70 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq) goto unlock; }
- _opp_remove(dev_opp, opp); + _opp_remove(dev_opp, opp, true); unlock: mutex_unlock(&dev_opp_list_lock); } EXPORT_SYMBOL_GPL(dev_pm_opp_remove);
+static struct dev_pm_opp *_allocate_opp(struct device *dev, + struct device_opp **dev_opp) +{ + struct dev_pm_opp *opp; + + /* allocate new OPP node */ + opp = kzalloc(sizeof(*opp), GFP_KERNEL); + if (!opp) + return NULL; + + INIT_LIST_HEAD(&opp->node); + + *dev_opp = _add_device_opp(dev); + if (!*dev_opp) { + kfree(opp); + return NULL; + } + + return opp; +} + +static int _opp_add(struct dev_pm_opp *new_opp, struct device_opp *dev_opp) +{ + struct dev_pm_opp *opp; + struct list_head *head = &dev_opp->opp_list; + + /* + * Insert new OPP in order of increasing frequency and discard if + * already present. + * + * Need to use &dev_opp->opp_list in the condition part of the 'for' + * loop, don't replace it with head otherwise it will become an infinite + * loop. + */ + list_for_each_entry_rcu(opp, &dev_opp->opp_list, node) { + if (new_opp->rate > opp->rate) { + head = &opp->node; + continue; + } + + if (new_opp->rate < opp->rate) + break; + + /* Duplicate OPPs */ + dev_warn(dev_opp->dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", + __func__, opp->rate, opp->u_volt, opp->available, + new_opp->rate, new_opp->u_volt, new_opp->available); + + return opp->available && new_opp->u_volt == opp->u_volt ? + 0 : -EEXIST; + } + + new_opp->dev_opp = dev_opp; + list_add_rcu(&new_opp->node, head); + + return 0; +} + /** * _opp_add_dynamic() - Allocate a dynamic OPP. * @dev: device for which we do this operation @@ -581,62 +641,28 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, long u_volt, bool dynamic) { struct device_opp *dev_opp; - struct dev_pm_opp *opp, *new_opp; - struct list_head *head; + struct dev_pm_opp *new_opp; int ret;
- /* allocate new OPP node */ - new_opp = kzalloc(sizeof(*new_opp), GFP_KERNEL); - if (!new_opp) - return -ENOMEM; - /* Hold our list modification lock here */ mutex_lock(&dev_opp_list_lock);
+ new_opp = _allocate_opp(dev, &dev_opp); + if (!new_opp) { + ret = -ENOMEM; + goto unlock; + } + /* populate the opp table */ new_opp->rate = freq; new_opp->u_volt = u_volt; new_opp->available = true; + new_opp->dynamic = dynamic;
- dev_opp = _add_device_opp(dev); - if (!dev_opp) { - ret = -ENOMEM; - goto free_opp; - } - - /* - * Insert new OPP in order of increasing frequency - * and discard if already present - */ - head = &dev_opp->opp_list; - - /* - * Need to use &dev_opp->opp_list in the condition part of the 'for' - * loop, don't replace it with head otherwise it will become an infinite - * loop. - */ - list_for_each_entry_rcu(opp, &dev_opp->opp_list, node) { - if (new_opp->rate > opp->rate) { - head = &opp->node; - continue; - } - - if (new_opp->rate < opp->rate) - break; - - /* Duplicate OPPs */ - ret = opp->available && new_opp->u_volt == opp->u_volt ? - 0 : -EEXIST; - - dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", - __func__, opp->rate, opp->u_volt, opp->available, - new_opp->rate, new_opp->u_volt, new_opp->available); + ret = _opp_add(new_opp, dev_opp); + if (ret) goto free_opp; - }
- new_opp->dynamic = dynamic; - new_opp->dev_opp = dev_opp; - list_add_rcu(&new_opp->node, head); mutex_unlock(&dev_opp_list_lock);
/* @@ -647,8 +673,9 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, return 0;
free_opp: + _opp_remove(dev_opp, new_opp, false); +unlock: mutex_unlock(&dev_opp_list_lock); - kfree(new_opp); return ret; }
@@ -876,7 +903,7 @@ void of_free_opp_table(struct device *dev) /* Free static OPPs */ list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) { if (!opp->dynamic) - _opp_remove(dev_opp, opp); + _opp_remove(dev_opp, opp, true); }
mutex_unlock(&dev_opp_list_lock);
On 07/29, Viresh Kumar wrote:
Later commits would add support for new OPP bindings and this would be required then. So, lets do it in a separate patch to make it easily reviewable.
Another change worth noticing is INIT_LIST_HEAD(&opp->node). We weren't doing it earlier as we never tried to delete a list node before it is added to list. But this wouldn't be the case anymore. We might try to delete a node (just to reuse the same code paths), without it being getting added to the list.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
This adds support in OPP library to parse and create list of OPPs from operating-points-v2 bindings. It takes care of most of the properties of new bindings (except shared-opp, which will be handled separately).
For backward compatibility, we keep supporting earlier bindings. We try to search for the new bindings first, in case they aren't present we look for the old deprecated ones.
There are few things marked as TODO: - Support for multiple OPP tables - Support for multiple regulators
They should be fixed separately.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 257 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 233 insertions(+), 24 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 0d8dbf21c299..0e0eff4d9299 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -51,10 +51,15 @@ * order. * @dynamic: not-created from static DT entries. * @available: true/false - marks if this OPP as available or not + * @turbo: true if turbo (boost) OPP * @rate: Frequency in hertz - * @u_volt: Nominal voltage in microvolts corresponding to this OPP + * @u_volt: Target voltage in microvolts corresponding to this OPP + * @u_volt_min: Minimum voltage in microvolts corresponding to this OPP + * @u_volt_max: Maximum voltage in microvolts corresponding to this OPP + * @u_amp: Maximum current drawn by the device in microamperes * @dev_opp: points back to the device_opp struct this opp belongs to * @rcu_head: RCU callback head used for deferred freeing + * @np: OPP's device node. * * This structure stores the OPP information for a given device. */ @@ -63,11 +68,18 @@ struct dev_pm_opp {
bool available; bool dynamic; + bool turbo; unsigned long rate; + unsigned long u_volt; + unsigned long u_volt_min; + unsigned long u_volt_max; + unsigned long u_amp;
struct device_opp *dev_opp; struct rcu_head rcu_head; + + struct device_node *np; };
/** @@ -679,6 +691,125 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, return ret; }
+/* TODO: Support multiple regulators */ +static int opp_get_microvolt(struct dev_pm_opp *opp, struct device *dev) +{ + u32 microvolt[3] = {0}; + int count, ret; + + count = of_property_count_u32_elems(opp->np, "opp-microvolt"); + if (!count) + return 0; + + /* There can be one or three elements here */ + if (count != 1 && count != 3) { + dev_err(dev, "%s: Invalid number of elements in opp-microvolt property (%d)\n", + __func__, count); + return -EINVAL; + } + + ret = of_property_read_u32_array(opp->np, "opp-microvolt", microvolt, + count); + if (ret) { + dev_err(dev, "%s: error parsing opp-microvolt: %d\n", __func__, + ret); + return -EINVAL; + } + + opp->u_volt = microvolt[0]; + opp->u_volt_min = microvolt[1]; + opp->u_volt_max = microvolt[2]; + + return 0; +} + +/** + * _opp_add_static_v2() - Allocate static OPPs (As per 'v2' DT bindings) + * @dev: device for which we do this operation + * @np: device node + * + * This function adds an opp definition to the opp list and returns status. The + * opp can be controlled using dev_pm_opp_enable/disable functions and may be + * removed by dev_pm_opp_remove. + * + * Locking: The internal device_opp and opp structures are RCU protected. + * Hence this function internally uses RCU updater strategy with mutex locks + * to keep the integrity of the internal data structures. Callers should ensure + * that this function is *NOT* called under RCU protection or in contexts where + * mutex cannot be locked. + * + * 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 + * -EINVAL Failed parsing the OPP node + */ +static int _opp_add_static_v2(struct device *dev, struct device_node *np) +{ + struct device_opp *dev_opp; + struct dev_pm_opp *new_opp; + u64 rate; + int ret; + + /* Hold our list modification lock here */ + mutex_lock(&dev_opp_list_lock); + + new_opp = _allocate_opp(dev, &dev_opp); + if (!new_opp) { + ret = -ENOMEM; + goto unlock; + } + + ret = of_property_read_u64(np, "opp-hz", &rate); + if (ret < 0) { + dev_err(dev, "%s: opp-hz not found\n", __func__); + goto free_opp; + } + + /* + * Rate is defined as an unsigned long in clk API, and so casting + * explicitly to its type. Must be fixed once rate is 64 bit + * guaranteed in clk API. + */ + new_opp->rate = (unsigned long)rate; + new_opp->turbo = of_property_read_bool(np, "turbo-mode"); + + new_opp->np = np; + new_opp->dynamic = false; + new_opp->available = true; + + ret = opp_get_microvolt(new_opp, dev); + if (ret) + goto free_opp; + + of_property_read_u32(np, "opp-microamp", (u32 *)&new_opp->u_amp); + + ret = _opp_add(new_opp, dev_opp); + if (ret) + goto free_opp; + + mutex_unlock(&dev_opp_list_lock); + + pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu\n", + __func__, new_opp->turbo, new_opp->rate, new_opp->u_volt, + new_opp->u_volt_min, new_opp->u_volt_max); + + /* + * Notify the changes in the availability of the operable + * frequency/voltage list. + */ + srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADD, new_opp); + return 0; + +free_opp: + _opp_remove(dev_opp, new_opp, false); +unlock: + mutex_unlock(&dev_opp_list_lock); + return ret; +} + /** * dev_pm_opp_add() - Add an OPP table from a table definitions * @dev: device for which we do this operation @@ -910,29 +1041,64 @@ void of_free_opp_table(struct device *dev) } EXPORT_SYMBOL_GPL(of_free_opp_table);
-/** - * of_init_opp_table() - Initialize opp table from device tree - * @dev: device pointer used to lookup device OPPs. - * - * Register the initial OPP table with the OPP library for given device. - * - * Locking: The internal device_opp and opp structures are RCU protected. - * Hence this function indirectly uses RCU updater strategy with mutex locks - * to keep the integrity of the internal data structures. Callers should ensure - * that this function is *NOT* called under RCU protection or in contexts where - * mutex cannot be locked. - * - * 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 - */ -int of_init_opp_table(struct device *dev) +/* Returns opp descriptor node from its phandle. Caller must do of_node_put() */ +static struct device_node * +_of_get_opp_desc_node_from_prop(struct device *dev, const struct property *prop) +{ + struct device_node *opp_np; + + opp_np = of_find_node_by_phandle(be32_to_cpup(prop->value)); + if (!opp_np) { + dev_err(dev, "%s: Prop: %s contains invalid opp desc phandle\n", + __func__, prop->name); + return ERR_PTR(-EINVAL); + } + + return opp_np; +} + +/* Initializes OPP tables based on new bindings */ +static int _of_init_opp_table_v2(struct device *dev, + const struct property *prop) +{ + struct device_node *opp_np, *np; + int ret = 0, count = 0; + + if (!prop->value) + return -ENODATA; + + /* Get opp node */ + opp_np = _of_get_opp_desc_node_from_prop(dev, prop); + if (IS_ERR(opp_np)) + return PTR_ERR(opp_np); + + /* We have opp-list node now, iterate over it and add OPPs */ + for_each_available_child_of_node(opp_np, np) { + count++; + + ret = _opp_add_static_v2(dev, np); + if (ret) { + dev_err(dev, "%s: Failed to add OPP, %d\n", __func__, + ret); + break; + } + } + + /* There should be one of more OPP defined */ + if (WARN_ON(!count)) + goto put_opp_np; + + if (ret) + of_free_opp_table(dev); + +put_opp_np: + of_node_put(opp_np); + + return ret; +} + +/* Initializes OPP tables based on old-deprecated bindings */ +static int _of_init_opp_table_v1(struct device *dev) { const struct property *prop; const __be32 *val; @@ -967,5 +1133,48 @@ int of_init_opp_table(struct device *dev)
return 0; } + +/** + * of_init_opp_table() - Initialize opp table from device tree + * @dev: device pointer used to lookup device OPPs. + * + * Register the initial OPP table with the OPP library for given device. + * + * Locking: The internal device_opp and opp structures are RCU protected. + * Hence this function indirectly uses RCU updater strategy with mutex locks + * to keep the integrity of the internal data structures. Callers should ensure + * that this function is *NOT* called under RCU protection or in contexts where + * mutex cannot be locked. + * + * 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 of_init_opp_table(struct device *dev) +{ + const struct property *prop; + + /* + * OPPs have two version of bindings now. The older one is deprecated, + * try for the new binding first. + */ + prop = of_find_property(dev->of_node, "operating-points-v2", NULL); + if (!prop) { + /* + * Try old-deprecated bindings for backward compatibility with + * older dtbs. + */ + return _of_init_opp_table_v1(dev); + } + + return _of_init_opp_table_v2(dev, prop); +} EXPORT_SYMBOL_GPL(of_init_opp_table); #endif
On 07/29, Viresh Kumar wrote:
This adds support in OPP library to parse and create list of OPPs from operating-points-v2 bindings. It takes care of most of the properties of new bindings (except shared-opp, which will be handled separately).
For backward compatibility, we keep supporting earlier bindings. We try to search for the new bindings first, in case they aren't present we look for the old deprecated ones.
There are few things marked as TODO:
- Support for multiple OPP tables
- Support for multiple regulators
They should be fixed separately.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
One question below:
@@ -679,6 +691,125 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, return ret; } +/* TODO: Support multiple regulators */ +static int opp_get_microvolt(struct dev_pm_opp *opp, struct device *dev) +{
- u32 microvolt[3] = {0};
- int count, ret;
- count = of_property_count_u32_elems(opp->np, "opp-microvolt");
- if (!count)
return 0;
- /* There can be one or three elements here */
- if (count != 1 && count != 3) {
dev_err(dev, "%s: Invalid number of elements in opp-microvolt property (%d)\n",
__func__, count);
return -EINVAL;
- }
- ret = of_property_read_u32_array(opp->np, "opp-microvolt", microvolt,
count);
- if (ret) {
dev_err(dev, "%s: error parsing opp-microvolt: %d\n", __func__,
ret);
return -EINVAL;
- }
- opp->u_volt = microvolt[0];
- opp->u_volt_min = microvolt[1];
- opp->u_volt_max = microvolt[2];
Should the default be 0 and ULONG_MAX for volt_min/volt_max when there's on element?
On 30-07-15, 22:51, Stephen Boyd wrote:
- opp->u_volt = microvolt[0];
- opp->u_volt_min = microvolt[1];
- opp->u_volt_max = microvolt[2];
Should the default be 0 and ULONG_MAX for volt_min/volt_max when there's on element?
I am not still sure how the regulator API is going to look like for this target/min/max thing. So, lets defer it until that is resolved. For now they are initialized to 0.
And, because the user has just passed in a target voltage, maybe all three must be == u_volt. :)
On 07/31, Viresh Kumar wrote:
On 30-07-15, 22:51, Stephen Boyd wrote:
- opp->u_volt = microvolt[0];
- opp->u_volt_min = microvolt[1];
- opp->u_volt_max = microvolt[2];
Should the default be 0 and ULONG_MAX for volt_min/volt_max when there's on element?
I am not still sure how the regulator API is going to look like for this target/min/max thing. So, lets defer it until that is resolved. For now they are initialized to 0.
And, because the user has just passed in a target voltage, maybe all three must be == u_volt. :)
Ok. Sounds fair enough.
With "operating-points-v2" bindings, clock-latency is defined per OPP. Users of this value expect a single value which defines the latency to switch to any clock rate. Find maximum clock-latency-ns from the OPP table to service requests from such users.
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 41 +++++++++++++++++++++++++++++++++++++++-- include/linux/pm_opp.h | 6 ++++++ 2 files changed, 45 insertions(+), 2 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 0e0eff4d9299..8638204c457e 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -57,6 +57,8 @@ * @u_volt_min: Minimum voltage in microvolts corresponding to this OPP * @u_volt_max: Maximum voltage in microvolts corresponding to this OPP * @u_amp: Maximum current drawn by the device in microamperes + * @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's + * frequency from any other OPP's frequency. * @dev_opp: points back to the device_opp struct this opp belongs to * @rcu_head: RCU callback head used for deferred freeing * @np: OPP's device node. @@ -75,6 +77,7 @@ struct dev_pm_opp { unsigned long u_volt_min; unsigned long u_volt_max; unsigned long u_amp; + unsigned long clock_latency_ns;
struct device_opp *dev_opp; struct rcu_head rcu_head; @@ -109,6 +112,8 @@ struct device_opp { struct srcu_notifier_head srcu_head; struct rcu_head rcu_head; struct list_head opp_list; + + unsigned long clock_latency_ns_max; };
/* @@ -226,6 +231,32 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) EXPORT_SYMBOL_GPL(dev_pm_opp_get_freq);
/** + * dev_pm_opp_get_max_clock_latency() - Get max clock latency in nanoseconds + * @dev: device for which we do this operation + * + * Return: This function returns the max clock latency in nanoseconds. + * + * Locking: This function takes rcu_read_lock(). + */ +unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev) +{ + struct device_opp *dev_opp; + unsigned long clock_latency_ns; + + rcu_read_lock(); + + dev_opp = _find_device_opp(dev); + if (IS_ERR(dev_opp)) + clock_latency_ns = 0; + else + clock_latency_ns = dev_opp->clock_latency_ns_max; + + rcu_read_unlock(); + return clock_latency_ns; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_clock_latency); + +/** * dev_pm_opp_get_opp_count() - Get number of opps available in the opp list * @dev: device for which we do this operation * @@ -779,6 +810,8 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) new_opp->np = np; new_opp->dynamic = false; new_opp->available = true; + of_property_read_u32(np, "clock-latency-ns", + (u32 *)&new_opp->clock_latency_ns);
ret = opp_get_microvolt(new_opp, dev); if (ret) @@ -790,11 +823,15 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) if (ret) goto free_opp;
+ if (new_opp->clock_latency_ns > dev_opp->clock_latency_ns_max) + dev_opp->clock_latency_ns_max = new_opp->clock_latency_ns; + mutex_unlock(&dev_opp_list_lock);
- pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu\n", + pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n", __func__, new_opp->turbo, new_opp->rate, new_opp->u_volt, - new_opp->u_volt_min, new_opp->u_volt_max); + new_opp->u_volt_min, new_opp->u_volt_max, + new_opp->clock_latency_ns);
/* * Notify the changes in the availability of the operable diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index cec2d4540914..20324b579adc 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -31,6 +31,7 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp); unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp);
int dev_pm_opp_get_opp_count(struct device *dev); +unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev);
struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, unsigned long freq, @@ -67,6 +68,11 @@ static inline int dev_pm_opp_get_opp_count(struct device *dev) return 0; }
+static inline unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev) +{ + return 0; +} + static inline struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev, unsigned long freq, bool available) {
An opp can be shared by multiple devices, for example its very common for CPUs to share the OPPs, i.e. when they share clock/voltage rails.
This patch adds support of shared OPPs to the OPP library.
Instead of a single device, dev_opp will now contain a list of devices that use it. It also senses if the device (we are trying to initialize OPPs for) shares OPPs with a device added earlier and in that case we update the list of devices managed by OPPs instead of duplicating OPPs again.
The same infrastructure will be used for the old OPP bindings, with later patches.
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 174 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 150 insertions(+), 24 deletions(-)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 8638204c457e..5d699e3ec136 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -86,16 +86,33 @@ struct dev_pm_opp { };
/** + * struct device_list_opp - devices managed by 'struct device_opp' + * @node: list node + * @dev: device to which the struct object belongs + * @rcu_head: RCU callback head used for deferred freeing + * + * This is an internal data structure maintaining the list of devices that are + * managed by 'struct device_opp'. + */ +struct device_list_opp { + struct list_head node; + const struct device *dev; + struct rcu_head rcu_head; +}; + +/** * struct device_opp - Device opp structure * @node: list node - contains the devices with OPPs that * have been registered. Nodes once added are not modified in this * list. * RCU usage: nodes are not modified in the list of device_opp, * however addition is possible and is secured by dev_opp_list_lock - * @dev: device pointer * @srcu_head: notifier head to notify the OPP availability changes. * @rcu_head: RCU callback head used for deferred freeing + * @dev_list: list of devices that share these OPPs * @opp_list: list of opps + * @np: struct device_node pointer for opp's DT node. + * @shared_opp: OPP is shared between multiple devices. * * This is an internal data structure maintaining the link to opps attached to * a device. This structure is not meant to be shared to users as it is @@ -108,12 +125,14 @@ struct dev_pm_opp { struct device_opp { struct list_head node;
- struct device *dev; struct srcu_notifier_head srcu_head; struct rcu_head rcu_head; + struct list_head dev_list; struct list_head opp_list;
+ struct device_node *np; unsigned long clock_latency_ns_max; + bool shared_opp; };
/* @@ -133,6 +152,38 @@ do { \ "dev_opp_list_lock protection"); \ } while (0)
+static struct device_list_opp *_find_list_dev(const struct device *dev, + struct device_opp *dev_opp) +{ + struct device_list_opp *list_dev; + + list_for_each_entry(list_dev, &dev_opp->dev_list, node) + if (list_dev->dev == dev) + return list_dev; + + return NULL; +} + +static struct device_opp *_managed_opp(const struct device_node *np) +{ + struct device_opp *dev_opp; + + list_for_each_entry_rcu(dev_opp, &dev_opp_list, node) { + if (dev_opp->np == np) { + /* + * Multiple devices can point to the same OPP table and + * so will have same node-pointer, np. + * + * But the OPPs will be considered as shared only if the + * OPP table contains a "opp-shared" property. + */ + return dev_opp->shared_opp ? dev_opp : NULL; + } + } + + return NULL; +} + /** * _find_device_opp() - find device_opp struct using device pointer * @dev: device pointer used to lookup device OPPs @@ -149,21 +200,18 @@ do { \ */ static struct device_opp *_find_device_opp(struct device *dev) { - struct device_opp *tmp_dev_opp, *dev_opp = ERR_PTR(-ENODEV); + struct device_opp *dev_opp;
if (unlikely(IS_ERR_OR_NULL(dev))) { pr_err("%s: Invalid parameters\n", __func__); return ERR_PTR(-EINVAL); }
- list_for_each_entry_rcu(tmp_dev_opp, &dev_opp_list, node) { - if (tmp_dev_opp->dev == dev) { - dev_opp = tmp_dev_opp; - break; - } - } + list_for_each_entry_rcu(dev_opp, &dev_opp_list, node) + if (_find_list_dev(dev, dev_opp)) + return dev_opp;
- return dev_opp; + return ERR_PTR(-ENODEV); }
/** @@ -450,6 +498,39 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, } EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
+/* List-dev Helpers */ +static void _kfree_list_dev_rcu(struct rcu_head *head) +{ + struct device_list_opp *list_dev; + + list_dev = container_of(head, struct device_list_opp, rcu_head); + kfree_rcu(list_dev, rcu_head); +} + +static void _remove_list_dev(struct device_list_opp *list_dev, + struct device_opp *dev_opp) +{ + list_del(&list_dev->node); + call_srcu(&dev_opp->srcu_head.srcu, &list_dev->rcu_head, + _kfree_list_dev_rcu); +} + +static struct device_list_opp *_add_list_dev(const struct device *dev, + struct device_opp *dev_opp) +{ + struct device_list_opp *list_dev; + + list_dev = kzalloc(sizeof(*list_dev), GFP_KERNEL); + if (!list_dev) + return NULL; + + /* Initialize list-dev */ + list_dev->dev = dev; + list_add_rcu(&list_dev->node, &dev_opp->dev_list); + + return list_dev; +} + /** * _add_device_opp() - Find device OPP table or allocate a new one * @dev: device for which we do this operation @@ -462,6 +543,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor); static struct device_opp *_add_device_opp(struct device *dev) { struct device_opp *dev_opp; + struct device_list_opp *list_dev;
/* Check for existing list for 'dev' first */ dev_opp = _find_device_opp(dev); @@ -476,7 +558,14 @@ static struct device_opp *_add_device_opp(struct device *dev) if (!dev_opp) return NULL;
- dev_opp->dev = dev; + INIT_LIST_HEAD(&dev_opp->dev_list); + + list_dev = _add_list_dev(dev, dev_opp); + if (!list_dev) { + kfree(dev_opp); + return NULL; + } + srcu_init_notifier_head(&dev_opp->srcu_head); INIT_LIST_HEAD(&dev_opp->opp_list);
@@ -504,9 +593,19 @@ static void _kfree_device_rcu(struct rcu_head *head) */ static void _remove_device_opp(struct device_opp *dev_opp) { + struct device_list_opp *list_dev; + if (!list_empty(&dev_opp->opp_list)) return;
+ list_dev = list_first_entry(&dev_opp->dev_list, struct device_list_opp, + node); + + _remove_list_dev(list_dev, dev_opp); + + /* dev_list must be empty now */ + WARN_ON(!list_empty(&dev_opp->dev_list)); + list_del_rcu(&dev_opp->node); call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head, _kfree_device_rcu); @@ -616,7 +715,8 @@ static struct dev_pm_opp *_allocate_opp(struct device *dev, return opp; }
-static int _opp_add(struct dev_pm_opp *new_opp, struct device_opp *dev_opp) +static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, + struct device_opp *dev_opp) { struct dev_pm_opp *opp; struct list_head *head = &dev_opp->opp_list; @@ -639,7 +739,7 @@ static int _opp_add(struct dev_pm_opp *new_opp, struct device_opp *dev_opp) break;
/* Duplicate OPPs */ - dev_warn(dev_opp->dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", + dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", __func__, opp->rate, opp->u_volt, opp->available, new_opp->rate, new_opp->u_volt, new_opp->available);
@@ -702,7 +802,7 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq, new_opp->available = true; new_opp->dynamic = dynamic;
- ret = _opp_add(new_opp, dev_opp); + ret = _opp_add(dev, new_opp, dev_opp); if (ret) goto free_opp;
@@ -819,7 +919,7 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
of_property_read_u32(np, "opp-microamp", (u32 *)&new_opp->u_amp);
- ret = _opp_add(new_opp, dev_opp); + ret = _opp_add(dev, new_opp, dev_opp); if (ret) goto free_opp;
@@ -1052,6 +1152,9 @@ void of_free_opp_table(struct device *dev) struct device_opp *dev_opp; struct dev_pm_opp *opp, *tmp;
+ /* Hold our list modification lock here */ + mutex_lock(&dev_opp_list_lock); + /* Check for existing list for 'dev' */ dev_opp = _find_device_opp(dev); if (IS_ERR(dev_opp)) { @@ -1062,18 +1165,21 @@ void of_free_opp_table(struct device *dev) IS_ERR_OR_NULL(dev) ? "Invalid device" : dev_name(dev), error); - return; + goto unlock; }
- /* Hold our list modification lock here */ - mutex_lock(&dev_opp_list_lock); - - /* Free static OPPs */ - list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) { - if (!opp->dynamic) - _opp_remove(dev_opp, opp, true); + /* Find if dev_opp manages a single device */ + if (list_is_singular(&dev_opp->dev_list)) { + /* Free static OPPs */ + list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) { + if (!opp->dynamic) + _opp_remove(dev_opp, opp, true); + } + } else { + _remove_list_dev(_find_list_dev(dev, dev_opp), dev_opp); }
+unlock: mutex_unlock(&dev_opp_list_lock); } EXPORT_SYMBOL_GPL(of_free_opp_table); @@ -1099,6 +1205,7 @@ static int _of_init_opp_table_v2(struct device *dev, const struct property *prop) { struct device_node *opp_np, *np; + struct device_opp *dev_opp; int ret = 0, count = 0;
if (!prop->value) @@ -1109,6 +1216,14 @@ static int _of_init_opp_table_v2(struct device *dev, if (IS_ERR(opp_np)) return PTR_ERR(opp_np);
+ dev_opp = _managed_opp(opp_np); + if (dev_opp) { + /* OPPs are already managed */ + if (!_add_list_dev(dev, dev_opp)) + ret = -ENOMEM; + goto put_opp_np; + } + /* We have opp-list node now, iterate over it and add OPPs */ for_each_available_child_of_node(opp_np, np) { count++; @@ -1125,8 +1240,19 @@ static int _of_init_opp_table_v2(struct device *dev, if (WARN_ON(!count)) goto put_opp_np;
- if (ret) + if (!ret) { + if (!dev_opp) { + dev_opp = _find_device_opp(dev); + if (WARN_ON(!dev_opp)) + goto put_opp_np; + } + + dev_opp->np = opp_np; + dev_opp->shared_opp = of_property_read_bool(opp_np, + "opp-shared"); + } else { of_free_opp_table(dev); + }
put_opp_np: of_node_put(opp_np);
With "operating-points-v2" bindings, its possible to specify the OPP to which the device must be switched, before suspending.
This patch adds support for getting that information.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 5d699e3ec136..0ebcea49145a 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -133,6 +133,7 @@ struct device_opp { struct device_node *np; unsigned long clock_latency_ns_max; bool shared_opp; + struct dev_pm_opp *suspend_opp; };
/* @@ -923,6 +924,16 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) if (ret) goto free_opp;
+ /* OPP to select on device suspend */ + if (of_property_read_bool(np, "opp-suspend")) { + if (dev_opp->suspend_opp) + dev_warn(dev, "%s: Multiple suspend OPPs found (%lu %lu)\n", + __func__, dev_opp->suspend_opp->rate, + new_opp->rate); + else + dev_opp->suspend_opp = new_opp; + } + if (new_opp->clock_latency_ns > dev_opp->clock_latency_ns_max) dev_opp->clock_latency_ns_max = new_opp->clock_latency_ns;
On 07/29, Viresh Kumar wrote:
With "operating-points-v2" bindings, its possible to specify the OPP to
s/its/it's/ ?
which the device must be switched, before suspending.
This patch adds support for getting that information.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
On 30-07-15, 23:24, Stephen Boyd wrote:
On 07/29, Viresh Kumar wrote:
With "operating-points-v2" bindings, its possible to specify the OPP to
s/its/it's/ ?
which the device must be switched, before suspending.
This patch adds support for getting that information.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
---------------------8<-------------------
Message-Id: 5e0cf40cc78bfa327cbbccae016d03a1dfbd1a84.1438323941.git.viresh.kumar@linaro.org From: Viresh Kumar viresh.kumar@linaro.org Date: Sat, 13 Jun 2015 15:10:21 +0530 Subject: [PATCH] PM / OPP: Add support for opp-suspend
With "operating-points-v2" bindings, it's possible to specify the OPP to which the device must be switched, before suspending.
This patch adds support for getting that information.
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 5d699e3ec136..0ebcea49145a 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -133,6 +133,7 @@ struct device_opp { struct device_node *np; unsigned long clock_latency_ns_max; bool shared_opp; + struct dev_pm_opp *suspend_opp; };
/* @@ -923,6 +924,16 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np) if (ret) goto free_opp;
+ /* OPP to select on device suspend */ + if (of_property_read_bool(np, "opp-suspend")) { + if (dev_opp->suspend_opp) + dev_warn(dev, "%s: Multiple suspend OPPs found (%lu %lu)\n", + __func__, dev_opp->suspend_opp->rate, + new_opp->rate); + else + dev_opp->suspend_opp = new_opp; + } + if (new_opp->clock_latency_ns > dev_opp->clock_latency_ns_max) dev_opp->clock_latency_ns_max = new_opp->clock_latency_ns;
With "operating-points-v2" its possible to tell which devices share OPPs. We already have infrastructure to decode that information.
This patch adds following APIs: - of_get_cpus_sharing_opps: Returns cpumask of CPUs sharing OPPs (only valid with v2 bindings). - of_cpumask_init_opp_table: Initializes OPPs for all CPUs present in cpumask. - of_cpumask_free_opp_table: Frees OPPs for all CPUs present in cpumask.
- set_cpus_sharing_opps: Sets which CPUs share OPPs (only valid with old OPP bindings, as this information isn't present in DT).
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 23 +++++++ 2 files changed, 198 insertions(+)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 0ebcea49145a..9bc4e7c21deb 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -11,6 +11,7 @@ * published by the Free Software Foundation. */
+#include <linux/cpu.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/err.h> @@ -1195,6 +1196,26 @@ void of_free_opp_table(struct device *dev) } EXPORT_SYMBOL_GPL(of_free_opp_table);
+void of_cpumask_free_opp_table(cpumask_var_t cpumask) +{ + struct device *cpu_dev; + int cpu; + + WARN_ON(cpumask_empty(cpumask)); + + for_each_cpu(cpu, cpumask) { + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + cpu); + continue; + } + + of_free_opp_table(cpu_dev); + } +} +EXPORT_SYMBOL_GPL(of_cpumask_free_opp_table); + /* Returns opp descriptor node from its phandle. Caller must do of_node_put() */ static struct device_node * _of_get_opp_desc_node_from_prop(struct device *dev, const struct property *prop) @@ -1211,6 +1232,31 @@ _of_get_opp_desc_node_from_prop(struct device *dev, const struct property *prop) return opp_np; }
+/* Returns opp descriptor node for a device. Caller must do of_node_put() */ +static struct device_node *_of_get_opp_desc_node(struct device *dev) +{ + const struct property *prop; + + prop = of_find_property(dev->of_node, "operating-points-v2", NULL); + if (!prop) + return ERR_PTR(-ENODEV); + if (!prop->value) + return ERR_PTR(-ENODATA); + + /* + * TODO: Support for multiple OPP tables. + * + * There should be only ONE phandle present in "operating-points-v2" + * property. + */ + if (prop->length != sizeof(__be32)) { + dev_err(dev, "%s: Invalid opp desc phandle\n", __func__); + return ERR_PTR(-EINVAL); + } + + return _of_get_opp_desc_node_from_prop(dev, prop); +} + /* Initializes OPP tables based on new bindings */ static int _of_init_opp_table_v2(struct device *dev, const struct property *prop) @@ -1351,4 +1397,133 @@ int of_init_opp_table(struct device *dev) return _of_init_opp_table_v2(dev, prop); } EXPORT_SYMBOL_GPL(of_init_opp_table); + +int of_cpumask_init_opp_table(cpumask_var_t cpumask) +{ + struct device *cpu_dev; + int cpu, ret = 0; + + WARN_ON(cpumask_empty(cpumask)); + + for_each_cpu(cpu, cpumask) { + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + cpu); + continue; + } + + ret = of_init_opp_table(cpu_dev); + if (ret) { + pr_err("%s: couldn't find opp table for cpu:%d, %d\n", + __func__, cpu, ret); + + /* Free all other OPPs */ + of_cpumask_free_opp_table(cpumask); + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_cpumask_init_opp_table); + +/* Required only for V1 bindings, as v2 can manage it from DT itself */ +int set_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + struct device_list_opp *list_dev; + struct device_opp *dev_opp; + struct device *dev; + int cpu, ret = 0; + + rcu_read_lock(); + + dev_opp = _find_device_opp(cpu_dev); + if (IS_ERR(dev_opp)) { + ret = -EINVAL; + goto out_rcu_read_unlock; + } + + for_each_cpu(cpu, cpumask) { + if (cpu == cpu_dev->id) + continue; + + dev = get_cpu_device(cpu); + if (!dev) { + dev_err(cpu_dev, "%s: failed to get cpu%d device\n", + __func__, cpu); + continue; + } + + list_dev = _add_list_dev(dev, dev_opp); + if (!list_dev) { + dev_err(dev, "%s: failed to add list-dev for cpu%d device\n", + __func__, cpu); + continue; + } + } +out_rcu_read_unlock: + rcu_read_unlock(); + + return 0; +} +EXPORT_SYMBOL_GPL(set_cpus_sharing_opps); + +/* + * Works only for OPP v2 bindings. + * + * cpumask should be already set to mask of cpu_dev->id. + * Returns -ENOENT if operating-points-v2 bindings aren't supported. + */ +int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + struct device_node *np, *tmp_np; + struct device *tcpu_dev; + int cpu, ret = 0; + + /* Get OPP descriptor node */ + np = _of_get_opp_desc_node(cpu_dev); + if (IS_ERR(np)) { + dev_dbg(cpu_dev, "%s: Couldn't find opp node: %ld\n", __func__, + PTR_ERR(np)); + return -ENOENT; + } + + /* OPPs are shared ? */ + if (!of_get_property(np, "opp-shared", NULL)) + goto put_cpu_node; + + for_each_possible_cpu(cpu) { + if (cpu == cpu_dev->id) + continue; + + tcpu_dev = get_cpu_device(cpu); + if (!tcpu_dev) { + dev_err(cpu_dev, "%s: failed to get cpu%d device\n", + __func__, cpu); + ret = -ENODEV; + goto put_cpu_node; + } + + /* Get OPP descriptor node */ + tmp_np = _of_get_opp_desc_node(tcpu_dev); + if (IS_ERR(tmp_np)) { + dev_info(tcpu_dev, "%s: Couldn't find opp node: %ld\n", + __func__, PTR_ERR(tmp_np)); + ret = -EINVAL; + goto put_cpu_node; + } + + /* CPUs are sharing opp node */ + if (np == tmp_np) + cpumask_set_cpu(cpu, cpumask); + + of_node_put(tmp_np); + } + +put_cpu_node: + of_node_put(np); + return ret; +} +EXPORT_SYMBOL_GPL(of_get_cpus_sharing_opps); #endif diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 20324b579adc..bb52fae5b921 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -121,6 +121,10 @@ static inline struct srcu_notifier_head *dev_pm_opp_get_notifier( #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF) int of_init_opp_table(struct device *dev); void of_free_opp_table(struct device *dev); +int of_cpumask_init_opp_table(cpumask_var_t cpumask); +void of_cpumask_free_opp_table(cpumask_var_t cpumask); +int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask); +int set_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask); #else static inline int of_init_opp_table(struct device *dev) { @@ -130,6 +134,25 @@ static inline int of_init_opp_table(struct device *dev) static inline void of_free_opp_table(struct device *dev) { } + +static inline int of_cpumask_init_opp_table(cpumask_var_t cpumask) +{ + return -ENOSYS; +} + +static inline void of_cpumask_free_opp_table(cpumask_var_t cpumask) +{ +} + +static inline int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + return -ENOSYS; +} + +static inline int set_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + return -ENOSYS; +} #endif
#endif /* __LINUX_OPP_H__ */
On 07/29, Viresh Kumar wrote:
With "operating-points-v2" its possible to tell which devices share OPPs. We already have infrastructure to decode that information.
This patch adds following APIs:
of_get_cpus_sharing_opps: Returns cpumask of CPUs sharing OPPs (only valid with v2 bindings).
of_cpumask_init_opp_table: Initializes OPPs for all CPUs present in cpumask.
of_cpumask_free_opp_table: Frees OPPs for all CPUs present in cpumask.
set_cpus_sharing_opps: Sets which CPUs share OPPs (only valid with old OPP bindings, as this information isn't present in DT).
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
Some minor nitpicks below:
+/*
- Works only for OPP v2 bindings.
- cpumask should be already set to mask of cpu_dev->id.
- Returns -ENOENT if operating-points-v2 bindings aren't supported.
- */
+int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{
- struct device_node *np, *tmp_np;
- struct device *tcpu_dev;
- int cpu, ret = 0;
- /* Get OPP descriptor node */
- np = _of_get_opp_desc_node(cpu_dev);
- if (IS_ERR(np)) {
dev_dbg(cpu_dev, "%s: Couldn't find opp node: %ld\n", __func__,
PTR_ERR(np));
return -ENOENT;
- }
- /* OPPs are shared ? */
- if (!of_get_property(np, "opp-shared", NULL))
if (!of_property_read_bool(np, "opp-shared")) ?
goto put_cpu_node;
- for_each_possible_cpu(cpu) {
if (cpu == cpu_dev->id)
continue;
tcpu_dev = get_cpu_device(cpu);
if (!tcpu_dev) {
dev_err(cpu_dev, "%s: failed to get cpu%d device\n",
__func__, cpu);
ret = -ENODEV;
goto put_cpu_node;
}
/* Get OPP descriptor node */
tmp_np = _of_get_opp_desc_node(tcpu_dev);
if (IS_ERR(tmp_np)) {
dev_info(tcpu_dev, "%s: Couldn't find opp node: %ld\n",
dev_err?
__func__, PTR_ERR(tmp_np));
ret = -EINVAL;
Why aren't we returning the PTR_ERR value here?
goto put_cpu_node;
On 30-07-15, 23:07, Stephen Boyd wrote:
On 07/29, Viresh Kumar wrote:
With "operating-points-v2" its possible to tell which devices share OPPs. We already have infrastructure to decode that information.
This patch adds following APIs:
of_get_cpus_sharing_opps: Returns cpumask of CPUs sharing OPPs (only valid with v2 bindings).
of_cpumask_init_opp_table: Initializes OPPs for all CPUs present in cpumask.
of_cpumask_free_opp_table: Frees OPPs for all CPUs present in cpumask.
set_cpus_sharing_opps: Sets which CPUs share OPPs (only valid with old OPP bindings, as this information isn't present in DT).
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
Some minor nitpicks below:
if (!of_property_read_bool(np, "opp-shared")) ?
dev_err?
Why aren't we returning the PTR_ERR value here?
----------8<---------------
Message-Id: 6e499ed9e4d1eead26550f96002ef7ea9d067cb9.1438323186.git.viresh.kumar@linaro.org From: Viresh Kumar viresh.kumar@linaro.org Date: Fri, 12 Jun 2015 17:10:38 +0530 Subject: [PATCH] PM / OPP: Add helpers for initializing CPU OPPs
With "operating-points-v2" its possible to tell which devices share OPPs. We already have infrastructure to decode that information.
This patch adds following APIs: - of_get_cpus_sharing_opps: Returns cpumask of CPUs sharing OPPs (only valid with v2 bindings). - of_cpumask_init_opp_table: Initializes OPPs for all CPUs present in cpumask. - of_cpumask_free_opp_table: Frees OPPs for all CPUs present in cpumask.
- set_cpus_sharing_opps: Sets which CPUs share OPPs (only valid with old OPP bindings, as this information isn't present in DT).
Reviewed-by: Stephen Boyd sboyd@codeaurora.org Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 23 +++++++ 2 files changed, 198 insertions(+)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 0ebcea49145a..663aae1c9834 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -11,6 +11,7 @@ * published by the Free Software Foundation. */
+#include <linux/cpu.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/err.h> @@ -1195,6 +1196,26 @@ void of_free_opp_table(struct device *dev) } EXPORT_SYMBOL_GPL(of_free_opp_table);
+void of_cpumask_free_opp_table(cpumask_var_t cpumask) +{ + struct device *cpu_dev; + int cpu; + + WARN_ON(cpumask_empty(cpumask)); + + for_each_cpu(cpu, cpumask) { + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + cpu); + continue; + } + + of_free_opp_table(cpu_dev); + } +} +EXPORT_SYMBOL_GPL(of_cpumask_free_opp_table); + /* Returns opp descriptor node from its phandle. Caller must do of_node_put() */ static struct device_node * _of_get_opp_desc_node_from_prop(struct device *dev, const struct property *prop) @@ -1211,6 +1232,31 @@ _of_get_opp_desc_node_from_prop(struct device *dev, const struct property *prop) return opp_np; }
+/* Returns opp descriptor node for a device. Caller must do of_node_put() */ +static struct device_node *_of_get_opp_desc_node(struct device *dev) +{ + const struct property *prop; + + prop = of_find_property(dev->of_node, "operating-points-v2", NULL); + if (!prop) + return ERR_PTR(-ENODEV); + if (!prop->value) + return ERR_PTR(-ENODATA); + + /* + * TODO: Support for multiple OPP tables. + * + * There should be only ONE phandle present in "operating-points-v2" + * property. + */ + if (prop->length != sizeof(__be32)) { + dev_err(dev, "%s: Invalid opp desc phandle\n", __func__); + return ERR_PTR(-EINVAL); + } + + return _of_get_opp_desc_node_from_prop(dev, prop); +} + /* Initializes OPP tables based on new bindings */ static int _of_init_opp_table_v2(struct device *dev, const struct property *prop) @@ -1351,4 +1397,133 @@ int of_init_opp_table(struct device *dev) return _of_init_opp_table_v2(dev, prop); } EXPORT_SYMBOL_GPL(of_init_opp_table); + +int of_cpumask_init_opp_table(cpumask_var_t cpumask) +{ + struct device *cpu_dev; + int cpu, ret = 0; + + WARN_ON(cpumask_empty(cpumask)); + + for_each_cpu(cpu, cpumask) { + cpu_dev = get_cpu_device(cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + cpu); + continue; + } + + ret = of_init_opp_table(cpu_dev); + if (ret) { + pr_err("%s: couldn't find opp table for cpu:%d, %d\n", + __func__, cpu, ret); + + /* Free all other OPPs */ + of_cpumask_free_opp_table(cpumask); + break; + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_cpumask_init_opp_table); + +/* Required only for V1 bindings, as v2 can manage it from DT itself */ +int set_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + struct device_list_opp *list_dev; + struct device_opp *dev_opp; + struct device *dev; + int cpu, ret = 0; + + rcu_read_lock(); + + dev_opp = _find_device_opp(cpu_dev); + if (IS_ERR(dev_opp)) { + ret = -EINVAL; + goto out_rcu_read_unlock; + } + + for_each_cpu(cpu, cpumask) { + if (cpu == cpu_dev->id) + continue; + + dev = get_cpu_device(cpu); + if (!dev) { + dev_err(cpu_dev, "%s: failed to get cpu%d device\n", + __func__, cpu); + continue; + } + + list_dev = _add_list_dev(dev, dev_opp); + if (!list_dev) { + dev_err(dev, "%s: failed to add list-dev for cpu%d device\n", + __func__, cpu); + continue; + } + } +out_rcu_read_unlock: + rcu_read_unlock(); + + return 0; +} +EXPORT_SYMBOL_GPL(set_cpus_sharing_opps); + +/* + * Works only for OPP v2 bindings. + * + * cpumask should be already set to mask of cpu_dev->id. + * Returns -ENOENT if operating-points-v2 bindings aren't supported. + */ +int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + struct device_node *np, *tmp_np; + struct device *tcpu_dev; + int cpu, ret = 0; + + /* Get OPP descriptor node */ + np = _of_get_opp_desc_node(cpu_dev); + if (IS_ERR(np)) { + dev_dbg(cpu_dev, "%s: Couldn't find opp node: %ld\n", __func__, + PTR_ERR(np)); + return -ENOENT; + } + + /* OPPs are shared ? */ + if (!of_property_read_bool(np, "opp-shared")) + goto put_cpu_node; + + for_each_possible_cpu(cpu) { + if (cpu == cpu_dev->id) + continue; + + tcpu_dev = get_cpu_device(cpu); + if (!tcpu_dev) { + dev_err(cpu_dev, "%s: failed to get cpu%d device\n", + __func__, cpu); + ret = -ENODEV; + goto put_cpu_node; + } + + /* Get OPP descriptor node */ + tmp_np = _of_get_opp_desc_node(tcpu_dev); + if (IS_ERR(tmp_np)) { + dev_err(tcpu_dev, "%s: Couldn't find opp node: %ld\n", + __func__, PTR_ERR(tmp_np)); + ret = PTR_ERR(tmp_np); + goto put_cpu_node; + } + + /* CPUs are sharing opp node */ + if (np == tmp_np) + cpumask_set_cpu(cpu, cpumask); + + of_node_put(tmp_np); + } + +put_cpu_node: + of_node_put(np); + return ret; +} +EXPORT_SYMBOL_GPL(of_get_cpus_sharing_opps); #endif diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 20324b579adc..bb52fae5b921 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -121,6 +121,10 @@ static inline struct srcu_notifier_head *dev_pm_opp_get_notifier( #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF) int of_init_opp_table(struct device *dev); void of_free_opp_table(struct device *dev); +int of_cpumask_init_opp_table(cpumask_var_t cpumask); +void of_cpumask_free_opp_table(cpumask_var_t cpumask); +int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask); +int set_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask); #else static inline int of_init_opp_table(struct device *dev) { @@ -130,6 +134,25 @@ static inline int of_init_opp_table(struct device *dev) static inline void of_free_opp_table(struct device *dev) { } + +static inline int of_cpumask_init_opp_table(cpumask_var_t cpumask) +{ + return -ENOSYS; +} + +static inline void of_cpumask_free_opp_table(cpumask_var_t cpumask) +{ +} + +static inline int of_get_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + return -ENOSYS; +} + +static inline int set_cpus_sharing_opps(struct device *cpu_dev, cpumask_var_t cpumask) +{ + return -ENOSYS; +} #endif
#endif /* __LINUX_OPP_H__ */
From: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com
Add dev_pm_opp_is_turbo() helper to verify if an opp is to be used only for turbo mode or not.
Cc: Tomasz Figa tomasz.figa@gmail.com Cc: Michael Turquette mturquette@baylibre.com Cc: Javier Martinez Canillas javier@dowhile0.org Cc: Thomas Abraham thomas.ab@samsung.com Signed-off-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 7 +++++++ 2 files changed, 41 insertions(+)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 9bc4e7c21deb..da8ec7f824d5 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -281,6 +281,40 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) EXPORT_SYMBOL_GPL(dev_pm_opp_get_freq);
/** + * dev_pm_opp_is_turbo() - Returns if opp is turbo OPP or not + * @opp: opp for which turbo mode is being verified + * + * Turbo OPPs are not for normal use, and can be enabled (under certain + * conditions) for short duration of times to finish high throughput work + * quickly. Running on them for longer times may overheat the chip. + * + * Return: true if opp is turbo opp, else false. + * + * Locking: This function must be called under rcu_read_lock(). opp is a rcu + * protected pointer. This means that opp which could have been fetched by + * opp_find_freq_{exact,ceil,floor} functions is valid as long as we are + * under RCU lock. The pointer returned by the opp_find_freq family must be + * used in the same section as the usage of this function with the pointer + * prior to unlocking with rcu_read_unlock() to maintain the integrity of the + * pointer. + */ +bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) +{ + struct dev_pm_opp *tmp_opp; + + opp_rcu_lockdep_assert(); + + tmp_opp = rcu_dereference(opp); + if (unlikely(IS_ERR_OR_NULL(tmp_opp)) || !tmp_opp->available) { + pr_err("%s: Invalid parameters\n", __func__); + return false; + } + + return tmp_opp->turbo; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_is_turbo); + +/** * dev_pm_opp_get_max_clock_latency() - Get max clock latency in nanoseconds * @dev: device for which we do this operation * diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index bb52fae5b921..cab7ba55bedb 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -30,6 +30,8 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp);
+bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp); + int dev_pm_opp_get_opp_count(struct device *dev); unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev);
@@ -63,6 +65,11 @@ static inline unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) return 0; }
+static inline bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) +{ + return false; +} + static inline int dev_pm_opp_get_opp_count(struct device *dev) { return 0;
On 07/29, Viresh Kumar wrote:
+bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) +{
- struct dev_pm_opp *tmp_opp;
- opp_rcu_lockdep_assert();
- tmp_opp = rcu_dereference(opp);
- if (unlikely(IS_ERR_OR_NULL(tmp_opp)) || !tmp_opp->available) {
IS_ERR_OR_NULL already has unlikely inside it on the error pointer path so it seems redundant here.
Otherwise
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
On 30-07-15, 23:10, Stephen Boyd wrote:
On 07/29, Viresh Kumar wrote:
+bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) +{
- struct dev_pm_opp *tmp_opp;
- opp_rcu_lockdep_assert();
- tmp_opp = rcu_dereference(opp);
- if (unlikely(IS_ERR_OR_NULL(tmp_opp)) || !tmp_opp->available) {
IS_ERR_OR_NULL already has unlikely inside it on the error pointer path so it seems redundant here.
Otherwise
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
----------------------8<---------------------- Message-Id: f925cccfbe4765bc6b15740a43e5688d2a283fb0.1438323394.git.viresh.kumar@linaro.org From: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Date: Thu, 9 Jul 2015 17:43:35 +0200 Subject: [PATCH] PM / OPP: add dev_pm_opp_is_turbo() helper
Add dev_pm_opp_is_turbo() helper to verify if an opp is to be used only for turbo mode or not.
Cc: Tomasz Figa tomasz.figa@gmail.com Cc: Michael Turquette mturquette@baylibre.com Cc: Javier Martinez Canillas javier@dowhile0.org Cc: Thomas Abraham thomas.ab@samsung.com Reviewed-by: Stephen Boyd sboyd@codeaurora.org Signed-off-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/base/power/opp.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pm_opp.h | 7 +++++++ 2 files changed, 41 insertions(+)
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 663aae1c9834..204c6c945168 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -281,6 +281,40 @@ unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) EXPORT_SYMBOL_GPL(dev_pm_opp_get_freq);
/** + * dev_pm_opp_is_turbo() - Returns if opp is turbo OPP or not + * @opp: opp for which turbo mode is being verified + * + * Turbo OPPs are not for normal use, and can be enabled (under certain + * conditions) for short duration of times to finish high throughput work + * quickly. Running on them for longer times may overheat the chip. + * + * Return: true if opp is turbo opp, else false. + * + * Locking: This function must be called under rcu_read_lock(). opp is a rcu + * protected pointer. This means that opp which could have been fetched by + * opp_find_freq_{exact,ceil,floor} functions is valid as long as we are + * under RCU lock. The pointer returned by the opp_find_freq family must be + * used in the same section as the usage of this function with the pointer + * prior to unlocking with rcu_read_unlock() to maintain the integrity of the + * pointer. + */ +bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) +{ + struct dev_pm_opp *tmp_opp; + + opp_rcu_lockdep_assert(); + + tmp_opp = rcu_dereference(opp); + if (IS_ERR_OR_NULL(tmp_opp) || !tmp_opp->available) { + pr_err("%s: Invalid parameters\n", __func__); + return false; + } + + return tmp_opp->turbo; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_is_turbo); + +/** * dev_pm_opp_get_max_clock_latency() - Get max clock latency in nanoseconds * @dev: device for which we do this operation * diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index bb52fae5b921..cab7ba55bedb 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -30,6 +30,8 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp);
+bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp); + int dev_pm_opp_get_opp_count(struct device *dev); unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev);
@@ -63,6 +65,11 @@ static inline unsigned long dev_pm_opp_get_freq(struct dev_pm_opp *opp) return 0; }
+static inline bool dev_pm_opp_is_turbo(struct dev_pm_opp *opp) +{ + return false; +} + static inline int dev_pm_opp_get_opp_count(struct device *dev) { return 0;
From: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com
cpufreq table entries for OPPs with turbo modes enabled, should be marked with CPUFREQ_BOOST_FREQ flag. This ensures that these states are only used while operating in boost or turbo mode.
Cc: Tomasz Figa tomasz.figa@gmail.com Cc: Michael Turquette mturquette@baylibre.com Cc: Javier Martinez Canillas javier@dowhile0.org Cc: Thomas Abraham thomas.ab@samsung.com Signed-off-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/cpufreq/cpufreq_opp.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/cpufreq/cpufreq_opp.c b/drivers/cpufreq/cpufreq_opp.c index 773bcde893c0..0f5e6d5f6da0 100644 --- a/drivers/cpufreq/cpufreq_opp.c +++ b/drivers/cpufreq/cpufreq_opp.c @@ -75,6 +75,10 @@ int dev_pm_opp_init_cpufreq_table(struct device *dev, } freq_table[i].driver_data = i; freq_table[i].frequency = rate / 1000; + + /* Is Boost/turbo opp ? */ + if (dev_pm_opp_is_turbo(opp)) + freq_table[i].flags = CPUFREQ_BOOST_FREQ; }
freq_table[i].driver_data = i;
On 07/29, Viresh Kumar wrote:
From: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com
cpufreq table entries for OPPs with turbo modes enabled, should be marked with CPUFREQ_BOOST_FREQ flag. This ensures that these states are only used while operating in boost or turbo mode.
Cc: Tomasz Figa tomasz.figa@gmail.com Cc: Michael Turquette mturquette@baylibre.com Cc: Javier Martinez Canillas javier@dowhile0.org Cc: Thomas Abraham thomas.ab@samsung.com Signed-off-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
In some cases it wouldn't be known at time of driver registration, if the driver needs to support boost frequencies.
For example, while getting boost information from DT with opp-v2 bindings, we need to parse the bindings for all the CPUs to know if turbo/boost OPPs are supported or not.
One way out to do that efficiently is to delay supporting boost mode (i.e. creating /sys/devices/system/cpu/cpufreq/boost file), until the time OPP bindings are parsed.
At that point, the driver can enable boost support. This can be done at ->init(), where the frequency table is created.
To do that, the driver requires few APIs from cpufreq core that let him do this. This patch provides these APIs.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/cpufreq/cpufreq.c | 68 +++++++++++++++++++++++++++++++------------- drivers/cpufreq/freq_table.c | 15 ++++++++++ include/linux/cpufreq.h | 12 ++++++++ 3 files changed, 75 insertions(+), 20 deletions(-)
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 46251e8d30f2..db4390ca5243 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -2459,6 +2459,49 @@ int cpufreq_boost_supported(void) } EXPORT_SYMBOL_GPL(cpufreq_boost_supported);
+static int create_boost_sysfs_file(void) +{ + int ret; + + if (!cpufreq_boost_supported()) + return 0; + + /* + * Check if driver provides function to enable boost - + * if not, use cpufreq_boost_set_sw as default + */ + if (!cpufreq_driver->set_boost) + cpufreq_driver->set_boost = cpufreq_boost_set_sw; + + ret = cpufreq_sysfs_create_file(&boost.attr); + if (ret) + pr_err("%s: cannot register global BOOST sysfs file\n", + __func__); + + return ret; +} + +static void remove_boost_sysfs_file(void) +{ + if (cpufreq_boost_supported()) + cpufreq_sysfs_remove_file(&boost.attr); +} + +int cpufreq_enable_boost_support(void) +{ + if (!cpufreq_driver) + return -EINVAL; + + if (cpufreq_boost_supported()) + return 0; + + cpufreq_driver->boost_supported = true; + + /* This will get removed on driver unregister */ + return create_boost_sysfs_file(); +} +EXPORT_SYMBOL_GPL(cpufreq_enable_boost_support); + int cpufreq_boost_enabled(void) { return cpufreq_driver->boost_enabled; @@ -2508,21 +2551,9 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) if (driver_data->setpolicy) driver_data->flags |= CPUFREQ_CONST_LOOPS;
- if (cpufreq_boost_supported()) { - /* - * Check if driver provides function to enable boost - - * if not, use cpufreq_boost_set_sw as default - */ - if (!cpufreq_driver->set_boost) - cpufreq_driver->set_boost = cpufreq_boost_set_sw; - - ret = cpufreq_sysfs_create_file(&boost.attr); - if (ret) { - pr_err("%s: cannot register global BOOST sysfs file\n", - __func__); - goto err_null_driver; - } - } + ret = create_boost_sysfs_file(); + if (ret) + goto err_null_driver;
ret = subsys_interface_register(&cpufreq_interface); if (ret) @@ -2543,8 +2574,7 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) err_if_unreg: subsys_interface_unregister(&cpufreq_interface); err_boost_unreg: - if (cpufreq_boost_supported()) - cpufreq_sysfs_remove_file(&boost.attr); + remove_boost_sysfs_file(); err_null_driver: write_lock_irqsave(&cpufreq_driver_lock, flags); cpufreq_driver = NULL; @@ -2573,9 +2603,7 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver) /* Protect against concurrent cpu hotplug */ get_online_cpus(); subsys_interface_unregister(&cpufreq_interface); - if (cpufreq_boost_supported()) - cpufreq_sysfs_remove_file(&boost.attr); - + remove_boost_sysfs_file(); unregister_hotcpu_notifier(&cpufreq_cpu_notifier);
write_lock_irqsave(&cpufreq_driver_lock, flags); diff --git a/drivers/cpufreq/freq_table.c b/drivers/cpufreq/freq_table.c index dfbbf981ed56..a8f1daffc9bc 100644 --- a/drivers/cpufreq/freq_table.c +++ b/drivers/cpufreq/freq_table.c @@ -18,6 +18,21 @@ * FREQUENCY TABLE HELPERS * *********************************************************************/
+bool policy_has_boost_freq(struct cpufreq_policy *policy) +{ + struct cpufreq_frequency_table *pos, *table = policy->freq_table; + + if (!table) + return false; + + cpufreq_for_each_valid_entry(pos, table) + if (pos->flags & CPUFREQ_BOOST_FREQ) + return true; + + return false; +} +EXPORT_SYMBOL_GPL(policy_has_boost_freq); + int cpufreq_frequency_table_cpuinfo(struct cpufreq_policy *policy, struct cpufreq_frequency_table *table) { diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index bde1e567b3a9..95f018649abf 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -578,6 +578,8 @@ ssize_t cpufreq_show_cpus(const struct cpumask *mask, char *buf); int cpufreq_boost_trigger_state(int state); int cpufreq_boost_supported(void); int cpufreq_boost_enabled(void); +int cpufreq_enable_boost_support(void); +bool policy_has_boost_freq(struct cpufreq_policy *policy); #else static inline int cpufreq_boost_trigger_state(int state) { @@ -591,6 +593,16 @@ static inline int cpufreq_boost_enabled(void) { return 0; } + +static inline int cpufreq_enable_boost_support(void) +{ + return -EINVAL; +} + +static inline bool policy_has_boost_freq(struct cpufreq_policy *policy) +{ + return false; +} #endif /* the following funtion is for cpufreq core use only */ struct cpufreq_frequency_table *cpufreq_frequency_get_table(unsigned int cpu);
On 07/29, Viresh Kumar wrote:
In some cases it wouldn't be known at time of driver registration, if the driver needs to support boost frequencies.
For example, while getting boost information from DT with opp-v2 bindings, we need to parse the bindings for all the CPUs to know if turbo/boost OPPs are supported or not.
One way out to do that efficiently is to delay supporting boost mode (i.e. creating /sys/devices/system/cpu/cpufreq/boost file), until the time OPP bindings are parsed.
At that point, the driver can enable boost support. This can be done at ->init(), where the frequency table is created.
To do that, the driver requires few APIs from cpufreq core that let him do this. This patch provides these APIs.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
Support for parsing operating-points-v2 bindings is in place now, lets modify cpufreq-dt driver to use them.
For backward compatibility we will continue to support earlier bindings. Special handling for that is required, to make sure OPPs are initialized for all the CPUs.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/cpufreq/cpufreq-dt.c | 56 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 10 deletions(-)
diff --git a/drivers/cpufreq/cpufreq-dt.c b/drivers/cpufreq/cpufreq-dt.c index 528a82bf5038..c6e7033076de 100644 --- a/drivers/cpufreq/cpufreq-dt.c +++ b/drivers/cpufreq/cpufreq-dt.c @@ -184,7 +184,6 @@ static int allocate_resources(int cpu, struct device **cdev,
static int cpufreq_init(struct cpufreq_policy *policy) { - struct cpufreq_dt_platform_data *pd; struct cpufreq_frequency_table *freq_table; struct device_node *np; struct private_data *priv; @@ -193,6 +192,7 @@ static int cpufreq_init(struct cpufreq_policy *policy) struct clk *cpu_clk; unsigned long min_uV = ~0, max_uV = 0; unsigned int transition_latency; + bool need_update = false; int ret;
ret = allocate_resources(policy->cpu, &cpu_dev, &cpu_reg, &cpu_clk); @@ -208,8 +208,47 @@ static int cpufreq_init(struct cpufreq_policy *policy) goto out_put_reg_clk; }
- /* OPPs might be populated at runtime, don't check for error here */ - of_init_opp_table(cpu_dev); + /* Get OPP-sharing information from "operating-points-v2" bindings */ + ret = of_get_cpus_sharing_opps(cpu_dev, policy->cpus); + if (ret) { + /* + * operating-points-v2 not supported, fallback to old method of + * finding shared-OPPs for backward compatibility. + */ + if (ret == -ENOENT) + need_update = true; + else + goto out_node_put; + } + + /* + * Initialize OPP tables for all policy->cpus. They will be shared by + * all CPUs which have marked their CPUs shared with OPP bindings. + * + * For platforms not using operating-points-v2 bindings, we do this + * before updating policy->cpus. Otherwise, we will end up creating + * duplicate OPPs for policy->cpus. + * + * OPPs might be populated at runtime, don't check for error here + */ + of_cpumask_init_opp_table(policy->cpus); + + if (need_update) { + struct cpufreq_dt_platform_data *pd = cpufreq_get_driver_data(); + + if (!pd || !pd->independent_clocks) + cpumask_setall(policy->cpus); + + /* + * OPP tables are initialized only for policy->cpu, do it for + * others as well. + */ + set_cpus_sharing_opps(cpu_dev, policy->cpus); + + of_property_read_u32(np, "clock-latency", &transition_latency); + } else { + transition_latency = dev_pm_opp_get_max_clock_latency(cpu_dev); + }
/* * But we need OPP table to function so if it is not there let's @@ -230,7 +269,7 @@ static int cpufreq_init(struct cpufreq_policy *policy)
of_property_read_u32(np, "voltage-tolerance", &priv->voltage_tolerance);
- if (of_property_read_u32(np, "clock-latency", &transition_latency)) + if (!transition_latency) transition_latency = CPUFREQ_ETERNAL;
if (!IS_ERR(cpu_reg)) { @@ -293,10 +332,6 @@ static int cpufreq_init(struct cpufreq_policy *policy)
policy->cpuinfo.transition_latency = transition_latency;
- pd = cpufreq_get_driver_data(); - if (!pd || !pd->independent_clocks) - cpumask_setall(policy->cpus); - of_node_put(np);
return 0; @@ -306,7 +341,8 @@ static int cpufreq_init(struct cpufreq_policy *policy) out_free_priv: kfree(priv); out_free_opp: - of_free_opp_table(cpu_dev); + of_cpumask_free_opp_table(policy->cpus); +out_node_put: of_node_put(np); out_put_reg_clk: clk_put(cpu_clk); @@ -322,7 +358,7 @@ static int cpufreq_exit(struct cpufreq_policy *policy)
cpufreq_cooling_unregister(priv->cdev); dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table); - of_free_opp_table(priv->cpu_dev); + of_cpumask_free_opp_table(policy->related_cpus); clk_put(policy->clk); if (!IS_ERR(priv->cpu_reg)) regulator_put(priv->cpu_reg);
On 07/29, Viresh Kumar wrote:
Support for parsing operating-points-v2 bindings is in place now, lets modify cpufreq-dt driver to use them.
For backward compatibility we will continue to support earlier bindings. Special handling for that is required, to make sure OPPs are initialized for all the CPUs.
Reviewed-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
With opp-v2 DT bindings, few OPPs can be used only for the boost mode. But using such OPPs require the boost mode to be supported by cpufreq driver.
We will parse DT bindings only during ->init() and so can enable boost support only after registering cpufreq driver.
This enables boost support as soon as any policy has boost/turbo OPPs for its CPUs.
We don't need to disable boost support as that is done by the core, when the driver is unregistered.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/cpufreq/cpufreq-dt.c | 8 ++++++++ 1 file changed, 8 insertions(+)
diff --git a/drivers/cpufreq/cpufreq-dt.c b/drivers/cpufreq/cpufreq-dt.c index c6e7033076de..b9259abd25d4 100644 --- a/drivers/cpufreq/cpufreq-dt.c +++ b/drivers/cpufreq/cpufreq-dt.c @@ -330,6 +330,14 @@ static int cpufreq_init(struct cpufreq_policy *policy) goto out_free_cpufreq_table; }
+ /* Support turbo/boost mode */ + if (policy_has_boost_freq(policy)) { + /* This gets disabled by core on driver unregister */ + ret = cpufreq_enable_boost_support(); + if (ret) + goto out_free_cpufreq_table; + } + policy->cpuinfo.transition_latency = transition_latency;
of_node_put(np);
On 07/29, Viresh Kumar wrote:
With opp-v2 DT bindings, few OPPs can be used only for the boost mode. But using such OPPs require the boost mode to be supported by cpufreq driver.
We will parse DT bindings only during ->init() and so can enable boost support only after registering cpufreq driver.
This enables boost support as soon as any policy has boost/turbo OPPs for its CPUs.
We don't need to disable boost support as that is done by the core, when the driver is unregistered.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org
Reviewed-by: Stephen Boyd sboyd@codeaurora.org
Hi,
On Wednesday, July 29, 2015 04:22:55 PM Viresh Kumar wrote:
Hi Guys,
Resending this again because:
- opp-hz is now 64 bit long and so that required a binding update.
- There is another 4 patch series that as updating turbo OPP support, along with updates to cpufreq core and cpufreq-dt driver.
- A minor bug-fix in V2 4/11, which caused false positive for duplicate OPPs in some cases.
Tested this on dual-core exynos board with the driver inbuilt as well as a module. Tried multiple insertion/removals of the module. Have tested cpufreq-dt driver with both old and new bindings.
I have tested these patches (including boost support) together with my cpufreq-dt conversion patches for Exynos4x12 platforms on Odroid-U3 and Trats2 boards.
Tested-by: Bartlomiej Zolnierkiewicz b.zolnierkie@samsung.com
Best regards, -- Bartlomiej Zolnierkiewicz Samsung R&D Institute Poland Samsung Electronics
linaro-kernel@lists.linaro.org