Linux Power Management development
 help / color / mirror / Atom feed
* Re: [IMX] [DRM]: suspend/resume support
From: Pintu Agarwal @ 2019-06-21 15:12 UTC (permalink / raw)
  To: Fabio Estevam
  Cc: open list,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	Kernelnewbies, linux-pm
In-Reply-To: <CAOuPNLjrAU_C_TUKFMs1d0eGsw=AxuG6d6FhNHtHFwVhfYZGgA@mail.gmail.com>

On Wed, Jun 19, 2019 at 8:59 PM Pintu Agarwal <pintu.ping@gmail.com> wrote:

> > > This scenario is not with suspend/resume.
> > > This hang is, when we make hdmi as a loadable module (.ko) and trying
> > > to install it after resume.
> > > In this case, suspend/resume will not come into picture. Not sure why
> > > it still hangs.
> > > Do you have any clue for this scenario?
> >
> > I haven't tried this one.
> >

Okay there is some update on the 2nd part.
Now I am able to successfully install all imx modules after the resume
(no hang).
But, I got some errors after install finish:
[drm] disabling vblank on crtc 1
[IMX]: imx_drm_disable_vblank - called
[drm:drm_atomic_helper_commit_cleanup_done] *ERROR* [CRTC:24:crtc-0]
flip_done timed out

Also I am able to start the weston successfully.
But I see LCD/HDMI display is not working (only some backlight is visible).

And, I noticed, weston also reports the following errors:
imx-ipuv3 2400000.ipu: DC stop timeout after 50 ms
[IMX]: drm_crtc_vblank_off - called
[IMX]: imx_drm_disable_vblank - called
INFO: rcu_preempt detected stalls on CPUs/tasks: { 1} (detected by 0,
t=6002 jiffies, g=289, c=288, q=8)
Task dump for CPU 1:
weston          R running      0   306      1 0x00000000
[<c05282d8>] (__schedule) from [<00080193>] (0x80193)

Do you have any clue about these errors ?

Thanks,
Pintu

^ permalink raw reply

* Re: [IMX] [DRM]: suspend/resume support
From: Pintu Agarwal @ 2019-06-21 15:04 UTC (permalink / raw)
  To: Valdis Klētnieks
  Cc: Greg KH, Fabio Estevam, linux-pm, open list,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	Kernelnewbies
In-Reply-To: <22247.1560961607@turing-police>

On Wed, Jun 19, 2019 at 9:56 PM Valdis Klētnieks
<valdis.kletnieks@vt.edu> wrote:
>
> On Wed, 19 Jun 2019 20:47:34 +0530, Pintu Agarwal said:
>
> > No I mean to say, there are lots of features and customization already
> > done on this version and stabilized.
> > Upgrading again may require months of effort.
>
> This is what happens when you don't upstream your local changes.
>
> And no, saying "But we're a small company and nobody cares" isn't an
> excuse - Linux carried the entire Voyager architecture around for several years
> for 2 machines. Not two models, 2 physical machines, the last 2 operational
> systems of the product line.
>
> (Not the Xubuntu-based Voyage distribution either - the Voyager was a mid-80s
> SMP fault-tolerant system from NCR with up to 32 486/586 cores and 4G of
> memory, which was a honking big system for the day...)
>
> https://kernel.googlesource.com/pub/scm/linux/kernel/git/rzhang/linux/+/v2.6.20-rc1/Documentation/voyager.txt
>
> The architecture was finally dropped in 2009 when enough hardware failures
> had happened that James Bottomley was unable to create a bootable
> system from the parts from both...
>
> So if your production run is several thousand systems, that's *plenty* big
> enough for patches and drivers (especially since drivers for hardware you
> included in your several-thousand system run are also likely applicable to
> a half dozen other vendors who made several thousand systems using the
> same chipset....

Yes, I agree, but unfortunately I don't have any control.
I normally keep finding things, which seems interesting for me and
propose for upstream (even if it is very small).
And, I completely agree that system should be designed such a way that
it can be easily up-gradable.
But, as I said, its not fully in our hand :(

Also, I think this case is slightly different.
Here, even if I try with latest kernel, there is no use, since
hibernation feature is not fully support for IMX.
And I think that is already known.
I came here looking for some pointers and help and get some clue to
proceed further.
If I succeed in making it I will definitely like to contribute upstream :)

Thank You!

Regards,
Pintu

^ permalink raw reply

* Re: [PATCH v10 00/16] Add utilization clamping support
From: Patrick Bellasi @ 2019-06-21 14:55 UTC (permalink / raw)
  To: linux-kernel, linux-pm
  Cc: Ingo Molnar, Peter Zijlstra, Tejun Heo, Rafael J . Wysocki,
	Vincent Guittot, Viresh Kumar, Paul Turner, Quentin Perret,
	Dietmar Eggemann, Morten Rasmussen, Juri Lelli, Todd Kjos,
	Joel Fernandes, Steve Muckle, Suren Baghdasaryan, Alessio Balsini
In-Reply-To: <20190621084217.8167-1-patrick.bellasi@arm.com>

On 21-Jun 09:42, Patrick Bellasi wrote:
> Hi all, this is a respin of:
> 
>   https://lore.kernel.org/lkml/20190515094459.10317-1-patrick.bellasi@arm.com/
> 
> which addresses all Tejun's concerns:
> 
>  - rename cgroup attributes to be cpu.uclamp.{min,max}
>  - update initialization of subgroups clamps to be "no clamps" by default
>  - use percentage rational numbers for clamp attributes, e.g. "12.34" for 12.34%.
> 
> by introducing modifications impacting only patches:
> 
>  [PATCH v10 12/16] sched/core: uclamp: Extend CPU's cgroup controller
>  [PATCH v10 13/16] sched/core: uclamp: Propagate parent clamps
> 
> The rest of the patches are the same as per in v9, they have been just rebased
> on top of:
> 
>    tj/cgroup.git	for-5.3
>    tip/tip.git		sched/core
> 
> AFAIU, all the first 11 patches have been code reviewed and should be at a
> "ready to merge" quality level. Please let me know if I'm wrong and there
> is something else I need/can to do on those patches.
> 
> Otherwise, now that we should have settled all the behavioral aspects, I'm

Regarding the behavioral aspects, here I have a report with some
simple tests for the current implementation:

   https://gist.github.com/derkling/519459b5a2be35d8681fbaf1d6efe225

There are a couple of sections at the end to test the "Delegation
Model" with both CGroups v1 and v2.

I'm sharing the link just in case it can be helpful to verify if what
has been implemented is actually matching what Tejun expects as a sane
cgroups interface.

Cheers,
Patrick

-- 
#include <best/regards.h>

Patrick Bellasi

^ permalink raw reply

* Re: [PATCH v10 11/16] sched/fair: uclamp: Add uclamp support to energy_compute()
From: Patrick Bellasi @ 2019-06-21 14:47 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: linux-kernel, linux-pm, Ingo Molnar, Tejun Heo,
	Rafael J . Wysocki, Vincent Guittot, Viresh Kumar, Paul Turner,
	Quentin Perret, Dietmar Eggemann, Morten Rasmussen, Juri Lelli,
	Todd Kjos, Joel Fernandes, Steve Muckle, Suren Baghdasaryan,
	Alessio Balsini
In-Reply-To: <20190621140128.GL3436@hirez.programming.kicks-ass.net>

On 21-Jun 16:01, Peter Zijlstra wrote:
> On Fri, Jun 21, 2019 at 09:42:12AM +0100, Patrick Bellasi wrote:
> 
> > diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
> > index ce2da8b9ff8c..f81e8930ff19 100644
> > --- a/kernel/sched/sched.h
> > +++ b/kernel/sched/sched.h
> > @@ -2322,7 +2322,6 @@ static inline unsigned long capacity_orig_of(int cpu)
> >  }
> >  #endif
> >  
> > -#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL
> >  /**
> >   * enum schedutil_type - CPU utilization type
> >   * @FREQUENCY_UTIL:	Utilization used to select frequency
> > @@ -2338,15 +2337,11 @@ enum schedutil_type {
> >  	ENERGY_UTIL,
> >  };
> >  
> > -unsigned long schedutil_freq_util(int cpu, unsigned long util_cfs,
> > -				  unsigned long max, enum schedutil_type type);
> > -
> > -static inline unsigned long schedutil_energy_util(int cpu, unsigned long cfs)
> > -{
> > -	unsigned long max = arch_scale_cpu_capacity(NULL, cpu);
> 
> That conflicts with the patch I have removing that NULL argument, fixed
> it up.

Ok, I notice only know you have this:

   commit 119fd437f412 ("sched/topology: Remove unused sd param from arch_scale_cpu_capacity()")

from Vincent on your queue. :/


> > +#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL
> >  
> > -	return schedutil_freq_util(cpu, cfs, max, ENERGY_UTIL);
> > -}
> > +unsigned long schedutil_cpu_util(int cpu, unsigned long util_cfs,
> > +				 unsigned long max, enum schedutil_type type,
> > +				 struct task_struct *p);
> >  
> >  static inline unsigned long cpu_bw_dl(struct rq *rq)
> >  {
> > @@ -2375,11 +2370,8 @@ static inline unsigned long cpu_util_rt(struct rq *rq)
> >  	return READ_ONCE(rq->avg_rt.util_avg);
> >  }
> >  #else /* CONFIG_CPU_FREQ_GOV_SCHEDUTIL */
> > -static inline unsigned long schedutil_energy_util(int cpu, unsigned long cfs)
> > -{
> > -	return cfs;
> > -}
> > -#endif
> > +#define schedutil_cpu_util(cpu, util_cfs, max, type, p) 0
> 
> Was there a good reason for this to be a macro and not an inline
> function?

Mmm... not really, apart perhaps saving some lines.

I notice sometimes we use macros (e.g. perf_domain_span), but it's
certainly not the most common pattern.

> I've changed it, if it explodes in 0day, it's all my fault ;-)

Sure, I guess if 0day explodes will not be for that change. :)


-- 
#include <best/regards.h>

Patrick Bellasi

^ permalink raw reply

* Re: [PATCH v10 11/16] sched/fair: uclamp: Add uclamp support to energy_compute()
From: Peter Zijlstra @ 2019-06-21 14:01 UTC (permalink / raw)
  To: Patrick Bellasi
  Cc: linux-kernel, linux-pm, Ingo Molnar, Tejun Heo,
	Rafael J . Wysocki, Vincent Guittot, Viresh Kumar, Paul Turner,
	Quentin Perret, Dietmar Eggemann, Morten Rasmussen, Juri Lelli,
	Todd Kjos, Joel Fernandes, Steve Muckle, Suren Baghdasaryan,
	Alessio Balsini
In-Reply-To: <20190621084217.8167-12-patrick.bellasi@arm.com>

On Fri, Jun 21, 2019 at 09:42:12AM +0100, Patrick Bellasi wrote:

> diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
> index ce2da8b9ff8c..f81e8930ff19 100644
> --- a/kernel/sched/sched.h
> +++ b/kernel/sched/sched.h
> @@ -2322,7 +2322,6 @@ static inline unsigned long capacity_orig_of(int cpu)
>  }
>  #endif
>  
> -#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL
>  /**
>   * enum schedutil_type - CPU utilization type
>   * @FREQUENCY_UTIL:	Utilization used to select frequency
> @@ -2338,15 +2337,11 @@ enum schedutil_type {
>  	ENERGY_UTIL,
>  };
>  
> -unsigned long schedutil_freq_util(int cpu, unsigned long util_cfs,
> -				  unsigned long max, enum schedutil_type type);
> -
> -static inline unsigned long schedutil_energy_util(int cpu, unsigned long cfs)
> -{
> -	unsigned long max = arch_scale_cpu_capacity(NULL, cpu);

That conflicts with the patch I have removing that NULL argument, fixed
it up.

> +#ifdef CONFIG_CPU_FREQ_GOV_SCHEDUTIL
>  
> -	return schedutil_freq_util(cpu, cfs, max, ENERGY_UTIL);
> -}
> +unsigned long schedutil_cpu_util(int cpu, unsigned long util_cfs,
> +				 unsigned long max, enum schedutil_type type,
> +				 struct task_struct *p);
>  
>  static inline unsigned long cpu_bw_dl(struct rq *rq)
>  {
> @@ -2375,11 +2370,8 @@ static inline unsigned long cpu_util_rt(struct rq *rq)
>  	return READ_ONCE(rq->avg_rt.util_avg);
>  }
>  #else /* CONFIG_CPU_FREQ_GOV_SCHEDUTIL */
> -static inline unsigned long schedutil_energy_util(int cpu, unsigned long cfs)
> -{
> -	return cfs;
> -}
> -#endif
> +#define schedutil_cpu_util(cpu, util_cfs, max, type, p) 0

Was there a good reason for this to be a macro and not an inline
function? I've changed it, if it explodes in 0day, it's all my fault ;-)

> +#endif /* CONFIG_CPU_FREQ_GOV_SCHEDUTIL */

^ permalink raw reply

* [PATCH 3/6] cpufreq/drivers/arm_big_little: Remove cooling device usage
From: Daniel Lezcano @ 2019-06-21 13:22 UTC (permalink / raw)
  To: viresh.kumar
  Cc: edubezval, linux-kernel, Sudeep Holla, Rafael J. Wysocki,
	open list:CPU FREQUENCY DRIVERS - ARM BIG LITTLE
In-Reply-To: <20190621132302.30414-1-daniel.lezcano@linaro.org>

The cpufreq_cooling_unregister() function uses now the policy to
unregister itself. The only purpose of the cooling device pointer is
to unregister the cpu cooling device.

As there is no more need of this pointer, remove it.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/cpufreq/arm_big_little.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c
index 6b243202caa9..718c63231e66 100644
--- a/drivers/cpufreq/arm_big_little.c
+++ b/drivers/cpufreq/arm_big_little.c
@@ -56,7 +56,6 @@ static bool bL_switching_enabled;
 #define ACTUAL_FREQ(cluster, freq)  ((cluster == A7_CLUSTER) ? freq << 1 : freq)
 #define VIRT_FREQ(cluster, freq)    ((cluster == A7_CLUSTER) ? freq >> 1 : freq)
 
-static struct thermal_cooling_device *cdev[MAX_CLUSTERS];
 static const struct cpufreq_arm_bL_ops *arm_bL_ops;
 static struct clk *clk[MAX_CLUSTERS];
 static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS + 1];
@@ -501,10 +500,8 @@ static int bL_cpufreq_exit(struct cpufreq_policy *policy)
 	struct device *cpu_dev;
 	int cur_cluster = cpu_to_cluster(policy->cpu);
 
-	if (cur_cluster < MAX_CLUSTERS) {
+	if (cur_cluster < MAX_CLUSTERS)
 		cpufreq_cooling_unregister(policy);
-		cdev[cur_cluster] = NULL;
-	}
 
 	cpu_dev = get_cpu_device(policy->cpu);
 	if (!cpu_dev) {
@@ -527,7 +524,7 @@ static void bL_cpufreq_ready(struct cpufreq_policy *policy)
 	if (cur_cluster >= MAX_CLUSTERS)
 		return;
 
-	cdev[cur_cluster] = of_cpufreq_cooling_register(policy);
+	of_cpufreq_cooling_register(policy);
 }
 
 static struct cpufreq_driver bL_cpufreq_driver = {
-- 
2.17.1


^ permalink raw reply related

* [PATCH 2/6] thermal/drivers/cpu_cooling: Unregister with the policy
From: Daniel Lezcano @ 2019-06-21 13:22 UTC (permalink / raw)
  To: viresh.kumar
  Cc: edubezval, linux-kernel, Sudeep Holla, Rafael J. Wysocki,
	Amit Daniel Kachhap, Javi Merino, Zhang Rui, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	NXP Linux Team, Keerthy,
	open list:CPU FREQUENCY DRIVERS - ARM BIG LITTLE,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE,
	open list:TI BANDGAP AND THERMAL DRIVER
In-Reply-To: <20190621132302.30414-1-daniel.lezcano@linaro.org>

Currently the function cpufreq_cooling_register() returns a cooling
device pointer which is used back as a pointer to call the function
cpufreq_cooling_unregister(). Even if it is correct, it would make
sense to not leak the structure inside a cpufreq driver and keep the
code thermal code self-encapsulate. Moreover, that forces to add an
extra variable in each driver using this function.

Instead of passing the cooling device to unregister, pass the policy.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/cpufreq/arm_big_little.c               |  2 +-
 drivers/cpufreq/cpufreq.c                      |  2 +-
 drivers/thermal/cpu_cooling.c                  | 18 ++++++++++--------
 drivers/thermal/imx_thermal.c                  |  4 ++--
 .../thermal/ti-soc-thermal/ti-thermal-common.c |  2 +-
 include/linux/cpu_cooling.h                    |  6 +++---
 6 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c
index 7fe52fcddcf1..6b243202caa9 100644
--- a/drivers/cpufreq/arm_big_little.c
+++ b/drivers/cpufreq/arm_big_little.c
@@ -502,7 +502,7 @@ static int bL_cpufreq_exit(struct cpufreq_policy *policy)
 	int cur_cluster = cpu_to_cluster(policy->cpu);
 
 	if (cur_cluster < MAX_CLUSTERS) {
-		cpufreq_cooling_unregister(cdev[cur_cluster]);
+		cpufreq_cooling_unregister(policy);
 		cdev[cur_cluster] = NULL;
 	}
 
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 7c72f7d3509c..dfbc9bea606c 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -1469,7 +1469,7 @@ static int cpufreq_offline(unsigned int cpu)
 	}
 
 	if (cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV) {
-		cpufreq_cooling_unregister(policy->cdev);
+		cpufreq_cooling_unregister(policy);
 		policy->cdev = NULL;
 	}
 
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
index 83486775e593..007c7c6bf845 100644
--- a/drivers/thermal/cpu_cooling.c
+++ b/drivers/thermal/cpu_cooling.c
@@ -78,6 +78,7 @@ struct cpufreq_cooling_device {
 	struct cpufreq_policy *policy;
 	struct list_head node;
 	struct time_in_idle *idle_time;
+	struct thermal_cooling_device *cdev;
 };
 
 static DEFINE_IDA(cpufreq_ida);
@@ -606,6 +607,7 @@ __cpufreq_cooling_register(struct device_node *np,
 		goto remove_ida;
 
 	cpufreq_cdev->clipped_freq = get_state_freq(cpufreq_cdev, 0);
+	cpufreq_cdev->cdev = cdev;
 
 	mutex_lock(&cooling_list_lock);
 	/* Register the notifier for first cpufreq cooling device */
@@ -699,18 +701,18 @@ EXPORT_SYMBOL_GPL(of_cpufreq_cooling_register);
  *
  * This interface function unregisters the "thermal-cpufreq-%x" cooling device.
  */
-void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+void cpufreq_cooling_unregister(struct cpufreq_policy *policy)
 {
 	struct cpufreq_cooling_device *cpufreq_cdev;
 	bool last;
 
-	if (!cdev)
-		return;
-
-	cpufreq_cdev = cdev->devdata;
-
 	mutex_lock(&cooling_list_lock);
-	list_del(&cpufreq_cdev->node);
+	list_for_each_entry(cpufreq_cdev, &cpufreq_cdev_list, node) {
+		if (cpufreq_cdev->policy == policy) {
+			list_del(&cpufreq_cdev->node);
+			break;
+		}
+	}
 	/* Unregister the notifier for the last cpufreq cooling device */
 	last = list_empty(&cpufreq_cdev_list);
 	mutex_unlock(&cooling_list_lock);
@@ -719,7 +721,7 @@ void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
 		cpufreq_unregister_notifier(&thermal_cpufreq_notifier_block,
 					    CPUFREQ_POLICY_NOTIFIER);
 
-	thermal_cooling_device_unregister(cdev);
+	thermal_cooling_device_unregister(cpufreq_cdev->cdev);
 	ida_simple_remove(&cpufreq_ida, cpufreq_cdev->id);
 	kfree(cpufreq_cdev->idle_time);
 	kfree(cpufreq_cdev);
diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
index bb6754a5342c..6746f1b73eb7 100644
--- a/drivers/thermal/imx_thermal.c
+++ b/drivers/thermal/imx_thermal.c
@@ -680,7 +680,7 @@ static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
 
 static void imx_thermal_unregister_legacy_cooling(struct imx_thermal_data *data)
 {
-	cpufreq_cooling_unregister(data->cdev);
+	cpufreq_cooling_unregister(data->policy);
 	cpufreq_cpu_put(data->policy);
 }
 
@@ -872,7 +872,7 @@ static int imx_thermal_remove(struct platform_device *pdev)
 		clk_disable_unprepare(data->thermal_clk);
 
 	thermal_zone_device_unregister(data->tz);
-	cpufreq_cooling_unregister(data->cdev);
+	cpufreq_cooling_unregister(data->policy);
 	cpufreq_cpu_put(data->policy);
 
 	return 0;
diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
index b4f981daeaf2..217b1aae8b4f 100644
--- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -277,7 +277,7 @@ int ti_thermal_unregister_cpu_cooling(struct ti_bandgap *bgp, int id)
 	data = ti_bandgap_get_sensor_data(bgp, id);
 
 	if (data) {
-		cpufreq_cooling_unregister(data->cool_dev);
+		cpufreq_cooling_unregister(data->policy);
 		if (data->policy)
 			cpufreq_cpu_put(data->policy);
 	}
diff --git a/include/linux/cpu_cooling.h b/include/linux/cpu_cooling.h
index bae54bb7c048..89f469ee4be4 100644
--- a/include/linux/cpu_cooling.h
+++ b/include/linux/cpu_cooling.h
@@ -29,9 +29,9 @@ cpufreq_cooling_register(struct cpufreq_policy *policy);
 
 /**
  * cpufreq_cooling_unregister - function to remove cpufreq cooling device.
- * @cdev: thermal cooling device pointer.
+ * @policy: cpufreq policy
  */
-void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev);
+void cpufreq_cooling_unregister(struct cpufreq_policy *policy);
 
 #else /* !CONFIG_CPU_THERMAL */
 static inline struct thermal_cooling_device *
@@ -41,7 +41,7 @@ cpufreq_cooling_register(struct cpufreq_policy *policy)
 }
 
 static inline
