2021-07-26 22:46:48 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2013 Red Hat
|
|
|
|
* Author: Rob Clark <robdclark@gmail.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "msm_gpu.h"
|
|
|
|
#include "msm_gpu_trace.h"
|
|
|
|
|
|
|
|
#include <linux/devfreq.h>
|
|
|
|
#include <linux/devfreq_cooling.h>
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Power Management:
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int msm_devfreq_target(struct device *dev, unsigned long *freq,
|
|
|
|
u32 flags)
|
|
|
|
{
|
|
|
|
struct msm_gpu *gpu = dev_to_gpu(dev);
|
|
|
|
struct dev_pm_opp *opp;
|
|
|
|
|
|
|
|
opp = devfreq_recommended_opp(dev, freq, flags);
|
|
|
|
|
drm/msm: Devfreq tuning
This adds a few things to try and make frequency scaling better match
the workload:
1) Longer polling interval to avoid whip-lashing between too-high and
too-low frequencies in certain workloads, like mobile games which
throttle themselves to 30fps.
Previously our polling interval was short enough to let things
ramp down to minimum freq in the "off" frame, but long enough to
not react quickly enough when rendering started on the next frame,
leading to uneven frame times. (Ie. rather than a consistent 33ms
it would alternate between 16/33/48ms.)
2) Awareness of when the GPU is active vs idle. Since we know when
the GPU is active vs idle, we can clamp the frequency down to the
minimum while it is idle. (If it is idle for long enough, then
the autosuspend delay will eventually kick in and power down the
GPU.)
Since devfreq has no knowledge of powered-but-idle, this takes a
small bit of trickery to maintain a "fake" frequency while idle.
This, combined with the longer polling period allows devfreq to
arrive at a reasonable "active" frequency, while still clamping
to minimum freq when idle to reduce power draw.
3) Boost. Because simple_ondemand needs to see a certain threshold
of busyness to ramp up, we could end up needing multiple polling
cycles before it reacts appropriately on interactive workloads
(ex. scrolling a web page after reading for some time), on top
of the already lengthened polling interval, when we see a idle
to active transition after a period of idle time we boost the
frequency that we return to.
Signed-off-by: Rob Clark <robdclark@chromium.org>
Link: https://lore.kernel.org/r/20210726144653.2180096-4-robdclark@gmail.com
Signed-off-by: Rob Clark <robdclark@chromium.org>
2021-07-26 22:46:50 +08:00
|
|
|
/*
|
|
|
|
* If the GPU is idle, devfreq is not aware, so just ignore
|
|
|
|
* it's requests
|
|
|
|
*/
|
|
|
|
if (gpu->devfreq.idle_freq) {
|
|
|
|
gpu->devfreq.idle_freq = *freq;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-07-26 22:46:48 +08:00
|
|
|
if (IS_ERR(opp))
|
|
|
|
return PTR_ERR(opp);
|
|
|
|
|
|
|
|
trace_msm_gpu_freq_change(dev_pm_opp_get_freq(opp));
|
|
|
|
|
|
|
|
if (gpu->funcs->gpu_set_freq)
|
|
|
|
gpu->funcs->gpu_set_freq(gpu, opp);
|
|
|
|
else
|
|
|
|
clk_set_rate(gpu->core_clk, *freq);
|
|
|
|
|
|
|
|
dev_pm_opp_put(opp);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-07-26 22:46:49 +08:00
|
|
|
static unsigned long get_freq(struct msm_gpu *gpu)
|
|
|
|
{
|
drm/msm: Devfreq tuning
This adds a few things to try and make frequency scaling better match
the workload:
1) Longer polling interval to avoid whip-lashing between too-high and
too-low frequencies in certain workloads, like mobile games which
throttle themselves to 30fps.
Previously our polling interval was short enough to let things
ramp down to minimum freq in the "off" frame, but long enough to
not react quickly enough when rendering started on the next frame,
leading to uneven frame times. (Ie. rather than a consistent 33ms
it would alternate between 16/33/48ms.)
2) Awareness of when the GPU is active vs idle. Since we know when
the GPU is active vs idle, we can clamp the frequency down to the
minimum while it is idle. (If it is idle for long enough, then
the autosuspend delay will eventually kick in and power down the
GPU.)
Since devfreq has no knowledge of powered-but-idle, this takes a
small bit of trickery to maintain a "fake" frequency while idle.
This, combined with the longer polling period allows devfreq to
arrive at a reasonable "active" frequency, while still clamping
to minimum freq when idle to reduce power draw.
3) Boost. Because simple_ondemand needs to see a certain threshold
of busyness to ramp up, we could end up needing multiple polling
cycles before it reacts appropriately on interactive workloads
(ex. scrolling a web page after reading for some time), on top
of the already lengthened polling interval, when we see a idle
to active transition after a period of idle time we boost the
frequency that we return to.
Signed-off-by: Rob Clark <robdclark@chromium.org>
Link: https://lore.kernel.org/r/20210726144653.2180096-4-robdclark@gmail.com
Signed-off-by: Rob Clark <robdclark@chromium.org>
2021-07-26 22:46:50 +08:00
|
|
|
if (gpu->devfreq.idle_freq)
|
|
|
|
return gpu->devfreq.idle_freq;
|
|
|
|
|
2021-07-26 22:46:49 +08:00
|
|
|
if (gpu->funcs->gpu_get_freq)
|
|
|
|
return gpu->funcs->gpu_get_freq(gpu);
|
|
|
|
|
|
|
|
return clk_get_rate(gpu->core_clk);
|
|
|
|
}
|
|
|
|
|
2021-07-26 22:46:48 +08:00
|
|
|
static int msm_devfreq_get_dev_status(struct device *dev,
|
|
|
|
struct devfreq_dev_status *status)
|
|
|
|
{
|
|
|
|
struct msm_gpu *gpu = dev_to_gpu(dev);
|
|
|
|
ktime_t time;
|
|
|
|
|
2021-07-26 22:46:49 +08:00
|
|
|
status->current_frequency = get_freq(gpu);
|
2021-07-26 22:46:48 +08:00
|
|
|
status->busy_time = gpu->funcs->gpu_busy(gpu);
|
|
|
|
|
|
|
|
time = ktime_get();
|
|
|
|
status->total_time = ktime_us_delta(time, gpu->devfreq.time);
|
|
|
|
gpu->devfreq.time = time;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int msm_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
|
|
|
|
{
|
2021-07-26 22:46:49 +08:00
|
|
|
*freq = get_freq(dev_to_gpu(dev));
|
2021-07-26 22:46:48 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct devfreq_dev_profile msm_devfreq_profile = {
|
drm/msm: Devfreq tuning
This adds a few things to try and make frequency scaling better match
the workload:
1) Longer polling interval to avoid whip-lashing between too-high and
too-low frequencies in certain workloads, like mobile games which
throttle themselves to 30fps.
Previously our polling interval was short enough to let things
ramp down to minimum freq in the "off" frame, but long enough to
not react quickly enough when rendering started on the next frame,
leading to uneven frame times. (Ie. rather than a consistent 33ms
it would alternate between 16/33/48ms.)
2) Awareness of when the GPU is active vs idle. Since we know when
the GPU is active vs idle, we can clamp the frequency down to the
minimum while it is idle. (If it is idle for long enough, then
the autosuspend delay will eventually kick in and power down the
GPU.)
Since devfreq has no knowledge of powered-but-idle, this takes a
small bit of trickery to maintain a "fake" frequency while idle.
This, combined with the longer polling period allows devfreq to
arrive at a reasonable "active" frequency, while still clamping
to minimum freq when idle to reduce power draw.
3) Boost. Because simple_ondemand needs to see a certain threshold
of busyness to ramp up, we could end up needing multiple polling
cycles before it reacts appropriately on interactive workloads
(ex. scrolling a web page after reading for some time), on top
of the already lengthened polling interval, when we see a idle
to active transition after a period of idle time we boost the
frequency that we return to.
Signed-off-by: Rob Clark <robdclark@chromium.org>
Link: https://lore.kernel.org/r/20210726144653.2180096-4-robdclark@gmail.com
Signed-off-by: Rob Clark <robdclark@chromium.org>
2021-07-26 22:46:50 +08:00
|
|
|
.timer = DEVFREQ_TIMER_DELAYED,
|
|
|
|
.polling_ms = 50,
|
2021-07-26 22:46:48 +08:00
|
|
|
.target = msm_devfreq_target,
|
|
|
|
.get_dev_status = msm_devfreq_get_dev_status,
|
|
|
|
.get_cur_freq = msm_devfreq_get_cur_freq,
|
|
|
|
};
|
|
|
|
|
|
|
|
void msm_devfreq_init(struct msm_gpu *gpu)
|
|
|
|
{
|
|
|
|
/* We need target support to do devfreq */
|
|
|
|
if (!gpu->funcs->gpu_busy)
|
|
|
|
return;
|
|
|
|
|
|
|
|
msm_devfreq_profile.initial_freq = gpu->fast_rate;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't set the freq_table or max_state and let devfreq build the table
|
|
|
|
* from OPP
|
|
|
|
* After a deferred probe, these may have be left to non-zero values,
|
|
|
|
* so set them back to zero before creating the devfreq device
|
|
|
|
*/
|
|
|
|
msm_devfreq_profile.freq_table = NULL;
|
|
|
|
msm_devfreq_profile.max_state = 0;
|
|
|
|
|
|
|
|
gpu->devfreq.devfreq = devm_devfreq_add_device(&gpu->pdev->dev,
|
|
|
|
&msm_devfreq_profile, DEVFREQ_GOV_SIMPLE_ONDEMAND,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (IS_ERR(gpu->devfreq.devfreq)) {
|
|
|
|
DRM_DEV_ERROR(&gpu->pdev->dev, "Couldn't initialize GPU devfreq\n");
|
|
|
|
gpu->devfreq.devfreq = NULL;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
devfreq_suspend_device(gpu->devfreq.devfreq);
|
|
|
|
|
|
|
|
gpu->cooling = of_devfreq_cooling_register(gpu->pdev->dev.of_node,
|
|
|
|
gpu->devfreq.devfreq);
|
|
|
|
if (IS_ERR(gpu->cooling)) {
|
|
|
|
DRM_DEV_ERROR(&gpu->pdev->dev,
|
|
|
|
"Couldn't register GPU cooling device\n");
|
|
|
|
gpu->cooling = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void msm_devfreq_cleanup(struct msm_gpu *gpu)
|
|
|
|
{
|
|
|
|
devfreq_cooling_unregister(gpu->cooling);
|
|
|
|
}
|
|
|
|
|
|
|
|
void msm_devfreq_resume(struct msm_gpu *gpu)
|
|
|
|
{
|
|
|
|
gpu->devfreq.busy_cycles = 0;
|
|
|
|
gpu->devfreq.time = ktime_get();
|
|
|
|
|
|
|
|
devfreq_resume_device(gpu->devfreq.devfreq);
|
|
|
|
}
|
|
|
|
|
|
|
|
void msm_devfreq_suspend(struct msm_gpu *gpu)
|
|
|
|
{
|
|
|
|
devfreq_suspend_device(gpu->devfreq.devfreq);
|
|
|
|
}
|
drm/msm: Devfreq tuning
This adds a few things to try and make frequency scaling better match
the workload:
1) Longer polling interval to avoid whip-lashing between too-high and
too-low frequencies in certain workloads, like mobile games which
throttle themselves to 30fps.
Previously our polling interval was short enough to let things
ramp down to minimum freq in the "off" frame, but long enough to
not react quickly enough when rendering started on the next frame,
leading to uneven frame times. (Ie. rather than a consistent 33ms
it would alternate between 16/33/48ms.)
2) Awareness of when the GPU is active vs idle. Since we know when
the GPU is active vs idle, we can clamp the frequency down to the
minimum while it is idle. (If it is idle for long enough, then
the autosuspend delay will eventually kick in and power down the
GPU.)
Since devfreq has no knowledge of powered-but-idle, this takes a
small bit of trickery to maintain a "fake" frequency while idle.
This, combined with the longer polling period allows devfreq to
arrive at a reasonable "active" frequency, while still clamping
to minimum freq when idle to reduce power draw.
3) Boost. Because simple_ondemand needs to see a certain threshold
of busyness to ramp up, we could end up needing multiple polling
cycles before it reacts appropriately on interactive workloads
(ex. scrolling a web page after reading for some time), on top
of the already lengthened polling interval, when we see a idle
to active transition after a period of idle time we boost the
frequency that we return to.
Signed-off-by: Rob Clark <robdclark@chromium.org>
Link: https://lore.kernel.org/r/20210726144653.2180096-4-robdclark@gmail.com
Signed-off-by: Rob Clark <robdclark@chromium.org>
2021-07-26 22:46:50 +08:00
|
|
|
|
|
|
|
void msm_devfreq_active(struct msm_gpu *gpu)
|
|
|
|
{
|
|
|
|
struct msm_gpu_devfreq *df = &gpu->devfreq;
|
|
|
|
struct devfreq_dev_status status;
|
|
|
|
unsigned int idle_time;
|
|
|
|
unsigned long target_freq = df->idle_freq;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hold devfreq lock to synchronize with get_dev_status()/
|
|
|
|
* target() callbacks
|
|
|
|
*/
|
|
|
|
mutex_lock(&df->devfreq->lock);
|
|
|
|
|
|
|
|
idle_time = ktime_to_ms(ktime_sub(ktime_get(), df->idle_time));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we've been idle for a significant fraction of a polling
|
|
|
|
* interval, then we won't meet the threshold of busyness for
|
|
|
|
* the governor to ramp up the freq.. so give some boost
|
|
|
|
*/
|
|
|
|
if (idle_time > msm_devfreq_profile.polling_ms/2) {
|
|
|
|
target_freq *= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
df->idle_freq = 0;
|
|
|
|
|
|
|
|
msm_devfreq_target(&gpu->pdev->dev, &target_freq, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reset the polling interval so we aren't inconsistent
|
|
|
|
* about freq vs busy/total cycles
|
|
|
|
*/
|
|
|
|
msm_devfreq_get_dev_status(&gpu->pdev->dev, &status);
|
|
|
|
|
|
|
|
mutex_unlock(&df->devfreq->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
void msm_devfreq_idle(struct msm_gpu *gpu)
|
|
|
|
{
|
|
|
|
struct msm_gpu_devfreq *df = &gpu->devfreq;
|
|
|
|
unsigned long idle_freq, target_freq = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hold devfreq lock to synchronize with get_dev_status()/
|
|
|
|
* target() callbacks
|
|
|
|
*/
|
|
|
|
mutex_lock(&df->devfreq->lock);
|
|
|
|
|
|
|
|
idle_freq = get_freq(gpu);
|
|
|
|
|
|
|
|
msm_devfreq_target(&gpu->pdev->dev, &target_freq, 0);
|
|
|
|
|
|
|
|
df->idle_time = ktime_get();
|
|
|
|
df->idle_freq = idle_freq;
|
|
|
|
|
|
|
|
mutex_unlock(&df->devfreq->lock);
|
|
|
|
}
|