linux/drivers/gpu/drm/panfrost/panfrost_devfreq.c
Lukasz Luba 1f8644d56e drm/panfrost: Add governor data with pre-defined thresholds
The simple_ondemand devfreq governor uses two thresholds to decide about
the frequency change: upthreshold, downdifferential. These two tunable
change the behavior of the governor decision, e.g. how fast to increase
the frequency or how rapidly limit the frequency. This patch adds needed
governor data with thresholds values gathered experimentally in different
workloads.

Signed-off-by: Lukasz Luba <lukasz.luba@arm.com>
Reviewed-by: Steven Price <steven.price@arm.com>
Signed-off-by: Steven Price <steven.price@arm.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20210121170445.19761-1-lukasz.luba@arm.com
2021-01-22 14:13:31 +00:00

234 lines
5.5 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright 2019 Collabora ltd. */
#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/devfreq_cooling.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include "panfrost_device.h"
#include "panfrost_devfreq.h"
static void panfrost_devfreq_update_utilization(struct panfrost_devfreq *pfdevfreq)
{
ktime_t now, last;
now = ktime_get();
last = pfdevfreq->time_last_update;
if (pfdevfreq->busy_count > 0)
pfdevfreq->busy_time += ktime_sub(now, last);
else
pfdevfreq->idle_time += ktime_sub(now, last);
pfdevfreq->time_last_update = now;
}
static int panfrost_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct dev_pm_opp *opp;
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp))
return PTR_ERR(opp);
dev_pm_opp_put(opp);
return dev_pm_opp_set_rate(dev, *freq);
}
static void panfrost_devfreq_reset(struct panfrost_devfreq *pfdevfreq)
{
pfdevfreq->busy_time = 0;
pfdevfreq->idle_time = 0;
pfdevfreq->time_last_update = ktime_get();
}
static int panfrost_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *status)
{
struct panfrost_device *pfdev = dev_get_drvdata(dev);
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
unsigned long irqflags;
status->current_frequency = clk_get_rate(pfdev->clock);
spin_lock_irqsave(&pfdevfreq->lock, irqflags);
panfrost_devfreq_update_utilization(pfdevfreq);
status->total_time = ktime_to_ns(ktime_add(pfdevfreq->busy_time,
pfdevfreq->idle_time));
status->busy_time = ktime_to_ns(pfdevfreq->busy_time);
panfrost_devfreq_reset(pfdevfreq);
spin_unlock_irqrestore(&pfdevfreq->lock, irqflags);
dev_dbg(pfdev->dev, "busy %lu total %lu %lu %% freq %lu MHz\n",
status->busy_time, status->total_time,
status->busy_time / (status->total_time / 100),
status->current_frequency / 1000 / 1000);
return 0;
}
static struct devfreq_dev_profile panfrost_devfreq_profile = {
.timer = DEVFREQ_TIMER_DELAYED,
.polling_ms = 50, /* ~3 frames */
.target = panfrost_devfreq_target,
.get_dev_status = panfrost_devfreq_get_dev_status,
};
int panfrost_devfreq_init(struct panfrost_device *pfdev)
{
int ret;
struct dev_pm_opp *opp;
unsigned long cur_freq;
struct device *dev = &pfdev->pdev->dev;
struct devfreq *devfreq;
struct opp_table *opp_table;
struct thermal_cooling_device *cooling;
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
opp_table = dev_pm_opp_set_regulators(dev, pfdev->comp->supply_names,
pfdev->comp->num_supplies);
if (IS_ERR(opp_table)) {
ret = PTR_ERR(opp_table);
/* Continue if the optional regulator is missing */
if (ret != -ENODEV) {
DRM_DEV_ERROR(dev, "Couldn't set OPP regulators\n");
goto err_fini;
}
} else {
pfdevfreq->regulators_opp_table = opp_table;
}
ret = dev_pm_opp_of_add_table(dev);
if (ret) {
/* Optional, continue without devfreq */
if (ret == -ENODEV)
ret = 0;
goto err_fini;
}
pfdevfreq->opp_of_table_added = true;
spin_lock_init(&pfdevfreq->lock);
panfrost_devfreq_reset(pfdevfreq);
cur_freq = clk_get_rate(pfdev->clock);
opp = devfreq_recommended_opp(dev, &cur_freq, 0);
if (IS_ERR(opp)) {
ret = PTR_ERR(opp);
goto err_fini;
}
panfrost_devfreq_profile.initial_freq = cur_freq;
dev_pm_opp_put(opp);
/*
* Setup default thresholds for the simple_ondemand governor.
* The values are chosen based on experiments.
*/
pfdevfreq->gov_data.upthreshold = 45;
pfdevfreq->gov_data.downdifferential = 5;
devfreq = devm_devfreq_add_device(dev, &panfrost_devfreq_profile,
DEVFREQ_GOV_SIMPLE_ONDEMAND,
&pfdevfreq->gov_data);
if (IS_ERR(devfreq)) {
DRM_DEV_ERROR(dev, "Couldn't initialize GPU devfreq\n");
ret = PTR_ERR(devfreq);
goto err_fini;
}
pfdevfreq->devfreq = devfreq;
cooling = devfreq_cooling_em_register(devfreq, NULL);
if (IS_ERR(cooling))
DRM_DEV_INFO(dev, "Failed to register cooling device\n");
else
pfdevfreq->cooling = cooling;
return 0;
err_fini:
panfrost_devfreq_fini(pfdev);
return ret;
}
void panfrost_devfreq_fini(struct panfrost_device *pfdev)
{
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
if (pfdevfreq->cooling) {
devfreq_cooling_unregister(pfdevfreq->cooling);
pfdevfreq->cooling = NULL;
}
if (pfdevfreq->opp_of_table_added) {
dev_pm_opp_of_remove_table(&pfdev->pdev->dev);
pfdevfreq->opp_of_table_added = false;
}
dev_pm_opp_put_regulators(pfdevfreq->regulators_opp_table);
pfdevfreq->regulators_opp_table = NULL;
}
void panfrost_devfreq_resume(struct panfrost_device *pfdev)
{
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
if (!pfdevfreq->devfreq)
return;
panfrost_devfreq_reset(pfdevfreq);
devfreq_resume_device(pfdevfreq->devfreq);
}
void panfrost_devfreq_suspend(struct panfrost_device *pfdev)
{
struct panfrost_devfreq *pfdevfreq = &pfdev->pfdevfreq;
if (!pfdevfreq->devfreq)
return;
devfreq_suspend_device(pfdevfreq->devfreq);
}
void panfrost_devfreq_record_busy(struct panfrost_devfreq *pfdevfreq)
{
unsigned long irqflags;
if (!pfdevfreq->devfreq)
return;
spin_lock_irqsave(&pfdevfreq->lock, irqflags);
panfrost_devfreq_update_utilization(pfdevfreq);
pfdevfreq->busy_count++;
spin_unlock_irqrestore(&pfdevfreq->lock, irqflags);
}
void panfrost_devfreq_record_idle(struct panfrost_devfreq *pfdevfreq)
{
unsigned long irqflags;
if (!pfdevfreq->devfreq)
return;
spin_lock_irqsave(&pfdevfreq->lock, irqflags);
panfrost_devfreq_update_utilization(pfdevfreq);
WARN_ON(--pfdevfreq->busy_count < 0);
spin_unlock_irqrestore(&pfdevfreq->lock, irqflags);
}