-void cpufreq_cooling_unregister(struct thermal_cooling_device *cdev)
+void cpufreq_cooling_unregister(struct cpufreq_policy *policy)
 {
 	return;
 }
-- 
2.17.1


^ permalink raw reply related

* [PATCH 4/6] cpufreq:  Remove cooling device usage
From: Daniel Lezcano @ 2019-06-21 13:23 UTC (permalink / raw)
  To: viresh.kumar
  Cc: edubezval, linux-kernel, Rafael J. Wysocki,
	open list:CPU FREQUENCY SCALING FRAMEWORK
In-Reply-To: <20190621132302.30414-1-daniel.lezcano@linaro.org>

The cpufreq_cooling_unregister() function uses now the policy to
unregister itself. The only purpose of the cooling device pointer is
to unregister the cpu cooling device.

As there is no more need of this pointer, remove it.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/cpufreq/cpufreq.c | 6 ++----
 include/linux/cpufreq.h   | 3 ---
 2 files changed, 2 insertions(+), 7 deletions(-)

diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index dfbc9bea606c..1d8f85faeaca 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -1379,7 +1379,7 @@ static int cpufreq_online(unsigned int cpu)
 		cpufreq_driver->ready(policy);
 
 	if (cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV)
-		policy->cdev = of_cpufreq_cooling_register(policy);
+		of_cpufreq_cooling_register(policy);
 
 	pr_debug("initialization complete\n");
 
@@ -1468,10 +1468,8 @@ static int cpufreq_offline(unsigned int cpu)
 		goto unlock;
 	}
 
