/* * 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 #include #include #include #include #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; }