kernel_samsung_a53x/drivers/gpu/drm/samsung/sgpu/sgpu_devfreq.c
2024-06-15 16:02:09 -03:00

248 lines
6.8 KiB
C
Executable file

/*
* @file sgpu_devfreq.c
*
* Copyright (c) 2019 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* @brief Contains the implementaton of main devfreq handler.
* Devfreq handler manages devfreq feature.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*/
#include "amdgpu.h"
#include "sgpu_governor.h"
#include "sgpu_user_interface.h"
#include "sgpu_utilization.h"
#include "amdgpu_trace.h"
#ifdef CONFIG_DRM_SGPU_EXYNOS
#include <soc/samsung/cal-if.h>
#include <soc/samsung/tmu.h>
#include "exynos_gpu_interface.h"
#endif /* CONFIG_DRM_SGPU_EXYNOS */
static int sgpu_devfreq_target(struct device *dev, unsigned long *target_freq, u32 flags)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = ddev->dev_private;
struct devfreq *df = adev->devfreq;
struct sgpu_governor_data *data = df->data;
unsigned long cur_freq, qos_min_freq, qos_max_freq;
struct dev_pm_opp *target_opp;
int i;
#if defined(CONFIG_DRM_SGPU_EXYNOS) && !defined(CONFIG_SOC_S5E9925_EVT0)
uint32_t cnt_value;
#endif /* CONFIG_DRM_SGPU_EXYNOS && !CONFIG_SOC_S5E9925_EVT0 */
if (df->profile->get_cur_freq)
df->profile->get_cur_freq(df->dev.parent, &cur_freq);
else
cur_freq = df->previous_freq;
qos_min_freq = dev_pm_qos_read_value(dev, DEV_PM_QOS_MIN_FREQUENCY);
qos_max_freq = dev_pm_qos_read_value(dev, DEV_PM_QOS_MAX_FREQUENCY);
if (qos_min_freq >= qos_max_freq)
flags = 1;
target_opp = devfreq_recommended_opp(dev, target_freq, flags);
if (IS_ERR(target_opp)) {
dev_err(dev, "target_freq: not found valid OPP table\n");
return PTR_ERR(target_opp);
}
dev_pm_opp_put(target_opp);
if (cur_freq == *target_freq)
return 0;
/* in suspend or power_off*/
if (atomic_read(&df->suspend_count) > 0) {
*target_freq = df->previous_freq;
return 0;
}
#ifdef CONFIG_DRM_SGPU_EXYNOS
#ifndef CONFIG_SOC_S5E9925_EVT0
cnt_value = readl(data->bg3d_dvfs + 0x5c);
DRM_DEBUG("[SWAT CNT1] CUR_2AD Count : 0x%x, CUR_1AD Count : 0x%x",
(cnt_value >> 16) & 0xffff, cnt_value & 0xffff);
cnt_value = readl(data->bg3d_dvfs + 0x60);
DRM_DEBUG("[SWAT CNT2] CUR_2BD Count : 0x%x, CUR_1BD Count : 0x%x",
(cnt_value >> 16) & 0xffff, cnt_value & 0xffff);
/* BG3D_DVFS_CTL (EVT1) 0x058 disable */
writel(0x0, data->bg3d_dvfs + 0x58);
/* Polling busy bit (BG3D_DVFS_CTL[12]) */
while(readl(data->bg3d_dvfs + 0x58) & 0x1000) ;
#endif /* CONFIG_SOC_S5E9925_EVT0 */
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
if (adev->gpu_dss_freq_id)
dbg_snapshot_freq(adev->gpu_dss_freq_id, cur_freq, *target_freq, DSS_FLAG_IN);
#endif
mutex_lock(&adev->ifpo_mutex);
/* cal i/f for change clock */
cal_dfs_set_rate(adev->cal_id, *target_freq);
mutex_unlock(&adev->ifpo_mutex);
#if IS_ENABLED(CONFIG_DEBUG_SNAPSHOT)
if (adev->gpu_dss_freq_id)
dbg_snapshot_freq(adev->gpu_dss_freq_id, cur_freq, *target_freq, DSS_FLAG_OUT);
#endif
#ifndef CONFIG_SOC_S5E9925_EVT0
/* BG3D_DVFS_CTL (EVT1) 0x058 enable */
writel(0x1, data->bg3d_dvfs + 0x58);
#endif
#endif /* CONFIG_DRM_SGPU_EXYNOS */
for (i = 0; i < df->profile->max_state; i++) {
if (df->profile->freq_table[i] == *target_freq)
data->current_level = i;
}
trace_sgpu_devfreq_set_target(cur_freq, *target_freq);
SGPU_LOG(adev, DMSG_INFO, DMSG_DVFS, "set_freq:%8lu", *target_freq);
return 0;
}
static int sgpu_devfreq_status(struct device *dev, struct devfreq_dev_status *stat)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = ddev->dev_private;
stat->current_frequency = adev->devfreq->previous_freq;
return sgpu_utilization_capture(stat);
}
static int sgpu_register_pm_qos(struct devfreq *df)
{
struct sgpu_governor_data *data = df->data;
data->devfreq = df;
data->sys_min_freq = df->scaling_min_freq;
data->sys_max_freq = df->scaling_max_freq;
/* Round down to kHz for PM QoS */
dev_pm_qos_add_request(df->dev.parent, &data->sys_pm_qos_min,
DEV_PM_QOS_MIN_FREQUENCY,
data->sys_min_freq / HZ_PER_KHZ);
dev_pm_qos_add_request(df->dev.parent, &data->sys_pm_qos_max,
DEV_PM_QOS_MAX_FREQUENCY,
data->sys_max_freq / HZ_PER_KHZ);
return 0;
}
static int sgpu_unregister_pm_qos(struct devfreq *df)
{
struct sgpu_governor_data *data = df->data;
if (!data)
return -EINVAL;
dev_pm_qos_remove_request(&data->sys_pm_qos_min);
dev_pm_qos_remove_request(&data->sys_pm_qos_max);
return 0;
}
static int sgpu_devfreq_cur_freq(struct device *dev, unsigned long *freq)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = ddev->dev_private;
struct devfreq *df = adev->devfreq;
if (atomic_read(&df->suspend_count) > 0) {
*freq = 0;
return 0;
}
#ifdef CONFIG_DRM_SGPU_EXYNOS
*freq = cal_dfs_cached_get_rate(adev->cal_id);
#else
*freq = adev->devfreq->previous_freq;
#endif /* CONFIG_DRM_SGPU_EXYNOS */
return 0;
}
static void sgpu_devfreq_exit(struct device *dev)
{
struct drm_device *ddev = dev_get_drvdata(dev);
struct amdgpu_device *adev = ddev->dev_private;
#ifdef CONFIG_DRM_SGPU_EXYNOS
exynos_interface_deinit(adev->devfreq);
#endif /* CONFIG_DRM_SGPU_EXYNOS */
sgpu_unregister_pm_qos(adev->devfreq);
sgpu_governor_deinit(adev->devfreq);
kfree(adev->devfreq->profile);
return;
}
int sgpu_devfreq_init(struct amdgpu_device *adev)
{
struct devfreq_dev_profile *dp;
struct sgpu_governor_data *data;
int ret = 0;
dp = kzalloc(sizeof(struct devfreq_dev_profile), GFP_KERNEL);
if (!dp)
return -ENOMEM;
dp->target = sgpu_devfreq_target;
dp->get_dev_status = sgpu_devfreq_status;
dp->get_cur_freq = sgpu_devfreq_cur_freq;
dp->exit = sgpu_devfreq_exit;
dp->timer = DEVFREQ_TIMER_DELAYED;
ret = sgpu_governor_init(adev->dev, dp, &data);
if (ret)
goto err_gov;
adev->devfreq = devfreq_add_device(adev->dev, dp, "sgpu_governor", data);
if (IS_ERR(adev->devfreq)) {
dev_err(adev->dev, "Unable to register with devfreq %d\n", ret);
ret = PTR_ERR(adev->devfreq);
goto err_devfreq;
}
adev->devfreq->suspend_freq = dp->initial_freq;
ret = sgpu_register_pm_qos(adev->devfreq);
if (ret) {
dev_err(adev->dev, "Unable to register pm QoS requests of devfreq %d\n", ret);
goto err_noti;
}
ret = sgpu_create_sysfs_file(adev->devfreq);
if (ret) {
dev_err(adev->dev, "Unable to create sysfs node %d\n", ret);
goto err_sysfs;
}
#ifdef CONFIG_DRM_SGPU_EXYNOS
ret = exynos_interface_init(adev->devfreq);
if (ret) {
dev_err(adev->dev, "Unable to create exynos interface %d\n", ret);
goto err_sysfs;
}
#endif /* CONFIG_DRM_SGPU_EXYNOS */
return ret;
err_sysfs:
sgpu_unregister_pm_qos(adev->devfreq);
err_noti:
devfreq_remove_device(adev->devfreq);
err_devfreq:
sgpu_governor_deinit(adev->devfreq);
err_gov:
kfree(dp);
return ret;
}