-	if (cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV) {
+	if (cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV)
 		cpufreq_cooling_unregister(policy);
-		policy->cdev = NULL;
-	}
 
 	if (cpufreq_driver->stop_cpu)
 		cpufreq_driver->stop_cpu(policy);
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index d01a74fbc4db..9a42711f338b 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -144,9 +144,6 @@ struct cpufreq_policy {
 
 	/* For cpufreq driver's internal use */
 	void			*driver_data;
-
-	/* Pointer to the cooling device if used for thermal mitigation */
-	struct thermal_cooling_device *cdev;
 };
 
 struct cpufreq_freqs {
-- 
2.17.1


^ permalink raw reply related

* [PATCH 6/6] thermal/drivers/ti: Remove cooling device usage
From: Daniel Lezcano @ 2019-06-21 13:23 UTC (permalink / raw)
  To: viresh.kumar
  Cc: edubezval, linux-kernel, Keerthy, Zhang Rui,
	open list:TI BANDGAP AND THERMAL DRIVER,
	open list:TI BANDGAP AND THERMAL DRIVER
In-Reply-To: <20190621132302.30414-1-daniel.lezcano@linaro.org>

The cpufreq_cooling_unregister() function uses now the policy to
unregister itself. The only purpose of the cooling device pointer is
to unregister the cpu cooling device.

As there is no more need of this pointer, remove it.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/thermal/ti-soc-thermal/ti-thermal-common.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
index 217b1aae8b4f..170b70b6ec61 100644
--- a/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
+++ b/drivers/thermal/ti-soc-thermal/ti-thermal-common.c
@@ -41,7 +41,6 @@ struct ti_thermal_data {
 	struct cpufreq_policy *policy;
 	struct thermal_zone_device *ti_thermal;
 	struct thermal_zone_device *pcb_tz;
-	struct thermal_cooling_device *cool_dev;
 	struct ti_bandgap *bgp;
 	enum thermal_device_mode mode;
 	struct work_struct thermal_wq;
@@ -233,6 +232,7 @@ int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id)
 {
 	struct ti_thermal_data *data;
 	struct device_node *np = bgp->dev->of_node;
+	struct thermal_cooling_device *cdev;
 
 	/*
 	 * We are assuming here that if one deploys the zone
@@ -256,9 +256,9 @@ int ti_thermal_register_cpu_cooling(struct ti_bandgap *bgp, int id)
 	}
 
 	/* Register cooling device */
-	data->cool_dev = cpufreq_cooling_register(data->policy);
-	if (IS_ERR(data->cool_dev)) {
-		int ret = PTR_ERR(data->cool_dev);
+	cdev = cpufreq_cooling_register(data->policy);
+	if (IS_ERR(cdev)) {
+		int ret = PTR_ERR(cdev);
 		dev_err(bgp->dev, "Failed to register cpu cooling device %d\n",
 			ret);
 		cpufreq_cpu_put(data->policy);
-- 
2.17.1


^ permalink raw reply related

* [PATCH 5/6] thermal/drivers/imx: Remove cooling device usage
From: Daniel Lezcano @ 2019-06-21 13:23 UTC (permalink / raw)
  To: viresh.kumar
  Cc: edubezval, linux-kernel, Zhang Rui, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, NXP Linux Team,
	open list:THERMAL,
	moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE
In-Reply-To: <20190621132302.30414-1-daniel.lezcano@linaro.org>

The cpufreq_cooling_unregister() function uses now the policy to
unregister itself. The only purpose of the cooling device pointer is
to unregister the cpu cooling device.

As there is no more need of this pointer, remove it.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/thermal/imx_thermal.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/thermal/imx_thermal.c b/drivers/thermal/imx_thermal.c
index 6746f1b73eb7..021c0948b740 100644
--- a/drivers/thermal/imx_thermal.c
+++ b/drivers/thermal/imx_thermal.c
@@ -203,7 +203,6 @@ static struct thermal_soc_data thermal_imx7d_data = {
 struct imx_thermal_data {
 	struct cpufreq_policy *policy;
 	struct thermal_zone_device *tz;
-	struct thermal_cooling_device *cdev;
 	enum thermal_device_mode mode;
 	struct regmap *tempmon;
 	u32 c1, c2; /* See formula in imx_init_calib() */
@@ -656,6 +655,7 @@ MODULE_DEVICE_TABLE(of, of_imx_thermal_match);
 static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
 {
 	struct device_node *np;
+	struct thermal_cooling_device *cdev;
 	int ret;
 
 	data->policy = cpufreq_cpu_get(0);
@@ -667,9 +667,9 @@ static int imx_thermal_register_legacy_cooling(struct imx_thermal_data *data)
 	np = of_get_cpu_node(data->policy->cpu, NULL);
 
 	if (!np || !of_find_property(np, "#cooling-cells", NULL)) {
-		data->cdev = cpufreq_cooling_register(data->policy);
-		if (IS_ERR(data->cdev)) {
-			ret = PTR_ERR(data->cdev);
+		cdev = cpufreq_cooling_register(data->policy);
+		if (IS_ERR(cdev)) {
+			ret = PTR_ERR(cdev);
 			cpufreq_cpu_put(data->policy);
 			return ret;
 		}
-- 
2.17.1


^ permalink raw reply related

* [PATCH 1/6] cpufreq: Use existing stub functions instead of IS_ENABLED macro
From: Daniel Lezcano @ 2019-06-21 13:22 UTC (permalink / raw)
  To: viresh.kumar
  Cc: edubezval, linux-kernel, Rafael J. Wysocki,
	open list:CPU FREQUENCY SCALING FRAMEWORK

The functions stub already exist for the condition the IS_ENABLED
is trying to avoid.

Remove the IS_ENABLED macros as they are pointless.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
---
 drivers/cpufreq/cpufreq.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 85ff958e01f1..7c72f7d3509c 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -1378,8 +1378,7 @@ static int cpufreq_online(unsigned int cpu)
 	if (cpufreq_driver->ready)
 		cpufreq_driver->ready(policy);
 
-	if (IS_ENABLED(CONFIG_CPU_THERMAL) &&
-	    cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV)
+	if (cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV)
 		policy->cdev = of_cpufreq_cooling_register(policy);
 
 	pr_debug("initialization complete\n");
@@ -1469,8 +1468,7 @@ static int cpufreq_offline(unsigned int cpu)
 		goto unlock;
 	}
 
-	if (IS_ENABLED(CONFIG_CPU_THERMAL) &&
-	    cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV) {
+	if (cpufreq_driver->flags & CPUFREQ_IS_COOLING_DEV) {
 		cpufreq_cooling_unregister(policy->cdev);
 		policy->cdev = NULL;
 	}
-- 
2.17.1


^ permalink raw reply related

* [PATCH v4 2/2] dt-bindings: mfd: max8998: Add charger subnode binding
From: Paweł Chmiel @ 2019-06-21 11:56 UTC (permalink / raw)
  To: sre
  Cc: lee.jones, robh+dt, mark.rutland, linux-kernel, linux-pm,
	devicetree, linux-samsung-soc, Paweł Chmiel
In-Reply-To: <20190621115602.17559-1-pawel.mikolaj.chmiel@gmail.com>

This patch adds devicetree bindings documentation for
battery charging controller as the subnode of MAX8998 PMIC.

Signed-off-by: Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
---
Changes from v3:
  - Property prefix should be maxim, not max8998
  - Describe what End of Charge in percent means

Changes from v2:
  - Make charge-restart-level-microvolt optional.
  - Make charge-timeout-hours optional.

Changes from v1:
  - Removed unneeded Fixes tag
  - Correct description of all charger values
  - Added missing property unit
---
 .../devicetree/bindings/mfd/max8998.txt       | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/max8998.txt b/Documentation/devicetree/bindings/mfd/max8998.txt
index 5f2f07c09c90..368f787d6079 100644
--- a/Documentation/devicetree/bindings/mfd/max8998.txt
+++ b/Documentation/devicetree/bindings/mfd/max8998.txt
@@ -48,6 +48,25 @@ Additional properties required if max8998,pmic-buck2-dvs-gpio is defined:
 - max8998,pmic-buck2-dvs-voltage: An array of 2 voltage values in microvolts
   for buck2 regulator that can be selected using dvs gpio.
 
+Charger: Configuration for battery charging controller should be added
+inside a child node named 'charger'.
+  Required properties:
+  - maxim,end-of-charge-percentage: End of Charge in percent.
+    When the charge current in constant-voltage phase drops below
+    end-of-charge-percentage of it's start value, charging is terminated.
+    If value equals 0, leave it unchanged. Otherwise it should be value
+    from 10 to 45 by 5 step.
+
+  Optional properties:
+  - maxim,charge-restart-threshold: Charge restart threshold in millivolts.
+    If property is not present, this will be disabled.
+    Valid values are: 0, 100, 150, 200. If the value equals 0, leave it
+    unchanged.
+
+  - maxim,charge-timeout: Charge timeout in hours. If property is not
+    present, this will be disabled. Valid values are: 0, 5, 6, 7.
+    If the value equals 0, leave it unchanged.
+
 Regulators: All the regulators of MAX8998 to be instantiated shall be
 listed in a child node named 'regulators'. Each regulator is represented
 by a child node of the 'regulators' node.
@@ -97,6 +116,13 @@ Example:
 		max8998,pmic-buck2-dvs-gpio = <&gpx0 0 3 0 0>; /* SET3 */
 		max8998,pmic-buck2-dvs-voltage = <1350000>, <1300000>;
 
+		/* Charger configuration */
+		charger {
+			maxim,end-of-charge-percentage = <20>;
+			maxim,charge-restart-threshold = <100>;
+			maxim,charge-timeout = <7>;
+		};
+
 		/* Regulators to instantiate */
 		regulators {
 			ldo2_reg: LDO2 {
-- 
2.17.1


^ permalink raw reply related

* [PATCH v4 1/2] power: supply: max8998-charger: Parse device tree for required data.
From: Paweł Chmiel @ 2019-06-21 11:56 UTC (permalink / raw)
  To: sre
  Cc: lee.jones, robh+dt, mark.rutland, linux-kernel, linux-pm,
	devicetree, linux-samsung-soc, Paweł Chmiel
In-Reply-To: <20190621115602.17559-1-pawel.mikolaj.chmiel@gmail.com>

This patch adds missing code for reading charger configuration
from devicetree.

Signed-off-by: Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
---
Changes from v3:
  - Property prefix should be maxim, not max8998
  - Changed property name to more meaning full

Changes from v2:
  - Make restart level and charge timeout properties optional.
    If they're not present in devicetree, assume they're disabled.

Changes from v1:
  - Removed unneeded Fixes tag
  - Use new property names
---
 drivers/power/supply/max8998_charger.c | 60 ++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/drivers/power/supply/max8998_charger.c b/drivers/power/supply/max8998_charger.c
index 9a926c7c0f22..dfd473ed4c5b 100644
--- a/drivers/power/supply/max8998_charger.c
+++ b/drivers/power/supply/max8998_charger.c
@@ -8,6 +8,7 @@
 #include <linux/err.h>
 #include <linux/module.h>
 #include <linux/mod_devicetable.h>
+#include <linux/of.h>
 #include <linux/slab.h>
 #include <linux/platform_device.h>
 #include <linux/power_supply.h>
@@ -69,6 +70,59 @@ static const struct power_supply_desc max8998_battery_desc = {
 	.num_properties	= ARRAY_SIZE(max8998_battery_props),
 };
 
+static int max8998_pmic_dt_parse_pdata(struct max8998_dev *iodev,
+					struct max8998_platform_data *pdata)
+{
+	struct device_node *pmic_np = iodev->dev->of_node;
+	struct device_node *charger_np;
+	int ret;
+
+	charger_np = of_get_child_by_name(pmic_np, "charger");
+	if (!charger_np) {
+		dev_err(iodev->dev, "could not find charger sub-node\n");
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32(charger_np,
+					"maxim,end-of-charge-percentage",
+					&pdata->eoc);
+	if (ret < 0) {
+		dev_err(iodev->dev,
+			"Could not find maxim,end-of-charge-percentage in devicetree\n");
+		return ret;
+	}
+
+	ret = of_property_read_u32(charger_np,
+					"maxim,charge-restart-threshold",
+					&pdata->restart);
+	if (ret < 0) {
+		if (ret != -EINVAL) {
+			dev_err(iodev->dev,
+				"Failed to read maxim,charge-restart-threshold\n");
+			return ret;
+		}
+
+		pdata->restart = -1;
+		dev_dbg(iodev->dev, "Charge Restart Threshold disabled\n");
+	}
+
+	ret = of_property_read_u32(charger_np,
+					"maxim,charge-timeout",
+					&pdata->timeout);
+	if (ret < 0) {
+		if (ret != -EINVAL) {
+			dev_err(iodev->dev,
+				"Failed to read maxim,charge-timeout\n");
+			return ret;
+		}
+
+		pdata->timeout = -1;
+		dev_dbg(iodev->dev, "Charge Full Timeout disabled\n");
+	}
+
+	return 0;
+}
+
 static int max8998_battery_probe(struct platform_device *pdev)
 {
 	struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent);
@@ -83,6 +137,12 @@ static int max8998_battery_probe(struct platform_device *pdev)
 		return -ENODEV;
 	}
 
+	if (IS_ENABLED(CONFIG_OF) && iodev->dev->of_node) {
+		ret = max8998_pmic_dt_parse_pdata(iodev, pdata);
+		if (ret)
+			return ret;
+	}
+
 	max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data),
 				GFP_KERNEL);
 	if (!max8998)
-- 
2.17.1


^ permalink raw reply related

* [PATCH v4 0/2] power: supply: max8998-charger: Device Tree support
From: Paweł Chmiel @ 2019-06-21 11:56 UTC (permalink / raw)
  To: sre
  Cc: lee.jones, robh+dt, mark.rutland, linux-kernel, linux-pm,
	devicetree, linux-samsung-soc, Paweł Chmiel

This patch series compose of 2 patches.

First patch, updates max8998 charger driver, so it's possible to parse
devicetree for configuration.

Second patch, updates max8998 documentation, so it includes new node 
and properties, needed for charger.

Patches has been tested on, Samsung Galaxy S (i9000) phone.

Changes from v3:
  - Property prefix should be maxim, not max8998
  - Changed property name to more meaning full
  - Describe what End of Charge in percent means

Changes from v2:
  - Make charge-restart-level-microvolt and charge-timeout-hours
    properties optional. If they're not present, assume they're disabled.

Changes from v1:
  - Removed unneeded Fixes tag
  - Correct description of all charger values
  - Added missing property unit for charger properties
  - Removed already applied patch

Paweł Chmiel (2):
  power: supply: max8998-charger: Parse device tree for required data.
  dt-bindings: mfd: max8998: Add charger subnode binding

 .../devicetree/bindings/mfd/max8998.txt       | 26 ++++++++
 drivers/power/supply/max8998_charger.c        | 60 +++++++++++++++++++
 2 files changed, 86 insertions(+)

-- 
2.17.1


^ permalink raw reply

* Re: [RFC PATCH 1/7] PM: Introduce em_pd_get_higher_freq()
From: Quentin Perret @ 2019-06-21 10:22 UTC (permalink / raw)
  To: Patrick Bellasi
  Cc: Douglas Raillard, linux-kernel, linux-pm, mingo, peterz,
	dietmar.eggemann
In-Reply-To: <20190621101704.pw7oluum4eqgdgzp@queper01-lin>

On Friday 21 Jun 2019 at 11:17:05 (+0100), Quentin Perret wrote:
> On Thursday 20 Jun 2019 at 14:04:39 (+0100), Patrick Bellasi wrote:
> > On 19-Jun 17:08, Douglas Raillard wrote:
> > > Hi Patrick,
> > > 
> > > On 5/16/19 2:22 PM, Patrick Bellasi wrote:
> > > > On 16-May 14:01, Quentin Perret wrote:
> > > > > On Thursday 16 May 2019 at 13:42:00 (+0100), Patrick Bellasi wrote:
> > > > > > > +static inline unsigned long em_pd_get_higher_freq(struct em_perf_domain *pd,
> > > > > > > +	unsigned long min_freq, unsigned long cost_margin)
> > > > > > > +{
> > > > > > > +	unsigned long max_cost = 0;
> > > > > > > +	struct em_cap_state *cs;
> > > > > > > +	int i;
> > > > > > > +
> > > > > > > +	if (!pd)
> > > > > > > +		return min_freq;
> > > > > > > +
> > > > > > > +	/* Compute the maximum allowed cost */
> > > > > > > +	for (i = 0; i < pd->nr_cap_states; i++) {
> > > > > > > +		cs = &pd->table[i];
> > > > > > > +		if (cs->frequency >= min_freq) {
> > > > > > > +			max_cost = cs->cost + (cs->cost * cost_margin) / 1024;
> > > > > >                                                                           ^^^^
> > > > > > ... end here we should probably better use SCHED_CAPACITY_SCALE
> > > > > > instead of hard-coding in values, isn't it?
> > > > > 
> > > > > I'm not sure to agree. This isn't part of the scheduler per se, and the
> > > > > cost thing isn't in units of capacity, but in units of power, so I don't
> > > > > think SCHED_CAPACITY_SCALE is correct here.
> > > > 
> > > > Right, I get the units do not match and it would not be elegant to use
> > > > it here...
> > > > 
> > > > > But I agree these hard coded values (that one, and the 512 in one of the
> > > > > following patches) could use some motivation :-)
> > > > 
> > > > ... ultimately SCHED_CAPACITY_SCALE is just SCHED_FIXEDPOINT_SCALE,
> > > > which is adimensional. Perhaps we should use that or yet another alias
> > > > for the same.
> > > 
> > > Would it be a good idea to use SCHED_FIXEDPOINT_SCALE in energy.c ?
> > > Since it's not part of the scheduler, maybe there is a scale covering a wider scope,
> > > or we can introduce a similar ENERGY_FIXEDPOINT_SCALE in energy_model.h.
> > 
> > Well, in energy_model.c we have references to "capacity" and
> > "utilization" which are all SCHED_FIXEDPOINT_SCALE range values.
> > That symbol is defined in <linux/sched.h> and we already pull
> > in other <linux/sched/*> headers.
> > 
> > So, to me it seems it's not unreasonable to say that we use scheduler
> > related concepts and it makes more sense than introducing yet another
> > scaling factor.
> > 
> > But that's just my two cents ;)
> 
> Perhaps use this ?
> 
> https://elixir.bootlin.com/linux/latest/source/include/linux/energy_model.h#L43
> 

Nah, bad idea actually ... Sorry for the noise

^ permalink raw reply

* Re: [RFC PATCH 1/7] PM: Introduce em_pd_get_higher_freq()
From: Quentin Perret @ 2019-06-21 10:17 UTC (permalink / raw)
  To: Patrick Bellasi
  Cc: Douglas Raillard, linux-kernel, linux-pm, mingo, peterz,
	dietmar.eggemann
In-Reply-To: <20190620130439.c3tk7osezd37pfmj@e110439-lin>

On Thursday 20 Jun 2019 at 14:04:39 (+0100), Patrick Bellasi wrote:
> On 19-Jun 17:08, Douglas Raillard wrote:
> > Hi Patrick,
> > 
> > On 5/16/19 2:22 PM, Patrick Bellasi wrote:
> > > On 16-May 14:01, Quentin Perret wrote:
> > > > On Thursday 16 May 2019 at 13:42:00 (+0100), Patrick Bellasi wrote:
> > > > > > +static inline unsigned long em_pd_get_higher_freq(struct em_perf_domain *pd,
> > > > > > +	unsigned long min_freq, unsigned long cost_margin)
> > > > > > +{
> > > > > > +	unsigned long max_cost = 0;
> > > > > > +	struct em_cap_state *cs;
> > > > > > +	int i;
> > > > > > +
> > > > > > +	if (!pd)
> > > > > > +		return min_freq;
> > > > > > +
> > > > > > +	/* Compute the maximum allowed cost */
> > > > > > +	for (i = 0; i < pd->nr_cap_states; i++) {
> > > > > > +		cs = &pd->table[i];
> > > > > > +		if (cs->frequency >= min_freq) {
> > > > > > +			max_cost = cs->cost + (cs->cost * cost_margin) / 1024;
> > > > >                                                                           ^^^^
> > > > > ... end here we should probably better use SCHED_CAPACITY_SCALE
> > > > > instead of hard-coding in values, isn't it?
> > > > 
> > > > I'm not sure to agree. This isn't part of the scheduler per se, and the
> > > > cost thing isn't in units of capacity, but in units of power, so I don't
> > > > think SCHED_CAPACITY_SCALE is correct here.
> > > 
> > > Right, I get the units do not match and it would not be elegant to use
> > > it here...
> > > 
> > > > But I agree these hard coded values (that one, and the 512 in one of the
> > > > following patches) could use some motivation :-)
> > > 
> > > ... ultimately SCHED_CAPACITY_SCALE is just SCHED_FIXEDPOINT_SCALE,
> > > which is adimensional. Perhaps we should use that or yet another alias
> > > for the same.
> > 
> > Would it be a good idea to use SCHED_FIXEDPOINT_SCALE in energy.c ?
> > Since it's not part of the scheduler, maybe there is a scale covering a wider scope,
> > or we can introduce a similar ENERGY_FIXEDPOINT_SCALE in energy_model.h.
> 
> Well, in energy_model.c we have references to "capacity" and
> "utilization" which are all SCHED_FIXEDPOINT_SCALE range values.
> That symbol is defined in <linux/sched.h> and we already pull
> in other <linux/sched/*> headers.
> 
> So, to me it seems it's not unreasonable to say that we use scheduler
> related concepts and it makes more sense than introducing yet another
> scaling factor.
> 
> But that's just my two cents ;)

Perhaps use this ?

https://elixir.bootlin.com/linux/latest/source/include/linux/energy_model.h#L43

Thanks,

^ permalink raw reply

* [RESEND,v2] cpufreq: s5pv210: Don't flood kernel log after cpufreq change
From: Paweł Chmiel @ 2019-06-21 10:10 UTC (permalink / raw)
  To: kgene
  Cc: krzk, rjw, viresh.kumar, linux-arm-kernel, linux-samsung-soc,
	linux-pm, linux-kernel

This commit replaces printk with pr_debug, so we don't flood kernel log.

Signed-off-by: Paweł Chmiel <pawel.mikolaj.chmiel@gmail.com>
Acked-by: Krzysztof Kozlowski <krzk@kernel.org>
---
Changes from v1:
  - Added Acked-by
---
 drivers/cpufreq/s5pv210-cpufreq.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c
index 5b4289460bc9..c7b7d1e65b08 100644
--- a/drivers/cpufreq/s5pv210-cpufreq.c
+++ b/drivers/cpufreq/s5pv210-cpufreq.c
@@ -481,7 +481,7 @@ static int s5pv210_target(struct cpufreq_policy *policy, unsigned int index)
 				arm_volt, arm_volt_max);
 	}
 
-	printk(KERN_DEBUG "Perf changed[L%d]\n", index);
+	pr_debug("Perf changed[L%d]\n", index);
 
 exit:
 	mutex_unlock(&set_freq_lock);
-- 
2.17.1


^ permalink raw reply related

* [PATCH v3 2/2] PM / AVS: SVS: Introduce SVS engine
From: Roger Lu @ 2019-06-21  8:43 UTC (permalink / raw)
  To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
  Cc: Fan Chen, HenryC Chen, Mark Rutland, Matthias Brugger,
	Nishanth Menon, Roger Lu, devicetree, linux-arm-kernel,
	linux-mediatek, linux-kernel, linux-pm
In-Reply-To: <20190621084348.16834-1-roger.lu@mediatek.com>

The SVS (Smart Voltage Scaling) engine is a piece of hardware which is
used to calculate optimized voltage values of several power domains, e.g.
CPU/GPU/CCI, according to chip process corner, temperatures, and other
factors. Then DVFS driver could apply those optimized voltage values to
reduce power consumption.

Signed-off-by: Roger Lu <roger.lu@mediatek.com>
---
 drivers/power/avs/Kconfig     |   10 +
 drivers/power/avs/Makefile    |    1 +
 drivers/power/avs/mtk_svs.c   | 2093 +++++++++++++++++++++++++++++++++
 include/linux/power/mtk_svs.h |   23 +
 4 files changed, 2127 insertions(+)
 create mode 100644 drivers/power/avs/mtk_svs.c
 create mode 100644 include/linux/power/mtk_svs.h

diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig
index a67eeace6a89..da9baf5b5a03 100644
--- a/drivers/power/avs/Kconfig
+++ b/drivers/power/avs/Kconfig
@@ -18,3 +18,13 @@ config ROCKCHIP_IODOMAIN
           Say y here to enable support io domains on Rockchip SoCs. It is
           necessary for the io domain setting of the SoC to match the
           voltage supplied by the regulators.
+
+config MTK_SVS
+	bool "MediaTek Smart Voltage Scaling(SVS)"
+	depends on POWER_AVS && MTK_EFUSE
+	help
+	  The SVS engine is a piece of hardware which is used to calculate
+	  optimized voltage values of several power domains, e.g.
+	  CPU clusters/GPU/CCI, according to chip process corner, temperatures,
+	  and other factors. Then DVFS driver could apply those optimized voltage
+	  values to reduce power consumption.
diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile
index ba4c7bc69225..b487701cc4a6 100644
--- a/drivers/power/avs/Makefile
+++ b/drivers/power/avs/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_POWER_AVS_OMAP)		+= smartreflex.o
 obj-$(CONFIG_ROCKCHIP_IODOMAIN)		+= rockchip-io-domain.o
+obj-$(CONFIG_MTK_SVS)			+= mtk_svs.o
diff --git a/drivers/power/avs/mtk_svs.c b/drivers/power/avs/mtk_svs.c
new file mode 100644
index 000000000000..e7ecb963466b
--- /dev/null
+++ b/drivers/power/avs/mtk_svs.c
@@ -0,0 +1,2093 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 MediaTek Inc.
+ */
+
+#define pr_fmt(fmt)	"[mtk_svs] " fmt
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/pm_runtime.h>
+#include <linux/power/mtk_svs.h>
+#include <linux/proc_fs.h>
+#include <linux/regulator/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/thermal.h>
+#include <linux/uaccess.h>
+
+#define SVS_INIT01_VOLT_IGNORE		1
+#define SVS_INIT01_VOLT_INC_ONLY	2
+
+#define SVS_PHASE_INIT01		0
+#define SVS_PHASE_INIT02		1
+#define SVS_PHASE_MON			2
+#define SVS_PHASE_ERROR			3
+
+#define SVS_CPU_LITTLE			1
+#define SVS_CPU_BIG			2
+#define SVS_CCI				3
+#define SVS_GPU				4
+
+#define proc_fops_rw(name) \
+	static int name ## _proc_open(struct inode *inode,	\
+		struct file *file)				\
+	{							\
+		return single_open(file, name ## _proc_show,	\
+			PDE_DATA(inode));			\
+	}							\
+	static const struct file_operations name ## _proc_fops = {	\
+		.owner          = THIS_MODULE,				\
+		.open           = name ## _proc_open,			\
+		.read           = seq_read,				\
+		.llseek         = seq_lseek,				\
+		.release        = single_release,			\
+		.write          = name ## _proc_write,			\
+	}
+
+#define proc_fops_ro(name) \
+	static int name ## _proc_open(struct inode *inode,	\
+		struct file *file)				\
+	{							\
+		return single_open(file, name ## _proc_show,	\
+			PDE_DATA(inode));			\
+	}							\
+	static const struct file_operations name ## _proc_fops = {	\
+		.owner          = THIS_MODULE,				\
+		.open           = name ## _proc_open,			\
+		.read           = seq_read,				\
+		.llseek         = seq_lseek,				\
+		.release        = single_release,			\
+	}
+
+#define proc_entry(name)	{__stringify(name), &name ## _proc_fops}
+
+static DEFINE_SPINLOCK(mtk_svs_lock);
+struct mtk_svs;
+
+enum reg_index {
+	TEMPMONCTL0 = 0,
+	TEMPMONCTL1,
+	TEMPMONCTL2,
+	TEMPMONINT,
+	TEMPMONINTSTS,
+	TEMPMONIDET0,
+	TEMPMONIDET1,
+	TEMPMONIDET2,
+	TEMPH2NTHRE,
+	TEMPHTHRE,
+	TEMPCTHRE,
+	TEMPOFFSETH,
+	TEMPOFFSETL,
+	TEMPMSRCTL0,
+	TEMPMSRCTL1,
+	TEMPAHBPOLL,
+	TEMPAHBTO,
+	TEMPADCPNP0,
+	TEMPADCPNP1,
+	TEMPADCPNP2,
+	TEMPADCMUX,
+	TEMPADCEXT,
+	TEMPADCEXT1,
+	TEMPADCEN,
+	TEMPPNPMUXADDR,
+	TEMPADCMUXADDR,
+	TEMPADCEXTADDR,
+	TEMPADCEXT1ADDR,
+	TEMPADCENADDR,
+	TEMPADCVALIDADDR,
+	TEMPADCVOLTADDR,
+	TEMPRDCTRL,
+	TEMPADCVALIDMASK,
+	TEMPADCVOLTAGESHIFT,
+	TEMPADCWRITECTRL,
+	TEMPMSR0,
+	TEMPMSR1,
+	TEMPMSR2,
+	TEMPADCHADDR,
+	TEMPIMMD0,
+	TEMPIMMD1,
+	TEMPIMMD2,
+	TEMPMONIDET3,
+	TEMPADCPNP3,
+	TEMPMSR3,
+	TEMPIMMD3,
+	TEMPPROTCTL,
+	TEMPPROTTA,
+	TEMPPROTTB,
+	TEMPPROTTC,
+	TEMPSPARE0,
+	TEMPSPARE1,
+	TEMPSPARE2,
+	TEMPSPARE3,
+	TEMPMSR0_1,
+	TEMPMSR1_1,
+	TEMPMSR2_1,
+	TEMPMSR3_1,
+	DESCHAR,
+	TEMPCHAR,
+	DETCHAR,
+	AGECHAR,
+	DCCONFIG,
+	AGECONFIG,
+	FREQPCT30,
+	FREQPCT74,
+	LIMITVALS,
+	VBOOT,
+	DETWINDOW,
+	CONFIG,
+	TSCALCS,
+	RUNCONFIG,
+	SVSEN,
+	INIT2VALS,
+	DCVALUES,
+	AGEVALUES,
+	VOP30,
+	VOP74,
+	TEMP,
+	INTSTS,
+	INTSTSRAW,
+	INTEN,
+	CHKINT,
+	CHKSHIFT,
+	STATUS,
+	VDESIGN30,
+	VDESIGN74,
+	DVT30,
+	DVT74,
+	AGECOUNT,
+	SMSTATE0,
+	SMSTATE1,
+	CTL0,
+	DESDETSEC,
+	TEMPAGESEC,
+	CTRLSPARE0,
+	CTRLSPARE1,
+	CTRLSPARE2,
+	CTRLSPARE3,
+	CORESEL,
+	THERMINTST,
+	INTST,
+	THSTAGE0ST,
+	THSTAGE1ST,
+	THSTAGE2ST,
+	THAHBST0,
+	THAHBST1,
+	SPARE0,
+	SPARE1,
+	SPARE2,
+	SPARE3,
+	THSLPEVEB,
+	reg_num,
+};
+
+static const u32 svs_regs_v2[] = {
+	[TEMPMONCTL0]		= 0x000,
+	[TEMPMONCTL1]		= 0x004,
+	[TEMPMONCTL2]		= 0x008,
+	[TEMPMONINT]		= 0x00c,
+	[TEMPMONINTSTS]		= 0x010,
+	[TEMPMONIDET0]		= 0x014,
+	[TEMPMONIDET1]		= 0x018,
+	[TEMPMONIDET2]		= 0x01c,
+	[TEMPH2NTHRE]		= 0x024,
+	[TEMPHTHRE]		= 0x028,
+	[TEMPCTHRE]		= 0x02c,
+	[TEMPOFFSETH]		= 0x030,
+	[TEMPOFFSETL]		= 0x034,
+	[TEMPMSRCTL0]		= 0x038,
+	[TEMPMSRCTL1]		= 0x03c,
+	[TEMPAHBPOLL]		= 0x040,
+	[TEMPAHBTO]		= 0x044,
+	[TEMPADCPNP0]		= 0x048,
+	[TEMPADCPNP1]		= 0x04c,
+	[TEMPADCPNP2]		= 0x050,
+	[TEMPADCMUX]		= 0x054,
+	[TEMPADCEXT]		= 0x058,
+	[TEMPADCEXT1]		= 0x05c,
+	[TEMPADCEN]		= 0x060,
+	[TEMPPNPMUXADDR]	= 0x064,
+	[TEMPADCMUXADDR]	= 0x068,
+	[TEMPADCEXTADDR]	= 0x06c,
+	[TEMPADCEXT1ADDR]	= 0x070,
+	[TEMPADCENADDR]		= 0x074,
+	[TEMPADCVALIDADDR]	= 0x078,
+	[TEMPADCVOLTADDR]	= 0x07c,
+	[TEMPRDCTRL]		= 0x080,
+	[TEMPADCVALIDMASK]	= 0x084,
+	[TEMPADCVOLTAGESHIFT]	= 0x088,
+	[TEMPADCWRITECTRL]	= 0x08c,
+	[TEMPMSR0]		= 0x090,
+	[TEMPMSR1]		= 0x094,
+	[TEMPMSR2]		= 0x098,
+	[TEMPADCHADDR]		= 0x09c,
+	[TEMPIMMD0]		= 0x0a0,
+	[TEMPIMMD1]		= 0x0a4,
+	[TEMPIMMD2]		= 0x0a8,
+	[TEMPMONIDET3]		= 0x0b0,
+	[TEMPADCPNP3]		= 0x0b4,
+	[TEMPMSR3]		= 0x0b8,
+	[TEMPIMMD3]		= 0x0bc,
+	[TEMPPROTCTL]		= 0x0c0,
+	[TEMPPROTTA]		= 0x0c4,
+	[TEMPPROTTB]		= 0x0c8,
+	[TEMPPROTTC]		= 0x0cc,
+	[TEMPSPARE0]		= 0x0f0,
+	[TEMPSPARE1]		= 0x0f4,
+	[TEMPSPARE2]		= 0x0f8,
+	[TEMPSPARE3]		= 0x0fc,
+	[TEMPMSR0_1]		= 0x190,
+	[TEMPMSR1_1]		= 0x194,
+	[TEMPMSR2_1]		= 0x198,
+	[TEMPMSR3_1]		= 0x1b8,
+	[DESCHAR]		= 0xc00,
+	[TEMPCHAR]		= 0xc04,
+	[DETCHAR]		= 0xc08,
+	[AGECHAR]		= 0xc0c,
+	[DCCONFIG]		= 0xc10,
+	[AGECONFIG]		= 0xc14,
+	[FREQPCT30]		= 0xc18,
+	[FREQPCT74]		= 0xc1c,
+	[LIMITVALS]		= 0xc20,
+	[VBOOT]			= 0xc24,
+	[DETWINDOW]		= 0xc28,
+	[CONFIG]		= 0xc2c,
+	[TSCALCS]		= 0xc30,
+	[RUNCONFIG]		= 0xc34,
+	[SVSEN]			= 0xc38,
+	[INIT2VALS]		= 0xc3c,
+	[DCVALUES]		= 0xc40,
+	[AGEVALUES]		= 0xc44,
+	[VOP30]			= 0xc48,
+	[VOP74]			= 0xc4c,
+	[TEMP]			= 0xc50,
+	[INTSTS]		= 0xc54,
+	[INTSTSRAW]		= 0xc58,
+	[INTEN]			= 0xc5c,
+	[CHKINT]		= 0xc60,
+	[CHKSHIFT]		= 0xc64,
+	[STATUS]		= 0xc68,
+	[VDESIGN30]		= 0xc6c,
+	[VDESIGN74]		= 0xc70,
+	[DVT30]			= 0xc74,
+	[DVT74]			= 0xc78,
+	[AGECOUNT]		= 0xc7c,
+	[SMSTATE0]		= 0xc80,
+	[SMSTATE1]		= 0xc84,
+	[CTL0]			= 0xc88,
+	[DESDETSEC]		= 0xce0,
+	[TEMPAGESEC]		= 0xce4,
+	[CTRLSPARE0]		= 0xcf0,
+	[CTRLSPARE1]		= 0xcf4,
+	[CTRLSPARE2]		= 0xcf8,
+	[CTRLSPARE3]		= 0xcfc,
+	[CORESEL]		= 0xf00,
+	[THERMINTST]		= 0xf04,
+	[INTST]			= 0xf08,
+	[THSTAGE0ST]		= 0xf0c,
+	[THSTAGE1ST]		= 0xf10,
+	[THSTAGE2ST]		= 0xf14,
+	[THAHBST0]		= 0xf18,
+	[THAHBST1]		= 0xf1c,
+	[SPARE0]		= 0xf20,
+	[SPARE1]		= 0xf24,
+	[SPARE2]		= 0xf28,
+	[SPARE3]		= 0xf2c,
+	[THSLPEVEB]		= 0xf30,
+};
+
+struct thermal_parameter {
+	int adc_ge_t;
+	int adc_oe_t;
+	int ge;
+	int oe;
+	int gain;
+	int o_vtsabb;
+	int o_vtsmcu1;
+	int o_vtsmcu2;
+	int o_vtsmcu3;
+	int o_vtsmcu4;
+	int o_vtsmcu5;
+	int degc_cali;
+	int adc_cali_en_t;
+	int o_slope;
+	int o_slope_sign;
+	int ts_id;
+};
+
+struct svs_bank_ops {
+	void (*set_freqs_pct)(struct mtk_svs *svs);
+	void (*get_vops)(struct mtk_svs *svs);
+};
+
+struct svs_bank {
+	struct svs_bank_ops *ops;
+	struct completion init_completion;
+	struct device *dev;
+	struct regulator *buck;
+	struct mutex lock;	/* lock to protect update voltage process */
+	bool suspended;
+	bool mtcmos_request;
+	bool init01_support;
+	bool init02_support;
+	bool mon_mode_support;
+	s32 volt_offset;
+	u32 *opp_freqs;
+	u32 *freqs_pct;
+	u32 *opp_volts;
+	u32 *init02_volts;
+	u32 *volts;
+	u32 reg_data[3][reg_num];
+	u32 freq_base;
+	u32 vboot;
+	u32 volt_step;
+	u32 volt_base;
+	u32 init01_volt_flag;
+	u32 phase;
+	u32 vmax;
+	u32 vmin;
+	u32 bts;
+	u32 mts;
+	u32 bdes;
+	u32 mdes;
+	u32 mtdes;
+	u32 dcbdet;
+	u32 dcmdet;
+	u32 dthi;
+	u32 dtlo;
+	u32 det_window;
+	u32 det_max;
+	u32 age_config;
+	u32 age_voffset_in;
+	u32 agem;
+	u32 dc_config;
+	u32 dc_voffset_in;
+	u32 dvt_fixed;
+	u32 vco;
+	u32 chkshift;
+	u32 svs_temp;
+	u32 upper_temp_bound;
+	u32 lower_temp_bound;
+	u32 low_temp_threashold;
+	u32 low_temp_offset;
+	u32 coresel;
+	u32 opp_count;
+	u32 intst;
+	u32 systemclk_en;
+	u32 sw_id;
+	u32 hw_id;
+	u32 ctl0;
+	u8 *of_compatible;
+	u8 *name;
+	u8 *zone_name;
+	u8 *buck_name;
+};
+
+struct svs_platform {
+	struct svs_bank *banks;
+	int (*efuse_parsing)(struct mtk_svs *svs);
+	bool fake_efuse;
+	const u32 *regs;
+	u32 bank_num;
+	u32 efuse_num;
+	u32 efuse_check;
+	u32 thermal_efuse_num;
+	u8 *name;
+};
+
+struct mtk_svs {
+	const struct svs_platform *platform;
+	struct svs_bank *bank;
+	struct device *dev;
+	void __iomem *base;
+	struct clk *main_clk;
+	u32 *efuse;
+	u32 *thermal_efuse;
+};
+
+unsigned long claim_mtk_svs_lock(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&mtk_svs_lock, flags);
+
+	return flags;
+}
+EXPORT_SYMBOL_GPL(claim_mtk_svs_lock);
+
+void release_mtk_svs_lock(unsigned long flags)
+{
+	spin_unlock_irqrestore(&mtk_svs_lock, flags);
+}
+EXPORT_SYMBOL_GPL(release_mtk_svs_lock);
+
+static u32 percent(u32 numerator, u32 denominator)
+{
+	u32 percent;
+
+	/* If not divide 1000, "numerator * 100" would be data overflow. */
+	numerator /= 1000;
+	denominator /= 1000;
+	percent = ((numerator * 100) + denominator - 1) / denominator;
+
+	return percent;
+}
+
+static u32 svs_readl(struct mtk_svs *svs, enum reg_index i)
+{
+	return readl(svs->base + svs->platform->regs[i]);
+}
+
+static void svs_writel(struct mtk_svs *svs, u32 val, enum reg_index i)
+{
+	writel(val, svs->base + svs->platform->regs[i]);
+}
+
+static void svs_switch_bank(struct mtk_svs *svs)
+{
+	struct svs_bank *svsb = svs->bank;
+
+	svs_writel(svs, svsb->coresel, CORESEL);
+}
+
+static u32 svs_volt_to_opp_volt(u32 svsb_volt,
+				u32 svsb_volt_step, u32 svsb_volt_base)
+{
+	u32 u_volt;
+
+	u_volt = (svsb_volt * svsb_volt_step) + svsb_volt_base;
+
+	return u_volt;
+}
+
+static int svs_get_zone_temperature(struct svs_bank *svsb, int *zone_temp)
+{
+	struct thermal_zone_device *tzd;
+	int ret;
+
+	tzd = thermal_zone_get_zone_by_name(svsb->zone_name);
+	ret = thermal_zone_get_temp(tzd, zone_temp);
+
+	return ret;
+}
+
+static int svs_set_volts(struct svs_bank *svsb, bool force_update)
+{
+	u32 i, svsb_volt, opp_volt, low_temp_offset = 0;
+	int zone_temp, ret;
+
+	mutex_lock(&svsb->lock);
+
+	/* If bank is suspended, it means init02 voltage is applied.
+	 * Don't need to update opp voltage anymore.
+	 */
+	if (svsb->suspended && !force_update) {
+		pr_notice("%s: bank is suspended\n", svsb->name);
+		mutex_unlock(&svsb->lock);
+		return -EPERM;
+	}
+
+	/* get thermal effect */
+	if (svsb->phase == SVS_PHASE_MON) {
+		if (svsb->svs_temp > svsb->upper_temp_bound &&
+		    svsb->svs_temp < svsb->lower_temp_bound) {
+			pr_err("%s: svs_temp is abnormal (0x%x)?\n",
+			       svsb->name, svsb->svs_temp);
+			mutex_unlock(&svsb->lock);
+			return -EINVAL;
+		}
+
+		ret = svs_get_zone_temperature(svsb, &zone_temp);
+		if (ret) {
+			pr_err("%s: cannot get zone \"%s\" temperature\n",
+			       svsb->name, svsb->zone_name);
+			pr_err("%s: add low_temp_offset = %u\n",
+			       svsb->name, svsb->low_temp_offset);
+			zone_temp = svsb->low_temp_threashold;
+		}
+
+		if (zone_temp <= svsb->low_temp_threashold)
+			low_temp_offset = svsb->low_temp_offset;
+	}
+
+	/* vmin <= svsb_volt (opp_volt) <= signed-off voltage */
+	for (i = 0; i < svsb->opp_count; i++) {
+		if (svsb->phase == SVS_PHASE_MON) {
+			svsb_volt = max((svsb->volts[i] + svsb->volt_offset +
+					 low_temp_offset), svsb->vmin);
+			opp_volt = svs_volt_to_opp_volt(svsb_volt,
+							svsb->volt_step,
+							svsb->volt_base);
+		} else if (svsb->phase == SVS_PHASE_INIT02) {
+			svsb_volt = max((svsb->init02_volts[i] +
+					 svsb->volt_offset), svsb->vmin);
+			opp_volt = svs_volt_to_opp_volt(svsb_volt,
+							svsb->volt_step,
+							svsb->volt_base);
+		} else if (svsb->phase == SVS_PHASE_ERROR) {
+			opp_volt = svsb->opp_volts[i];
+		} else {
+			pr_err("%s: unknown phase: %u?\n",
+			       svsb->name, svsb->phase);
+			mutex_unlock(&svsb->lock);
+			return -EINVAL;
+		}
+
+		opp_volt = min(opp_volt, svsb->opp_volts[i]);
+		ret = dev_pm_opp_adjust_voltage(svsb->dev, svsb->opp_freqs[i],
+						opp_volt);
+		if (ret) {
+			pr_err("%s: set voltage failed: %d\n", svsb->name, ret);
+			mutex_unlock(&svsb->lock);
+			return ret;
+		}
+	}
+
+	mutex_unlock(&svsb->lock);
+
+	return 0;
+}
+
+static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx)
+{
+	u32 vy;
+
+	if (v0 == v1 || f0 == f1)
+		return v0;
+
+	/* *100 to have decimal fraction factor, +99 for rounding up. */
+	vy = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx));
+	vy = (vy + 99) / 100;
+
+	return vy;
+}
+
+static void svs_get_vops_v2(struct mtk_svs *svs)
+{
+	struct svs_bank *svsb = svs->bank;
+	u32 temp, i;
+
+	temp = svs_readl(svs, VOP30);
+	svsb->volts[6] = (temp >> 24) & 0xff;
+	svsb->volts[4] = (temp >> 16) & 0xff;
+	svsb->volts[2] = (temp >> 8)  & 0xff;
+	svsb->volts[0] = (temp & 0xff);
+
+	temp = svs_readl(svs, VOP74);
+	svsb->volts[14] = (temp >> 24) & 0xff;
+	svsb->volts[12] = (temp >> 16) & 0xff;
+	svsb->volts[10] = (temp >> 8)  & 0xff;
+	svsb->volts[8] = (temp & 0xff);
+
+	for (i = 0; i <= 7; i++) {
+		if (i < 7) {
+			svsb->volts[(i * 2) + 1] =
+				interpolate(svsb->freqs_pct[i * 2],
+					    svsb->freqs_pct[(i + 1) * 2],
+					    svsb->volts[i * 2],
+					    svsb->volts[(i + 1) * 2],
+					    svsb->freqs_pct[(i * 2) + 1]);
+		} else if (i == 7) {
+			svsb->volts[(i * 2) + 1] =
+				interpolate(svsb->freqs_pct[(i - 1) * 2],
+					    svsb->freqs_pct[i * 2],
+					    svsb->volts[(i - 1) * 2],
+					    svsb->volts[i * 2],
+					    svsb->freqs_pct[(i * 2) + 1]);
+		}
+	}
+}
+
+static void svs_set_freqs_pct_v2(struct mtk_svs *svs)
+{
+	struct svs_bank *svsb = svs->bank;
+
+	svs_writel(svs,
+		   ((svsb->freqs_pct[6] << 24) & 0xff000000) |
+		   ((svsb->freqs_pct[4] << 16) & 0xff0000) |
+		   ((svsb->freqs_pct[2] << 8) & 0xff00) |
+		   (svsb->freqs_pct[0] & 0xff),
+		   FREQPCT30);
+	svs_writel(svs,
+		   ((svsb->freqs_pct[14] << 24) & 0xff000000) |
+		   ((svsb->freqs_pct[12] << 16) & 0xff0000) |
+		   ((svsb->freqs_pct[10] << 8) & 0xff00) |
+		   ((svsb->freqs_pct[8]) & 0xff),
+		   FREQPCT74);
+}
+
+static void svs_set_phase(struct mtk_svs *svs, u32 target_phase)
+{
+	struct svs_bank *svsb = svs->bank;
+	u32 des_char, temp_char, det_char, limit_vals;
+	u32 init2vals, ts_calcs, val, filter, i;
+
+	svs_switch_bank(svs);
+
+	des_char = ((svsb->bdes << 8) & 0xff00) | (svsb->mdes & 0xff);
+	svs_writel(svs, des_char, DESCHAR);
+
+	temp_char = ((svsb->vco << 16) & 0xff0000) |
+		    ((svsb->mtdes << 8) & 0xff00) |
+		    (svsb->dvt_fixed & 0xff);
+	svs_writel(svs, temp_char, TEMPCHAR);
+
+	det_char = ((svsb->dcbdet << 8) & 0xff00) | (svsb->dcmdet & 0xff);
+	svs_writel(svs, det_char, DETCHAR);
+
+	svs_writel(svs, svsb->dc_config, DCCONFIG);
+	svs_writel(svs, svsb->age_config, AGECONFIG);
+
+	if (svsb->agem == 0x0) {
+		svs_writel(svs, 0x80000000, RUNCONFIG);
+	} else {
+		val = 0x0;
+
+		for (i = 0; i < 24; i += 2) {
+			filter = 0x3 << i;
+
+			if ((svsb->age_config & filter) == 0x0)
+				val |= (0x1 << i);
+			else
+				val |= (svsb->age_config & filter);
+		}
+		svs_writel(svs, val, RUNCONFIG);
+	}
+
+	svsb->ops->set_freqs_pct(svs);
+
+	limit_vals = ((svsb->vmax << 24) & 0xff000000) |
+		     ((svsb->vmin << 16) & 0xff0000) |
+		     ((svsb->dthi << 8) & 0xff00) |
+		     (svsb->dtlo & 0xff);
+	svs_writel(svs, limit_vals, LIMITVALS);
+	svs_writel(svs, (svsb->vboot & 0xff), VBOOT);
+	svs_writel(svs, (svsb->det_window & 0xffff), DETWINDOW);
+	svs_writel(svs, (svsb->det_max & 0xffff), CONFIG);
+
+	if (svsb->chkshift != 0)
+		svs_writel(svs, (svsb->chkshift & 0xff), CHKSHIFT);
+
+	if (svsb->ctl0 != 0)
+		svs_writel(svs, svsb->ctl0, CTL0);
+
+	svs_writel(svs, 0x00ffffff, INTSTS);
+
+	switch (target_phase) {
+	case SVS_PHASE_INIT01:
+		svs_writel(svs, 0x00005f01, INTEN);
+		svs_writel(svs, 0x00000001, SVSEN);
+		break;
+	case SVS_PHASE_INIT02:
+		svs_writel(svs, 0x00005f01, INTEN);
+		init2vals = ((svsb->age_voffset_in << 16) & 0xffff0000) |
+			    (svsb->dc_voffset_in & 0xffff);
+		svs_writel(svs, init2vals, INIT2VALS);
+		svs_writel(svs, 0x00000005, SVSEN);
+		break;
+	case SVS_PHASE_MON:
+		ts_calcs = ((svsb->bts << 12) & 0xfff000) | (svsb->mts & 0xfff);
+		svs_writel(svs, ts_calcs, TSCALCS);
+		svs_writel(svs, 0x00FF0000, INTEN);
+		svs_writel(svs, 0x00000002, SVSEN);
+		break;
+	default:
+		WARN_ON(1);
+		break;
+	}
+}
+
+static inline void svs_init01_isr_handler(struct mtk_svs *svs)
+{
+	struct svs_bank *svsb = svs->bank;
+	enum reg_index rg_i;
+
+	pr_notice("%s: %s: VDN74:0x%08x, VDN30:0x%08x, DCVALUES:0x%08x\n",
+		  svsb->name, __func__, svs_readl(svs, VDESIGN74),
+		  svs_readl(svs, VDESIGN30), svs_readl(svs, DCVALUES));
+
+	for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+		svsb->reg_data[SVS_PHASE_INIT01][rg_i] = svs_readl(svs, rg_i);
+
+	svsb->dc_voffset_in = ~(svs_readl(svs, DCVALUES) & 0xffff) + 1;
+	if (svsb->init01_volt_flag == SVS_INIT01_VOLT_IGNORE)
+		svsb->dc_voffset_in = 0;
+	else if ((svsb->dc_voffset_in & 0x8000) &&
+		 (svsb->init01_volt_flag == SVS_INIT01_VOLT_INC_ONLY))
+		svsb->dc_voffset_in = 0;
+
+	svsb->age_voffset_in = svs_readl(svs, AGEVALUES) & 0xffff;
+
+	svs_writel(svs, 0x0, SVSEN);
+	svs_writel(svs, 0x1, INTSTS);
+
+	/* svs init01 clock gating */
+	svsb->coresel &= ~svsb->systemclk_en;
+
+	svsb->phase = SVS_PHASE_INIT01;
+	complete(&svsb->init_completion);
+}
+
+static inline void svs_init02_isr_handler(struct mtk_svs *svs)
+{
+	struct svs_bank *svsb = svs->bank;
+	enum reg_index rg_i;
+
+	pr_notice("%s: %s: VOP74:0x%08x, VOP30:0x%08x, DCVALUES:0x%08x\n",
+		  svsb->name, __func__, svs_readl(svs, VOP74),
+		  svs_readl(svs, VOP30), svs_readl(svs, DCVALUES));
+
+	for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+		svsb->reg_data[SVS_PHASE_INIT02][rg_i] = svs_readl(svs, rg_i);
+
+	svsb->ops->get_vops(svs);
+	memcpy(svsb->init02_volts, svsb->volts, 4 * svsb->opp_count);
+	svsb->phase = SVS_PHASE_INIT02;
+
+	svs_writel(svs, 0x0, SVSEN);
+	svs_writel(svs, 0x1, INTSTS);
+
+	complete(&svsb->init_completion);
+}
+
+static inline void svs_mon_mode_isr_handler(struct mtk_svs *svs)
+{
+	struct svs_bank *svsb = svs->bank;
+	enum reg_index rg_i;
+
+	for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+		svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+	svsb->svs_temp = svs_readl(svs, TEMP) & 0xff;
+
+	svsb->ops->get_vops(svs);
+	svsb->phase = SVS_PHASE_MON;
+
+	svs_writel(svs, 0x00ff0000, INTSTS);
+}
+
+static inline void svs_error_isr_handler(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb = svs->bank;
+	enum reg_index rg_i;
+
+	pr_err("%s(): %s(%s)", __func__, svsp->name, svsb->name);
+	pr_err("CORESEL(0x%x) = 0x%08x\n",
+	       svsp->regs[CORESEL], svs_readl(svs, CORESEL)),
+	pr_err("SVSEN(0x%x) = 0x%08x, INTSTS(0x%x) = 0x%08x\n",
+	       svsp->regs[SVSEN], svs_readl(svs, SVSEN),
+	       svsp->regs[INTSTS], svs_readl(svs, INTSTS));
+	pr_err("SMSTATE0(0x%x) = 0x%08x, SMSTATE1(0x%x) = 0x%08x\n",
+	       svsp->regs[SMSTATE0], svs_readl(svs, SMSTATE0),
+	       svsp->regs[SMSTATE1], svs_readl(svs, SMSTATE1));
+
+	for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+		svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+	svsb->init01_support = false;
+	svsb->init02_support = false;
+	svsb->mon_mode_support = false;
+
+	if (svsb->phase == SVS_PHASE_MON)
+		svsb->phase = SVS_PHASE_INIT02;
+
+	svs_writel(svs, 0x0, SVSEN);
+	svs_writel(svs, 0x00ffffff, INTSTS);
+}
+
+static inline void svs_isr_handler(struct mtk_svs *svs)
+{
+	u32 intsts, svsen;
+
+	svs_switch_bank(svs);
+
+	intsts = svs_readl(svs, INTSTS);
+	svsen = svs_readl(svs, SVSEN);
+
+	if (intsts == 0x1 && ((svsen & 0x7) == 0x1))
+		svs_init01_isr_handler(svs);
+	else if ((intsts == 0x1) && ((svsen & 0x7) == 0x5))
+		svs_init02_isr_handler(svs);
+	else if ((intsts & 0x00ff0000) != 0x0)
+		svs_mon_mode_isr_handler(svs);
+	else
+		svs_error_isr_handler(svs);
+}
+
+static irqreturn_t svs_isr(int irq, void *data)
+{
+	struct mtk_svs *svs = (struct mtk_svs *)data;
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb = NULL;
+	unsigned long flags;
+	u32 idx;
+
+	flags = claim_mtk_svs_lock();
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svs->bank = svsb;
+
+		if (svsb->suspended)
+			continue;
+		else if (svsb->intst & svs_readl(svs, INTST))
+			continue;
+
+		svs_isr_handler(svs);
+		break;
+	}
+	release_mtk_svs_lock(flags);
+
+	if (svsb->phase != SVS_PHASE_INIT01)
+		svs_set_volts(svsb, false);
+
+	return IRQ_HANDLED;
+}
+
+static void svs_mon_mode(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	unsigned long flags;
+	u32 idx;
+
+	flags = claim_mtk_svs_lock();
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svs->bank = svsb;
+
+		if (!svsb->mon_mode_support)
+			continue;
+
+		svs_set_phase(svs, SVS_PHASE_MON);
+	}
+	release_mtk_svs_lock(flags);
+}
+
+static int svs_init02(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	unsigned long flags, time_left;
+	u32 idx;
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svs->bank = svsb;
+
+		if (!svsb->init02_support)
+			continue;
+
+		reinit_completion(&svsb->init_completion);
+		flags = claim_mtk_svs_lock();
+		svs_set_phase(svs, SVS_PHASE_INIT02);
+		release_mtk_svs_lock(flags);
+		time_left =
+			wait_for_completion_timeout(&svsb->init_completion,
+						    msecs_to_jiffies(2000));
+		if (time_left == 0) {
+			pr_err("%s: init02 completion timeout\n", svsb->name);
+			return -EBUSY;
+		}
+	}
+
+	return 0;
+}
+
+static int svs_init01(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	struct pm_qos_request qos_request = { {0} };
+	unsigned long flags, time_left;
+	bool search_done;
+	int ret = -EINVAL;
+	u32 opp_freqs, opp_vboot, buck_volt, idx, i;
+
+	/* Let CPUs leave idle-off state for initializing svs_init01. */
+	pm_qos_add_request(&qos_request, PM_QOS_CPU_DMA_LATENCY, 0);
+
+	/* Sometimes two svs_bank use the same buck.
+	 * Therefore, we set each svs_bank to vboot voltage first.
+	 */
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		search_done = false;
+
+		if (!svsb->init01_support)
+			continue;
+
+		ret = regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST);
+		if (ret)
+			pr_notice("%s: fail to set fast mode: %d\n",
+				  svsb->name, ret);
+
+		if (svsb->mtcmos_request) {
+			ret = regulator_enable(svsb->buck);
+			if (ret) {
+				pr_err("%s: fail to enable %s power: %d\n",
+				       svsb->name, svsb->buck_name, ret);
+				goto init01_finish;
+			}
+
+			ret = dev_pm_domain_attach(svsb->dev, false);
+			if (ret) {
+				pr_err("%s: attach pm domain fail: %d\n",
+				       svsb->name, ret);
+				goto init01_finish;
+			}
+
+			pm_runtime_enable(svsb->dev);
+			ret = pm_runtime_get_sync(svsb->dev);
+			if (ret < 0) {
+				pr_err("%s: turn mtcmos on fail: %d\n",
+				       svsb->name, ret);
+				goto init01_finish;
+			}
+		}
+
+		/* Find the fastest freq that can be run at vboot and
+		 * fix to that freq until svs_init01 is done.
+		 */
+		opp_vboot = svs_volt_to_opp_volt(svsb->vboot,
+						 svsb->volt_step,
+						 svsb->volt_base);
+
+		for (i = 0; i < svsb->opp_count; i++) {
+			opp_freqs = svsb->opp_freqs[i];
+			if (!search_done && svsb->opp_volts[i] <= opp_vboot) {
+				ret = dev_pm_opp_adjust_voltage(svsb->dev,
+								opp_freqs,
+								opp_vboot);
+				if (ret) {
+					pr_err("%s: set voltage failed: %d\n",
+					       svsb->name, ret);
+					goto init01_finish;
+				}
+
+				search_done = true;
+			} else {
+				dev_pm_opp_disable(svsb->dev,
+						   svsb->opp_freqs[i]);
+			}
+		}
+	}
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svs->bank = svsb;
+
+		if (!svsb->init01_support)
+			continue;
+
+		opp_vboot = svs_volt_to_opp_volt(svsb->vboot,
+						 svsb->volt_step,
+						 svsb->volt_base);
+
+		buck_volt = regulator_get_voltage(svsb->buck);
+		if (buck_volt != opp_vboot) {
+			pr_err("%s: buck voltage: %u, expected vboot: %u\n",
+			       svsb->name, buck_volt, opp_vboot);
+			ret = -EPERM;
+			goto init01_finish;
+		}
+
+		init_completion(&svsb->init_completion);
+		flags = claim_mtk_svs_lock();
+		svs_set_phase(svs, SVS_PHASE_INIT01);
+		release_mtk_svs_lock(flags);
+		time_left =
+			wait_for_completion_timeout(&svsb->init_completion,
+						    msecs_to_jiffies(2000));
+		if (time_left == 0) {
+			pr_err("%s: init01 completion timeout\n", svsb->name);
+			ret = -EBUSY;
+			goto init01_finish;
+		}
+	}
+
+init01_finish:
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+
+		if (!svsb->init01_support)
+			continue;
+
+		for (i = 0; i < svsb->opp_count; i++)
+			dev_pm_opp_enable(svsb->dev, svsb->opp_freqs[i]);
+
+		if (regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL))
+			pr_notice("%s: fail to set normal mode: %d\n",
+				  svsb->name, ret);
+
+		if (svsb->mtcmos_request) {
+			if (pm_runtime_put_sync(svsb->dev))
+				pr_err("%s: turn mtcmos off fail: %d\n",
+				       svsb->name, ret);
+			pm_runtime_disable(svsb->dev);
+			dev_pm_domain_detach(svsb->dev, 0);
+			if (regulator_disable(svsb->buck))
+				pr_err("%s: fail to disable %s power: %d\n",
+				       svsb->name, svsb->buck_name, ret);
+		}
+	}
+
+	pm_qos_remove_request(&qos_request);
+
+	return ret;
+}
+
+static int svs_start(struct mtk_svs *svs)
+{
+	int ret;
+
+	ret = svs_init01(svs);
+	if (ret)
+		return ret;
+
+	ret = svs_init02(svs);
+	if (ret)
+		return ret;
+
+	svs_mon_mode(svs);
+
+	return ret;
+}
+
+static int svs_mt8183_efuse_parsing(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct thermal_parameter tp;
+	struct svs_bank *svsb;
+	bool mon_mode_support = true;
+	int format[6], x_roomt[6], tb_roomt;
+	u32 idx, i, ft_pgm, mts, temp0, temp1, temp2;
+
+	if (svsp->fake_efuse) {
+		pr_notice("fake efuse\n");
+		svs->efuse[0] = 0x00310080;
+		svs->efuse[1] = 0xabfbf757;
+		svs->efuse[2] = 0x47c747c7;
+		svs->efuse[3] = 0xabfbf757;
+		svs->efuse[4] = 0xe7fca0ec;
+		svs->efuse[5] = 0x47bf4b88;
+		svs->efuse[6] = 0xabfb8fa5;
+		svs->efuse[7] = 0xabfb217b;
+		svs->efuse[8] = 0x4bf34be1;
+		svs->efuse[9] = 0xabfb670d;
+		svs->efuse[16] = 0xabfbc653;
+		svs->efuse[17] = 0x47f347e1;
+		svs->efuse[18] = 0xabfbd848;
+
+		svs->thermal_efuse[0] = 0x02873f69;
+		svs->thermal_efuse[1] = 0xa11d9142;
+		svs->thermal_efuse[2] = 0xa2526900;
+	}
+
+	/* svs efuse parsing */
+	ft_pgm = (svs->efuse[0] >> 4) & 0xf;
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		if (ft_pgm <= 1)
+			svsb->init01_volt_flag = SVS_INIT01_VOLT_IGNORE;
+
+		switch (svsb->sw_id) {
+		case SVS_CPU_LITTLE:
+			svsb->bdes = svs->efuse[16] & 0xff;
+			svsb->mdes = (svs->efuse[16] >> 8) & 0xff;
+			svsb->dcbdet = (svs->efuse[16] >> 16) & 0xff;
+			svsb->dcmdet = (svs->efuse[16] >> 24) & 0xff;
+			svsb->mtdes  = (svs->efuse[17] >> 16) & 0xff;
+
+			if (ft_pgm <= 3)
+				svsb->volt_offset += 10;
+			else
+				svsb->volt_offset += 2;
+			break;
+		case SVS_CPU_BIG:
+			svsb->bdes = svs->efuse[18] & 0xff;
+			svsb->mdes = (svs->efuse[18] >> 8) & 0xff;
+			svsb->dcbdet = (svs->efuse[18] >> 16) & 0xff;
+			svsb->dcmdet = (svs->efuse[18] >> 24) & 0xff;
+			svsb->mtdes  = svs->efuse[17] & 0xff;
+
+			if (ft_pgm <= 3)
+				svsb->volt_offset += 15;
+			else
+				svsb->volt_offset += 12;
+			break;
+		case SVS_CCI:
+			svsb->bdes = svs->efuse[4] & 0xff;
+			svsb->mdes = (svs->efuse[4] >> 8) & 0xff;
+			svsb->dcbdet = (svs->efuse[4] >> 16) & 0xff;
+			svsb->dcmdet = (svs->efuse[4] >> 24) & 0xff;
+			svsb->mtdes  = (svs->efuse[5] >> 16) & 0xff;
+
+			if (ft_pgm <= 3)
+				svsb->volt_offset += 10;
+			else
+				svsb->volt_offset += 2;
+			break;
+		case SVS_GPU:
+			svsb->bdes = svs->efuse[6] & 0xff;
+			svsb->mdes = (svs->efuse[6] >> 8) & 0xff;
+			svsb->dcbdet = (svs->efuse[6] >> 16) & 0xff;
+			svsb->dcmdet = (svs->efuse[6] >> 24) & 0xff;
+			svsb->mtdes  = svs->efuse[5] & 0xff;
+
+			if (ft_pgm >= 2) {
+				svsb->freq_base = 800000000; /* 800MHz */
+				svsb->dvt_fixed = 2;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	for (i = 0; i < svsp->efuse_num; i++) {
+		if (svs->efuse[i])
+			pr_notice("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]);
+	}
+
+	/* thermal efuse parsing */
+	if (!svs->thermal_efuse)
+		return 0;
+
+	tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & 0x3ff;
+	tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & 0x3ff;
+
+	tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & 0x1ff;
+	tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & 0x1ff;
+	tp.o_vtsmcu3 = svs->thermal_efuse[1] & 0x1ff;
+	tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & 0x1ff;
+	tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & 0x1ff;
+	tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & 0x1ff;
+
+	tp.degc_cali = (svs->thermal_efuse[0] >> 1) & 0x3f;
+	tp.adc_cali_en_t = svs->thermal_efuse[0] & BIT(0);
+	tp.o_slope_sign = (svs->thermal_efuse[0] >> 7) & BIT(0);
+
+	tp.ts_id = (svs->thermal_efuse[1] >> 9) & BIT(0);
+	tp.o_slope = (svs->thermal_efuse[0] >> 26) & 0x3f;
+
+	if (tp.adc_cali_en_t == 1) {
+		if (tp.ts_id == 0)
+			tp.o_slope = 0;
+
+		if ((tp.adc_ge_t < 265 || tp.adc_ge_t > 758) ||
+		    (tp.adc_oe_t < 265 || tp.adc_oe_t > 758) ||
+		    (tp.o_vtsmcu1 < -8 || tp.o_vtsmcu1 > 484) ||
+		    (tp.o_vtsmcu2 < -8 || tp.o_vtsmcu2 > 484) ||
+		    (tp.o_vtsmcu3 < -8 || tp.o_vtsmcu3 > 484) ||
+		    (tp.o_vtsmcu4 < -8 || tp.o_vtsmcu4 > 484) ||
+		    (tp.o_vtsmcu5 < -8 || tp.o_vtsmcu5 > 484) ||
+		    (tp.o_vtsabb < -8 || tp.o_vtsabb > 484) ||
+		    (tp.degc_cali < 1 || tp.degc_cali > 63)) {
+			pr_err("bad thermal efuse data. disable mon mode\n");
+			mon_mode_support = false;
+		}
+	} else {
+		pr_err("no thermal efuse data. disable mon mode\n");
+		mon_mode_support = false;
+	}
+
+	if (!mon_mode_support) {
+		for (i = 0; i < svsp->thermal_efuse_num; i++)
+			pr_err("thermal_efuse[%u] = 0x%08x\n",
+			       i, svs->thermal_efuse[i]);
+
+		for (idx = 0; idx < svsp->bank_num; idx++) {
+			svsb = &svsp->banks[idx];
+			svsb->mon_mode_support = false;
+		}
+
+		return 0;
+	}
+
+	tp.ge = ((tp.adc_ge_t - 512) * 10000) / 4096;
+	tp.oe = (tp.adc_oe_t - 512);
+	tp.gain = (10000 + tp.ge);
+
+	format[0] = (tp.o_vtsmcu1 + 3350 - tp.oe);
+	format[1] = (tp.o_vtsmcu2 + 3350 - tp.oe);
+	format[2] = (tp.o_vtsmcu3 + 3350 - tp.oe);
+	format[3] = (tp.o_vtsmcu4 + 3350 - tp.oe);
+	format[4] = (tp.o_vtsmcu5 + 3350 - tp.oe);
+	format[5] = (tp.o_vtsabb + 3350 - tp.oe);
+
+	for (i = 0; i < 6; i++)
+		x_roomt[i] = (((format[i] * 10000) / 4096) * 10000) / tp.gain;
+
+	temp0 = (10000 * 100000 / tp.gain) * 15 / 18;
+
+	if (tp.o_slope_sign == 0)
+		mts = (temp0 * 10) / (1534 + tp.o_slope * 10);
+	else
+		mts = (temp0 * 10) / (1534 - tp.o_slope * 10);
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svsb->mts = mts;
+
+		switch (svsb->sw_id) {
+		case SVS_CPU_LITTLE:
+			tb_roomt = x_roomt[3];
+			break;
+		case SVS_CPU_BIG:
+			tb_roomt = x_roomt[4];
+			break;
+		case SVS_CCI:
+			tb_roomt = x_roomt[3];
+			break;
+		case SVS_GPU:
+			tb_roomt = x_roomt[1];
+			break;
+		default:
+			pr_err("unknown svsb_id = %u? disable svs\n",
+			       svsb->sw_id);
+			return -EINVAL;
+		}
+
+		temp0 = (tp.degc_cali * 10 / 2);
+		temp1 = ((10000 * 100000 / 4096 / tp.gain) *
+			 tp.oe + tb_roomt * 10) * 15 / 18;
+
+		if (tp.o_slope_sign == 0)
+			temp2 = temp1 * 100 / (1534 + tp.o_slope * 10);
+		else
+			temp2 = temp1 * 100 / (1534 - tp.o_slope * 10);
+
+		svsb->bts = (temp0 + temp2 - 250) * 4 / 10;
+	}
+
+	return 0;
+}
+
+static int svs_is_support(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	struct nvmem_cell *cell;
+	size_t len;
+	int ret;
+	u32 idx, i;
+
+	if (svsp->fake_efuse) {
+		len = svsp->efuse_num * 4;
+		svs->efuse = kzalloc(len, GFP_KERNEL);
+		if (!svs->efuse)
+			return -ENOMEM;
+
+		len = svsp->thermal_efuse_num * 4;
+		svs->thermal_efuse = kzalloc(len, GFP_KERNEL);
+		if (!svs->thermal_efuse)
+			return -ENOMEM;
+
+		goto svsp_efuse_parsing;
+	}
+
+	/* get svs efuse by nvmem */
+	cell = nvmem_cell_get(svs->dev, "svs-calibration-data");
+	if (IS_ERR(cell)) {
+		pr_err("no \"svs-calibration-data\" from dts? disable svs\n");
+		return PTR_ERR(cell);
+	}
+
+	svs->efuse = (u32 *)nvmem_cell_read(cell, &len);
+	nvmem_cell_put(cell);
+
+	ret = (svs->efuse[svsp->efuse_check] == 0) ? -EPERM : 0;
+	if (ret) {
+		pr_err("no svs efuse. disable svs.\n");
+		for (i = 0; i < svsp->efuse_num; i++)
+			pr_err("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]);
+		return ret;
+	}
+
+	/* get thermal efuse by nvmem */
+	cell = nvmem_cell_get(svs->dev, "calibration-data");
+	if (IS_ERR(cell)) {
+		pr_err("no \"calibration-data\" from dts? disable mon mode\n");
+		svs->thermal_efuse = NULL;
+		for (idx = 0; idx < svsp->bank_num; idx++) {
+			svsb = &svsp->banks[idx];
+			svsb->mon_mode_support = false;
+		}
+		goto svsp_efuse_parsing;
+	}
+
+	svs->thermal_efuse = (u32 *)nvmem_cell_read(cell, &len);
+	nvmem_cell_put(cell);
+
+svsp_efuse_parsing:
+	ret = svsp->efuse_parsing(svs);
+
+	return ret;
+}
+
+static int svs_resource_setup(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	struct platform_device *pdev;
+	struct device_node *np = NULL;
+	struct dev_pm_opp *opp;
+	unsigned long freq;
+	int count, ret;
+	u32 idx, i;
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+
+		if (!svsb->init01_support)
+			continue;
+
+		switch (svsb->sw_id) {
+		case SVS_CPU_LITTLE:
+			svsb->name = "SVS_CPU_LITTLE";
+			break;
+		case SVS_CPU_BIG:
+			svsb->name = "SVS_CPU_BIG";
+			break;
+		case SVS_CCI:
+			svsb->name = "SVS_CCI";
+			break;
+		case SVS_GPU:
+			svsb->name = "SVS_GPU";
+			break;
+		default:
+			WARN_ON(1);
+			return -EINVAL;
+		}
+
+		/* Add svs_bank device for opp-table/mtcmos/buck control */
+		pdev = platform_device_alloc(svsb->name, 0);
+		if (!pdev) {
+			pr_err("%s: fail to alloc pdev for svs_bank\n",
+			       svsb->name);
+			return -ENOMEM;
+		}
+
+		for_each_child_of_node(svs->dev->of_node, np) {
+			if (of_device_is_compatible(np, svsb->of_compatible)) {
+				pdev->dev.of_node = np;
+				break;
+			}
+		}
+
+		ret = platform_device_add(pdev);
+		if (ret) {
+			pr_err("%s: fail to add svs_bank device: %d\n",
+			       svsb->name, ret);
+			return ret;
+		}
+
+		svsb->dev = &pdev->dev;
+		dev_set_drvdata(svsb->dev, svs);
+		ret = dev_pm_opp_of_add_table(svsb->dev);
+		if (ret) {
+			pr_err("%s: fail to add opp table: %d\n",
+			       svsb->name, ret);
+			return ret;
+		}
+
+		mutex_init(&svsb->lock);
+
+		svsb->buck = devm_regulator_get_optional(svsb->dev,
+							 svsb->buck_name);
+		if (IS_ERR(svsb->buck)) {
+			pr_err("%s: cannot get regulator \"%s-supply\"\n",
+			       svsb->name, svsb->buck_name);
+			return PTR_ERR(svsb->buck);
+		}
+
+		count = dev_pm_opp_get_opp_count(svsb->dev);
+		if (svsb->opp_count != count) {
+			pr_err("%s: opp_count not \"%u\" but get \"%d\"?\n",
+			       svsb->name, svsb->opp_count, count);
+			return count;
+		}
+
+		svsb->opp_volts = kmalloc(4 * svsb->opp_count,
+					  GFP_KERNEL);
+		if (IS_ERR(svsb->opp_volts)) {
+			pr_err("%s: fail kmalloc %u(bytes) to opp_volts\n",
+			       svsb->name, (4 * svsb->opp_count));
+			return PTR_ERR(svsb->opp_volts);
+		}
+
+		svsb->init02_volts = kmalloc(4 * svsb->opp_count,
+					     GFP_KERNEL);
+		if (IS_ERR(svsb->init02_volts)) {
+			pr_err("%s: fail kmalloc %u(bytes) to init02_volts\n",
+			       svsb->name, (4 * svsb->opp_count));
+			return PTR_ERR(svsb->init02_volts);
+		}
+
+		svsb->volts = kmalloc(4 * svsb->opp_count,
+				      GFP_KERNEL);
+		if (IS_ERR(svsb->volts)) {
+			pr_err("%s: fail kmalloc %u(bytes) to volts\n",
+			       svsb->name, (4 * svsb->opp_count));
+			return PTR_ERR(svsb->volts);
+		}
+
+		svsb->opp_freqs = kmalloc(4 * svsb->opp_count,
+					  GFP_KERNEL);
+		if (IS_ERR(svsb->opp_freqs)) {
+			pr_err("%s: fail kmalloc %u(bytes) to opp_freqs\n",
+			       svsb->name, (4 * svsb->opp_count));
+			return PTR_ERR(svsb->opp_freqs);
+		}
+
+		svsb->freqs_pct = kmalloc(4 * svsb->opp_count,
+					  GFP_KERNEL);
+		if (IS_ERR(svsb->freqs_pct)) {
+			pr_err("%s: fail kmalloc %u(bytes) to freqs_pct\n",
+			       svsb->name, (4 * svsb->opp_count));
+			return PTR_ERR(svsb->freqs_pct);
+		}
+
+		for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
+			opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq);
+			if (IS_ERR(opp)) {
+				pr_err("%s: error opp entry!!, err = %ld\n",
+				       svsb->name, PTR_ERR(opp));
+				return PTR_ERR(opp);
+			}
+
+			svsb->opp_freqs[i] = freq;
+			svsb->opp_volts[i] = dev_pm_opp_get_voltage(opp);
+			svsb->freqs_pct[i] = percent(svsb->opp_freqs[i],
+						     svsb->freq_base) & 0xff;
+		}
+	}
+
+	return 0;
+}
+
+static int svs_suspend(struct device *dev)
+{
+	struct mtk_svs *svs = dev_get_drvdata(dev);
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	unsigned long flags;
+	u32 idx;
+
+	/* Wait if there is processing svs_isr(). Suspend all banks. */
+	flags = claim_mtk_svs_lock();
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svs->bank = svsb;
+		svs_switch_bank(svs);
+		svs_writel(svs, 0x0, SVSEN);
+		svs_writel(svs, 0x00ffffff, INTSTS);
+		svsb->suspended = true;
+	}
+	release_mtk_svs_lock(flags);
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		if (svsb->phase == SVS_PHASE_MON) {
+			svsb->phase = SVS_PHASE_INIT02;
+			svs_set_volts(svsb, true);
+		}
+	}
+
+	clk_disable_unprepare(svs->main_clk);
+
+	return 0;
+}
+
+static int svs_resume(struct device *dev)
+{
+	struct mtk_svs *svs = dev_get_drvdata(dev);
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	int ret;
+	u32 idx;
+
+	ret = clk_prepare_enable(svs->main_clk);
+	if (ret)
+		pr_err("%s(): cannot enable main_clk\n", __func__);
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+		svsb->suspended = false;
+	}
+
+	ret = svs_init02(svs);
+	if (ret)
+		return ret;
+
+	svs_mon_mode(svs);
+
+	return 0;
+}
+
+static int svs_debug_proc_show(struct seq_file *m, void *v)
+{
+	struct svs_bank *svsb = (struct svs_bank *)m->private;
+
+	if (svsb->phase == SVS_PHASE_INIT01)
+		seq_puts(m, "init01\n");
+	else if (svsb->phase == SVS_PHASE_INIT02)
+		seq_puts(m, "init02\n");
+	else if (svsb->phase == SVS_PHASE_MON)
+		seq_puts(m, "mon mode\n");
+	else if (svsb->phase == SVS_PHASE_ERROR)
+		seq_puts(m, "disabled\n");
+	else
+		seq_puts(m, "unknown\n");
+
+	return 0;
+}
+
+static ssize_t svs_debug_proc_write(struct file *file,
+				    const char __user *buffer,
+				    size_t count, loff_t *pos)
+{
+	struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file));
+	struct mtk_svs *svs = dev_get_drvdata(svsb->dev);
+	char *buf = (char *)__get_free_page(GFP_USER);
+	unsigned long flags;
+	int enabled, ret;
+
+	if (svsb->phase == SVS_PHASE_ERROR)
+		return count;
+
+	if (!buf)
+		return -ENOMEM;
+
+	if (count >= PAGE_SIZE) {
+		free_page((unsigned long)buf);
+		return -EINVAL;
+	}
+
+	if (copy_from_user(buf, buffer, count)) {
+		free_page((unsigned long)buf);
+		return -EFAULT;
+	}
+
+	buf[count] = '\0';
+
+	ret = kstrtoint(buf, 10, &enabled);
+	if (ret)
+		return ret;
+
+	if (!enabled) {
+		flags = claim_mtk_svs_lock();
+		svs->bank = svsb;
+
+		svsb->init01_support = false;
+		svsb->init02_support = false;
+		svsb->mon_mode_support = false;
+
+		svs_switch_bank(svs);
+		svs_writel(svs, 0x0, SVSEN);
+		svs_writel(svs, 0x00ffffff, INTSTS);
+		release_mtk_svs_lock(flags);
+	}
+
+	svsb->phase = SVS_PHASE_ERROR;
+	svs_set_volts(svsb, true);
+
+	return count;
+}
+
+proc_fops_rw(svs_debug);
+
+static int svs_dump_proc_show(struct seq_file *m, void *v)
+{
+	struct mtk_svs *svs = (struct mtk_svs *)m->private;
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	unsigned long svs_reg_addr;
+	u32 idx, i, j;
+
+	for (i = 0; i < svsp->efuse_num; i++) {
+		if (svs->efuse[i])
+			seq_printf(m, "M_HW_RES%d = 0x%08x\n",
+				   i, svs->efuse[i]);
+	}
+
+	for (i = 0; i < svsp->thermal_efuse_num; i++) {
+		if (svs->thermal_efuse[i])
+			seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n",
+				   i, svs->thermal_efuse[i]);
+	}
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+
+		if (!svsb->init01_support)
+			continue;
+
+		for (i = SVS_PHASE_INIT01; i <= SVS_PHASE_MON; i++) {
+			seq_printf(m, "bank number = %u\n", svsb->hw_id);
+
+			if (i < SVS_PHASE_MON)
+				seq_printf(m, "mode = init0%d\n", i + 1);
+			else
+				seq_puts(m, "mode = mon\n");
+
+			for (j = TEMPMONCTL0; j < reg_num; j++) {
+				svs_reg_addr = (unsigned long)(svs->base +
+							       svsp->regs[j]);
+				seq_printf(m, "0x%08lx = 0x%08x\n",
+					   svs_reg_addr, svsb->reg_data[i][j]);
+			}
+		}
+	}
+
+	return 0;
+}
+
+proc_fops_ro(svs_dump);
+
+static int svs_status_proc_show(struct seq_file *m, void *v)
+{
+	struct svs_bank *svsb = (struct svs_bank *)m->private;
+	struct dev_pm_opp *opp;
+	unsigned long freq;
+	int zone_temp, ret;
+	u32 i;
+
+	ret = svs_get_zone_temperature(svsb, &zone_temp);
+	if (ret)
+		seq_printf(m, "%s: cannot get zone \"%s\" temperature\n",
+			   svsb->name, svsb->zone_name);
+	else
+		seq_printf(m, "%s: temperature = %d\n", svsb->name, zone_temp);
+
+	for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
+		opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq);
+		if (IS_ERR(opp)) {
+			seq_printf(m, "%s: error opp entry!!, err = %ld\n",
+				   svsb->name, PTR_ERR(opp));
+			return PTR_ERR(opp);
+		}
+
+		seq_printf(m, "opp_freqs[%02u]: %lu, volts[%02u]: %lu, ",
+			   i, freq, i, dev_pm_opp_get_voltage(opp));
+		seq_printf(m, "svsb_volts[%02u]: 0x%x, freqs_pct[%02u]: %u\n",
+			   i, svsb->volts[i], i, svsb->freqs_pct[i]);
+	}
+
+	return 0;
+}
+
+proc_fops_ro(svs_status);
+
+static int svs_volt_offset_proc_show(struct seq_file *m, void *v)
+{
+	struct svs_bank *svsb = (struct svs_bank *)m->private;
+
+	seq_printf(m, "%d\n", svsb->volt_offset);
+
+	return 0;
+}
+
+static ssize_t svs_volt_offset_proc_write(struct file *file,
+					  const char __user *buffer,
+					  size_t count, loff_t *pos)
+{
+	struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file));
+	char *buf = (char *)__get_free_page(GFP_USER);
+	int ret, volt_offset;
+
+	if (!buf)
+		return -ENOMEM;
+
+	if (count >= PAGE_SIZE) {
+		free_page((unsigned long)buf);
+		return -EINVAL;
+	}
+
+	if (copy_from_user(buf, buffer, count)) {
+		free_page((unsigned long)buf);
+		return -EFAULT;
+	}
+
+	buf[count] = '\0';
+
+	if (!kstrtoint(buf, 10, &volt_offset)) {
+		svsb->volt_offset = volt_offset;
+		ret = svs_set_volts(svsb, true);
+		if (ret)
+			return ret;
+	}
+
+	return count;
+}
+
+proc_fops_rw(svs_volt_offset);
+
+static int svs_create_svs_procfs(struct mtk_svs *svs)
+{
+	const struct svs_platform *svsp = svs->platform;
+	struct svs_bank *svsb;
+	struct proc_dir_entry *svs_dir, *bank_dir;
+	u32 idx, i;
+
+	struct pentry {
+		const char *name;
+		const struct file_operations *fops;
+	};
+
+	struct pentry svs_entries[] = {
+		proc_entry(svs_dump),
+	};
+
+	struct pentry bank_entries[] = {
+		proc_entry(svs_debug),
+		proc_entry(svs_status),
+		proc_entry(svs_volt_offset),
+	};
+
+	svs_dir = proc_mkdir("svs", NULL);
+	if (!svs_dir) {
+		pr_err("mkdir /proc/svs failed\n");
+		return -EPERM;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(svs_entries); i++) {
+		if (!proc_create_data(svs_entries[i].name, 0664,
+				      svs_dir, svs_entries[i].fops, svs)) {
+			pr_err("create /proc/svs/%s failed\n",
+			       svs_entries[i].name);
+			return -EPERM;
+		}
+	}
+
+	for (idx = 0; idx < svsp->bank_num; idx++) {
+		svsb = &svsp->banks[idx];
+
+		if (!svsb->init01_support)
+			continue;
+
+		bank_dir = proc_mkdir(svsb->name, svs_dir);
+		if (!bank_dir) {
+			pr_err("mkdir /proc/svs/%s failed\n", svsb->name);
+			return -EPERM;
+		}
+
+		for (i = 0; i < ARRAY_SIZE(bank_entries); i++) {
+			if (!proc_create_data(bank_entries[i].name, 0664,
+					      bank_dir, bank_entries[i].fops,
+					      svsb)) {
+				pr_err("create /proc/svs/%s/%s failed\n",
+				       svsb->name, bank_entries[i].name);
+				return -EPERM;
+			}
+		}
+	}
+
+	return 0;
+}
+
+struct svs_bank_ops svs_mt8183_banks_ops = {
+	.set_freqs_pct	= svs_set_freqs_pct_v2,
+	.get_vops	= svs_get_vops_v2,
+};
+
+struct svs_bank svs_mt8183_banks[4] = {
+	{
+		.of_compatible		= "mediatek,mt8183-svs-cpu-little",
+		.sw_id			= SVS_CPU_LITTLE,
+		.hw_id			= 0,
+		.ops			= &svs_mt8183_banks_ops,
+		.zone_name		= "tzts4",
+		.buck_name		= "vcpu-little",
+		.mtcmos_request		= false,
+		.init01_volt_flag	= SVS_INIT01_VOLT_INC_ONLY,
+		.init01_support		= true,
+		.init02_support		= true,
+		.mon_mode_support	= false,
+		.opp_count		= 16,
+		.freq_base		= 1989000000,
+		.vboot			= 0x30,
+		.volt_step		= 6250,
+		.volt_base		= 500000,
+		.volt_offset		= 0,
+		.vmax			= 0x64,
+		.vmin			= 0x18,
+		.dthi			= 0x1,
+		.dtlo			= 0xfe,
+		.det_window		= 0xa28,
+		.det_max		= 0xffff,
+		.age_config		= 0x555555,
+		.agem			= 0x0,
+		.dc_config		= 0x555555,
+		.dvt_fixed		= 0x7,
+		.vco			= 0x10,
+		.chkshift		= 0x77,
+		.upper_temp_bound	= 0x64,
+		.lower_temp_bound	= 0xb2,
+		.low_temp_threashold	= 25000,
+		.low_temp_offset	= 0,
+		.coresel		= 0x8fff0000,
+		.systemclk_en		= BIT(31),
+		.intst			= BIT(0),
+		.ctl0			= 0x00010001,
+	},
+	{
+		.of_compatible		= "mediatek,mt8183-svs-cpu-big",
+		.sw_id			= SVS_CPU_BIG,
+		.hw_id			= 1,
+		.ops			= &svs_mt8183_banks_ops,
+		.zone_name		= "tzts5",
+		.buck_name		= "vcpu-big",
+		.mtcmos_request		= false,
+		.init01_volt_flag	= SVS_INIT01_VOLT_INC_ONLY,
+		.init01_support		= true,
+		.init02_support		= true,
+		.mon_mode_support	= false,
+		.opp_count		= 16,
+		.freq_base		= 1989000000,
+		.vboot			= 0x30,
+		.volt_step		= 6250,
+		.volt_base		= 500000,
+		.volt_offset		= 0,
+		.vmax			= 0x58,
+		.vmin			= 0x10,
+		.dthi			= 0x1,
+		.dtlo			= 0xfe,
+		.det_window		= 0xa28,
+		.det_max		= 0xffff,
+		.age_config		= 0x555555,
+		.agem			= 0x0,
+		.dc_config		= 0x555555,
+		.dvt_fixed		= 0x7,
+		.vco			= 0x10,
+		.chkshift		= 0x77,
+		.upper_temp_bound	= 0x64,
+		.lower_temp_bound	= 0xb2,
+		.low_temp_threashold	= 25000,
+		.low_temp_offset	= 0,
+		.coresel		= 0x8fff0001,
+		.systemclk_en		= BIT(31),
+		.intst			= BIT(1),
+		.ctl0			= 0x00000001,
+	},
+	{
+		.of_compatible		= "mediatek,mt8183-svs-cci",
+		.sw_id			= SVS_CCI,
+		.hw_id			= 2,
+		.ops			= &svs_mt8183_banks_ops,
+		.zone_name		= "tzts4",
+		.buck_name		= "vcci",
+		.mtcmos_request		= false,
+		.init01_volt_flag	= SVS_INIT01_VOLT_INC_ONLY,
+		.init01_support		= true,
+		.init02_support		= true,
+		.mon_mode_support	= false,
+		.opp_count		= 16,
+		.freq_base		= 1196000000,
+		.vboot			= 0x30,
+		.volt_step		= 6250,
+		.volt_base		= 500000,
+		.volt_offset		= 0,
+		.vmax			= 0x64,
+		.vmin			= 0x18,
+		.dthi			= 0x1,
+		.dtlo			= 0xfe,
+		.det_window		= 0xa28,
+		.det_max		= 0xffff,
+		.age_config		= 0x555555,
+		.agem			= 0x0,
+		.dc_config		= 0x555555,
+		.dvt_fixed		= 0x7,
+		.vco			= 0x10,
+		.chkshift		= 0x77,
+		.upper_temp_bound	= 0x64,
+		.lower_temp_bound	= 0xb2,
+		.low_temp_threashold	= 25000,
+		.low_temp_offset	= 0,
+		.coresel		= 0x8fff0002,
+		.systemclk_en		= BIT(31),
+		.intst			= BIT(2),
+		.ctl0			= 0x00100003,
+	},
+	{
+		.of_compatible		= "mediatek,mt8183-svs-gpu",
+		.sw_id			= SVS_GPU,
+		.hw_id			= 3,
+		.ops			= &svs_mt8183_banks_ops,
+		.zone_name		= "tzts2",
+		.buck_name		= "vgpu",
+		.mtcmos_request		= true,
+		.init01_volt_flag	= SVS_INIT01_VOLT_INC_ONLY,
+		.init01_support		= true,
+		.init02_support		= true,
+		.mon_mode_support	= true,
+		.opp_count		= 16,
+		.freq_base		= 900000000,
+		.vboot			= 0x30,
+		.volt_step		= 6250,
+		.volt_base		= 500000,
+		.volt_offset		= 0,
+		.vmax			= 0x40,
+		.vmin			= 0x14,
+		.dthi			= 0x1,
+		.dtlo			= 0xfe,
+		.det_window		= 0xa28,
+		.det_max		= 0xffff,
+		.age_config		= 0x555555,
+		.agem			= 0x0,
+		.dc_config		= 0x555555,
+		.dvt_fixed		= 0x3,
+		.vco			= 0x10,
+		.chkshift		= 0x77,
+		.upper_temp_bound	= 0x64,
+		.lower_temp_bound	= 0xb2,
+		.low_temp_threashold	= 25000,
+		.low_temp_offset	= 3,
+		.coresel		= 0x8fff0003,
+		.systemclk_en		= BIT(31),
+		.intst			= BIT(3),
+		.ctl0			= 0x00050001,
+	},
+};
+
+static const struct svs_platform svs_mt8183_platform = {
+	.name		= "mt8183-svs",
+	.banks		= svs_mt8183_banks,
+	.efuse_parsing	= svs_mt8183_efuse_parsing,
+	.regs		= svs_regs_v2,
+	.fake_efuse	= false,
+	.bank_num	= 4,
+	.efuse_num	= 25,
+	.efuse_check	= 2,
+	.thermal_efuse_num = 3,
+};
+
+static const struct of_device_id mtk_svs_of_match[] = {
+	{
+		.compatible = "mediatek,mt8183-svs",
+		.data = &svs_mt8183_platform,
+	}, {
+		/* sentinel */
+	},
+};
+
+static int svs_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *of_dev_id;
+	struct mtk_svs *svs;
+	int ret;
+	u32 svs_irq;
+
+	svs = devm_kzalloc(&pdev->dev, sizeof(*svs), GFP_KERNEL);
+	if (IS_ERR(svs))
+		return PTR_ERR(svs);
+
+	svs->dev = &pdev->dev;
+	if (!svs->dev->of_node) {
+		pr_err("cannot find device node\n");
+		return -ENODEV;
+	}
+
+	svs->base = of_iomap(svs->dev->of_node, 0);
+	if (IS_ERR(svs->base)) {
+		pr_err("cannot find svs register base\n");
+		return PTR_ERR(svs->base);
+	}
+
+	svs_irq = irq_of_parse_and_map(svs->dev->of_node, 0);
+	ret = devm_request_threaded_irq(svs->dev, svs_irq, NULL, svs_isr,
+					IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+					"mtk-svs", svs);
+	if (ret) {
+		pr_err("register irq(%d) failed: %d\n", svs_irq, ret);
+		return ret;
+	}
+
+	of_dev_id = of_match_node(mtk_svs_of_match, svs->dev->of_node);
+	if (!of_dev_id || !of_dev_id->data)
+		return -EINVAL;
+
+	svs->platform = of_dev_id->data;
+	dev_set_drvdata(svs->dev, svs);
+
+	svs->main_clk = devm_clk_get(svs->dev, "main_clk");
+	if (IS_ERR(svs->main_clk)) {
+		pr_err("failed to get clock: %ld\n", PTR_ERR(svs->main_clk));
+		return PTR_ERR(svs->main_clk);
+	}
+
+	ret = clk_prepare_enable(svs->main_clk);
+	if (ret) {
+		pr_err("cannot enable main_clk: %d\n", ret);
+		return ret;
+	}
+
+	ret = svs_is_support(svs);
+	if (ret)
+		goto svs_probe_fail;
+
+	ret = svs_resource_setup(svs);
+	if (ret)
+		goto svs_probe_fail;
+
+	ret = svs_start(svs);
+	if (ret)
+		goto svs_probe_fail;
+
+	ret = svs_create_svs_procfs(svs);
+	if (ret)
+		goto svs_probe_fail;
+
+	return 0;
+
+svs_probe_fail:
+	clk_disable_unprepare(svs->main_clk);
+
+	return ret;
+}
+
+static const struct dev_pm_ops svs_pm_ops = {
+	.suspend	= svs_suspend,
+	.resume		= svs_resume,
+};
+
+static struct platform_driver svs_driver = {
+	.probe	= svs_probe,
+	.driver	= {
+		.name		= "mtk-svs",
+		.pm		= &svs_pm_ops,
+		.of_match_table	= of_match_ptr(mtk_svs_of_match),
+	},
+};
+
+static int __init svs_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&svs_driver);
+	if (ret) {
+		pr_err("svs platform driver register failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+late_initcall_sync(svs_init);
+
+MODULE_DESCRIPTION("MediaTek SVS Driver v1.0");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/mtk_svs.h b/include/linux/power/mtk_svs.h
new file mode 100644
index 000000000000..d5efca8d9dca
--- /dev/null
+++ b/include/linux/power/mtk_svs.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 MediaTek Inc.
+ */
+
+#ifndef __MTK_SVS_H__
+#define __MTK_SVS_H__
+
+#if defined(CONFIG_MTK_SVS)
+unsigned long claim_mtk_svs_lock(void);
+void release_mtk_svs_lock(unsigned long flags);
+#else
+static inline unsigned long claim_mtk_svs_lock(void)
+{
+	return 0;
+}
+
+static inline void release_mtk_svs_lock(unsigned long flags)
+{
+}
+#endif /* CONFIG_MTK_SVS */
+
+#endif /* __MTK_SVS_H__ */
-- 
2.18.0


^ permalink raw reply related

* [PATCH v3 1/2] dt-bindings: soc: add mtk svs dt-bindings
From: Roger Lu @ 2019-06-21  8:43 UTC (permalink / raw)
  To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
  Cc: Fan Chen, HenryC Chen, Mark Rutland, Matthias Brugger,
	Nishanth Menon, Roger Lu, devicetree, linux-arm-kernel,
	linux-mediatek, linux-kernel, linux-pm
In-Reply-To: <20190621084348.16834-1-roger.lu@mediatek.com>

Document the binding for enabling mtk svs on MediaTek SoC.

Signed-off-by: Roger Lu <roger.lu@mediatek.com>
---
 .../devicetree/bindings/power/mtk-svs.txt     | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt

diff --git a/Documentation/devicetree/bindings/power/mtk-svs.txt b/Documentation/devicetree/bindings/power/mtk-svs.txt
new file mode 100644
index 000000000000..6a71992ef162
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
@@ -0,0 +1,88 @@
+* Mediatek Smart Voltage Scaling (MTK SVS)
+
+This describes the device tree binding for the MTK SVS controller (bank)
+which helps provide the optimized CPU/GPU/CCI voltages. This device also
+needs thermal data to calculate thermal slope for accurately compensate
+the voltages when temperature change.
+
+Required properties:
+- compatible:
+  - "mediatek,mt8183-svs" : For MT8183 family of SoCs
+- reg: Address range of the MTK SVS controller.
+- interrupts: IRQ for the MTK SVS controller.
+- clocks, clock-names: Clocks needed for the svs controller. required
+                       clocks are:
+		       "main_clk": Main clock needed for register access
+- nvmem-cells: Phandle to the calibration data provided by a nvmem device.
+- nvmem-cell-names: Should be "svs-calibration-data" and "calibration-data"
+
+Subnodes:
+- svs_cpu_little: SVS bank device node of little CPU
+  compatible: "mediatek,mt8183-svs-cpu-little"
+  operating-points-v2: OPP table hooked by SVS little CPU bank.
+		       SVS will optimze this OPP table voltage part.
+  vcpu-little-supply: PMIC buck of little CPU
+- svs_cpu_big: SVS bank device node of big CPU
+  compatible: "mediatek,mt8183-svs-cpu-big"
+  operating-points-v2: OPP table hooked by SVS big CPU bank.
+		       SVS will optimze this OPP table voltage part.
+  vcpu-big-supply: PMIC buck of big CPU
+- svs_cci: SVS bank device node of CCI
+  compatible: "mediatek,mt8183-svs-cci"
+  operating-points-v2: OPP table hooked by SVS CCI bank.
+		       SVS will optimze this OPP table voltage part.
+  vcci-supply: PMIC buck of CCI
+- svs_gpu: SVS bank device node of GPU
+  compatible: "mediatek,mt8183-svs-gpu"
+  operating-points-v2: OPP table hooked by SVS GPU bank.
+		       SVS will optimze this OPP table voltage part.
+  vgpu-spply: PMIC buck of GPU
+
+Example:
+
+	svs: svs@1100b000 {
+		compatible = "mediatek,mt8183-svs";
+		reg = <0 0x1100b000 0 0x1000>;
+		interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW 0>;
+		clocks = <&infracfg CLK_INFRA_THERM>;
+		clock-names = "main_clk";
+		nvmem-cells = <&svs_calibration>, <&thermal_calibration>;
+		nvmem-cell-names = "svs-calibration-data", "calibration-data";
+
+		svs_cpu_little: svs_cpu_little {
+			compatible = "mediatek,mt8183-svs-cpu-little";
+			operating-points-v2 = <&cluster0_opp>;
+		};
+
+		svs_cpu_big: svs_cpu_big {
+			compatible = "mediatek,mt8183-svs-cpu-big";
+			operating-points-v2 = <&cluster1_opp>;
+		};
+
+		svs_cci: svs_cci {
+			compatible = "mediatek,mt8183-svs-cci";
+			operating-points-v2 = <&cci_opp>;
+		};
+
+		svs_gpu: svs_gpu {
+			compatible = "mediatek,mt8183-svs-gpu";
+			power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
+			operating-points-v2 = <&gpu_opp_table>;
+		};
+	};
+
+	&svs_cpu_little {
+		vcpu-little-supply = <&mt6358_vproc12_reg>;
+	};
+
+	&svs_cpu_big {
+		vcpu-big-supply = <&mt6358_vproc11_reg>;
+	};
+
+	&svs_cci {
+		vcci-supply = <&mt6358_vproc12_reg>;
+	};
+
+	&svs_gpu {
+		vgpu-spply = <&mt6358_vgpu_reg>;
+	};
-- 
2.18.0


^ permalink raw reply related

* [PATCH v3 0/2] PM / AVS: SVS: Introduce SVS engine
From: Roger Lu @ 2019-06-21  8:43 UTC (permalink / raw)
  To: Kevin Hilman, Rob Herring, Nicolas Boichat, Stephen Boyd
  Cc: Fan Chen, HenryC Chen, Mark Rutland, Matthias Brugger,
	Nishanth Menon, Roger Lu, devicetree, linux-arm-kernel,
	linux-mediatek, linux-kernel, linux-pm

SVS driver use OPP adjust event in [1] to update
OPP table voltage part.

[1] https://patchwork.kernel.org/patch/10946069/

changes since v2:
- Improve SVS driver: For power saving point of view,
disable SVS main clk when probe fail.

Roger Lu (2):
  dt-bindings: soc: add mtk svs dt-bindings
  PM / AVS: SVS: Introduce SVS engine

 .../devicetree/bindings/power/mtk-svs.txt     |   88 +
 drivers/power/avs/Kconfig                     |   10 +
 drivers/power/avs/Makefile                    |    1 +
 drivers/power/avs/mtk_svs.c                   | 2093 +++++++++++++++++
 include/linux/power/mtk_svs.h                 |   23 +
 5 files changed, 2215 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
 create mode 100644 drivers/power/avs/mtk_svs.c
 create mode 100644 include/linux/power/mtk_svs.h



^ permalink raw reply

* [PATCH v10 01/16] sched/core: uclamp: Add CPU's clamp buckets refcounting
From: Patrick Bellasi @ 2019-06-21  8:42 UTC (permalink / raw)
  To: linux-kernel, linux-pm
  Cc: Ingo Molnar, Peter Zijlstra, Tejun Heo, Rafael J . Wysocki,
	Vincent Guittot, Viresh Kumar, Paul Turner, Quentin Perret,
	Dietmar Eggemann, Morten Rasmussen, Juri Lelli, Todd Kjos,
	Joel Fernandes, Steve Muckle, Suren Baghdasaryan, Alessio Balsini
In-Reply-To: <20190621084217.8167-1-patrick.bellasi@arm.com>

Utilization clamping allows to clamp the CPU's utilization within a
[util_min, util_max] range, depending on the set of RUNNABLE tasks on
that CPU. Each task references two "clamp buckets" defining its minimum
and maximum (util_{min,max}) utilization "clamp values". A CPU's clamp
bucket is active if there is at least one RUNNABLE tasks enqueued on
that CPU and refcounting that bucket.

When a task is {en,de}queued {on,from} a rq, the set of active clamp
buckets on that CPU can change. If the set of active clamp buckets
changes for a CPU a new "aggregated" clamp value is computed for that
CPU. This is because each clamp bucket enforces a different utilization
clamp value.

Clamp values are always MAX aggregated for both util_min and util_max.
This ensures that no task can affect the performance of other
co-scheduled tasks which are more boosted (i.e. with higher util_min
clamp) or less capped (i.e. with higher util_max clamp).

A task has:
   task_struct::uclamp[clamp_id]::bucket_id
to track the "bucket index" of the CPU's clamp bucket it refcounts while
enqueued, for each clamp index (clamp_id).

A runqueue has:
   rq::uclamp[clamp_id]::bucket[bucket_id].tasks
to track how many RUNNABLE tasks on that CPU refcount each
clamp bucket (bucket_id) of a clamp index (clamp_id).
It also has a:
   rq::uclamp[clamp_id]::bucket[bucket_id].value
to track the clamp value of each clamp bucket (bucket_id) of a clamp
index (clamp_id).

The rq::uclamp::bucket[clamp_id][] array is scanned every time it's
needed to find a new MAX aggregated clamp value for a clamp_id. This
operation is required only when it's dequeued the last task of a clamp
bucket tracking the current MAX aggregated clamp value. In this case,
the CPU is either entering IDLE or going to schedule a less boosted or
more clamped task.
The expected number of different clamp values configured at build time
is small enough to fit the full unordered array into a single cache
line, for configurations of up to 7 buckets.

Add to struct rq the basic data structures required to refcount the
number of RUNNABLE tasks for each clamp bucket. Add also the max
aggregation required to update the rq's clamp value at each
enqueue/dequeue event.

Use a simple linear mapping of clamp values into clamp buckets.
Pre-compute and cache bucket_id to avoid integer divisions at
enqueue/dequeue time.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
---
 include/linux/log2.h           |  34 +++++++
 include/linux/sched.h          |  39 ++++++++
 include/linux/sched/topology.h |   6 --
 init/Kconfig                   |  53 +++++++++++
 kernel/sched/core.c            | 166 +++++++++++++++++++++++++++++++++
 kernel/sched/sched.h           |  51 ++++++++++
 6 files changed, 343 insertions(+), 6 deletions(-)

diff --git a/include/linux/log2.h b/include/linux/log2.h
index 1aec01365ed4..83a4a3ca3e8a 100644
--- a/include/linux/log2.h
+++ b/include/linux/log2.h
@@ -220,4 +220,38 @@ int __order_base_2(unsigned long n)
 		ilog2((n) - 1) + 1) :		\
 	__order_base_2(n)			\
 )
