295 lines
8 KiB
C
Executable file
295 lines
8 KiB
C
Executable file
/* SPDX-License-Identifier: GPL-2.0 */
|
|
|
|
/*
|
|
* (C) COPYRIGHT 2021 Samsung Electronics Inc. All rights reserved.
|
|
*
|
|
* This program is free software and is provided to you under the terms of the
|
|
* GNU General Public License version 2 as published by the Free Software
|
|
* Foundation, and any use by you of this program is subject to the terms
|
|
* of such GNU licence.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you can access it online at
|
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
|
*/
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/device.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <gpexbe_devicetree.h>
|
|
#include <gpex_utils.h>
|
|
#include <gpex_debug.h>
|
|
#include <gpex_qos.h>
|
|
#include <gpex_clock.h>
|
|
#include <gpex_tsg.h>
|
|
#include <gpexbe_qos.h>
|
|
#include <gpexbe_bts.h>
|
|
#include <gpex_clboost.h>
|
|
#include <gpex_gts.h>
|
|
#include <gpexbe_llc_coherency.h>
|
|
|
|
#include <gpexwa_peak_notify.h>
|
|
|
|
#define HCM_MODE_A (1 << 0)
|
|
#define HCM_MODE_B (1 << 1)
|
|
#define HCM_MODE_C (1 << 2)
|
|
|
|
struct _qos_info {
|
|
bool is_pm_qos_init;
|
|
int mo_min_clock;
|
|
unsigned int is_set_bts; // Check the pair of bts scenario.
|
|
bool gpu_bts_support;
|
|
spinlock_t bts_spinlock;
|
|
|
|
int cpu_cluster_count; // is this worth keeping after init?
|
|
|
|
int qos_table_size;
|
|
int clqos_table_size;
|
|
|
|
/* HCM Stuff */
|
|
int gpu_heavy_compute_cpu0_min_clock;
|
|
int gpu_heavy_compute_vk_cpu0_min_clock;
|
|
};
|
|
|
|
struct _qos_table {
|
|
int gpu_clock;
|
|
int mem_freq;
|
|
int cpu_little_min_freq;
|
|
int cpu_middle_min_freq;
|
|
int cpu_big_max_freq;
|
|
int llc_ways;
|
|
};
|
|
|
|
struct _clqos_table {
|
|
int gpu_clock;
|
|
int mif_min;
|
|
int little_min;
|
|
int middle_min;
|
|
int big_max;
|
|
};
|
|
|
|
static struct _qos_info qos_info;
|
|
static struct _qos_table *qos_table;
|
|
static struct _clqos_table *clqos_table;
|
|
|
|
static int gpex_qos_get_table_idx(int clock)
|
|
{
|
|
int idx;
|
|
|
|
for (idx = 0; idx < qos_info.qos_table_size; idx++) {
|
|
if (qos_table[idx].gpu_clock == clock)
|
|
return idx;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int gpex_qos_set(gpex_qos_flag flags, int val)
|
|
{
|
|
if (!qos_info.is_pm_qos_init) {
|
|
GPU_LOG(MALI_EXYNOS_ERROR, "%s: PM QOS ERROR : pm_qos not initialized\n", __func__);
|
|
return -ENOENT;
|
|
}
|
|
|
|
gpexbe_qos_request_update((mali_pmqos_flags)flags, val);
|
|
|
|
/* TODO: record PMQOS state somewhere */
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gpex_qos_unset(gpex_qos_flag flags)
|
|
{
|
|
if (!qos_info.is_pm_qos_init) {
|
|
GPU_LOG(MALI_EXYNOS_ERROR, "%s: PM QOS ERROR : pm_qos not initialized\n", __func__);
|
|
return -ENOENT;
|
|
}
|
|
gpexbe_qos_request_unset((mali_pmqos_flags)flags);
|
|
|
|
/* TODO: record PMQOS state somewhere */
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gpex_qos_init()
|
|
{
|
|
int i = 0;
|
|
gpu_dt *dt = gpexbe_devicetree_get_gpu_dt();
|
|
|
|
/* TODO: check dependent backends are initializaed */
|
|
|
|
spin_lock_init(&qos_info.bts_spinlock);
|
|
|
|
qos_info.gpu_bts_support = (bool)gpexbe_devicetree_get_int(gpu_bts_support);
|
|
qos_info.mo_min_clock = gpexbe_devicetree_get_int(gpu_mo_min_clock);
|
|
|
|
qos_info.qos_table_size = dt->gpu_dvfs_table_size.row;
|
|
qos_table = kcalloc(qos_info.qos_table_size, sizeof(*qos_table), GFP_KERNEL);
|
|
|
|
for (i = 0; i < qos_info.qos_table_size; i++) {
|
|
qos_table[i].gpu_clock = dt->gpu_dvfs_table[i].clock;
|
|
qos_table[i].mem_freq = dt->gpu_dvfs_table[i].mem_freq;
|
|
qos_table[i].cpu_little_min_freq = dt->gpu_dvfs_table[i].cpu_little_min_freq;
|
|
qos_table[i].cpu_middle_min_freq = dt->gpu_dvfs_table[i].cpu_middle_min_freq;
|
|
qos_table[i].cpu_big_max_freq = dt->gpu_dvfs_table[i].cpu_big_max_freq;
|
|
qos_table[i].llc_ways = dt->gpu_dvfs_table[i].llc_ways;
|
|
}
|
|
|
|
#if 0
|
|
if (gpex_qos_get_table_idx(qos_info.mo_min_clock) < 0) {
|
|
/* TODO: print error msg */
|
|
BUG();
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
qos_info.clqos_table_size = dt->gpu_cl_pmqos_table_size.row;
|
|
clqos_table = kcalloc(qos_info.clqos_table_size, sizeof(*clqos_table), GFP_KERNEL);
|
|
|
|
for (i = 0; i < qos_info.clqos_table_size; i++) {
|
|
clqos_table[i].gpu_clock = dt->gpu_cl_pmqos_table[i].clock;
|
|
clqos_table[i].mif_min = dt->gpu_cl_pmqos_table[i].mif_min;
|
|
clqos_table[i].little_min = dt->gpu_cl_pmqos_table[i].little_min;
|
|
clqos_table[i].middle_min = dt->gpu_cl_pmqos_table[i].middle_min;
|
|
clqos_table[i].big_max = dt->gpu_cl_pmqos_table[i].big_max;
|
|
}
|
|
|
|
qos_info.gpu_heavy_compute_cpu0_min_clock =
|
|
gpexbe_devicetree_get_int(gpu_heavy_compute_cpu0_min_clock);
|
|
qos_info.gpu_heavy_compute_vk_cpu0_min_clock =
|
|
gpexbe_devicetree_get_int(gpu_heavy_compute_vk_cpu0_min_clock);
|
|
|
|
/* Request to set QOS of other IPs */
|
|
gpexbe_qos_request_add(PMQOS_MIF | PMQOS_LITTLE | PMQOS_MIDDLE | PMQOS_BIG | PMQOS_MIN |
|
|
PMQOS_MAX);
|
|
|
|
qos_info.is_pm_qos_init = true;
|
|
|
|
gpex_utils_get_exynos_context()->qos_info = &qos_info;
|
|
gpex_utils_get_exynos_context()->qos_table = qos_table;
|
|
gpex_utils_get_exynos_context()->clqos_table = clqos_table;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void gpex_qos_term()
|
|
{
|
|
gpexbe_qos_request_remove(PMQOS_MIF | PMQOS_LITTLE | PMQOS_MIDDLE | PMQOS_BIG | PMQOS_MIN |
|
|
PMQOS_MAX);
|
|
kfree(qos_table);
|
|
kfree(clqos_table);
|
|
qos_info.is_pm_qos_init = false;
|
|
}
|
|
|
|
int gpex_qos_set_bts_mo(int clock)
|
|
{
|
|
int ret = 0;
|
|
unsigned long flags;
|
|
|
|
if (!qos_info.gpu_bts_support) {
|
|
if (qos_info.is_set_bts) {
|
|
/* TODO: print error */
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
spin_lock_irqsave(&qos_info.bts_spinlock, flags);
|
|
|
|
if (clock >= qos_info.mo_min_clock && !qos_info.is_set_bts) {
|
|
gpex_debug_new_record(HIST_BTS);
|
|
|
|
ret = gpexbe_bts_set_bts_mo(1);
|
|
gpex_debug_record(HIST_BTS, qos_info.is_set_bts, 1, ret);
|
|
|
|
if (ret) {
|
|
GPU_LOG(MALI_EXYNOS_WARNING, "BTS MO could not be set to gpu performance");
|
|
gpex_debug_incr_error_cnt(HIST_BTS);
|
|
} else
|
|
qos_info.is_set_bts = 1;
|
|
|
|
} else if ((clock == 0 || clock < qos_info.mo_min_clock) && qos_info.is_set_bts) {
|
|
gpex_debug_new_record(HIST_BTS);
|
|
|
|
ret = gpexbe_bts_set_bts_mo(0);
|
|
gpex_debug_record(HIST_BTS, qos_info.is_set_bts, 0, ret);
|
|
|
|
if (ret) {
|
|
GPU_LOG(MALI_EXYNOS_WARNING, "BTS MO could not be unset from gpu performance");
|
|
gpex_debug_incr_error_cnt(HIST_BTS);
|
|
} else
|
|
qos_info.is_set_bts = 0;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&qos_info.bts_spinlock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int gpex_qos_set_from_clock(int gpu_clock)
|
|
{
|
|
int idx = 0;
|
|
|
|
if (gpu_clock == 0) {
|
|
gpex_qos_unset(QOS_MIF | QOS_LITTLE | QOS_MIDDLE | QOS_MIN);
|
|
gpex_qos_set_bts_mo(gpu_clock);
|
|
gpexbe_llc_coherency_set_ways(0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
idx = gpex_qos_get_table_idx(gpu_clock);
|
|
|
|
if (idx < 0) {
|
|
/* TODO: print error msg */
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (gpex_clboost_check_activation_condition()) {
|
|
gpex_qos_set(QOS_MIF | QOS_MIN, clqos_table[idx].mif_min);
|
|
gpex_qos_set(QOS_LITTLE | QOS_MIN, clqos_table[idx].little_min);
|
|
gpex_qos_set(QOS_MIDDLE | QOS_MIN, clqos_table[idx].middle_min);
|
|
gpex_qos_set(QOS_BIG | QOS_MAX, INT_MAX);
|
|
/* TODO: revamp the qos interface so default max min can be set without knowing the clock */
|
|
//gpex_qos_set(QOS_BIG | QOS_MAX, BIG_MAX);
|
|
} else {
|
|
if (gpex_tsg_get_pmqos() == true) {
|
|
gpex_qos_set(QOS_MIF | QOS_MIN, 0);
|
|
gpex_qos_set(QOS_LITTLE | QOS_MIN, 0);
|
|
gpex_qos_set(QOS_MIDDLE | QOS_MIN, 0);
|
|
gpex_qos_set(QOS_BIG | QOS_MAX, INT_MAX);
|
|
} else {
|
|
gpex_qos_set(QOS_MIF | QOS_MIN, qos_table[idx].mem_freq);
|
|
|
|
if (!(gpex_gts_get_hcm_mode() & (HCM_MODE_A | HCM_MODE_C))) {
|
|
gpex_qos_set(QOS_LITTLE | QOS_MIN,
|
|
qos_table[idx].cpu_little_min_freq);
|
|
} else if (gpex_gts_get_hcm_mode() & HCM_MODE_A) {
|
|
gpex_qos_set(QOS_LITTLE | QOS_MIN,
|
|
qos_info.gpu_heavy_compute_cpu0_min_clock);
|
|
//pr_info("HCM: mode A QOS");
|
|
} else if (gpex_gts_get_hcm_mode() & HCM_MODE_C) {
|
|
gpex_qos_set(QOS_LITTLE | QOS_MIN,
|
|
qos_info.gpu_heavy_compute_vk_cpu0_min_clock);
|
|
//pr_info("HCM: mode B QOS");
|
|
}
|
|
|
|
gpex_qos_set(QOS_MIDDLE | QOS_MIN, qos_table[idx].cpu_middle_min_freq);
|
|
gpex_qos_set(QOS_BIG | QOS_MAX, qos_table[idx].cpu_big_max_freq);
|
|
}
|
|
|
|
gpexwa_peak_notify_update();
|
|
}
|
|
|
|
gpex_qos_set_bts_mo(gpu_clock);
|
|
gpexbe_llc_coherency_set_ways(qos_table[idx].llc_ways);
|
|
|
|
return 0;
|
|
}
|