mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
devfreq: Get rid of unused CONFIGs
Remove the config variables as there is no Kconfig to define these configs: CONFIG_DEVFREQ_GOV_WMARK_SIMPLE CONFIG_DEVFREQ_GOV_WMARK_ACTIVE Remove the handling of non-module build as devfreq is always build as module. Remove the wmark-related devfreq governors as we plan to keep them under drivers/gpu/drm/tegra. They are tightly bound with the actmon and drm drivers. Bug 4074863 Signed-off-by: Johnny Liu <johnliu@nvidia.com> Change-Id: Iba8f5da770d86ddcfb6315f72fd74fc9a781ab39 Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2893701 Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com> GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
committed by
mobile promotions
parent
8fd904b84e
commit
85c4d41d99
@@ -1,15 +1,4 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
|
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
|
||||||
ccflags-y += -I$(srctree)/drivers/devfreq
|
obj-m += governor_pod_scaling.o
|
||||||
|
|
||||||
obj-$(CONFIG_DEVFREQ_GOV_WMARK_SIMPLE) += governor_wmark_simple.o
|
|
||||||
obj-$(CONFIG_DEVFREQ_GOV_WMARK_ACTIVE) += governor_wmark_active.o
|
|
||||||
|
|
||||||
ifeq ($(CONFIG_TEGRA_OOT_MODULE),m)
|
|
||||||
ccflags-y += -DGOVERNOR_POD_SCALING_V2_MODULE
|
|
||||||
obj-m += governor_pod_scaling_v2.o
|
|
||||||
else
|
|
||||||
obj-$(CONFIG_DEVFREQ_GOV_POD_SCALING) += governor_pod_scaling.o
|
|
||||||
obj-$(CONFIG_DEVFREQ_GOV_POD_SCALING_V2) += governor_pod_scaling_v2.o
|
|
||||||
endif
|
|
||||||
|
|||||||
@@ -13,22 +13,21 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/devfreq.h>
|
|
||||||
#include <linux/debugfs.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/export.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/clk/tegra.h>
|
#include <linux/clk/tegra.h>
|
||||||
|
#include <linux/debugfs.h>
|
||||||
|
#include <linux/devfreq.h>
|
||||||
|
#include <linux/export.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <linux/version.h>
|
||||||
#define CREATE_TRACE_POINTS
|
#define CREATE_TRACE_POINTS
|
||||||
#include <trace/events/nvhost_podgov.h>
|
#include <trace/events/nvhost_podgov.h>
|
||||||
|
|
||||||
#include "governor.h"
|
#include <drivers-private/devfreq/governor.h>
|
||||||
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/pm_runtime.h>
|
|
||||||
|
|
||||||
#define GET_TARGET_FREQ_DONTSCALE 1
|
#define GET_TARGET_FREQ_DONTSCALE 1
|
||||||
|
|
||||||
@@ -108,6 +107,45 @@ enum podgov_adjustment_type {
|
|||||||
ADJUSTMENT_DEVICE_REQ = 1
|
ADJUSTMENT_DEVICE_REQ = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define HZ_PER_KHZ 1000
|
||||||
|
|
||||||
|
static void get_freq_range(struct devfreq *devfreq,
|
||||||
|
unsigned long *min_freq,
|
||||||
|
unsigned long *max_freq)
|
||||||
|
{
|
||||||
|
unsigned long *freq_table = devfreq->profile->freq_table;
|
||||||
|
|
||||||
|
lockdep_assert_held(&devfreq->lock);
|
||||||
|
|
||||||
|
if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
|
||||||
|
*min_freq = freq_table[0];
|
||||||
|
*max_freq = freq_table[devfreq->profile->max_state - 1];
|
||||||
|
} else {
|
||||||
|
*min_freq = freq_table[devfreq->profile->max_state - 1];
|
||||||
|
*max_freq = freq_table[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply constraints from OPP interface */
|
||||||
|
*min_freq = max(*min_freq, devfreq->scaling_min_freq);
|
||||||
|
*max_freq = min(*max_freq, devfreq->scaling_max_freq);
|
||||||
|
|
||||||
|
if (*min_freq > *max_freq)
|
||||||
|
*min_freq = *max_freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_min_freq_limit(struct devfreq *df, unsigned long *min_freq_hz)
|
||||||
|
{
|
||||||
|
unsigned long max_freq_hz;
|
||||||
|
|
||||||
|
get_freq_range(df, min_freq_hz, &max_freq_hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_max_freq_limit(struct devfreq *df, unsigned long *max_freq_hz)
|
||||||
|
{
|
||||||
|
unsigned long min_freq_hz;
|
||||||
|
|
||||||
|
get_freq_range(df, &min_freq_hz, max_freq_hz);
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* scaling_limit(df, freq)
|
* scaling_limit(df, freq)
|
||||||
@@ -117,10 +155,16 @@ enum podgov_adjustment_type {
|
|||||||
|
|
||||||
static void scaling_limit(struct devfreq *df, unsigned long *freq)
|
static void scaling_limit(struct devfreq *df, unsigned long *freq)
|
||||||
{
|
{
|
||||||
if (*freq < df->min_freq)
|
unsigned long min_freq_hz = 0;
|
||||||
*freq = df->min_freq;
|
unsigned long max_freq_hz = 0;
|
||||||
else if (*freq > df->max_freq)
|
|
||||||
*freq = df->max_freq;
|
get_min_freq_limit(df, &min_freq_hz);
|
||||||
|
get_max_freq_limit(df, &max_freq_hz);
|
||||||
|
|
||||||
|
if (*freq < min_freq_hz)
|
||||||
|
*freq = min_freq_hz;
|
||||||
|
else if (*freq > max_freq_hz)
|
||||||
|
*freq = max_freq_hz;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@@ -145,14 +189,6 @@ static void podgov_enable(struct devfreq *df, int enable)
|
|||||||
|
|
||||||
trace_podgov_enabled(df->dev.parent, enable);
|
trace_podgov_enabled(df->dev.parent, enable);
|
||||||
|
|
||||||
/* bad configuration. quit. */
|
|
||||||
if (df->min_freq == df->max_freq) {
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
mutex_unlock(&podgov->lock);
|
|
||||||
pm_runtime_put(dev);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* store the enable information */
|
/* store the enable information */
|
||||||
podgov->enable = enable;
|
podgov->enable = enable;
|
||||||
|
|
||||||
@@ -160,7 +196,7 @@ static void podgov_enable(struct devfreq *df, int enable)
|
|||||||
* suspended */
|
* suspended */
|
||||||
if (!enable && pm_runtime_active(dev)) {
|
if (!enable && pm_runtime_active(dev)) {
|
||||||
/* full speed */
|
/* full speed */
|
||||||
podgov->adjustment_frequency = df->max_freq;
|
get_max_freq_limit(df, &podgov->adjustment_frequency);
|
||||||
podgov->adjustment_type = ADJUSTMENT_LOCAL;
|
podgov->adjustment_type = ADJUSTMENT_LOCAL;
|
||||||
update_devfreq(df);
|
update_devfreq(df);
|
||||||
}
|
}
|
||||||
@@ -284,6 +320,7 @@ static unsigned long scaling_state_check(struct devfreq *df, ktime_t time)
|
|||||||
struct devfreq_dev_status *ds = &df->last_status;
|
struct devfreq_dev_status *ds = &df->last_status;
|
||||||
unsigned long dt, busyness, rt_load = pg->rt_load;
|
unsigned long dt, busyness, rt_load = pg->rt_load;
|
||||||
long max_boost, damp, freq, boost, res;
|
long max_boost, damp, freq, boost, res;
|
||||||
|
unsigned long max_freq_hz = 0;
|
||||||
|
|
||||||
dt = (unsigned long) ktime_us_delta(time, pg->last_scale);
|
dt = (unsigned long) ktime_us_delta(time, pg->last_scale);
|
||||||
if (dt < pg->p_block_window || df->previous_freq == 0)
|
if (dt < pg->p_block_window || df->previous_freq == 0)
|
||||||
@@ -291,7 +328,8 @@ static unsigned long scaling_state_check(struct devfreq *df, ktime_t time)
|
|||||||
|
|
||||||
/* convert to mhz to avoid overflow */
|
/* convert to mhz to avoid overflow */
|
||||||
freq = df->previous_freq / 1000000;
|
freq = df->previous_freq / 1000000;
|
||||||
max_boost = (df->max_freq/3) / 1000000;
|
get_max_freq_limit(df, &max_freq_hz);
|
||||||
|
max_boost = ((max_freq_hz / 3) / 1000000);
|
||||||
|
|
||||||
/* calculate and trace load */
|
/* calculate and trace load */
|
||||||
busyness = 1000ULL * pg->cycles_avg / ds->current_frequency;
|
busyness = 1000ULL * pg->cycles_avg / ds->current_frequency;
|
||||||
@@ -366,11 +404,12 @@ static int freqlist_up(struct podgov_info_rec *podgov, unsigned long target,
|
|||||||
static void nvhost_scale_emc_debug_init(struct devfreq *df)
|
static void nvhost_scale_emc_debug_init(struct devfreq *df)
|
||||||
{
|
{
|
||||||
struct podgov_info_rec *podgov = df->data;
|
struct podgov_info_rec *podgov = df->data;
|
||||||
struct dentry *f;
|
|
||||||
char dirname[128];
|
char dirname[128];
|
||||||
|
int err;
|
||||||
|
|
||||||
snprintf(dirname, sizeof(dirname), "%s_scaling",
|
err = snprintf(dirname, sizeof(dirname), "%s_scaling",
|
||||||
to_platform_device(df->dev.parent)->name);
|
to_platform_device(df->dev.parent)->name);
|
||||||
|
WARN_ON(err < 0);
|
||||||
|
|
||||||
if (!podgov)
|
if (!podgov)
|
||||||
return;
|
return;
|
||||||
@@ -383,12 +422,8 @@ static void nvhost_scale_emc_debug_init(struct devfreq *df)
|
|||||||
|
|
||||||
#define CREATE_PODGOV_FILE(fname) \
|
#define CREATE_PODGOV_FILE(fname) \
|
||||||
do {\
|
do {\
|
||||||
f = debugfs_create_u32(#fname, S_IRUGO | S_IWUSR, \
|
debugfs_create_u32(#fname, S_IRUGO | S_IWUSR, \
|
||||||
podgov->debugdir, &podgov->p_##fname); \
|
podgov->debugdir, &podgov->p_##fname); \
|
||||||
if (NULL == f) { \
|
|
||||||
pr_err("podgov: can\'t create file " #fname "\n"); \
|
|
||||||
return; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
CREATE_PODGOV_FILE(block_window);
|
CREATE_PODGOV_FILE(block_window);
|
||||||
@@ -433,6 +468,7 @@ static ssize_t enable_3d_scaling_show(struct kobject *kobj,
|
|||||||
ssize_t res;
|
ssize_t res;
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->enable);
|
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->enable);
|
||||||
|
WARN_ON(res < 0);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -469,6 +505,7 @@ static ssize_t user_show(struct kobject *kobj,
|
|||||||
ssize_t res;
|
ssize_t res;
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->p_user);
|
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->p_user);
|
||||||
|
WARN_ON(res < 0);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -498,6 +535,7 @@ static ssize_t freq_request_show(struct kobject *kobj,
|
|||||||
ssize_t res;
|
ssize_t res;
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->p_freq_request);
|
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->p_freq_request);
|
||||||
|
WARN_ON(res < 0);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -549,7 +587,7 @@ static int nvhost_pod_estimate_freq(struct devfreq *df,
|
|||||||
* min freq.
|
* min freq.
|
||||||
*/
|
*/
|
||||||
if (pg->suspended) {
|
if (pg->suspended) {
|
||||||
*freq = df->min_freq;
|
*freq = DEVFREQ_MIN_FREQ;
|
||||||
pg->last_scale = ktime_get();
|
pg->last_scale = ktime_get();
|
||||||
i = 0;
|
i = 0;
|
||||||
for (; i < MAX_HISTORY_BUF_SIZE; i++)
|
for (; i < MAX_HISTORY_BUF_SIZE; i++)
|
||||||
@@ -563,7 +601,7 @@ static int nvhost_pod_estimate_freq(struct devfreq *df,
|
|||||||
|
|
||||||
/* Ensure maximal clock when scaling is disabled */
|
/* Ensure maximal clock when scaling is disabled */
|
||||||
if (!pg->enable) {
|
if (!pg->enable) {
|
||||||
*freq = df->max_freq;
|
*freq = DEVFREQ_MAX_FREQ;
|
||||||
if (*freq == df->previous_freq)
|
if (*freq == df->previous_freq)
|
||||||
return GET_TARGET_FREQ_DONTSCALE;
|
return GET_TARGET_FREQ_DONTSCALE;
|
||||||
else
|
else
|
||||||
@@ -735,9 +773,7 @@ static int nvhost_pod_init(struct devfreq *df)
|
|||||||
goto err_get_freqs;
|
goto err_get_freqs;
|
||||||
|
|
||||||
/* store the limits */
|
/* store the limits */
|
||||||
df->min_freq = podgov->freqlist[0];
|
podgov->p_freq_request = podgov->freqlist[podgov->freq_count - 1];
|
||||||
df->max_freq = podgov->freqlist[podgov->freq_count - 1];
|
|
||||||
podgov->p_freq_request = df->max_freq;
|
|
||||||
|
|
||||||
podgov->freq_avg = 0;
|
podgov->freq_avg = 0;
|
||||||
|
|
||||||
@@ -803,10 +839,9 @@ static void nvhost_pod_suspend(struct devfreq *df)
|
|||||||
|
|
||||||
// Update frequency for the final time before going into suspension.
|
// Update frequency for the final time before going into suspension.
|
||||||
mutex_lock(&df->lock);
|
mutex_lock(&df->lock);
|
||||||
df->suspended = false;
|
|
||||||
update_devfreq(df);
|
update_devfreq(df);
|
||||||
df->suspended = true;
|
|
||||||
mutex_unlock(&df->lock);
|
mutex_unlock(&df->lock);
|
||||||
|
|
||||||
devfreq_monitor_suspend(df);
|
devfreq_monitor_suspend(df);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,8 +874,8 @@ static int nvhost_pod_event_handler(struct devfreq *df,
|
|||||||
case DEVFREQ_GOV_STOP:
|
case DEVFREQ_GOV_STOP:
|
||||||
nvhost_pod_exit(df);
|
nvhost_pod_exit(df);
|
||||||
break;
|
break;
|
||||||
case DEVFREQ_GOV_INTERVAL:
|
case DEVFREQ_GOV_UPDATE_INTERVAL:
|
||||||
devfreq_interval_update(df, (unsigned int *)data);
|
devfreq_update_interval(df, (unsigned int *)data);
|
||||||
break;
|
break;
|
||||||
case DEVFREQ_GOV_SUSPEND:
|
case DEVFREQ_GOV_SUSPEND:
|
||||||
nvhost_pod_suspend(df);
|
nvhost_pod_suspend(df);
|
||||||
|
|||||||
@@ -1,943 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2012-2023, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Power-on-demand clock scaling for nvhost devices
|
|
||||||
*
|
|
||||||
* devfreq calls nvhost_pod_estimate_freq() for estimating the new
|
|
||||||
* frequency for the device. The clocking is done using the load of the device
|
|
||||||
* is estimated using the busy times from the device profile. This information
|
|
||||||
* indicates if the device frequency should be altered.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/devfreq.h>
|
|
||||||
#include <linux/debugfs.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/clk.h>
|
|
||||||
#include <linux/export.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/clk/tegra.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/version.h>
|
|
||||||
#define CREATE_TRACE_POINTS
|
|
||||||
#include <trace/events/nvhost_podgov.h>
|
|
||||||
|
|
||||||
#ifndef GOVERNOR_POD_SCALING_V2_MODULE
|
|
||||||
#include "governor.h"
|
|
||||||
#else
|
|
||||||
#include "governor_v2.h"
|
|
||||||
#endif // GOVERNOR_POD_SCALING_V2_MODULE
|
|
||||||
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/pm_runtime.h>
|
|
||||||
|
|
||||||
#define GET_TARGET_FREQ_DONTSCALE 1
|
|
||||||
|
|
||||||
#ifdef CONFIG_DEVFREQ_GOV_POD_SCALING_HISTORY_BUFFER_SIZE_MAX
|
|
||||||
#define MAX_HISTORY_BUF_SIZE \
|
|
||||||
CONFIG_DEVFREQ_GOV_POD_SCALING_HISTORY_BUFFER_SIZE_MAX
|
|
||||||
#else
|
|
||||||
#define MAX_HISTORY_BUF_SIZE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void podgov_enable(struct devfreq *df, int enable);
|
|
||||||
static void podgov_set_user_ctl(struct devfreq *df, int enable);
|
|
||||||
|
|
||||||
static struct devfreq_governor nvhost_podgov;
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* podgov_info_rec - gr3d scaling governor specific parameters
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
struct podgov_info_rec {
|
|
||||||
int enable;
|
|
||||||
int suspended;
|
|
||||||
int init;
|
|
||||||
|
|
||||||
ktime_t last_scale;
|
|
||||||
|
|
||||||
unsigned int p_block_window;
|
|
||||||
unsigned int p_smooth;
|
|
||||||
int p_damp;
|
|
||||||
int p_load_max;
|
|
||||||
int p_load_target;
|
|
||||||
int p_bias;
|
|
||||||
unsigned int p_user;
|
|
||||||
unsigned int p_freq_request;
|
|
||||||
|
|
||||||
unsigned long cycles_norm;
|
|
||||||
unsigned long cycles_avg;
|
|
||||||
|
|
||||||
unsigned long *cycles_history_buf;
|
|
||||||
int p_history_buf_size;
|
|
||||||
int history_next;
|
|
||||||
int history_count;
|
|
||||||
unsigned long recent_high;
|
|
||||||
|
|
||||||
unsigned long rt_load;
|
|
||||||
|
|
||||||
int adjustment_type;
|
|
||||||
unsigned long adjustment_frequency;
|
|
||||||
|
|
||||||
struct devfreq *power_manager;
|
|
||||||
struct dentry *debugdir;
|
|
||||||
|
|
||||||
unsigned long *freqlist;
|
|
||||||
int freq_count;
|
|
||||||
|
|
||||||
int freq_avg;
|
|
||||||
|
|
||||||
struct kobj_attribute enable_3d_scaling_attr;
|
|
||||||
struct kobj_attribute user_attr;
|
|
||||||
struct kobj_attribute freq_request_attr;
|
|
||||||
|
|
||||||
struct mutex lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* Adjustment type is used to tell the source that requested frequency re-
|
|
||||||
* estimation. Type ADJUSTMENT_LOCAL indicates that the re-estimation was
|
|
||||||
* initiated by the governor itself. This happens when one of the worker
|
|
||||||
* threads want to adjust the frequency.
|
|
||||||
*
|
|
||||||
* ADJUSTMENT_DEVICE_REQ (default value) indicates that the adjustment was
|
|
||||||
* initiated by a device event.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
enum podgov_adjustment_type {
|
|
||||||
ADJUSTMENT_LOCAL = 0,
|
|
||||||
ADJUSTMENT_DEVICE_REQ = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
#define HZ_PER_KHZ 1000
|
|
||||||
|
|
||||||
#ifdef GOVERNOR_POD_SCALING_V2_MODULE
|
|
||||||
static void get_freq_range(struct devfreq *devfreq,
|
|
||||||
unsigned long *min_freq,
|
|
||||||
unsigned long *max_freq)
|
|
||||||
{
|
|
||||||
unsigned long *freq_table = devfreq->profile->freq_table;
|
|
||||||
|
|
||||||
lockdep_assert_held(&devfreq->lock);
|
|
||||||
|
|
||||||
if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
|
|
||||||
*min_freq = freq_table[0];
|
|
||||||
*max_freq = freq_table[devfreq->profile->max_state - 1];
|
|
||||||
} else {
|
|
||||||
*min_freq = freq_table[devfreq->profile->max_state - 1];
|
|
||||||
*max_freq = freq_table[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Apply constraints from OPP interface */
|
|
||||||
*min_freq = max(*min_freq, devfreq->scaling_min_freq);
|
|
||||||
*max_freq = min(*max_freq, devfreq->scaling_max_freq);
|
|
||||||
|
|
||||||
if (*min_freq > *max_freq)
|
|
||||||
*min_freq = *max_freq;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_min_freq_limit(struct devfreq *df, unsigned long *min_freq_hz)
|
|
||||||
{
|
|
||||||
unsigned long max_freq_hz;
|
|
||||||
|
|
||||||
get_freq_range(df, min_freq_hz, &max_freq_hz);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_max_freq_limit(struct devfreq *df, unsigned long *max_freq_hz)
|
|
||||||
{
|
|
||||||
unsigned long min_freq_hz;
|
|
||||||
|
|
||||||
get_freq_range(df, &min_freq_hz, max_freq_hz);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static void get_min_freq_limit(struct devfreq *df, unsigned long *min_freq_hz)
|
|
||||||
{
|
|
||||||
s32 qos_min_freq = 0;
|
|
||||||
|
|
||||||
/* Apply constraints from PM QoS */
|
|
||||||
qos_min_freq = dev_pm_qos_read_value(df->dev.parent,
|
|
||||||
DEV_PM_QOS_MIN_FREQUENCY);
|
|
||||||
|
|
||||||
*min_freq_hz = (unsigned long)HZ_PER_KHZ * qos_min_freq;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_max_freq_limit(struct devfreq *df, unsigned long *max_freq_hz)
|
|
||||||
{
|
|
||||||
s32 qos_max_freq = 0;
|
|
||||||
|
|
||||||
/* Apply constraints from PM QoS */
|
|
||||||
qos_max_freq = dev_pm_qos_read_value(df->dev.parent,
|
|
||||||
DEV_PM_QOS_MAX_FREQUENCY);
|
|
||||||
|
|
||||||
*max_freq_hz = (unsigned long)HZ_PER_KHZ * qos_max_freq;
|
|
||||||
}
|
|
||||||
#endif // GOVERNOR_POD_SCALING_V2_MODULE
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* scaling_limit(df, freq)
|
|
||||||
*
|
|
||||||
* Limit the given frequency
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static void scaling_limit(struct devfreq *df, unsigned long *freq)
|
|
||||||
{
|
|
||||||
unsigned long min_freq_hz = 0;
|
|
||||||
unsigned long max_freq_hz = 0;
|
|
||||||
|
|
||||||
get_min_freq_limit(df, &min_freq_hz);
|
|
||||||
get_max_freq_limit(df, &max_freq_hz);
|
|
||||||
|
|
||||||
if (*freq < min_freq_hz)
|
|
||||||
*freq = min_freq_hz;
|
|
||||||
else if (*freq > max_freq_hz)
|
|
||||||
*freq = max_freq_hz;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* podgov_enable(dev, enable)
|
|
||||||
*
|
|
||||||
* This function enables (enable=1) or disables (enable=0) the automatic scaling
|
|
||||||
* of the device. If the device is disabled, the device's clock is set to its
|
|
||||||
* maximum.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static void podgov_enable(struct devfreq *df, int enable)
|
|
||||||
{
|
|
||||||
struct device *dev = df->dev.parent;
|
|
||||||
struct podgov_info_rec *podgov = df->data;
|
|
||||||
bool polling;
|
|
||||||
|
|
||||||
/* make sure the device is alive before doing any scaling */
|
|
||||||
pm_runtime_get_noresume(dev);
|
|
||||||
|
|
||||||
mutex_lock(&podgov->lock);
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
|
|
||||||
trace_podgov_enabled(df->dev.parent, enable);
|
|
||||||
|
|
||||||
/* store the enable information */
|
|
||||||
podgov->enable = enable;
|
|
||||||
|
|
||||||
/* skip local adjustment if we are enabling or the device is
|
|
||||||
* suspended */
|
|
||||||
if (!enable && pm_runtime_active(dev)) {
|
|
||||||
/* full speed */
|
|
||||||
get_max_freq_limit(df, &podgov->adjustment_frequency);
|
|
||||||
podgov->adjustment_type = ADJUSTMENT_LOCAL;
|
|
||||||
update_devfreq(df);
|
|
||||||
}
|
|
||||||
|
|
||||||
polling = podgov->enable && !podgov->p_user;
|
|
||||||
|
|
||||||
/* Need to unlock to call devfreq_monitor_suspend/resume()
|
|
||||||
* still holding podgov->lock to guarantee atomicity
|
|
||||||
*/
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
|
|
||||||
if (polling)
|
|
||||||
devfreq_monitor_resume(df);
|
|
||||||
else
|
|
||||||
devfreq_monitor_suspend(df);
|
|
||||||
|
|
||||||
mutex_unlock(&podgov->lock);
|
|
||||||
pm_runtime_put(dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************************************************************************
|
|
||||||
* podgov_set_user_ctl(dev, user)
|
|
||||||
*
|
|
||||||
* This function enables or disables user control of the gpu. If user control
|
|
||||||
* is enabled, setting the freq_request controls the gpu frequency, and other
|
|
||||||
* gpu scaling mechanisms are disabled.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static void podgov_set_user_ctl(struct devfreq *df, int user)
|
|
||||||
{
|
|
||||||
struct device *dev = df->dev.parent;
|
|
||||||
struct podgov_info_rec *podgov = df->data;
|
|
||||||
int old_user;
|
|
||||||
bool polling;
|
|
||||||
|
|
||||||
/* make sure the device is alive before doing any scaling */
|
|
||||||
pm_runtime_get_noresume(dev);
|
|
||||||
|
|
||||||
mutex_lock(&podgov->lock);
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
|
|
||||||
trace_podgov_set_user_ctl(df->dev.parent, user);
|
|
||||||
|
|
||||||
/* store the new user value */
|
|
||||||
old_user = podgov->p_user;
|
|
||||||
podgov->p_user = user;
|
|
||||||
|
|
||||||
/* skip scaling, if scaling (or the whole device) is turned off
|
|
||||||
* - or the scaling already was in user mode */
|
|
||||||
if (pm_runtime_active(dev) && podgov->enable && user && !old_user) {
|
|
||||||
/* write request */
|
|
||||||
podgov->adjustment_frequency = podgov->p_freq_request;
|
|
||||||
podgov->adjustment_type = ADJUSTMENT_LOCAL;
|
|
||||||
update_devfreq(df);
|
|
||||||
}
|
|
||||||
|
|
||||||
polling = podgov->enable && !podgov->p_user;
|
|
||||||
|
|
||||||
/* Need to unlock to call devfreq_monitor_suspend/resume()
|
|
||||||
* still holding podgov->lock to guarantee atomicity
|
|
||||||
*/
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
|
|
||||||
if (polling)
|
|
||||||
devfreq_monitor_resume(df);
|
|
||||||
else
|
|
||||||
devfreq_monitor_suspend(df);
|
|
||||||
|
|
||||||
mutex_unlock(&podgov->lock);
|
|
||||||
pm_runtime_put(dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************************************************************************
|
|
||||||
* podgov_set_freq_request(dev, user)
|
|
||||||
*
|
|
||||||
* Set the current freq request. If scaling is enabled, and podgov user space
|
|
||||||
* control is enabled, this will set the gpu frequency.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static void podgov_set_freq_request(struct devfreq *df, int freq_request)
|
|
||||||
{
|
|
||||||
struct device *dev = df->dev.parent;
|
|
||||||
struct podgov_info_rec *podgov;
|
|
||||||
|
|
||||||
/* make sure the device is alive before doing any scaling */
|
|
||||||
pm_runtime_get_noresume(dev);
|
|
||||||
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
|
|
||||||
podgov = df->data;
|
|
||||||
|
|
||||||
trace_podgov_set_freq_request(df->dev.parent, freq_request);
|
|
||||||
|
|
||||||
podgov->p_freq_request = freq_request;
|
|
||||||
|
|
||||||
/* update the request only if podgov is enabled, device is turned on
|
|
||||||
* and the scaling is in user mode */
|
|
||||||
if (podgov->enable && podgov->p_user &&
|
|
||||||
pm_runtime_active(dev)) {
|
|
||||||
podgov->adjustment_frequency = freq_request;
|
|
||||||
podgov->adjustment_type = ADJUSTMENT_LOCAL;
|
|
||||||
update_devfreq(df);
|
|
||||||
}
|
|
||||||
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
pm_runtime_put(dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* freq = scaling_state_check(df, time)
|
|
||||||
*
|
|
||||||
* This handler is called to adjust the frequency of the device. The function
|
|
||||||
* returns the desired frequency for the clock. If there is no need to tune the
|
|
||||||
* clock immediately, 0 is returned.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static unsigned long scaling_state_check(struct devfreq *df, ktime_t time)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *pg = df->data;
|
|
||||||
struct devfreq_dev_status *ds = &df->last_status;
|
|
||||||
unsigned long dt, busyness, rt_load = pg->rt_load;
|
|
||||||
long max_boost, damp, freq, boost, res;
|
|
||||||
unsigned long max_freq_hz = 0;
|
|
||||||
|
|
||||||
dt = (unsigned long) ktime_us_delta(time, pg->last_scale);
|
|
||||||
if (dt < pg->p_block_window || df->previous_freq == 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* convert to mhz to avoid overflow */
|
|
||||||
freq = df->previous_freq / 1000000;
|
|
||||||
get_max_freq_limit(df, &max_freq_hz);
|
|
||||||
max_boost = ((max_freq_hz / 3) / 1000000);
|
|
||||||
|
|
||||||
/* calculate and trace load */
|
|
||||||
busyness = 1000ULL * pg->cycles_avg / ds->current_frequency;
|
|
||||||
|
|
||||||
/* consider recent high load if required */
|
|
||||||
if (pg->p_history_buf_size && pg->history_count)
|
|
||||||
busyness = 1000ULL * pg->recent_high / ds->current_frequency;
|
|
||||||
|
|
||||||
trace_podgov_load(df->dev.parent, rt_load);
|
|
||||||
trace_podgov_busy(df->dev.parent, busyness);
|
|
||||||
|
|
||||||
damp = pg->p_damp;
|
|
||||||
|
|
||||||
if (rt_load > pg->p_load_max) {
|
|
||||||
/* if too busy, scale up max/3, do not damp */
|
|
||||||
boost = max_boost;
|
|
||||||
damp = 10;
|
|
||||||
} else {
|
|
||||||
/* boost = bias * freq * (busyness - target)/target */
|
|
||||||
boost = busyness - pg->p_load_target;
|
|
||||||
boost *= (pg->p_bias * freq);
|
|
||||||
boost /= (100 * pg->p_load_target);
|
|
||||||
|
|
||||||
/* clamp to max boost */
|
|
||||||
boost = (boost < max_boost) ? boost : max_boost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* calculate new request */
|
|
||||||
res = freq + boost;
|
|
||||||
|
|
||||||
/* Maintain average request */
|
|
||||||
pg->freq_avg = (pg->freq_avg * pg->p_smooth) + res;
|
|
||||||
pg->freq_avg /= (pg->p_smooth+1);
|
|
||||||
|
|
||||||
/* Applying damping to frequencies */
|
|
||||||
res = ((damp * res) + ((10 - damp)*pg->freq_avg)) / 10;
|
|
||||||
|
|
||||||
/* Convert to hz, limit, and apply */
|
|
||||||
res = res * 1000000;
|
|
||||||
scaling_limit(df, &res);
|
|
||||||
trace_podgov_scaling_state_check(df->dev.parent,
|
|
||||||
df->previous_freq, res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* freqlist_up(podgov, target, steps)
|
|
||||||
*
|
|
||||||
* This function determines the frequency that is "steps" frequency steps
|
|
||||||
* higher compared to the target frequency.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static int freqlist_up(struct podgov_info_rec *podgov, unsigned long target,
|
|
||||||
int steps)
|
|
||||||
{
|
|
||||||
int i, pos;
|
|
||||||
|
|
||||||
for (i = 0; i < podgov->freq_count; i++)
|
|
||||||
if (podgov->freqlist[i] >= target)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = min(podgov->freq_count - 1, i + steps);
|
|
||||||
return podgov->freqlist[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* debugfs interface for controlling 3d clock scaling on the fly
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUG_FS
|
|
||||||
|
|
||||||
static void nvhost_scale_emc_debug_init(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov = df->data;
|
|
||||||
char dirname[128];
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = snprintf(dirname, sizeof(dirname), "%s_scaling",
|
|
||||||
to_platform_device(df->dev.parent)->name);
|
|
||||||
WARN_ON(err < 0);
|
|
||||||
|
|
||||||
if (!podgov)
|
|
||||||
return;
|
|
||||||
|
|
||||||
podgov->debugdir = debugfs_create_dir(dirname, NULL);
|
|
||||||
if (!podgov->debugdir) {
|
|
||||||
pr_err("podgov: can\'t create debugfs directory\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CREATE_PODGOV_FILE(fname) \
|
|
||||||
do {\
|
|
||||||
debugfs_create_u32(#fname, S_IRUGO | S_IWUSR, \
|
|
||||||
podgov->debugdir, &podgov->p_##fname); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
CREATE_PODGOV_FILE(block_window);
|
|
||||||
CREATE_PODGOV_FILE(load_max);
|
|
||||||
CREATE_PODGOV_FILE(load_target);
|
|
||||||
CREATE_PODGOV_FILE(bias);
|
|
||||||
CREATE_PODGOV_FILE(damp);
|
|
||||||
CREATE_PODGOV_FILE(smooth);
|
|
||||||
#undef CREATE_PODGOV_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nvhost_scale_emc_debug_deinit(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov = df->data;
|
|
||||||
|
|
||||||
debugfs_remove_recursive(podgov->debugdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
static void nvhost_scale_emc_debug_init(struct devfreq *df)
|
|
||||||
{
|
|
||||||
(void)df;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nvhost_scale_emc_debug_deinit(struct devfreq *df)
|
|
||||||
{
|
|
||||||
(void)df;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* sysfs interface for enabling/disabling 3d scaling
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static ssize_t enable_3d_scaling_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov = container_of(attr,
|
|
||||||
struct podgov_info_rec,
|
|
||||||
enable_3d_scaling_attr);
|
|
||||||
ssize_t res;
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->enable);
|
|
||||||
WARN_ON(res < 0);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t enable_3d_scaling_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov = container_of(attr,
|
|
||||||
struct podgov_info_rec,
|
|
||||||
enable_3d_scaling_attr);
|
|
||||||
unsigned long val = 0;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
podgov_enable(podgov->power_manager, val);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* sysfs interface for user space control
|
|
||||||
* user = [0,1] disables / enabled user space control
|
|
||||||
* freq_request is the sysfs node user space writes frequency requests to
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static ssize_t user_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov =
|
|
||||||
container_of(attr, struct podgov_info_rec, user_attr);
|
|
||||||
ssize_t res;
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->p_user);
|
|
||||||
WARN_ON(res < 0);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t user_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov =
|
|
||||||
container_of(attr, struct podgov_info_rec, user_attr);
|
|
||||||
unsigned long val = 0;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
podgov_set_user_ctl(podgov->power_manager, val);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t freq_request_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov =
|
|
||||||
container_of(attr, struct podgov_info_rec, freq_request_attr);
|
|
||||||
ssize_t res;
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", podgov->p_freq_request);
|
|
||||||
WARN_ON(res < 0);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t freq_request_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov =
|
|
||||||
container_of(attr, struct podgov_info_rec, freq_request_attr);
|
|
||||||
unsigned long val = 0;
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
podgov_set_freq_request(podgov->power_manager, val);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* nvhost_pod_estimate_freq(df, freq)
|
|
||||||
*
|
|
||||||
* This function is called for re-estimating the frequency. The function is
|
|
||||||
* called in three conditions:
|
|
||||||
*
|
|
||||||
* (1) Internal request to change the frequency. In this case a new clock
|
|
||||||
* target is immediately set for the device.
|
|
||||||
* (2) Call from the client (something has happened and re-estimation
|
|
||||||
* is required).
|
|
||||||
* (3) Some other reason (i.e. periodic call)
|
|
||||||
*
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static int nvhost_pod_estimate_freq(struct devfreq *df,
|
|
||||||
unsigned long *freq)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *pg = df->data;
|
|
||||||
struct devfreq_dev_status *ds;
|
|
||||||
int err, i;
|
|
||||||
int buf_size = pg->p_history_buf_size;
|
|
||||||
int buf_next = pg->history_next;
|
|
||||||
int buf_count = pg->history_count;
|
|
||||||
unsigned long *cycles_buffer = pg->cycles_history_buf;
|
|
||||||
ktime_t now;
|
|
||||||
unsigned long long norm_load;
|
|
||||||
|
|
||||||
/* If the device is suspended, clear the history and set frequency to
|
|
||||||
* min freq.
|
|
||||||
*/
|
|
||||||
if (pg->suspended) {
|
|
||||||
*freq = DEVFREQ_MIN_FREQ;
|
|
||||||
pg->last_scale = ktime_get();
|
|
||||||
i = 0;
|
|
||||||
for (; i < MAX_HISTORY_BUF_SIZE; i++)
|
|
||||||
pg->cycles_history_buf[i] = 0;
|
|
||||||
pg->history_count = 0;
|
|
||||||
pg->history_next = 0;
|
|
||||||
pg->recent_high = 0;
|
|
||||||
pg->freq_avg = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure maximal clock when scaling is disabled */
|
|
||||||
if (!pg->enable) {
|
|
||||||
*freq = DEVFREQ_MAX_FREQ;
|
|
||||||
if (*freq == df->previous_freq)
|
|
||||||
return GET_TARGET_FREQ_DONTSCALE;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pg->p_user) {
|
|
||||||
*freq = pg->p_freq_request;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = devfreq_update_stats(df);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
ds = &df->last_status;
|
|
||||||
|
|
||||||
if (ds->total_time == 0) {
|
|
||||||
*freq = ds->current_frequency;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
now = ktime_get();
|
|
||||||
|
|
||||||
/* Local adjustments (i.e. requests from kernel threads) are
|
|
||||||
* handled here */
|
|
||||||
|
|
||||||
if (pg->adjustment_type == ADJUSTMENT_LOCAL) {
|
|
||||||
|
|
||||||
pg->adjustment_type = ADJUSTMENT_DEVICE_REQ;
|
|
||||||
|
|
||||||
/* Do not do unnecessary scaling */
|
|
||||||
scaling_limit(df, &pg->adjustment_frequency);
|
|
||||||
|
|
||||||
trace_podgov_estimate_freq(df->dev.parent,
|
|
||||||
df->previous_freq,
|
|
||||||
pg->adjustment_frequency);
|
|
||||||
|
|
||||||
*freq = pg->adjustment_frequency;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sustain local variables */
|
|
||||||
norm_load = (u64)ds->current_frequency * ds->busy_time / ds->total_time;
|
|
||||||
pg->cycles_norm = norm_load;
|
|
||||||
pg->cycles_avg = ((u64)pg->cycles_avg * pg->p_smooth + norm_load) /
|
|
||||||
(pg->p_smooth + 1);
|
|
||||||
pg->rt_load = 1000ULL * ds->busy_time / ds->total_time;
|
|
||||||
|
|
||||||
/* Update history of normalized cycle counts and recent highest count */
|
|
||||||
if (buf_size) {
|
|
||||||
if (buf_count == buf_size) {
|
|
||||||
pg->recent_high = 0;
|
|
||||||
i = (buf_next + 1) % buf_size;
|
|
||||||
for (; i != buf_next; i = (i + 1) % buf_size) {
|
|
||||||
if (cycles_buffer[i] > pg->recent_high)
|
|
||||||
pg->recent_high = cycles_buffer[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cycles_buffer[buf_next] = norm_load;
|
|
||||||
pg->history_next = (buf_next + 1) % buf_size;
|
|
||||||
if (buf_count < buf_size)
|
|
||||||
pg->history_count += 1;
|
|
||||||
if (norm_load > pg->recent_high)
|
|
||||||
pg->recent_high = norm_load;
|
|
||||||
}
|
|
||||||
|
|
||||||
*freq = scaling_state_check(df, now);
|
|
||||||
|
|
||||||
if (!(*freq)) {
|
|
||||||
*freq = ds->current_frequency;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((*freq = freqlist_up(pg, *freq, 0)) == ds->current_frequency)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
pg->last_scale = now;
|
|
||||||
|
|
||||||
trace_podgov_estimate_freq(df->dev.parent, df->previous_freq, *freq);
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* nvhost_pod_init(struct devfreq *df)
|
|
||||||
*
|
|
||||||
* Governor initialisation.
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static int nvhost_pod_init(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov;
|
|
||||||
struct platform_device *d = to_platform_device(df->dev.parent);
|
|
||||||
ktime_t now = ktime_get();
|
|
||||||
|
|
||||||
struct kobj_attribute *attr = NULL;
|
|
||||||
|
|
||||||
podgov = kzalloc(sizeof(struct podgov_info_rec), GFP_KERNEL);
|
|
||||||
if (!podgov)
|
|
||||||
goto err_alloc_podgov;
|
|
||||||
|
|
||||||
podgov->cycles_history_buf =
|
|
||||||
kzalloc(sizeof(unsigned long) * MAX_HISTORY_BUF_SIZE,
|
|
||||||
GFP_KERNEL);
|
|
||||||
if (!podgov->cycles_history_buf)
|
|
||||||
goto err_alloc_history_buffer;
|
|
||||||
|
|
||||||
podgov->p_history_buf_size =
|
|
||||||
MAX_HISTORY_BUF_SIZE < 100 ? MAX_HISTORY_BUF_SIZE : 100;
|
|
||||||
podgov->history_count = 0;
|
|
||||||
podgov->history_next = 0;
|
|
||||||
podgov->recent_high = 0;
|
|
||||||
|
|
||||||
df->data = (void *)podgov;
|
|
||||||
|
|
||||||
/* Set scaling parameter defaults */
|
|
||||||
podgov->enable = 1;
|
|
||||||
podgov->suspended = 0;
|
|
||||||
|
|
||||||
podgov->p_load_max = 900;
|
|
||||||
podgov->p_load_target = 700;
|
|
||||||
podgov->p_bias = 80;
|
|
||||||
podgov->p_smooth = 10;
|
|
||||||
podgov->p_damp = 7;
|
|
||||||
podgov->p_block_window = 50000;
|
|
||||||
|
|
||||||
podgov->adjustment_type = ADJUSTMENT_DEVICE_REQ;
|
|
||||||
podgov->p_user = 0;
|
|
||||||
|
|
||||||
/* Reset clock counters */
|
|
||||||
podgov->last_scale = now;
|
|
||||||
|
|
||||||
podgov->power_manager = df;
|
|
||||||
|
|
||||||
mutex_init(&podgov->lock);
|
|
||||||
|
|
||||||
attr = &podgov->enable_3d_scaling_attr;
|
|
||||||
attr->attr.name = "enable_3d_scaling";
|
|
||||||
attr->attr.mode = S_IWUSR | S_IRUGO;
|
|
||||||
attr->show = enable_3d_scaling_show;
|
|
||||||
attr->store = enable_3d_scaling_store;
|
|
||||||
sysfs_attr_init(&attr->attr);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_enable_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &podgov->freq_request_attr;
|
|
||||||
attr->attr.name = "freq_request";
|
|
||||||
attr->attr.mode = S_IWUSR | S_IRUGO;
|
|
||||||
attr->show = freq_request_show;
|
|
||||||
attr->store = freq_request_store;
|
|
||||||
sysfs_attr_init(&attr->attr);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_request_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &podgov->user_attr;
|
|
||||||
attr->attr.name = "user";
|
|
||||||
attr->attr.mode = S_IWUSR | S_IRUGO;
|
|
||||||
attr->show = user_show;
|
|
||||||
attr->store = user_store;
|
|
||||||
sysfs_attr_init(&attr->attr);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_user_sysfs_entry;
|
|
||||||
|
|
||||||
podgov->freq_count = df->profile->max_state;
|
|
||||||
podgov->freqlist = df->profile->freq_table;
|
|
||||||
if (!podgov->freq_count || !podgov->freqlist)
|
|
||||||
goto err_get_freqs;
|
|
||||||
|
|
||||||
/* store the limits */
|
|
||||||
podgov->p_freq_request = podgov->freqlist[podgov->freq_count - 1];
|
|
||||||
|
|
||||||
podgov->freq_avg = 0;
|
|
||||||
|
|
||||||
nvhost_scale_emc_debug_init(df);
|
|
||||||
|
|
||||||
devfreq_monitor_start(df);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_get_freqs:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj, &podgov->user_attr.attr);
|
|
||||||
err_create_user_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&podgov->freq_request_attr.attr);
|
|
||||||
err_create_request_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&podgov->enable_3d_scaling_attr.attr);
|
|
||||||
err_create_enable_sysfs_entry:
|
|
||||||
dev_err(&d->dev, "failed to create sysfs attributes");
|
|
||||||
kfree(podgov->cycles_history_buf);
|
|
||||||
err_alloc_history_buffer:
|
|
||||||
kfree(podgov);
|
|
||||||
err_alloc_podgov:
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
* nvhost_pod_exit(struct devfreq *df)
|
|
||||||
*
|
|
||||||
* Clean up governor data structures
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
static void nvhost_pod_exit(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct podgov_info_rec *podgov = df->data;
|
|
||||||
|
|
||||||
devfreq_monitor_stop(df);
|
|
||||||
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj, &podgov->user_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&podgov->freq_request_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&podgov->enable_3d_scaling_attr.attr);
|
|
||||||
|
|
||||||
nvhost_scale_emc_debug_deinit(df);
|
|
||||||
kfree(podgov->cycles_history_buf);
|
|
||||||
kfree(podgov);
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* nvhost_pod_suspend(struct devfreq *df)
|
|
||||||
*
|
|
||||||
* Suspends the governor.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
static void nvhost_pod_suspend(struct devfreq *df)
|
|
||||||
{
|
|
||||||
// Record suspension in our own data structure because we'll have to
|
|
||||||
// erase and restore devfreq's for this to work.
|
|
||||||
struct podgov_info_rec *pg = df->data;
|
|
||||||
|
|
||||||
pg->suspended = 1;
|
|
||||||
|
|
||||||
// Update frequency for the final time before going into suspension.
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
update_devfreq(df);
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
|
|
||||||
devfreq_monitor_suspend(df);
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* nvhost_pod_resume(struct devfreq *df)
|
|
||||||
*
|
|
||||||
* Resumes the governor.
|
|
||||||
*****************************************************************************/
|
|
||||||
|
|
||||||
static void nvhost_pod_resume(struct devfreq *df)
|
|
||||||
{
|
|
||||||
// Update our data structure's suspension field
|
|
||||||
struct podgov_info_rec *pg = df->data;
|
|
||||||
|
|
||||||
pg->suspended = 0;
|
|
||||||
|
|
||||||
// Resume
|
|
||||||
devfreq_monitor_resume(df);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nvhost_pod_event_handler(struct devfreq *df,
|
|
||||||
unsigned int event, void *data)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case DEVFREQ_GOV_START:
|
|
||||||
ret = nvhost_pod_init(df);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_STOP:
|
|
||||||
nvhost_pod_exit(df);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_UPDATE_INTERVAL:
|
|
||||||
devfreq_update_interval(df, (unsigned int *)data);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_SUSPEND:
|
|
||||||
nvhost_pod_suspend(df);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_RESUME:
|
|
||||||
nvhost_pod_resume(df);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct devfreq_governor nvhost_podgov = {
|
|
||||||
.name = "nvhost_podgov",
|
|
||||||
.get_target_freq = nvhost_pod_estimate_freq,
|
|
||||||
.event_handler = nvhost_pod_event_handler,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static int __init podgov_init(void)
|
|
||||||
{
|
|
||||||
return devfreq_add_governor(&nvhost_podgov);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit podgov_exit(void)
|
|
||||||
{
|
|
||||||
devfreq_remove_governor(&nvhost_podgov);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* governor must be registered before initialising client devices */
|
|
||||||
rootfs_initcall(podgov_init);
|
|
||||||
module_exit(podgov_exit);
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
@@ -1,823 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/devfreq.h>
|
|
||||||
#include <linux/debugfs.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/device.h>
|
|
||||||
#include <linux/notifier.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
|
|
||||||
#include "governor.h"
|
|
||||||
|
|
||||||
#define CREATE_TRACE_POINTS
|
|
||||||
#include <trace/events/watermark_gov.h>
|
|
||||||
|
|
||||||
struct wmark_gov_param {
|
|
||||||
unsigned int block_window;
|
|
||||||
unsigned int load_target;
|
|
||||||
unsigned int load_max;
|
|
||||||
unsigned int smooth;
|
|
||||||
unsigned int high_wmark_margin;
|
|
||||||
unsigned int low_wmark_margin;
|
|
||||||
bool freq_boost_en;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wmark_gov_info {
|
|
||||||
/* probed from the devfreq */
|
|
||||||
unsigned long *freqlist;
|
|
||||||
int freq_count;
|
|
||||||
|
|
||||||
/* algorithm parameters */
|
|
||||||
struct wmark_gov_param param;
|
|
||||||
|
|
||||||
struct kobj_attribute block_window_attr;
|
|
||||||
struct kobj_attribute load_target_attr;
|
|
||||||
struct kobj_attribute load_max_attr;
|
|
||||||
struct kobj_attribute smooth_attr;
|
|
||||||
struct kobj_attribute high_wmark_margin_attr;
|
|
||||||
struct kobj_attribute low_wmark_margin_attr;
|
|
||||||
struct kobj_attribute freq_boost_en_attr;
|
|
||||||
|
|
||||||
spinlock_t param_lock;
|
|
||||||
|
|
||||||
/* common data */
|
|
||||||
struct devfreq *df;
|
|
||||||
struct platform_device *pdev;
|
|
||||||
struct dentry *debugdir;
|
|
||||||
|
|
||||||
/* used for ensuring that we do not update frequency too often */
|
|
||||||
ktime_t last_frequency_update;
|
|
||||||
|
|
||||||
/* variable for keeping the average frequency request */
|
|
||||||
unsigned long long average_target_freq;
|
|
||||||
|
|
||||||
/* devfreq notifier_block */
|
|
||||||
struct notifier_block nb;
|
|
||||||
};
|
|
||||||
|
|
||||||
static unsigned long freqlist_up(struct wmark_gov_info *wmarkinfo,
|
|
||||||
unsigned long curr_freq)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
int i, pos;
|
|
||||||
int margin;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
if (wmarkinfo->param.high_wmark_margin > INT_MAX)
|
|
||||||
margin = wmarkinfo->freq_count - 1;
|
|
||||||
else
|
|
||||||
margin = wmarkinfo->param.high_wmark_margin;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
for (i = 0; i < wmarkinfo->freq_count; i++)
|
|
||||||
if (wmarkinfo->freqlist[i] > curr_freq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = min(wmarkinfo->freq_count - 1, i + margin);
|
|
||||||
|
|
||||||
return wmarkinfo->freqlist[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned long freqlist_down(struct wmark_gov_info *wmarkinfo,
|
|
||||||
unsigned long curr_freq)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
int i, pos;
|
|
||||||
int margin;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
if (wmarkinfo->param.low_wmark_margin > INT_MAX)
|
|
||||||
margin = wmarkinfo->freq_count - 1;
|
|
||||||
else
|
|
||||||
margin = wmarkinfo->param.low_wmark_margin;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
for (i = wmarkinfo->freq_count - 1; i >= 0; i--)
|
|
||||||
if (wmarkinfo->freqlist[i] < curr_freq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = max(0, i - margin);
|
|
||||||
return wmarkinfo->freqlist[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned long freqlist_round(struct wmark_gov_info *wmarkinfo,
|
|
||||||
unsigned long freq)
|
|
||||||
{
|
|
||||||
int i, pos;
|
|
||||||
|
|
||||||
for (i = 0; i < wmarkinfo->freq_count; i++)
|
|
||||||
if (wmarkinfo->freqlist[i] >= freq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = min(wmarkinfo->freq_count - 1, i);
|
|
||||||
return wmarkinfo->freqlist[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* update_watermarks - Re-estimate low and high watermarks
|
|
||||||
* @df: pointer to the devfreq structure
|
|
||||||
* @current_frequency: current frequency of the device
|
|
||||||
* @ideal_frequency: frequency that would change load to target load.
|
|
||||||
*
|
|
||||||
* This function updates the devfreq high and low watermarks. Target is
|
|
||||||
* to ensure that the interrupts are triggered whenever the load changes
|
|
||||||
* enough to make a change to the ideal frequency (given the DVFS table).
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void update_watermarks(struct devfreq *df,
|
|
||||||
unsigned long current_frequency,
|
|
||||||
unsigned long ideal_frequency)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
unsigned long long relation = 0, next_freq = 0;
|
|
||||||
unsigned long long current_frequency_khz = current_frequency / 1000;
|
|
||||||
unsigned long flags;
|
|
||||||
struct wmark_gov_param param;
|
|
||||||
|
|
||||||
/* get governor parameters */
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
param = wmarkinfo->param;
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
if (ideal_frequency == wmarkinfo->freqlist[0]) {
|
|
||||||
/* disable the low watermark if we are at lowest clock */
|
|
||||||
df->profile->set_low_wmark(df->dev.parent, 0);
|
|
||||||
} else {
|
|
||||||
/* calculate the low threshold; what is the load value
|
|
||||||
* at which we would go into lower frequency given the
|
|
||||||
* that we are running at the new frequency? */
|
|
||||||
next_freq = freqlist_down(wmarkinfo, ideal_frequency);
|
|
||||||
relation = ((next_freq / current_frequency_khz) *
|
|
||||||
param.load_target) / 1000;
|
|
||||||
df->profile->set_low_wmark(df->dev.parent, relation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ideal_frequency ==
|
|
||||||
wmarkinfo->freqlist[wmarkinfo->freq_count - 1]) {
|
|
||||||
/* disable the high watermark if we are at highest clock */
|
|
||||||
df->profile->set_high_wmark(df->dev.parent, 1000);
|
|
||||||
} else {
|
|
||||||
/* calculate the high threshold; what is the load value
|
|
||||||
* at which we would go into highest frequency given the
|
|
||||||
* that we are running at the new frequency? */
|
|
||||||
next_freq = freqlist_up(wmarkinfo, ideal_frequency);
|
|
||||||
relation = ((next_freq / current_frequency_khz) *
|
|
||||||
param.load_target) / 1000;
|
|
||||||
relation = min_t(unsigned long long, param.load_max,
|
|
||||||
relation);
|
|
||||||
df->profile->set_high_wmark(df->dev.parent, relation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_target_freq(struct devfreq *df,
|
|
||||||
unsigned long *freq)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
struct platform_device *pdev = wmarkinfo->pdev;
|
|
||||||
struct devfreq_dev_status dev_stat;
|
|
||||||
unsigned long long load, relation, ideal_freq;
|
|
||||||
ktime_t current_time = ktime_get();
|
|
||||||
s64 dt = ktime_us_delta(current_time, wmarkinfo->last_frequency_update);
|
|
||||||
int err;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
struct wmark_gov_param param;
|
|
||||||
|
|
||||||
err = df->profile->get_dev_status(df->dev.parent, &dev_stat);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
/* use current frequency by default */
|
|
||||||
*freq = dev_stat.current_frequency;
|
|
||||||
|
|
||||||
/* quit now if we are getting calls too often */
|
|
||||||
if (!dev_stat.total_time)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* get governor parameters */
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
param = wmarkinfo->param;
|
|
||||||
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
/* calculate first load and relation load/load_target */
|
|
||||||
load = (dev_stat.busy_time * 1000) / dev_stat.total_time;
|
|
||||||
|
|
||||||
/* if we cross load max... */
|
|
||||||
if (param.freq_boost_en && load >= param.load_max) {
|
|
||||||
/* we go directly to the highest frequency. depending
|
|
||||||
* on frequency table we might never go higher than
|
|
||||||
* the current frequency (i.e. load should be over 100%
|
|
||||||
* to make relation push to the next frequency). */
|
|
||||||
ideal_freq = wmarkinfo->freqlist[wmarkinfo->freq_count - 1];
|
|
||||||
} else {
|
|
||||||
/* otherwise, based on relation between current load and
|
|
||||||
* load target we calculate the "ideal" frequency
|
|
||||||
* where we would be just at the target */
|
|
||||||
relation = (load * 1000) / param.load_target;
|
|
||||||
ideal_freq = relation * (dev_stat.current_frequency / 1000);
|
|
||||||
|
|
||||||
/* round this frequency */
|
|
||||||
ideal_freq = freqlist_round(wmarkinfo, ideal_freq);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* update average target frequency */
|
|
||||||
wmarkinfo->average_target_freq =
|
|
||||||
(param.smooth * wmarkinfo->average_target_freq +
|
|
||||||
ideal_freq) / (param.smooth + 1);
|
|
||||||
|
|
||||||
/* do not scale too often */
|
|
||||||
if (dt < param.block_window)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* update the frequency */
|
|
||||||
*freq = freqlist_round(wmarkinfo, wmarkinfo->average_target_freq);
|
|
||||||
trace_devfreq_watermark_target_freq(pdev->name, load, *freq);
|
|
||||||
|
|
||||||
/* check if frequency actually got updated */
|
|
||||||
if (*freq == dev_stat.current_frequency)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* enable hysteresis - frequency is updated */
|
|
||||||
wmarkinfo->last_frequency_update = current_time;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t block_window_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
unsigned int val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
block_window_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.block_window;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%u\n", val);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t block_window_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned long val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
block_window_attr);
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.block_window = val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t load_target_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
unsigned int val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
load_target_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.load_target;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%u\n", val);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t load_target_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned long val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
load_target_attr);
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.load_target = val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t load_max_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
unsigned int val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
load_max_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.load_max;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%u\n", val);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t load_max_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned long val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
load_max_attr);
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.load_max = val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t smooth_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
unsigned int val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
smooth_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.smooth;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%u\n", val);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t smooth_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned long val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
smooth_attr);
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.smooth = val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t high_wmark_margin_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
unsigned int val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
high_wmark_margin_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.high_wmark_margin;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%u\n", val);
|
|
||||||
if (res >= PAGE_SIZE)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t high_wmark_margin_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned int val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
high_wmark_margin_attr);
|
|
||||||
|
|
||||||
if (kstrtou32(buf, 0, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.high_wmark_margin = val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count <= INT_MAX ? count : -EINVAL;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t low_wmark_margin_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
unsigned int val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
low_wmark_margin_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.low_wmark_margin;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%u\n", val);
|
|
||||||
if (res >= PAGE_SIZE)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t low_wmark_margin_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned int val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
low_wmark_margin_attr);
|
|
||||||
|
|
||||||
if (kstrtou32(buf, 0, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.low_wmark_margin = val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count <= INT_MAX ? count : -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t freq_boost_en_show(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
char *buf)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
ssize_t res;
|
|
||||||
bool val;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
freq_boost_en_attr);
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
val = wmarkinfo->param.freq_boost_en;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
res = snprintf(buf, PAGE_SIZE, "%d\n", val);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t freq_boost_en_store(struct kobject *kobj,
|
|
||||||
struct kobj_attribute *attr,
|
|
||||||
const char *buf, size_t count)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = NULL;
|
|
||||||
unsigned long val = 0;
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
wmarkinfo = container_of(attr,
|
|
||||||
struct wmark_gov_info,
|
|
||||||
freq_boost_en_attr);
|
|
||||||
|
|
||||||
if (kstrtoul(buf, 10, &val) < 0)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&wmarkinfo->param_lock, flags);
|
|
||||||
wmarkinfo->param.freq_boost_en = !!val;
|
|
||||||
spin_unlock_irqrestore(&wmarkinfo->param_lock, flags);
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define INIT_SYSFS_ATTR_RW(sysfs_name) \
|
|
||||||
do { \
|
|
||||||
attr->attr.name = #sysfs_name; \
|
|
||||||
attr->attr.mode = 0644; \
|
|
||||||
attr->show = sysfs_name##_show; \
|
|
||||||
attr->store = sysfs_name##_store; \
|
|
||||||
sysfs_attr_init(&attr->attr); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static int devfreq_watermark_debug_start(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
struct kobj_attribute *attr = NULL;
|
|
||||||
|
|
||||||
if (!wmarkinfo)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
spin_lock_init(&wmarkinfo->param_lock);
|
|
||||||
|
|
||||||
attr = &wmarkinfo->block_window_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(block_window);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_block_window_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &wmarkinfo->load_target_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(load_target);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_load_target_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &wmarkinfo->load_max_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(load_max);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_load_max_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &wmarkinfo->smooth_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(smooth);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_smooth_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &wmarkinfo->high_wmark_margin_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(high_wmark_margin);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_high_wmark_margin_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &wmarkinfo->low_wmark_margin_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(low_wmark_margin);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_low_wmark_margin_sysfs_entry;
|
|
||||||
|
|
||||||
attr = &wmarkinfo->freq_boost_en_attr;
|
|
||||||
INIT_SYSFS_ATTR_RW(freq_boost_en);
|
|
||||||
if (sysfs_create_file(&df->dev.parent->kobj, &attr->attr))
|
|
||||||
goto err_create_freq_boost_en_sysfs_entry;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_create_freq_boost_en_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->low_wmark_margin_attr.attr);
|
|
||||||
err_create_low_wmark_margin_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->high_wmark_margin_attr.attr);
|
|
||||||
err_create_high_wmark_margin_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->smooth_attr.attr);
|
|
||||||
err_create_smooth_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->load_max_attr.attr);
|
|
||||||
err_create_load_max_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->load_target_attr.attr);
|
|
||||||
err_create_load_target_sysfs_entry:
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->block_window_attr.attr);
|
|
||||||
err_create_block_window_sysfs_entry:
|
|
||||||
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void devfreq_watermark_debug_stop(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->freq_boost_en_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->high_wmark_margin_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->low_wmark_margin_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->smooth_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->load_max_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->load_target_attr.attr);
|
|
||||||
sysfs_remove_file(&df->dev.parent->kobj,
|
|
||||||
&wmarkinfo->block_window_attr.attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_start(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo;
|
|
||||||
struct platform_device *pdev = to_platform_device(df->dev.parent);
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!df->profile->freq_table) {
|
|
||||||
dev_err(&pdev->dev, "Frequency table missing\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
wmarkinfo = kzalloc(sizeof(struct wmark_gov_info), GFP_KERNEL);
|
|
||||||
if (!wmarkinfo)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
df->data = (void *)wmarkinfo;
|
|
||||||
wmarkinfo->freqlist = df->profile->freq_table;
|
|
||||||
wmarkinfo->freq_count = df->profile->max_state;
|
|
||||||
wmarkinfo->param.load_target = 700;
|
|
||||||
wmarkinfo->param.load_max = 900;
|
|
||||||
wmarkinfo->param.smooth = 10;
|
|
||||||
wmarkinfo->param.block_window = 50000;
|
|
||||||
wmarkinfo->param.freq_boost_en = true;
|
|
||||||
wmarkinfo->df = df;
|
|
||||||
wmarkinfo->pdev = pdev;
|
|
||||||
|
|
||||||
ret = devfreq_watermark_debug_start(df);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_notifier_call(struct notifier_block *nb,
|
|
||||||
unsigned long event, void *ptr)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *data
|
|
||||||
= container_of(nb, struct wmark_gov_info, nb);
|
|
||||||
struct devfreq *df = (struct devfreq *)data->df;
|
|
||||||
unsigned long freq = 0;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case DEVFREQ_PRECHANGE:
|
|
||||||
break;
|
|
||||||
case DEVFREQ_POSTCHANGE:
|
|
||||||
/* get device freq. */
|
|
||||||
df->profile->get_cur_freq(df->dev.parent, &freq);
|
|
||||||
|
|
||||||
/* update watermarks by current device freq. */
|
|
||||||
if (freq)
|
|
||||||
update_watermarks(df, freq, freq);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NOTIFY_DONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_event_handler(struct devfreq *df,
|
|
||||||
unsigned int event, void *wmark_type)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo;
|
|
||||||
int ret = 0;
|
|
||||||
struct notifier_block *nb;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case DEVFREQ_GOV_START:
|
|
||||||
{
|
|
||||||
struct devfreq_dev_status dev_stat;
|
|
||||||
ret = df->profile->get_dev_status(df->dev.parent, &dev_stat);
|
|
||||||
if (ret < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
ret = devfreq_watermark_start(df);
|
|
||||||
if (ret < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* initialize average target freq */
|
|
||||||
wmarkinfo = df->data;
|
|
||||||
wmarkinfo->average_target_freq = dev_stat.current_frequency;
|
|
||||||
|
|
||||||
update_watermarks(df, dev_stat.current_frequency,
|
|
||||||
dev_stat.current_frequency);
|
|
||||||
|
|
||||||
nb = &wmarkinfo->nb;
|
|
||||||
nb->notifier_call = devfreq_watermark_notifier_call;
|
|
||||||
ret = devm_devfreq_register_notifier(df->dev.parent,
|
|
||||||
df, nb, DEVFREQ_TRANSITION_NOTIFIER);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DEVFREQ_GOV_STOP:
|
|
||||||
devfreq_watermark_debug_stop(df);
|
|
||||||
|
|
||||||
wmarkinfo = df->data;
|
|
||||||
nb = &wmarkinfo->nb;
|
|
||||||
devm_devfreq_unregister_notifier(df->dev.parent,
|
|
||||||
df, nb, DEVFREQ_TRANSITION_NOTIFIER);
|
|
||||||
|
|
||||||
/* free wmark_gov_info struct */
|
|
||||||
if (df->data != NULL) {
|
|
||||||
kfree(df->data);
|
|
||||||
df->data = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_SUSPEND:
|
|
||||||
devfreq_monitor_suspend(df);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_RESUME:
|
|
||||||
{
|
|
||||||
struct devfreq_dev_status dev_stat;
|
|
||||||
ret = df->profile->get_dev_status(df->dev.parent, &dev_stat);
|
|
||||||
if (ret < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* reset average target freq to current freq */
|
|
||||||
wmarkinfo = df->data;
|
|
||||||
wmarkinfo->average_target_freq = dev_stat.current_frequency;
|
|
||||||
|
|
||||||
update_watermarks(df, dev_stat.current_frequency,
|
|
||||||
dev_stat.current_frequency);
|
|
||||||
devfreq_monitor_resume(df);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DEVFREQ_GOV_WMARK:
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
update_devfreq(df);
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct devfreq_governor devfreq_watermark_active = {
|
|
||||||
.name = "wmark_active",
|
|
||||||
.get_target_freq = devfreq_watermark_target_freq,
|
|
||||||
.event_handler = devfreq_watermark_event_handler,
|
|
||||||
.interrupt_driven = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static int __init devfreq_watermark_init(void)
|
|
||||||
{
|
|
||||||
return devfreq_add_governor(&devfreq_watermark_active);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit devfreq_watermark_exit(void)
|
|
||||||
{
|
|
||||||
devfreq_remove_governor(&devfreq_watermark_active);
|
|
||||||
}
|
|
||||||
|
|
||||||
subsys_initcall(devfreq_watermark_init);
|
|
||||||
module_exit(devfreq_watermark_exit);
|
|
||||||
MODULE_LICENSE("GPL v2");
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/devfreq.h>
|
|
||||||
#include <linux/debugfs.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/device.h>
|
|
||||||
#include <linux/notifier.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
|
|
||||||
#include <governor.h>
|
|
||||||
|
|
||||||
enum watermark_type {
|
|
||||||
NO_WATERMARK_EVENT = 0,
|
|
||||||
HIGH_WATERMARK_EVENT = 1,
|
|
||||||
LOW_WATERMARK_EVENT = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
struct wmark_gov_info {
|
|
||||||
/* probed from the devfreq */
|
|
||||||
unsigned long *freqlist;
|
|
||||||
int freq_count;
|
|
||||||
|
|
||||||
/* algorithm parameters */
|
|
||||||
unsigned int p_high_wmark;
|
|
||||||
unsigned int p_low_wmark;
|
|
||||||
|
|
||||||
/* dynamically changing data */
|
|
||||||
enum watermark_type event;
|
|
||||||
unsigned long last_request;
|
|
||||||
|
|
||||||
/* common data */
|
|
||||||
struct devfreq *df;
|
|
||||||
struct platform_device *pdev;
|
|
||||||
struct dentry *debugdir;
|
|
||||||
};
|
|
||||||
|
|
||||||
static unsigned long freqlist_up(struct wmark_gov_info *wmarkinfo,
|
|
||||||
unsigned long curr_freq)
|
|
||||||
{
|
|
||||||
int i, pos;
|
|
||||||
|
|
||||||
for (i = 0; i < wmarkinfo->freq_count; i++)
|
|
||||||
if (wmarkinfo->freqlist[i] > curr_freq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = min(wmarkinfo->freq_count - 1, i);
|
|
||||||
|
|
||||||
return wmarkinfo->freqlist[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned long freqlist_down(struct wmark_gov_info *wmarkinfo,
|
|
||||||
unsigned long curr_freq)
|
|
||||||
{
|
|
||||||
int i, pos;
|
|
||||||
|
|
||||||
for (i = wmarkinfo->freq_count - 1; i >= 0; i--)
|
|
||||||
if (wmarkinfo->freqlist[i] < curr_freq)
|
|
||||||
break;
|
|
||||||
|
|
||||||
pos = max(0, i);
|
|
||||||
return wmarkinfo->freqlist[pos];
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_target_freq(struct devfreq *df,
|
|
||||||
unsigned long *freq)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
struct devfreq_dev_status dev_stat;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = df->profile->get_dev_status(df->dev.parent, &dev_stat);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
switch (wmarkinfo->event) {
|
|
||||||
case HIGH_WATERMARK_EVENT:
|
|
||||||
*freq = freqlist_up(wmarkinfo, dev_stat.current_frequency);
|
|
||||||
|
|
||||||
/* always enable low watermark */
|
|
||||||
df->profile->set_low_wmark(df->dev.parent,
|
|
||||||
wmarkinfo->p_low_wmark);
|
|
||||||
|
|
||||||
/* disable high watermark if no change */
|
|
||||||
if (*freq == wmarkinfo->last_request)
|
|
||||||
df->profile->set_high_wmark(df->dev.parent, 1000);
|
|
||||||
break;
|
|
||||||
case LOW_WATERMARK_EVENT:
|
|
||||||
*freq = freqlist_down(wmarkinfo, dev_stat.current_frequency);
|
|
||||||
|
|
||||||
/* always enable high watermark */
|
|
||||||
df->profile->set_high_wmark(df->dev.parent,
|
|
||||||
wmarkinfo->p_high_wmark);
|
|
||||||
|
|
||||||
/* disable low watermark if no change */
|
|
||||||
if (*freq == wmarkinfo->last_request)
|
|
||||||
df->profile->set_low_wmark(df->dev.parent, 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mark that you handled event */
|
|
||||||
wmarkinfo->event = NO_WATERMARK_EVENT;
|
|
||||||
wmarkinfo->last_request = *freq;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void devfreq_watermark_debug_start(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
struct dentry *f;
|
|
||||||
char dirname[128];
|
|
||||||
|
|
||||||
snprintf(dirname, sizeof(dirname), "%s_scaling",
|
|
||||||
to_platform_device(df->dev.parent)->name);
|
|
||||||
|
|
||||||
if (!wmarkinfo)
|
|
||||||
return;
|
|
||||||
|
|
||||||
wmarkinfo->debugdir = debugfs_create_dir(dirname, NULL);
|
|
||||||
if (!wmarkinfo->debugdir) {
|
|
||||||
pr_warn("cannot create debugfs directory\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CREATE_DBG_FILE(fname) \
|
|
||||||
do {\
|
|
||||||
f = debugfs_create_u32(#fname, S_IRUGO | S_IWUSR, \
|
|
||||||
wmarkinfo->debugdir, &wmarkinfo->p_##fname); \
|
|
||||||
if (NULL == f) { \
|
|
||||||
pr_warn("cannot create debug entry " #fname "\n"); \
|
|
||||||
return; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
CREATE_DBG_FILE(low_wmark);
|
|
||||||
CREATE_DBG_FILE(high_wmark);
|
|
||||||
#undef CREATE_DBG_FILE
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static void devfreq_watermark_debug_stop(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
debugfs_remove_recursive(wmarkinfo->debugdir);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_start(struct devfreq *df)
|
|
||||||
{
|
|
||||||
struct wmark_gov_info *wmarkinfo;
|
|
||||||
struct platform_device *pdev = to_platform_device(df->dev.parent);
|
|
||||||
|
|
||||||
if (!df->profile->freq_table) {
|
|
||||||
dev_err(&pdev->dev, "Frequency table missing\n");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
wmarkinfo = kzalloc(sizeof(struct wmark_gov_info), GFP_KERNEL);
|
|
||||||
if (!wmarkinfo)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
df->data = (void *)wmarkinfo;
|
|
||||||
wmarkinfo->freqlist = df->profile->freq_table;
|
|
||||||
wmarkinfo->freq_count = df->profile->max_state;
|
|
||||||
wmarkinfo->event = NO_WATERMARK_EVENT;
|
|
||||||
wmarkinfo->df = df;
|
|
||||||
wmarkinfo->pdev = pdev;
|
|
||||||
wmarkinfo->p_low_wmark = 100;
|
|
||||||
wmarkinfo->p_high_wmark = 600;
|
|
||||||
|
|
||||||
devfreq_watermark_debug_start(df);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int devfreq_watermark_event_handler(struct devfreq *df,
|
|
||||||
unsigned int event, void *wmark_type)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
struct wmark_gov_info *wmarkinfo = df->data;
|
|
||||||
enum watermark_type *type = wmark_type;
|
|
||||||
|
|
||||||
switch (event) {
|
|
||||||
case DEVFREQ_GOV_START:
|
|
||||||
devfreq_watermark_start(df);
|
|
||||||
wmarkinfo = df->data;
|
|
||||||
if (df->profile->set_low_wmark)
|
|
||||||
df->profile->set_low_wmark(df->dev.parent,
|
|
||||||
wmarkinfo->p_low_wmark);
|
|
||||||
if (df->profile->set_high_wmark)
|
|
||||||
df->profile->set_high_wmark(df->dev.parent,
|
|
||||||
wmarkinfo->p_high_wmark);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_STOP:
|
|
||||||
devfreq_watermark_debug_stop(df);
|
|
||||||
break;
|
|
||||||
case DEVFREQ_GOV_SUSPEND:
|
|
||||||
devfreq_monitor_suspend(df);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DEVFREQ_GOV_RESUME:
|
|
||||||
if (df->profile->set_low_wmark)
|
|
||||||
df->profile->set_low_wmark(df->dev.parent,
|
|
||||||
wmarkinfo->p_low_wmark);
|
|
||||||
if (df->profile->set_high_wmark)
|
|
||||||
df->profile->set_high_wmark(df->dev.parent,
|
|
||||||
wmarkinfo->p_high_wmark);
|
|
||||||
devfreq_monitor_resume(df);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DEVFREQ_GOV_WMARK:
|
|
||||||
/* Set watermark interrupt type */
|
|
||||||
wmarkinfo->event = *type;
|
|
||||||
|
|
||||||
mutex_lock(&df->lock);
|
|
||||||
update_devfreq(df);
|
|
||||||
mutex_unlock(&df->lock);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct devfreq_governor devfreq_watermark = {
|
|
||||||
.name = "wmark_simple",
|
|
||||||
.get_target_freq = devfreq_watermark_target_freq,
|
|
||||||
.event_handler = devfreq_watermark_event_handler,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static int __init devfreq_watermark_init(void)
|
|
||||||
{
|
|
||||||
return devfreq_add_governor(&devfreq_watermark);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit devfreq_watermark_exit(void)
|
|
||||||
{
|
|
||||||
devfreq_remove_governor(&devfreq_watermark);
|
|
||||||
}
|
|
||||||
|
|
||||||
rootfs_initcall(devfreq_watermark_init);
|
|
||||||
module_exit(devfreq_watermark_exit);
|
|
||||||
@@ -7,9 +7,10 @@
|
|||||||
* governor.h - internal header for devfreq governors.
|
* governor.h - internal header for devfreq governors.
|
||||||
* This header is for devfreq governors in drivers/devfreq/
|
* This header is for devfreq governors in drivers/devfreq/
|
||||||
*/
|
*/
|
||||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
|
|
||||||
#ifndef _GOVERNOR_H
|
#ifndef _GOVERNOR_H
|
||||||
#define _GOVERNOR_H
|
#define _GOVERNOR_H
|
||||||
|
|
||||||
#include <linux/devfreq.h>
|
#include <linux/devfreq.h>
|
||||||
|
|
||||||
#define DEVFREQ_NAME_LEN 16
|
#define DEVFREQ_NAME_LEN 16
|
||||||
@@ -85,8 +86,6 @@ int devfreq_remove_governor(struct devfreq_governor *governor);
|
|||||||
|
|
||||||
int devfreq_update_status(struct devfreq *devfreq, unsigned long freq);
|
int devfreq_update_status(struct devfreq *devfreq, unsigned long freq);
|
||||||
int devfreq_update_target(struct devfreq *devfreq, unsigned long freq);
|
int devfreq_update_target(struct devfreq *devfreq, unsigned long freq);
|
||||||
void devfreq_get_freq_range(struct devfreq *devfreq, unsigned long *min_freq,
|
|
||||||
unsigned long *max_freq);
|
|
||||||
|
|
||||||
static inline int devfreq_update_stats(struct devfreq *df)
|
static inline int devfreq_update_stats(struct devfreq *df)
|
||||||
{
|
{
|
||||||
@@ -96,4 +95,3 @@ static inline int devfreq_update_stats(struct devfreq *df)
|
|||||||
return df->profile->get_dev_status(df->dev.parent, &df->last_status);
|
return df->profile->get_dev_status(df->dev.parent, &df->last_status);
|
||||||
}
|
}
|
||||||
#endif /* _GOVERNOR_H */
|
#endif /* _GOVERNOR_H */
|
||||||
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0) && LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0) */
|
|
||||||
Reference in New Issue
Block a user