+
+static inline __attribute__((const))
+int __bits_per(unsigned long n)
+{
+	if (n < 2)
+		return 1;
+	if (is_power_of_2(n))
+		return order_base_2(n) + 1;
+	return order_base_2(n);
+}
+
+/**
+ * bits_per - calculate the number of bits required for the argument
+ * @n: parameter
+ *
+ * This is constant-capable and can be used for compile time
+ * initializations, e.g bitfields.
+ *
+ * The first few values calculated by this routine:
+ * bf(0) = 1
+ * bf(1) = 1
+ * bf(2) = 2
+ * bf(3) = 2
+ * bf(4) = 3
+ * ... and so on.
+ */
+#define bits_per(n)				\
+(						\
+	__builtin_constant_p(n) ? (		\
+		((n) == 0 || (n) == 1)		\
+			? 1 : ilog2(n) + 1	\
+	) :					\
+	__bits_per(n)				\
+)
 #endif /* _LINUX_LOG2_H */
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 1b2590a8d038..0027b379fa9b 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -281,6 +281,18 @@ struct vtime {
 	u64			gtime;
 };
 
+/*
+ * Utilization clamp constraints.
+ * @UCLAMP_MIN:	Minimum utilization
+ * @UCLAMP_MAX:	Maximum utilization
+ * @UCLAMP_CNT:	Utilization clamp constraints count
+ */
+enum uclamp_id {
+	UCLAMP_MIN = 0,
+	UCLAMP_MAX,
+	UCLAMP_CNT
+};
+
 struct sched_info {
 #ifdef CONFIG_SCHED_INFO
 	/* Cumulative counters: */
@@ -312,6 +324,10 @@ struct sched_info {
 # define SCHED_FIXEDPOINT_SHIFT		10
 # define SCHED_FIXEDPOINT_SCALE		(1L << SCHED_FIXEDPOINT_SHIFT)
 
+/* Increase resolution of cpu_capacity calculations */
+# define SCHED_CAPACITY_SHIFT		SCHED_FIXEDPOINT_SHIFT
+# define SCHED_CAPACITY_SCALE		(1L << SCHED_CAPACITY_SHIFT)
+
 struct load_weight {
 	unsigned long			weight;
 	u32				inv_weight;
@@ -560,6 +576,25 @@ struct sched_dl_entity {
 	struct hrtimer inactive_timer;
 };
 
+#ifdef CONFIG_UCLAMP_TASK
+/* Number of utilization clamp buckets (shorter alias) */
+#define UCLAMP_BUCKETS CONFIG_UCLAMP_BUCKETS_COUNT
+
+/*
+ * Utilization clamp for a scheduling entity
+ * @value:		clamp value "assigned" to a se
+ * @bucket_id:		bucket index corresponding to the "assigned" value
+ *
+ * The bucket_id is the index of the clamp bucket matching the clamp value
+ * which is pre-computed and stored to avoid expensive integer divisions from
+ * the fast path.
+ */
+struct uclamp_se {
+	unsigned int value		: bits_per(SCHED_CAPACITY_SCALE);
+	unsigned int bucket_id		: bits_per(UCLAMP_BUCKETS);
+};
+#endif /* CONFIG_UCLAMP_TASK */
+
 union rcu_special {
 	struct {
 		u8			blocked;
@@ -640,6 +675,10 @@ struct task_struct {
 #endif
 	struct sched_dl_entity		dl;
 
+#ifdef CONFIG_UCLAMP_TASK
+	struct uclamp_se		uclamp[UCLAMP_CNT];
+#endif
+
 #ifdef CONFIG_PREEMPT_NOTIFIERS
 	/* List of struct preempt_notifier: */
 	struct hlist_head		preempt_notifiers;
diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h
index 53afbe07354a..2f8317e55c12 100644
--- a/include/linux/sched/topology.h
+++ b/include/linux/sched/topology.h
@@ -6,12 +6,6 @@
 
 #include <linux/sched/idle.h>
 
-/*
- * Increase resolution of cpu_capacity calculations
- */
-#define SCHED_CAPACITY_SHIFT	SCHED_FIXEDPOINT_SHIFT
-#define SCHED_CAPACITY_SCALE	(1L << SCHED_CAPACITY_SHIFT)
-
 /*
  * sched-domains (multiprocessor balancing) declarations:
  */
diff --git a/init/Kconfig b/init/Kconfig
index 086b72a99d60..bf96faf3fe43 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -677,6 +677,59 @@ config HAVE_UNSTABLE_SCHED_CLOCK
 config GENERIC_SCHED_CLOCK
 	bool
 
+menu "Scheduler features"
+
+config UCLAMP_TASK
+	bool "Enable utilization clamping for RT/FAIR tasks"
+	depends on CPU_FREQ_GOV_SCHEDUTIL
+	help
+	  This feature enables the scheduler to track the clamped utilization
+	  of each CPU based on RUNNABLE tasks scheduled on that CPU.
+
+	  With this option, the user can specify the min and max CPU
+	  utilization allowed for RUNNABLE tasks. The max utilization defines
+	  the maximum frequency a task should use while the min utilization
+	  defines the minimum frequency it should use.
+
+	  Both min and max utilization clamp values are hints to the scheduler,
+	  aiming at improving its frequency selection policy, but they do not
+	  enforce or grant any specific bandwidth for tasks.
+
+	  If in doubt, say N.
+
+config UCLAMP_BUCKETS_COUNT
+	int "Number of supported utilization clamp buckets"
+	range 5 20
+	default 5
+	depends on UCLAMP_TASK
+	help
+	  Defines the number of clamp buckets to use. The range of each bucket
+	  will be SCHED_CAPACITY_SCALE/UCLAMP_BUCKETS_COUNT. The higher the
+	  number of clamp buckets the finer their granularity and the higher
+	  the precision of clamping aggregation and tracking at run-time.
+
+	  For example, with the minimum configuration value we will have 5
+	  clamp buckets tracking 20% utilization each. A 25% boosted tasks will
+	  be refcounted in the [20..39]% bucket and will set the bucket clamp
+	  effective value to 25%.
+	  If a second 30% boosted task should be co-scheduled on the same CPU,
+	  that task will be refcounted in the same bucket of the first task and
+	  it will boost the bucket clamp effective value to 30%.
+	  The clamp effective value of a bucket is reset to its nominal value
+	  (20% in the example above) when there are no more tasks refcounted in
+	  that bucket.
+
+	  An additional boost/capping margin can be added to some tasks. In the
+	  example above the 25% task will be boosted to 30% until it exits the
+	  CPU. If that should be considered not acceptable on certain systems,
+	  it's always possible to reduce the margin by increasing the number of
+	  clamp buckets to trade off used memory for run-time tracking
+	  precision.
+
+	  If in doubt, use the default value.
+
+endmenu
+
 #
 # For architectures that want to enable the support for NUMA-affine scheduler
 # balancing logic:
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 83bd6bb32a34..bc0389d3e77d 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -761,6 +761,168 @@ static void set_load_weight(struct task_struct *p, bool update_load)
 	}
 }
 
+#ifdef CONFIG_UCLAMP_TASK
+
+/* Integer rounded range for each bucket */
+#define UCLAMP_BUCKET_DELTA DIV_ROUND_CLOSEST(SCHED_CAPACITY_SCALE, UCLAMP_BUCKETS)
+
+#define for_each_clamp_id(clamp_id) \
+	for ((clamp_id) = 0; (clamp_id) < UCLAMP_CNT; (clamp_id)++)
+
+static inline unsigned int uclamp_bucket_id(unsigned int clamp_value)
+{
+	return clamp_value / UCLAMP_BUCKET_DELTA;
+}
+
+static inline unsigned int uclamp_none(int clamp_id)
+{
+	if (clamp_id == UCLAMP_MIN)
+		return 0;
+	return SCHED_CAPACITY_SCALE;
+}
+
+static inline void uclamp_se_set(struct uclamp_se *uc_se, unsigned int value)
+{
+	uc_se->value = value;
+	uc_se->bucket_id = uclamp_bucket_id(value);
+}
+
+static inline
+unsigned int uclamp_rq_max_value(struct rq *rq, unsigned int clamp_id)
+{
+	struct uclamp_bucket *bucket = rq->uclamp[clamp_id].bucket;
+	int bucket_id = UCLAMP_BUCKETS - 1;
+
+	/*
+	 * Since both min and max clamps are max aggregated, find the
+	 * top most bucket with tasks in.
+	 */
+	for ( ; bucket_id >= 0; bucket_id--) {
+		if (!bucket[bucket_id].tasks)
+			continue;
+		return bucket[bucket_id].value;
+	}
+
+	/* No tasks -- default clamp values */
+	return uclamp_none(clamp_id);
+}
+
+/*
+ * When a task is enqueued on a rq, the clamp bucket currently defined by the
+ * task's uclamp::bucket_id is refcounted on that rq. This also immediately
+ * updates the rq's clamp value if required.
+ */
+static inline void uclamp_rq_inc_id(struct rq *rq, struct task_struct *p,
+				    unsigned int clamp_id)
+{
+	struct uclamp_rq *uc_rq = &rq->uclamp[clamp_id];
+	struct uclamp_se *uc_se = &p->uclamp[clamp_id];
+	struct uclamp_bucket *bucket;
+
+	lockdep_assert_held(&rq->lock);
+
+	bucket = &uc_rq->bucket[uc_se->bucket_id];
+	bucket->tasks++;
+
+	if (uc_se->value > READ_ONCE(uc_rq->value))
+		WRITE_ONCE(uc_rq->value, bucket->value);
+}
+
+/*
+ * When a task is dequeued from a rq, the clamp bucket refcounted by the task
+ * is released. If this is the last task reference counting the rq's max
+ * active clamp value, then the rq's clamp value is updated.
+ *
+ * Both refcounted tasks and rq's cached clamp values are expected to be
+ * always valid. If it's detected they are not, as defensive programming,
+ * enforce the expected state and warn.
+ */
+static inline void uclamp_rq_dec_id(struct rq *rq, struct task_struct *p,
+				    unsigned int clamp_id)
+{
+	struct uclamp_rq *uc_rq = &rq->uclamp[clamp_id];
+	struct uclamp_se *uc_se = &p->uclamp[clamp_id];
+	struct uclamp_bucket *bucket;
+	unsigned int rq_clamp;
+
+	lockdep_assert_held(&rq->lock);
+
+	bucket = &uc_rq->bucket[uc_se->bucket_id];
+	SCHED_WARN_ON(!bucket->tasks);
+	if (likely(bucket->tasks))
+		bucket->tasks--;
+
+	if (likely(bucket->tasks))
+		return;
+
+	rq_clamp = READ_ONCE(uc_rq->value);
+	/*
+	 * Defensive programming: this should never happen. If it happens,
+	 * e.g. due to future modification, warn and fixup the expected value.
+	 */
+	SCHED_WARN_ON(bucket->value > rq_clamp);
+	if (bucket->value >= rq_clamp)
+		WRITE_ONCE(uc_rq->value, uclamp_rq_max_value(rq, clamp_id));
+}
+
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p)
+{
+	unsigned int clamp_id;
+
+	if (unlikely(!p->sched_class->uclamp_enabled))
+		return;
+
+	for_each_clamp_id(clamp_id)
+		uclamp_rq_inc_id(rq, p, clamp_id);
+}
+
+static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p)
+{
+	unsigned int clamp_id;
+
+	if (unlikely(!p->sched_class->uclamp_enabled))
+		return;
+
+	for_each_clamp_id(clamp_id)
+		uclamp_rq_dec_id(rq, p, clamp_id);
+}
+
+static void __init init_uclamp(void)
+{
+	unsigned int clamp_id;
+	int cpu;
+
+	for_each_possible_cpu(cpu) {
+		struct uclamp_bucket *bucket;
+		struct uclamp_rq *uc_rq;
+		unsigned int bucket_id;
+
+		memset(&cpu_rq(cpu)->uclamp, 0, sizeof(struct uclamp_rq));
+
+		for_each_clamp_id(clamp_id) {
+			uc_rq = &cpu_rq(cpu)->uclamp[clamp_id];
+
+			bucket_id = 1;
+			while (bucket_id < UCLAMP_BUCKETS) {
+				bucket = &uc_rq->bucket[bucket_id];
+				bucket->value = bucket_id * UCLAMP_BUCKET_DELTA;
+				++bucket_id;
+			}
+		}
+	}
+
+	for_each_clamp_id(clamp_id) {
+		uclamp_se_set(&init_task.uclamp[clamp_id],
+			      uclamp_none(clamp_id));
+	}
+}
+
+#else /* CONFIG_UCLAMP_TASK */
+static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
+static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
+static inline void init_uclamp(void) { }
+#endif /* CONFIG_UCLAMP_TASK */
+
 static inline void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
 {
 	if (!(flags & ENQUEUE_NOCLOCK))
@@ -771,6 +933,7 @@ static inline void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
 		psi_enqueue(p, flags & ENQUEUE_WAKEUP);
 	}
 
+	uclamp_rq_inc(rq, p);
 	p->sched_class->enqueue_task(rq, p, flags);
 }
 
@@ -784,6 +947,7 @@ static inline void dequeue_task(struct rq *rq, struct task_struct *p, int flags)
 		psi_dequeue(p, flags & DEQUEUE_SLEEP);
 	}
 
