You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

446 lines
12 KiB

/*
* Copyright (c) Hisilicon Technologies Co., Ltd. 2019-2020. All rights reserved.
* Description: dvfs file
*/
#define LOG_MODULE_ID SOC_ID_DVFS
#define LOG_FUNC_TRACE 1
#include "drv_dvfs.h"
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <asm/io.h>
#include <mntn/rdr_platform.h>
#include <linux/regulator/consumer.h>
#include "soc_log.h"
#include "osal_ext.h"
#include "drv_sys_ext.h"
#include "td_type.h"
#include "drv_dvfs_common.h"
#include "drv_dvfs_volt_calc.h"
#ifdef CONFIG_SOCT_TEMP_CTRL_SUPPORT
#include "drv_dvfs_temp_ctrl.h"
#endif
#ifndef CONFIG_SOCT_COMMON_KERNEL
#include "drv_dvfs_update.h"
#endif
td_void dvfs_freq_volt_matching_check(td_void)
{
td_s32 ret;
td_s32 cur_volt_uv;
td_s32 diff;
td_u32 need_volt_mv = 0;
td_u32 cpu, freq;
struct hl_dvfs_info *info = TD_NULL;
for_each_online_cpu(cpu) {
info = dvfs_get_cluster(cpu);
if ((info != TD_NULL) && (info->cpu_reg != TD_NULL) && (info->cpu_clk != TD_NULL)) {
mutex_lock(&info->dvfs_lock);
freq = clk_get_rate(info->cpu_clk);
cur_volt_uv = regulator_get_voltage(info->cpu_reg);
if (cur_volt_uv <= 0) {
soc_err_print_call_fun_err(regulator_get_voltage, cur_volt_uv);
soc_err_print_s32(cur_volt_uv);
mutex_unlock(&info->dvfs_lock);
rdr_system_error(MODID_AP_S_DVFS_NOPANIC, 0, 0);
return;
}
ret = dvfs_get_volt_for_new_freq(&need_volt_mv, freq);
if (ret) {
soc_err_print_call_fun_err(dvfs_get_volt_for_new_freq, ret);
}
diff = cur_volt_uv / VOLT_UV_TO_MV - need_volt_mv;
if (diff < 0) {
soc_err_print_info("Cpu volt and freq do not match!");
soc_err_print_u32(cpu);
soc_err_print_u32(freq);
soc_err_print_u32(cur_volt_uv);
soc_err_print_u32(need_volt_mv);
rdr_system_error(MODID_AP_S_DVFS_PANIC, 0, 0);
} else if (diff >= VOLT_DIFF_VALUE) {
soc_err_print_info("Cpu volt and freq do not match!");
soc_err_print_u32(cpu);
soc_err_print_u32(freq);
soc_err_print_u32(cur_volt_uv);
soc_err_print_u32(need_volt_mv);
rdr_system_error(MODID_AP_S_DVFS_NOPANIC, 0, 0);
}
mutex_unlock(&info->dvfs_lock);
}
}
return;
}
#ifdef CONFIG_SOCT_CPU_DVFS_SUPPORT
td_s32 dvfs_round_freq(td_u32 cpu, td_u32 *rate)
{
td_u32 freq;
struct cpufreq_policy *policy = TD_NULL;
struct cpufreq_frequency_table *freq_table = TD_NULL;
soc_dbg_func_enter();
dvfs_check_param_return_if(rate == TD_NULL, TD_FAILURE);
freq = *rate;
policy = cpufreq_cpu_get_raw(cpu);
if ((policy == TD_NULL) || (policy->freq_table == TD_NULL)) {
soc_err_print_call_fun_err(cpufreq_cpu_get_raw, TD_FAILURE);
return TD_FAILURE;
}
freq_table = policy->freq_table;
while (freq_table->frequency != CPUFREQ_TABLE_END) {
if (freq >= freq_table->frequency - MAX_DIFF_VALUE && freq <= freq_table->frequency + MAX_DIFF_VALUE) {
*rate = freq_table->frequency;
break;
}
freq_table++;
}
if (freq_table->frequency == CPUFREQ_TABLE_END) {
soc_err_print_info("freq is not in freq_table\n");
return TD_FAILURE;
}
soc_dbg_func_exit();
return TD_SUCCESS;
}
td_s32 dvfs_set_governor(const char *buf)
{
td_s32 ret;
td_u32 cpu;
struct cpufreq_policy *policy = TD_NULL;
struct hl_dvfs_info *info = TD_NULL;
for_each_online_cpu(cpu) {
policy = cpufreq_cpu_get_raw(cpu);
if (policy == TD_NULL) {
soc_err_print_call_fun_err(cpufreq_cpu_get_raw, TD_FAILURE);
soc_err_print_u32(cpu);
return TD_FAILURE;
}
info = dvfs_get_cluster(cpu);
if (info != TD_NULL) {
soc_print("set cpufreq governor to %s\n", buf);
ret = set_scaling_governor(policy, buf);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(set_scaling_governor, ret);
soc_err_print_u32(cpu);
return ret;
}
}
}
return TD_SUCCESS;
}
td_s32 dvfs_enter_active_standby(td_u32 model)
{
td_s32 ret;
soc_dbg_func_enter();
ret = dvfs_set_governor("powersave");
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dvfs_set_governor, ret);
return ret;
}
soc_dbg_func_exit();
return TD_SUCCESS;
}
td_s32 dvfs_quit_active_standby(td_void)
{
td_s32 ret;
soc_dbg_func_enter();
ret = dvfs_set_governor("schedutil");
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dvfs_set_governor, ret);
return ret;
}
soc_dbg_func_exit();
return TD_SUCCESS;
}
#else
td_s32 dvfs_round_freq(td_u32 cpu, td_u32 *rate)
{
td_u8 i;
td_u32 freq;
soc_dbg_func_enter();
dvfs_check_param_return_if(rate == TD_NULL, TD_FAILURE);
freq = *rate;
for (i = 0; i < (td_u8)elements_in(g_volt_cal_info); i++) {
if ((freq >= g_volt_cal_info[i].freq - MAX_DIFF_VALUE) && (freq <= g_volt_cal_info[i].freq + MAX_DIFF_VALUE)) {
*rate = g_volt_cal_info[i].freq;
break;
}
}
if (i == (td_u8)elements_in(g_volt_cal_info)) {
soc_err_print_info("freq is not in freq_table\n");
soc_err_print_u32(freq);
return TD_FAILURE;
}
soc_dbg_func_exit();
return TD_SUCCESS;
}
td_s32 dvfs_enter_active_standby(td_u32 model)
{
soc_dbg_func_enter();
soc_dbg_func_exit();
return TD_SUCCESS;
}
td_s32 dvfs_quit_active_standby(td_void)
{
soc_dbg_func_enter();
soc_dbg_func_exit();
return TD_SUCCESS;
}
#endif
td_s32 __weak otp_reg_ioremap(td_void)
{
return TD_SUCCESS;
}
td_void __weak otp_reg_iounmap(td_void)
{
}
td_s32 dvfs_init_volt_cal_info(td_void)
{
td_s32 ret;
td_u8 i;
td_u32 volt_min, volt_max, volt_otp_added;
ret = otp_reg_ioremap();
if (ret == TD_FAILURE) {
soc_err_print_call_fun_err(otp_reg_ioremap, ret);
return ret;
}
for (i = 0; i < (td_u8)elements_in(g_volt_cal_info); i++) {
ret = dvfs_init_corner_volt(g_volt_cal_info[i].freq, &volt_min, &volt_max);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dvfs_init_corner_volt, ret);
otp_reg_iounmap();
return ret;
}
g_volt_cal_info[i].volt = volt_min;
soc_info_print_u32(g_volt_cal_info[i].volt);
g_volt_cal_info[i].volt_max = volt_max;
soc_info_print_u32(g_volt_cal_info[i].volt_max);
ret = dvfs_init_otp_compensation_volt(g_volt_cal_info[i].freq, &volt_otp_added);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dvfs_init_otp_compensation_volt, ret);
otp_reg_iounmap();
return ret;
}
g_volt_cal_info[i].volt_otp_added = volt_otp_added;
soc_info_print_u32(g_volt_cal_info[i].volt_otp_added);
}
otp_reg_iounmap();
return TD_SUCCESS;
}
#ifndef CONFIG_SOCT_COMMON_KERNEL
td_void dvfs_set_agint_test_volt(td_bool increase, td_u32 volt)
{
td_u8 i;
td_u32 data_increase;
td_u32 data_decrease;
efi_status_t status;
td_ulong data_size = sizeof(volt);
efi_guid_t guid = EFI_GLOBAL_VARIABLE_GUID;
if (efi.set_variable == TD_NULL) {
soc_err_print_info("efi.set_variable is NULL pointer");
return;
}
if (increase) {
for (i = 0; i < (td_u8)elements_in(g_volt_cal_info); i++) {
g_volt_cal_info[i].volt_increase_test = volt;
g_volt_cal_info[i].volt_decrease_test = 0;
}
data_increase = volt;
data_decrease = 0;
} else {
for (i = 0; i < (td_u8)elements_in(g_volt_cal_info); i++) {
g_volt_cal_info[i].volt_increase_test = 0;
g_volt_cal_info[i].volt_decrease_test = volt;
}
data_increase = 0;
data_decrease = volt;
}
status = efi.set_variable(L"cpu_volt_increase", &guid, EFI_VARIABLE_BUTE, data_size, &data_increase);
if (status != EFI_SUCCESS) {
soc_err_print_call_fun_err(efi.set_variable, status);
return;
}
status = efi.set_variable(L"cpu_volt_decrease", &guid, EFI_VARIABLE_BUTE, data_size, &data_decrease);
if (status != EFI_SUCCESS) {
soc_err_print_call_fun_err(efi.set_variable, status);
return;
}
return;
}
td_void dvfs_get_agint_test_volt(td_void)
{
td_u8 i;
td_s32 ret;
td_s32 cpu;
efi_status_t status;
td_u32 data_increase = 0;
td_u32 data_decrease = 0;
td_ulong data_size = sizeof(data_increase);
efi_guid_t guid = EFI_GLOBAL_VARIABLE_GUID;
struct hl_dvfs_info *info = TD_NULL;
if (efi.get_variable == TD_NULL) {
soc_err_print_info("efi.get_variable is NULL pointer");
return;
}
status = efi.get_variable(L"cpu_volt_increase", &guid, NULL, &data_size, &data_increase);
if (status != EFI_SUCCESS) {
soc_warn_print_call_fun_err(efi.get_variable, status);
return;
}
status = efi.get_variable(L"cpu_volt_decrease", &guid, NULL, &data_size, &data_decrease);
if (status != EFI_SUCCESS) {
soc_warn_print_call_fun_err(efi.get_variable, status);
return;
}
for (i = 0; i < (td_u8)elements_in(g_volt_cal_info); i++) {
g_volt_cal_info[i].volt_increase_test = data_increase;
g_volt_cal_info[i].volt_decrease_test = data_decrease;
}
for_each_possible_cpu(cpu) {
info = dvfs_get_cluster((td_u32)cpu);
if (info != TD_NULL) {
ret = dvfs_update(info, 0, 0);
if (ret != TD_SUCCESS) {
soc_err_print_call_fun_err(dvfs_update, ret);
return;
}
}
}
return;
}
#endif
td_s32 dvfs_get_volt_for_new_freq(td_u32 *volt, td_u32 new_freq)
{
td_u8 i;
td_u8 target = 0;
td_u32 new_volt;
dvfs_temperature_status status = {0};
dvfs_check_param_return_if(volt == TD_NULL, TD_FAILURE);
for (i = 0; i < (td_u8)elements_in(g_volt_cal_info); i++) {
if (new_freq >= g_volt_cal_info[i].freq - MAX_DIFF_VALUE &&
new_freq <= g_volt_cal_info[i].freq + MAX_DIFF_VALUE) {
target = i;
soc_info_print_u32(g_volt_cal_info[i].freq);
break;
}
}
if (i == (td_u8)elements_in(g_volt_cal_info)) {
soc_err_print_info("new freq is invalid\n");
soc_err_print_u32(new_freq);
return TD_FAILURE;
}
/* get volt for new freq by hpm line */
new_volt = g_volt_cal_info[target].volt;
soc_info_print_u32(g_volt_cal_info[target].volt);
soc_info_print_u32(g_volt_cal_info[target].volt_max);
/* otp compensation */
new_volt += g_volt_cal_info[target].volt_otp_added;
soc_info_print_u32(g_volt_cal_info[target].volt_otp_added);
/* limit */
if ((g_volt_cal_info[target].volt_down_limit != 0) &&
(new_volt < g_volt_cal_info[target].volt_down_limit)) {
new_volt = g_volt_cal_info[target].volt_down_limit;
}
if ((g_volt_cal_info[target].volt_up_limit != 0) &&
(new_volt > g_volt_cal_info[target].volt_up_limit)) {
new_volt = g_volt_cal_info[target].volt_up_limit;
}
/* low temperature compensation */
#ifdef CONFIG_SOCT_TEMP_CTRL_SUPPORT
dvfs_get_temperature_status(&status);
if (status.low_temp_comp) {
new_volt += g_volt_cal_info[target].volt_low_temperature_added;
soc_info_print_u32(g_volt_cal_info[target].volt_low_temperature_added);
} else if (status.high_temp_comp) {
new_volt += g_volt_cal_info[target].volt_high_temperature_added;
soc_info_print_u32(g_volt_cal_info[target].volt_high_temperature_added);
}
#endif
#ifndef CONFIG_SOCT_COMMON_KERNEL
/* increase or decrease cpu volt */
new_volt += g_volt_cal_info[target].volt_increase_test;
new_volt -= g_volt_cal_info[target].volt_decrease_test;
#endif
new_volt = (new_volt > g_volt_cal_info[target].volt_max) ? g_volt_cal_info[target].volt_max : new_volt;
*volt = new_volt;
return TD_SUCCESS;
}