android_kernel_samsung_on5x.../drivers/cpufreq/exynos-mp-cpufreq-cal.c
2018-06-19 23:16:04 +02:00

346 lines
9.2 KiB
C

/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* EXYNOS - MP CPU frequency scaling support for EXYNOS Big.Little series
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/cpufreq.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <soc/samsung/cpufreq.h>
#include <soc/samsung/exynos-pmu.h>
#include "../../drivers/soc/samsung/pwrcal/pwrcal.h"
#if defined(CONFIG_SOC_EXYNOS8890)
#include "../../drivers/soc/samsung/pwrcal/S5E8890/S5E8890-vclk.h"
#elif defined(CONFIG_SOC_EXYNOS7870)
#include "../../drivers/soc/samsung/pwrcal/S5E7870/S5E7870-vclk.h"
#endif
static struct exynos_dvfs_info *exynos_info[CL_END];
static unsigned int exynos_mp_cpufreq_cl0_get_freq(void)
{
unsigned int freq;
freq = (unsigned int)cal_dfs_get_rate(dvfs_cpucl0);
if (freq <= 0)
pr_err("CL_ZERO: falied cal_dfs_get_rate(%dKHz)\n", freq);
return freq;
}
static unsigned int exynos_mp_cpufreq_cl1_get_freq(void)
{
unsigned int freq;
freq = (unsigned int)cal_dfs_get_rate(dvfs_cpucl1);
if (freq <= 0)
pr_err("CL_ONE: falied cal_dfs_get_rate(%dKHz)\n", freq);
return freq;
}
static void exynos_mp_cpufreq_cl0_set_freq(unsigned int old_index,
unsigned int new_index)
{
if (cal_dfs_set_rate(dvfs_cpucl0,
(unsigned long)exynos_info[CL_ZERO]->freq_table[new_index].frequency) < 0)
pr_err("CL0 : failed to set_freq(%d -> %d)\n",old_index, new_index);
}
static void exynos_mp_cpufreq_cl0_set_ema(unsigned int volt)
{
if (cal_dfs_set_ema(dvfs_cpucl0, volt) < 0)
pr_err("failed to cl0_set_ema(volt %d)\n", volt);
}
static void exynos_mp_cpufreq_cl1_set_freq(unsigned int old_index,
unsigned int new_index)
{
if (cal_dfs_set_rate(dvfs_cpucl1,
(unsigned long)exynos_info[CL_ONE]->freq_table[new_index].frequency) < 0)
pr_err("CL1 : failed to set_freq(%d -> %d)\n",old_index, new_index);
}
static void exynos_mp_cpufreq_cl1_set_ema(unsigned int volt)
{
if (cal_dfs_set_ema(dvfs_cpucl1, volt) < 0)
pr_err("failed to cl1_set_ema(volt %d)\n", volt);
}
static int exynos_mp_cpufreq_init_smpl(void)
{
int ret;
ret = cal_dfs_ext_ctrl(dvfs_cpucl1, cal_dfs_initsmpl, 0);
if (ret < 0) {
pr_err("CL1 : SMPL_WARN init failed.\n");
return -EINVAL;
}
return 0;
}
static int exynos_mp_cpufreq_check_smpl(void)
{
int ret = 0;
ret = cal_dfs_ext_ctrl(dvfs_cpucl1, cal_dfs_get_smplstatus, 0);
if (ret == 0) {
return ret;
} else if (ret > 0) {
pr_info("CL1 : SMPL_WARN HAPPENED!\n");
return ret;
} else {
pr_err("CL1 : SMPL_WARN check failed.\n");
}
return ret;
}
static void exynos_mp_cpufreq_clear_smpl(void)
{
if (cal_dfs_ext_ctrl(dvfs_cpucl1, cal_dfs_setsmpl, 0) < 0)
pr_err("CL1 : SMPL_WARN clear failed.\n");
}
static void exynos_mp_cpufreq_set_cal_ops(cluster_type cluster)
{
/* set cal ops for cpucl0 core */
if (!cluster) {
exynos_info[cluster]->set_freq = exynos_mp_cpufreq_cl0_set_freq;
exynos_info[cluster]->get_freq = exynos_mp_cpufreq_cl0_get_freq;
/* set cal ops for cpucl1 core */
} else {
exynos_info[cluster]->set_freq = exynos_mp_cpufreq_cl1_set_freq;
exynos_info[cluster]->get_freq = exynos_mp_cpufreq_cl1_get_freq;
if (exynos_info[cluster]->en_smpl) {
exynos_info[cluster]->init_smpl = exynos_mp_cpufreq_init_smpl;
exynos_info[cluster]->clear_smpl = exynos_mp_cpufreq_clear_smpl;
exynos_info[cluster]->check_smpl = exynos_mp_cpufreq_check_smpl;
} else {
exynos_info[cluster]->init_smpl = NULL;
exynos_info[cluster]->clear_smpl = NULL;
exynos_info[cluster]->check_smpl = NULL;
}
}
}
static int exynos_mp_cpufreq_init_cal_table(cluster_type cluster)
{
int table_size, cl_id, i;
struct dvfs_rate_volt *ptr_temp_table;
struct exynos_dvfs_info *ptr = exynos_info[cluster];
unsigned int cal_max_freq;
unsigned int cal_max_support_idx = 0;
if (!ptr->freq_table || !ptr->volt_table) {
pr_err("%s: freq of volt table is NULL\n", __func__);
return -EINVAL;
}
if (!cluster)
cl_id = dvfs_cpucl0;
else
cl_id = dvfs_cpucl1;
/* allocate to temporary memory for getting table from cal */
ptr_temp_table = kzalloc(sizeof(struct dvfs_rate_volt)
* ptr->max_idx_num, GFP_KERNEL);
/* check freq_table with cal */
table_size = cal_dfs_get_rate_asv_table(cl_id, ptr_temp_table);
if (ptr->max_idx_num != table_size) {
pr_err("%s: DT is not matched cal table size\n", __func__);
kfree(ptr_temp_table);
return -EINVAL;
}
cal_max_freq = cal_dfs_get_max_freq(cl_id);
if (!cal_max_freq) {
pr_err("%s: failed get max frequency from PWRCAL\n", __func__);
kfree(ptr_temp_table);
return -EINVAL;
}
for (i = 0; i< ptr->max_idx_num; i++) {
if (ptr->freq_table[i].frequency != (unsigned int)ptr_temp_table[i].rate) {
pr_err("%s: DT is not matched cal frequency_table(dt : %d, cal : %d\n",
__func__, ptr->freq_table[i].frequency,
(unsigned int)ptr_temp_table[i].rate);
kfree(ptr_temp_table);
return -EINVAL;
} else {
/* copy cal voltage to cpufreq driver voltage table */
ptr->volt_table[i] = ptr_temp_table[i].volt;
}
if (ptr_temp_table[i].rate == cal_max_freq)
cal_max_support_idx = i;
}
pr_info("CPUFREQ of %s CAL max_freq %lu KHz, DT max_freq %lu\n",
cluster ? "CL1" : "CL0",
ptr_temp_table[cal_max_support_idx].rate,
ptr_temp_table[ptr->max_support_idx].rate);
if (ptr->max_support_idx < cal_max_support_idx)
ptr->max_support_idx = cal_max_support_idx;
pr_info("CPUFREQ of %s Current max freq %lu KHz\n",
cluster ? "CL1" : "CL0",
ptr_temp_table[ptr->max_support_idx].rate);
/* free temporary memory */
kfree(ptr_temp_table);
return 0;
}
static void exynos_mp_cpufreq_print_info(cluster_type cluster)
{
int i;
pr_info("CPUFREQ of %s max_support_idx %d, min_support_idx %d\n",
cluster? "CL1" : "CL0",
exynos_info[cluster]->max_support_idx,
exynos_info[cluster]->min_support_idx);
pr_info("CPUFREQ of %s max_boot_qos %d, min_boot_qos %d\n",
cluster? "CL1" : "CL0",
exynos_info[cluster]->boot_max_qos,
exynos_info[cluster]->boot_min_qos);
for (i = 0; i < exynos_info[cluster]->max_idx_num; i ++) {
pr_info("CPUFREQ of %s : %2dL %8d KHz %7d uV (mif%8d KHz)\n",
cluster? "CL1" : "CL0",
exynos_info[cluster]->freq_table[i].driver_data,
exynos_info[cluster]->freq_table[i].frequency,
exynos_info[cluster]->volt_table[i],
exynos_info[cluster]->bus_table[i]);
}
}
static int exynos_mp_cpufreq_cal_init(cluster_type cluster)
{
int ret;
/* parsing cal rate, voltage table */
ret = exynos_mp_cpufreq_init_cal_table(cluster);
if (ret < 0) {
goto err_init_cal;
}
/* print cluster DT information */
exynos_mp_cpufreq_print_info(cluster);
/* set ops for cal */
exynos_mp_cpufreq_set_cal_ops(cluster);
return 0;
err_init_cal:
pr_err("%s: init_cal_table failed\n", __func__);
return ret;
}
int exynos_cpufreq_cluster1_init(struct exynos_dvfs_info * info)
{
if (info != NULL)
exynos_info[CL_ONE] = info;
else {
pr_err("%s: exynos_info[CL_ONE] is NULL\n", __func__);
}
if (exynos_mp_cpufreq_cal_init(CL_ONE)) {
pr_err("%s: CL_ONE: exynos_init failed\n", __func__);
return -EINVAL;
}
return 0;
}
int exynos_cpufreq_cluster0_init(struct exynos_dvfs_info * info)
{
if (info != NULL)
exynos_info[CL_ZERO] = info;
else {
pr_err("%s: exynos_info[CL_ZERO] is NULL\n", __func__);
}
if (exynos_mp_cpufreq_cal_init(CL_ZERO)) {
pr_err("%s: CL_ZERO: exynos_init failed\n", __func__);
return -EINVAL;
}
return 0;
}
static int exynos_cpufreq_regulator_event_cl0(struct notifier_block *nb,
unsigned long event, void *data)
{
static struct pre_voltage_change_data change_data;
if (event & REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) {
change_data = *((struct pre_voltage_change_data *)data);
if (change_data.min_uV > change_data.old_uV)
exynos_mp_cpufreq_cl0_set_ema(change_data.min_uV);
} else if (event & REGULATOR_EVENT_VOLTAGE_CHANGE) {
if (change_data.min_uV < change_data.old_uV)
exynos_mp_cpufreq_cl0_set_ema(change_data.min_uV);
}
return 0;
}
static int exynos_cpufreq_regulator_event_cl1(struct notifier_block *nb,
unsigned long event, void *data)
{
static struct pre_voltage_change_data change_data;
if (event & REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) {
change_data = *((struct pre_voltage_change_data *)data);
if (change_data.min_uV > change_data.old_uV)
exynos_mp_cpufreq_cl1_set_ema(change_data.min_uV);
} else if (event & REGULATOR_EVENT_VOLTAGE_CHANGE) {
if (change_data.min_uV < change_data.old_uV)
exynos_mp_cpufreq_cl1_set_ema(change_data.min_uV);
}
return 0;
}
static struct notifier_block exynos_cpufreq_regulator_set_nb[CL_END] = {
{ .notifier_call = exynos_cpufreq_regulator_event_cl0 },
{ .notifier_call = exynos_cpufreq_regulator_event_cl1 }
};
int exynos_cpufreq_regulator_register_notifier(cluster_type cluster)
{
if (exynos_info[cluster]->en_ema)
regulator_register_notifier(exynos_info[cluster]->regulator,
&exynos_cpufreq_regulator_set_nb[cluster]);
return 0;
}