+	uclamp_rq_dec(rq, p);
 	p->sched_class->dequeue_task(rq, p, flags);
 }
 
@@ -6082,6 +6246,8 @@ void __init sched_init(void)
 
 	psi_init();
 
+	init_uclamp();
+
 	scheduler_running = 1;
 }
 
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index b08dee29ef5e..9149d90b8b7c 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -791,6 +791,48 @@ extern void rto_push_irq_work_func(struct irq_work *work);
 #endif
 #endif /* CONFIG_SMP */
 
+#ifdef CONFIG_UCLAMP_TASK
+/*
+ * struct uclamp_bucket - Utilization clamp bucket
+ * @value: utilization clamp value for tasks on this clamp bucket
+ * @tasks: number of RUNNABLE tasks on this clamp bucket
+ *
+ * Keep track of how many tasks are RUNNABLE for a given utilization
+ * clamp value.
+ */
+struct uclamp_bucket {
+	unsigned long value : bits_per(SCHED_CAPACITY_SCALE);
+	unsigned long tasks : BITS_PER_LONG - bits_per(SCHED_CAPACITY_SCALE);
+};
+
+/*
+ * struct uclamp_rq - rq's utilization clamp
+ * @value: currently active clamp values for a rq
+ * @bucket: utilization clamp buckets affecting a rq
+ *
+ * Keep track of RUNNABLE tasks on a rq to aggregate their clamp values.
+ * A clamp value is affecting a rq when there is at least one task RUNNABLE
+ * (or actually running) with that value.
+ *
+ * There are up to UCLAMP_CNT possible different clamp values, currently there
+ * are only two: minimum utilization and maximum utilization.
+ *
+ * All utilization clamping values are MAX aggregated, since:
+ * - for util_min: we want to run the CPU at least at the max of the minimum
+ *   utilization required by its currently RUNNABLE tasks.
+ * - for util_max: we want to allow the CPU to run up to the max of the
+ *   maximum utilization allowed by its currently RUNNABLE tasks.
+ *
+ * Since on each system we expect only a limited number of different
+ * utilization clamp values (UCLAMP_BUCKETS), use a simple array to track
+ * the metrics required to compute all the per-rq utilization clamp values.
+ */
+struct uclamp_rq {
+	unsigned int value;
+	struct uclamp_bucket bucket[UCLAMP_BUCKETS];
+};
+#endif /* CONFIG_UCLAMP_TASK */
+
 /*
  * This is the main, per-CPU runqueue data structure.
  *
@@ -825,6 +867,11 @@ struct rq {
 	unsigned long		nr_load_updates;
 	u64			nr_switches;
 
+#ifdef CONFIG_UCLAMP_TASK
+	/* Utilization clamp values based on CPU's RUNNABLE tasks */
+	struct uclamp_rq	uclamp[UCLAMP_CNT] ____cacheline_aligned;
+#endif
+
 	struct cfs_rq		cfs;
 	struct rt_rq		rt;
 	struct dl_rq		dl;
@@ -1639,6 +1686,10 @@ extern const u32		sched_prio_to_wmult[40];
 struct sched_class {
 	const struct sched_class *next;
 
+#ifdef CONFIG_UCLAMP_TASK
+	int uclamp_enabled;
+#endif
+
 	void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
 	void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
 	void (*yield_task)   (struct rq *rq);
-- 
2.21.0


^ permalink raw reply related

* [PATCH v10 02/16] sched/core: uclamp: Add bucket local max tracking
From: Patrick Bellasi @ 2019-06-21  8:42 UTC (permalink / raw)
  To: linux-kernel, linux-pm
  Cc: Ingo Molnar, Peter Zijlstra, Tejun Heo, Rafael J . Wysocki,
	Vincent Guittot, Viresh Kumar, Paul Turner, Quentin Perret,
	Dietmar Eggemann, Morten Rasmussen, Juri Lelli, Todd Kjos,
	Joel Fernandes, Steve Muckle, Suren Baghdasaryan, Alessio Balsini
In-Reply-To: <20190621084217.8167-1-patrick.bellasi@arm.com>

Because of bucketization, different task-specific clamp values are
tracked in the same bucket.  For example, with 20% bucket size and
assuming to have:
  Task1: util_min=25%
  Task2: util_min=35%
both tasks will be refcounted in the [20..39]% bucket and always boosted
only up to 20% thus implementing a simple floor aggregation normally
used in histograms.

In systems with only few and well-defined clamp values, it would be
useful to track the exact clamp value required by a task whenever
possible. For example, if a system requires only 23% and 47% boost
values then it's possible to track the exact boost required by each
task using only 3 buckets of ~33% size each.

Introduce a mechanism to max aggregate the requested clamp values of
RUNNABLE tasks in the same bucket. Keep it simple by resetting the
bucket value to its base value only when a bucket becomes inactive.
Allow a limited and controlled overboosting margin for tasks recounted
in the same bucket.

In systems where the boost values are not known in advance, it is still
possible to control the maximum acceptable overboosting margin by tuning
the number of clamp groups. For example, 20 groups ensure a 5% maximum
overboost.

Remove the rq bucket initialization code since a correct bucket value
is now computed when a task is refcounted into a CPU's rq.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
---
 kernel/sched/core.c | 43 +++++++++++++++++++++++++------------------
 1 file changed, 25 insertions(+), 18 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index bc0389d3e77d..9c805b83cb36 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -774,6 +774,11 @@ static inline unsigned int uclamp_bucket_id(unsigned int clamp_value)
 	return clamp_value / UCLAMP_BUCKET_DELTA;
 }
 
+static inline unsigned int uclamp_bucket_base_value(unsigned int clamp_value)
+{
+	return UCLAMP_BUCKET_DELTA * uclamp_bucket_id(clamp_value);
+}
+
 static inline unsigned int uclamp_none(int clamp_id)
 {
 	if (clamp_id == UCLAMP_MIN)
@@ -811,6 +816,11 @@ unsigned int uclamp_rq_max_value(struct rq *rq, unsigned int clamp_id)
  * When a task is enqueued on a rq, the clamp bucket currently defined by the
  * task's uclamp::bucket_id is refcounted on that rq. This also immediately
  * updates the rq's clamp value if required.
+ *
+ * Tasks can have a task-specific value requested from user-space, track
+ * within each bucket the maximum value for tasks refcounted in it.
+ * This "local max aggregation" allows to track the exact "requested" value
+ * for each bucket when all its RUNNABLE tasks require the same clamp.
  */
 static inline void uclamp_rq_inc_id(struct rq *rq, struct task_struct *p,
 				    unsigned int clamp_id)
@@ -824,8 +834,15 @@ static inline void uclamp_rq_inc_id(struct rq *rq, struct task_struct *p,
 	bucket = &uc_rq->bucket[uc_se->bucket_id];
 	bucket->tasks++;
 
+	/*
+	 * Local max aggregation: rq buckets always track the max
+	 * "requested" clamp value of its RUNNABLE tasks.
+	 */
+	if (bucket->tasks == 1 || uc_se->value > bucket->value)
+		bucket->value = uc_se->value;
+
 	if (uc_se->value > READ_ONCE(uc_rq->value))
-		WRITE_ONCE(uc_rq->value, bucket->value);
+		WRITE_ONCE(uc_rq->value, uc_se->value);
 }
 
 /*
@@ -852,6 +869,12 @@ static inline void uclamp_rq_dec_id(struct rq *rq, struct task_struct *p,
 	if (likely(bucket->tasks))
 		bucket->tasks--;
 
+	/*
+	 * Keep "local max aggregation" simple and accept to (possibly)
+	 * overboost some RUNNABLE tasks in the same bucket.
+	 * The rq clamp bucket value is reset to its base value whenever
+	 * there are no more RUNNABLE tasks refcounting it.
+	 */
 	if (likely(bucket->tasks))
 		return;
 
@@ -892,25 +915,9 @@ static void __init init_uclamp(void)
 	unsigned int clamp_id;
 	int cpu;
 
-	for_each_possible_cpu(cpu) {
-		struct uclamp_bucket *bucket;
-		struct uclamp_rq *uc_rq;
-		unsigned int bucket_id;
-
+	for_each_possible_cpu(cpu)
 		memset(&cpu_rq(cpu)->uclamp, 0, sizeof(struct uclamp_rq));
 
-		for_each_clamp_id(clamp_id) {
-			uc_rq = &cpu_rq(cpu)->uclamp[clamp_id];
-
-			bucket_id = 1;
-			while (bucket_id < UCLAMP_BUCKETS) {
-				bucket = &uc_rq->bucket[bucket_id];
-				bucket->value = bucket_id * UCLAMP_BUCKET_DELTA;
-				++bucket_id;
-			}
-		}
-	}
-
 	for_each_clamp_id(clamp_id) {
 		uclamp_se_set(&init_task.uclamp[clamp_id],
 			      uclamp_none(clamp_id));
-- 
2.21.0


^ permalink raw reply related

* [PATCH v10 05/16] sched/core: Allow sched_setattr() to use the current policy
From: Patrick Bellasi @ 2019-06-21  8:42 UTC (permalink / raw)
  To: linux-kernel, linux-pm
  Cc: Ingo Molnar, Peter Zijlstra, Tejun Heo, Rafael J . Wysocki,
	Vincent Guittot, Viresh Kumar, Paul Turner, Quentin Perret,
	Dietmar Eggemann, Morten Rasmussen, Juri Lelli, Todd Kjos,
	Joel Fernandes, Steve Muckle, Suren Baghdasaryan, Alessio Balsini
In-Reply-To: <20190621084217.8167-1-patrick.bellasi@arm.com>

The sched_setattr() syscall mandates that a policy is always specified.
This requires to always know which policy a task will have when
attributes are configured and this makes it impossible to add more
generic task attributes valid across different scheduling policies.
Reading the policy before setting generic tasks attributes is racy since
we cannot be sure it is not changed concurrently.

Introduce the required support to change generic task attributes without
affecting the current task policy. This is done by adding an attribute flag
(SCHED_FLAG_KEEP_POLICY) to enforce the usage of the current policy.

Add support for the SETPARAM_POLICY policy, which is already used by the
sched_setparam() POSIX syscall, to the sched_setattr() non-POSIX
syscall.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
---
 include/uapi/linux/sched.h | 4 +++-
 kernel/sched/core.c        | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h
index ed4ee170bee2..58b2368d3634 100644
--- a/include/uapi/linux/sched.h
+++ b/include/uapi/linux/sched.h
@@ -51,9 +51,11 @@
 #define SCHED_FLAG_RESET_ON_FORK	0x01
 #define SCHED_FLAG_RECLAIM		0x02
 #define SCHED_FLAG_DL_OVERRUN		0x04
+#define SCHED_FLAG_KEEP_POLICY		0x08
 
 #define SCHED_FLAG_ALL	(SCHED_FLAG_RESET_ON_FORK	| \
 			 SCHED_FLAG_RECLAIM		| \
-			 SCHED_FLAG_DL_OVERRUN)
+			 SCHED_FLAG_DL_OVERRUN		| \
+			 SCHED_FLAG_KEEP_POLICY)
 
 #endif /* _UAPI_LINUX_SCHED_H */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 4b8bb6678f16..0cf6d9270868 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4886,6 +4886,8 @@ SYSCALL_DEFINE3(sched_setattr, pid_t, pid, struct sched_attr __user *, uattr,
 
 	if ((int)attr.sched_policy < 0)
 		return -EINVAL;
+	if (attr.sched_flags & SCHED_FLAG_KEEP_POLICY)
+		attr.sched_policy = SETPARAM_POLICY;
 
 	rcu_read_lock();
 	retval = -ESRCH;
-- 
2.21.0


^ permalink raw reply related

* [PATCH v10 06/16] sched/core: uclamp: Extend sched_setattr() to support utilization clamping
From: Patrick Bellasi @ 2019-06-21  8:42 UTC (permalink / raw)
  To: linux-kernel, linux-pm
  Cc: Ingo Molnar, Peter Zijlstra, Tejun Heo, Rafael J . Wysocki,
	Vincent Guittot, Viresh Kumar, Paul Turner, Quentin Perret,
	Dietmar Eggemann, Morten Rasmussen, Juri Lelli, Todd Kjos,
	Joel Fernandes, Steve Muckle, Suren Baghdasaryan, Alessio Balsini
In-Reply-To: <20190621084217.8167-1-patrick.bellasi@arm.com>

The SCHED_DEADLINE scheduling class provides an advanced and formal
model to define tasks requirements that can translate into proper
decisions for both task placements and frequencies selections. Other
classes have a more simplified model based on the POSIX concept of
priorities.

Such a simple priority based model however does not allow to exploit
most advanced features of the Linux scheduler like, for example, driving
frequencies selection via the schedutil cpufreq governor. However, also
for non SCHED_DEADLINE tasks, it's still interesting to define tasks
properties to support scheduler decisions.

Utilization clamping exposes to user-space a new set of per-task
attributes the scheduler can use as hints about the expected/required
utilization for a task. This allows to implement a "proactive" per-task
frequency control policy, a more advanced policy than the current one
based just on "passive" measured task utilization. For example, it's
possible to boost interactive tasks (e.g. to get better performance) or
cap background tasks (e.g. to be more energy/thermal efficient).

Introduce a new API to set utilization clamping values for a specified
task by extending sched_setattr(), a syscall which already allows to
define task specific properties for different scheduling classes. A new
pair of attributes allows to specify a minimum and maximum utilization
the scheduler can consider for a task.

Do that by validating the required clamp values before and then applying
the required changes using _the_ same pattern already in use for
__setscheduler(). This ensures that the task is re-enqueued with the new
clamp values.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
---
 include/linux/sched.h            |  9 ++++
 include/uapi/linux/sched.h       | 12 ++++-
 include/uapi/linux/sched/types.h | 66 +++++++++++++++++++----
 kernel/sched/core.c              | 91 +++++++++++++++++++++++++++++---
 4 files changed, 161 insertions(+), 17 deletions(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index af978035ffbe..e2d80e6a187d 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -585,6 +585,7 @@ struct sched_dl_entity {
  * @value:		clamp value "assigned" to a se
  * @bucket_id:		bucket index corresponding to the "assigned" value
  * @active:		the se is currently refcounted in a rq's bucket
+ * @user_defined:	the requested clamp value comes from user-space
  *
  * The bucket_id is the index of the clamp bucket matching the clamp value
  * which is pre-computed and stored to avoid expensive integer divisions from
@@ -594,11 +595,19 @@ struct sched_dl_entity {
  * which can be different from the clamp value "requested" from user-space.
  * This allows to know a task is refcounted in the rq's bucket corresponding
  * to the "effective" bucket_id.
+ *
+ * The user_defined bit is set whenever a task has got a task-specific clamp
+ * value requested from userspace, i.e. the system defaults apply to this task
+ * just as a restriction. This allows to relax default clamps when a less
+ * restrictive task-specific value has been requested, thus allowing to
+ * implement a "nice" semantic. For example, a task running with a 20%
+ * default boost can still drop its own boosting to 0%.
  */
 struct uclamp_se {
 	unsigned int value		: bits_per(SCHED_CAPACITY_SCALE);
 	unsigned int bucket_id		: bits_per(UCLAMP_BUCKETS);
 	unsigned int active		: 1;
+	unsigned int user_defined	: 1;
 };
 #endif /* CONFIG_UCLAMP_TASK */
 
diff --git a/include/uapi/linux/sched.h b/include/uapi/linux/sched.h
index 58b2368d3634..617bb59aa8ba 100644
--- a/include/uapi/linux/sched.h
+++ b/include/uapi/linux/sched.h
@@ -52,10 +52,20 @@
 #define SCHED_FLAG_RECLAIM		0x02
 #define SCHED_FLAG_DL_OVERRUN		0x04
 #define SCHED_FLAG_KEEP_POLICY		0x08
+#define SCHED_FLAG_KEEP_PARAMS		0x10
+#define SCHED_FLAG_UTIL_CLAMP_MIN	0x20
+#define SCHED_FLAG_UTIL_CLAMP_MAX	0x40
+
+#define SCHED_FLAG_KEEP_ALL	(SCHED_FLAG_KEEP_POLICY | \
+				 SCHED_FLAG_KEEP_PARAMS)
+
+#define SCHED_FLAG_UTIL_CLAMP	(SCHED_FLAG_UTIL_CLAMP_MIN | \
+				 SCHED_FLAG_UTIL_CLAMP_MAX)
 
 #define SCHED_FLAG_ALL	(SCHED_FLAG_RESET_ON_FORK	| \
 			 SCHED_FLAG_RECLAIM		| \
 			 SCHED_FLAG_DL_OVERRUN		| \
-			 SCHED_FLAG_KEEP_POLICY)
+			 SCHED_FLAG_KEEP_ALL		| \
+			 SCHED_FLAG_UTIL_CLAMP)
 
 #endif /* _UAPI_LINUX_SCHED_H */
diff --git a/include/uapi/linux/sched/types.h b/include/uapi/linux/sched/types.h
index 10fbb8031930..c852153ddb0d 100644
--- a/include/uapi/linux/sched/types.h
+++ b/include/uapi/linux/sched/types.h
@@ -9,6 +9,7 @@ struct sched_param {
 };
 
 #define SCHED_ATTR_SIZE_VER0	48	/* sizeof first published struct */
+#define SCHED_ATTR_SIZE_VER1	56	/* add: util_{min,max} */
 
 /*
  * Extended scheduling parameters data structure.
@@ -21,8 +22,33 @@ struct sched_param {
  * the tasks may be useful for a wide variety of application fields, e.g.,
  * multimedia, streaming, automation and control, and many others.
  *
- * This variant (sched_attr) is meant at describing a so-called
- * sporadic time-constrained task. In such model a task is specified by:
+ * This variant (sched_attr) allows to define additional attributes to
+ * improve the scheduler knowledge about task requirements.
+ *
+ * Scheduling Class Attributes
+ * ===========================
+ *
+ * A subset of sched_attr attributes specifies the
+ * scheduling policy and relative POSIX attributes:
+ *
+ *  @size		size of the structure, for fwd/bwd compat.
+ *
+ *  @sched_policy	task's scheduling policy
+ *  @sched_nice		task's nice value      (SCHED_NORMAL/BATCH)
+ *  @sched_priority	task's static priority (SCHED_FIFO/RR)
+ *
+ * Certain more advanced scheduling features can be controlled by a
+ * predefined set of flags via the attribute:
+ *
+ *  @sched_flags	for customizing the scheduler behaviour
+ *
+ * Sporadic Time-Constrained Task Attributes
+ * =========================================
+ *
+ * A subset of sched_attr attributes allows to describe a so-called
+ * sporadic time-constrained task.
+ *
+ * In such a model a task is specified by:
  *  - the activation period or minimum instance inter-arrival time;
  *  - the maximum (or average, depending on the actual scheduling
  *    discipline) computation time of all instances, a.k.a. runtime;
@@ -34,14 +60,8 @@ struct sched_param {
  * than the runtime and must be completed by time instant t equal to
  * the instance activation time + the deadline.
  *
- * This is reflected by the actual fields of the sched_attr structure:
+ * This is reflected by the following fields of the sched_attr structure:
  *
- *  @size		size of the structure, for fwd/bwd compat.
- *
- *  @sched_policy	task's scheduling policy
- *  @sched_flags	for customizing the scheduler behaviour
- *  @sched_nice		task's nice value      (SCHED_NORMAL/BATCH)
- *  @sched_priority	task's static priority (SCHED_FIFO/RR)
  *  @sched_deadline	representative of the task's deadline
  *  @sched_runtime	representative of the task's runtime
  *  @sched_period	representative of the task's period
@@ -53,6 +73,29 @@ struct sched_param {
  * As of now, the SCHED_DEADLINE policy (sched_dl scheduling class) is the
  * only user of this new interface. More information about the algorithm
  * available in the scheduling class file or in Documentation/.
+ *
+ * Task Utilization Attributes
+ * ===========================
+ *
+ * A subset of sched_attr attributes allows to specify the utilization
+ * expected for a task. These attributes allow to inform the scheduler about
+ * the utilization boundaries within which it should schedule the task. These
+ * boundaries are valuable hints to support scheduler decisions on both task
+ * placement and frequency selection.
+ *
+ *  @sched_util_min	represents the minimum utilization
+ *  @sched_util_max	represents the maximum utilization
+ *
+ * Utilization is a value in the range [0..SCHED_CAPACITY_SCALE]. It
+ * represents the percentage of CPU time used by a task when running at the
+ * maximum frequency on the highest capacity CPU of the system. For example, a
+ * 20% utilization task is a task running for 2ms every 10ms at maximum
+ * frequency.
+ *
+ * A task with a min utilization value bigger than 0 is more likely scheduled
+ * on a CPU with a capacity big enough to fit the specified value.
+ * A task with a max utilization value smaller than 1024 is more likely
+ * scheduled on a CPU with no more capacity than the specified value.
  */
 struct sched_attr {
 	__u32 size;
@@ -70,6 +113,11 @@ struct sched_attr {
 	__u64 sched_runtime;
 	__u64 sched_deadline;
 	__u64 sched_period;
+
+	/* Utilization hints */
+	__u32 sched_util_min;
+	__u32 sched_util_max;
+
 };
 
 #endif /* _UAPI_LINUX_SCHED_TYPES_H */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 0cf6d9270868..7d4272440890 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -794,10 +794,12 @@ static inline unsigned int uclamp_none(int clamp_id)
 	return SCHED_CAPACITY_SCALE;
 }
 
-static inline void uclamp_se_set(struct uclamp_se *uc_se, unsigned int value)
+static inline void uclamp_se_set(struct uclamp_se *uc_se,
+				 unsigned int value, bool user_defined)
 {
 	uc_se->value = value;
 	uc_se->bucket_id = uclamp_bucket_id(value);
+	uc_se->user_defined = user_defined;
 }
 
 static inline unsigned int
@@ -1005,11 +1007,11 @@ int sysctl_sched_uclamp_handler(struct ctl_table *table, int write,
 
 	if (old_min != sysctl_sched_uclamp_util_min) {
 		uclamp_se_set(&uclamp_default[UCLAMP_MIN],
-			      sysctl_sched_uclamp_util_min);
+			      sysctl_sched_uclamp_util_min, false);
 	}
 	if (old_max != sysctl_sched_uclamp_util_max) {
 		uclamp_se_set(&uclamp_default[UCLAMP_MAX],
-			      sysctl_sched_uclamp_util_max);
+			      sysctl_sched_uclamp_util_max, false);
 	}
 
 	/*
@@ -1027,6 +1029,42 @@ int sysctl_sched_uclamp_handler(struct ctl_table *table, int write,
 	return result;
 }
 
+static int uclamp_validate(struct task_struct *p,
+			   const struct sched_attr *attr)
+{
+	unsigned int lower_bound = p->uclamp_req[UCLAMP_MIN].value;
+	unsigned int upper_bound = p->uclamp_req[UCLAMP_MAX].value;
+
+	if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MIN)
+		lower_bound = attr->sched_util_min;
+	if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MAX)
+		upper_bound = attr->sched_util_max;
+
+	if (lower_bound > upper_bound)
+		return -EINVAL;
+	if (upper_bound > SCHED_CAPACITY_SCALE)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void __setscheduler_uclamp(struct task_struct *p,
+				  const struct sched_attr *attr)
+{
+	if (likely(!(attr->sched_flags & SCHED_FLAG_UTIL_CLAMP)))
+		return;
+
+	if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MIN) {
+		uclamp_se_set(&p->uclamp_req[UCLAMP_MIN],
+			      attr->sched_util_min, true);
+	}
+
+	if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP_MAX) {
+		uclamp_se_set(&p->uclamp_req[UCLAMP_MAX],
+			      attr->sched_util_max, true);
+	}
+}
+
 static void uclamp_fork(struct task_struct *p)
 {
 	unsigned int clamp_id;
@@ -1048,11 +1086,11 @@ static void __init init_uclamp(void)
 
 	for_each_clamp_id(clamp_id) {
 		uclamp_se_set(&init_task.uclamp_req[clamp_id],
-			      uclamp_none(clamp_id));
+			      uclamp_none(clamp_id), false);
 	}
 
 	/* System defaults allow max clamp values for both indexes */
-	uclamp_se_set(&uc_max, uclamp_none(UCLAMP_MAX));
+	uclamp_se_set(&uc_max, uclamp_none(UCLAMP_MAX), false);
 	for_each_clamp_id(clamp_id)
 		uclamp_default[clamp_id] = uc_max;
 }
@@ -1060,6 +1098,13 @@ static void __init init_uclamp(void)
 #else /* CONFIG_UCLAMP_TASK */
 static inline void uclamp_rq_inc(struct rq *rq, struct task_struct *p) { }
 static inline void uclamp_rq_dec(struct rq *rq, struct task_struct *p) { }
+static inline int uclamp_validate(struct task_struct *p,
+				  const struct sched_attr *attr)
+{
+	return -EOPNOTSUPP;
+}
+static void __setscheduler_uclamp(struct task_struct *p,
+				  const struct sched_attr *attr) { }
 static inline void uclamp_fork(struct task_struct *p) { }
 static inline void init_uclamp(void) { }
 #endif /* CONFIG_UCLAMP_TASK */
@@ -4401,6 +4446,13 @@ static void __setscheduler_params(struct task_struct *p,
 static void __setscheduler(struct rq *rq, struct task_struct *p,
 			   const struct sched_attr *attr, bool keep_boost)
 {
+	/*
+	 * If params can't change scheduling class changes aren't allowed
+	 * either.
+	 */
+	if (attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)
+		return;
+
 	__setscheduler_params(p, attr);
 
 	/*
@@ -4538,6 +4590,13 @@ static int __sched_setscheduler(struct task_struct *p,
 			return retval;
 	}
 
+	/* Update task specific "requested" clamps */
+	if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) {
+		retval = uclamp_validate(p, attr);
+		if (retval)
+			return retval;
+	}
+
 	/*
 	 * Make sure no PI-waiters arrive (or leave) while we are
 	 * changing the priority of the task:
@@ -4567,6 +4626,8 @@ static int __sched_setscheduler(struct task_struct *p,
 			goto change;
 		if (dl_policy(policy) && dl_param_changed(p, attr))
 			goto change;
+		if (attr->sched_flags & SCHED_FLAG_UTIL_CLAMP)
+			goto change;
 
 		p->sched_reset_on_fork = reset_on_fork;
 		task_rq_unlock(rq, p, &rf);
@@ -4647,7 +4708,9 @@ static int __sched_setscheduler(struct task_struct *p,
 		put_prev_task(rq, p);
 
 	prev_class = p->sched_class;
+
 	__setscheduler(rq, p, attr, pi);
+	__setscheduler_uclamp(p, attr);
 
 	if (queued) {
 		/*
@@ -4823,6 +4886,10 @@ static int sched_copy_attr(struct sched_attr __user *uattr, struct sched_attr *a
 	if (ret)
 		return -EFAULT;
 
+	if ((attr->sched_flags & SCHED_FLAG_UTIL_CLAMP) &&
+	    size < SCHED_ATTR_SIZE_VER1)
+		return -EINVAL;
+
 	/*
 	 * XXX: Do we want to be lenient like existing syscalls; or do we want
 	 * to be strict and return an error on out-of-bounds values?
@@ -4892,10 +4959,15 @@ SYSCALL_DEFINE3(sched_setattr, pid_t, pid, struct sched_attr __user *, uattr,
 	rcu_read_lock();
 	retval = -ESRCH;
 	p = find_process_by_pid(pid);
-	if (p != NULL)
-		retval = sched_setattr(p, &attr);
+	if (likely(p))
+		get_task_struct(p);
 	rcu_read_unlock();
 
+	if (likely(p)) {
+		retval = sched_setattr(p, &attr);
+		put_task_struct(p);
+	}
+
 	return retval;
 }
 
@@ -5046,6 +5118,11 @@ SYSCALL_DEFINE4(sched_getattr, pid_t, pid, struct sched_attr __user *, uattr,
 	else
 		attr.sched_nice = task_nice(p);
 
+#ifdef CONFIG_UCLAMP_TASK
+	attr.sched_util_min = p->uclamp_req[UCLAMP_MIN].value;
+	attr.sched_util_max = p->uclamp_req[UCLAMP_MAX].value;
+#endif
+
 	rcu_read_unlock();
 
 	retval = sched_read_attr(uattr, &attr, size);
-- 
2.21.0


^ permalink raw reply related

* [PATCH v10 07/16] sched/core: uclamp: Reset uclamp values on RESET_ON_FORK
From: Patrick Bellasi @ 2019-06-21  8:42 UTC (permalink / raw)
  To: linux-kernel, linux-pm
  Cc: Ingo Molnar, Peter Zijlstra, Tejun Heo, Rafael J . Wysocki,
	Vincent Guittot, Viresh Kumar, Paul Turner, Quentin Perret,
	Dietmar Eggemann, Morten Rasmussen, Juri Lelli, Todd Kjos,
	Joel Fernandes, Steve Muckle, Suren Baghdasaryan, Alessio Balsini
In-Reply-To: <20190621084217.8167-1-patrick.bellasi@arm.com>

A forked tasks gets the same clamp values of its parent however, when
the RESET_ON_FORK flag is set on parent, e.g. via:

   sys_sched_setattr()
      sched_setattr()
         __sched_setscheduler(attr::SCHED_FLAG_RESET_ON_FORK)

the new forked task is expected to start with all attributes reset to
default values.

Do that for utilization clamp values too by checking the reset request
from the existing uclamp_fork() call which already provides the required
initialization for other uclamp related bits.

Signed-off-by: Patrick Bellasi <patrick.bellasi@arm.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
---
 kernel/sched/core.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 7d4272440890..2c49b23efc87 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -1071,6 +1071,14 @@ static void uclamp_fork(struct task_struct *p)
 
 	for_each_clamp_id(clamp_id)
 		p->uclamp[clamp_id].active = false;
+
+	if (likely(!p->sched_reset_on_fork))
+		return;
+
+	for_each_clamp_id(clamp_id) {
+		uclamp_se_set(&p->uclamp_req[clamp_id],
+			      uclamp_none(clamp_id), false);
+	}
 }
 
 static void __init init_uclamp(void)
-- 
2.21.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox