Files

399 lines
12 KiB
C
Executable File

/*
* cpufreq.c- Sigmastar
*
* Copyright (c) [2019~2020] SigmaStar Technology.
*
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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 version 2 for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/cpu.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/version.h>
#include <linux/pm_opp.h>
#include <linux/kthread.h>
#include "ms_types.h"
#include "ms_platform.h"
#include "registers.h"
#include "voltage_ctrl.h"
u32 sidd_th_100x = 1243; //sidd threshold=12.74mA
static struct device *cpu;
static struct cpufreq_frequency_table *freq_table;
int g_sCurrentTemp = 35;
static int g_sCurrentTempThreshLo = -20;
static int g_sCurrentTempThreshHi = -15;
struct timer_list timer_temp;
void cpu_dvfs(U32 u32TargetLpf, U32 u32TargetPostDiv);
static int ms_cpufreq_target_index(struct cpufreq_policy *policy, unsigned int index)
{
struct cpufreq_freqs freqs;
int ret;
struct dev_pm_opp *opp;
unsigned long new_freq;
int opp_voltage_mV;
freqs.old = policy->cur;
freqs.new = freq_table[index].frequency;
new_freq = freqs.new * 1000;
rcu_read_lock();
opp = dev_pm_opp_find_freq_ceil(cpu, &new_freq);
if (IS_ERR(opp)) {
rcu_read_unlock();
pr_err("[%s] %d not found in OPP\n", __func__, freqs.new);
return -EINVAL;
}
rcu_read_unlock();
opp_voltage_mV = (dev_pm_opp_get_voltage(opp)? dev_pm_opp_get_voltage(opp)/1000 : 0);
#ifdef CONFIG_SS_VOLTAGE_CTRL
if (opp_voltage_mV > get_core_voltage())
{
set_core_voltage(VOLTAGE_DEMANDER_CPUFREQ, opp_voltage_mV);
udelay(10); //delay 10us to wait voltage stable (from low to high).
ret = clk_set_rate(policy->clk, new_freq);
}
else
{
ret = clk_set_rate(policy->clk, new_freq);
set_core_voltage(VOLTAGE_DEMANDER_CPUFREQ, opp_voltage_mV);
}
#else
ret = clk_set_rate(policy->clk, new_freq);
#endif
return ret;
}
static ssize_t show_cpufreq_testout(struct kobject *kobj, struct attribute *attr, char *buf)
{
char *str = buf;
char *end = buf + PAGE_SIZE;
u16 reg_value = 0;
u32 freq = 0;
if( (INREG8(BASE_REG_RIU_PA + (0x102216 << 1))&BIT0) != BIT0)
{
OUTREG8(BASE_REG_RIU_PA + (0x102216 << 1), 0x01);
OUTREG8(BASE_REG_RIU_PA + (0x101EEE << 1), 0x04);
OUTREG8(BASE_REG_RIU_PA + (0x101EEA << 1), 0x04);
OUTREG8(BASE_REG_RIU_PA + (0x101EEA << 1)+1, 0x40);
OUTREG8(BASE_REG_RIU_PA + (0x101EEC << 1), 0x01);
OUTREG8(BASE_REG_RIU_PA + (0x101EE0 << 1)+1, 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EE0 << 1)+1, 0x80);
udelay(100);
}
reg_value = (INREG8(BASE_REG_RIU_PA + (0x101EE2 << 1)) | (INREG8(BASE_REG_RIU_PA + (0x101EE2 << 1)+1)<<8));
//freq = (reg_value * 4000)/83333;
freq = reg_value * 48000;
str += scnprintf(str, end - str, "%d\n", freq);
return (str - buf);
}
static ssize_t store_cpufreq_testout(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
u32 enable;
if (sscanf(buf, "%d", &enable)<=0)
return 0;
if(enable)
{
pr_info("[CPUFREQ] Freq testout ON\n");
OUTREG8(BASE_REG_RIU_PA + (0x102216 << 1), 0x01);
OUTREG8(BASE_REG_RIU_PA + (0x101EEE << 1), 0x04);
OUTREG8(BASE_REG_RIU_PA + (0x101EEA << 1), 0x04);
OUTREG8(BASE_REG_RIU_PA + (0x101EEA << 1)+1, 0x40);
OUTREG8(BASE_REG_RIU_PA + (0x101EEC << 1), 0x01);
OUTREG8(BASE_REG_RIU_PA + (0x101EE0 << 1)+1, 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EE0 << 1)+1, 0x80);
}
else
{
pr_info("[CPUFREQ] Freq testout OFF\n");
OUTREG8(BASE_REG_RIU_PA + (0x102216 << 1), 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EEE << 1), 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EEA << 1), 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EEA << 1)+1, 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EEC << 1), 0x00);
OUTREG8(BASE_REG_RIU_PA + (0x101EE0 << 1)+1, 0x00);
}
return count;
}
define_one_global_rw(cpufreq_testout);
static ssize_t show_cpufreq_force(struct kobject *kobj, struct attribute *attr, char *buf)
{
char *str = buf;
char *end = buf + PAGE_SIZE;
str += scnprintf(str, end - str, "\n");
return (str - buf);
}
static ssize_t store_cpufreq_force(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
u32 rate;
unsigned int lpf_value;
unsigned int post_div = 2;
if (sscanf(buf, "%d", &rate)<=0)
return 0;
/*
* The default of post_div is 2, choose appropriate post_div by CPU clock.
*/
if (rate >= 800000000)
post_div = 2;
else if (rate >= 400000000)
post_div = 4;
else if (rate >= 200000000)
post_div = 8;
else
post_div = 16;
/*
* Calculate LPF value for DFS
* LPF_value(5.19) = (432MHz / Ref_clk) * 2^19 => it's for post_div=2
* Ref_clk = CPU_CLK * 2 / 32
*/
lpf_value = (U32)(div64_u64(432000000llu * 524288, (rate*2/32) * post_div / 2));
cpu_dvfs(lpf_value, post_div);
return count;
}
define_one_global_rw(cpufreq_force);
static ssize_t show_temp_out(struct kobject *kobj, struct attribute *attr, char *buf)
{
char *str = buf;
char *end = buf + PAGE_SIZE;
str += scnprintf(str, end - str, "Temp=%d\n", g_sCurrentTemp);
return (str - buf);
}
define_one_global_ro(temp_out);
static ssize_t show_temp_adjust_threshold_lo(struct kobject *kobj, struct attribute *attr, char *buf)
{
char *str = buf;
char *end = buf + PAGE_SIZE;
str += scnprintf(str, end - str, "%d\n", g_sCurrentTempThreshLo);
return (str - buf);
}
static ssize_t store_temp_adjust_threshold_lo(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
u32 value;
if (sscanf(buf, "%d", &value)<=0)
return 0;
g_sCurrentTempThreshLo = value;
return count;
}
define_one_global_rw(temp_adjust_threshold_lo);
static ssize_t show_temp_adjust_threshold_hi(struct kobject *kobj, struct attribute *attr, char *buf)
{
char *str = buf;
char *end = buf + PAGE_SIZE;
str += scnprintf(str, end - str, "%d\n", g_sCurrentTempThreshHi);
return (str - buf);
}
static ssize_t store_temp_adjust_threshold_hi(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
u32 value;
if (sscanf(buf, "%d", &value)<=0)
return 0;
g_sCurrentTempThreshHi = value;
return count;
}
define_one_global_rw(temp_adjust_threshold_hi);
int ms_get_temp(void)
{
int vbe_code_ft;
int vbe_code;
CLRREG16(BASE_REG_PMSAR_PA + REG_ID_19, BIT6); //ch7 reference voltage select to 2.0V
CLRREG16(BASE_REG_PMSAR_PA + REG_ID_10, BIT0); //reg_pm_dmy
SETREG16(BASE_REG_PMSLEEP_PA + REG_ID_64, BIT10);
SETREG16(BASE_REG_PMSLEEP_PA + REG_ID_2F, BIT2);
OUTREG16(BASE_REG_PMSAR_PA + REG_ID_00, 0xA20);
SETREG16(BASE_REG_PMSAR_PA + REG_ID_00, BIT14);
vbe_code = INREG16(BASE_REG_PMSAR_PA + REG_ID_46);
vbe_code_ft = INREGMSK16(BASE_REG_EFUSE_PA + REG_ID_09, 0x3FF);
if (vbe_code_ft == 0) // if no trim info
vbe_code_ft = 400;
//GF28LP equation to calculate temperature
return (1270 * (vbe_code_ft - vbe_code) + 29000)/1000;
}
EXPORT_SYMBOL(ms_get_temp);
static int monitor_temp_thread_handler(void *data)
{
while (!kthread_should_stop())
{
msleep_interruptible(1000);
g_sCurrentTemp = ms_get_temp();
#ifdef CONFIG_SS_VOLTAGE_CTRL
if(get_core_voltage() > VOLTAGE_CORE_900 && g_sCurrentTemp > g_sCurrentTempThreshHi )
set_core_voltage(VOLTAGE_DEMANDER_TEMPERATURE, VOLTAGE_CORE_900);
if(get_core_voltage() < VOLTAGE_CORE_1000 && g_sCurrentTemp < g_sCurrentTempThreshLo )
set_core_voltage(VOLTAGE_DEMANDER_TEMPERATURE, VOLTAGE_CORE_1000);
#endif
}
return 0;
}
static int ms_cpufreq_init(struct cpufreq_policy *policy)
{
int ret;
struct task_struct *thr = NULL;
if (policy->cpu != 0)
return -EINVAL;
policy->clk = devm_clk_get(cpu, 0);
if (IS_ERR(policy->clk)) {
pr_err("[%s] get cpu clk fail\n", __func__);
return PTR_ERR(policy->clk);
}
ret = dev_pm_opp_init_cpufreq_table(cpu, &freq_table);
if (ret) {
pr_err("[%s] init OPP fail\n", __func__);
return ret;
}
ret = cpufreq_generic_init(policy, freq_table, 100000);
if (ret) {
pr_err("[%s] init policy fail\n", __func__);
goto fail;
}
policy->min = 800000;
policy->max = 800000;
//create a thread for monitor temperature
ms_get_temp(); //We will update temperature after 1sec. Drop first value due to one adc need cost 8ch*8.9usec.
thr = kthread_run(monitor_temp_thread_handler, NULL, "monitor_temp");
if (!thr) {
pr_info("kthread_run fail");
}
pr_info("[%s] Current clk=%lu\n", __func__, clk_get_rate(policy->clk));
return ret;
fail:
dev_pm_opp_free_cpufreq_table(cpu, &freq_table);
return ret;
}
static int ms_cpufreq_exit(struct cpufreq_policy *policy)
{
dev_pm_opp_free_cpufreq_table(cpu, &freq_table);
return 0;
}
static struct cpufreq_driver ms_cpufreq_driver = {
.verify = cpufreq_generic_frequency_table_verify,
.attr = cpufreq_generic_attr,
.target_index = ms_cpufreq_target_index,
.get = cpufreq_generic_get,
.init = ms_cpufreq_init,
.exit = ms_cpufreq_exit,
.name = "Mstar cpufreq",
};
static int ms_cpufreq_probe(struct platform_device *pdev)
{
int ret;
cpu = get_cpu_device(0);
if (dev_pm_opp_of_add_table(cpu)) {
pr_err("[%s] add OPP fail\n", __func__);
return -EINVAL;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,4,0)
ret = cpufreq_sysfs_create_file(&cpufreq_testout.attr);
ret |= cpufreq_sysfs_create_file(&temp_adjust_threshold_lo.attr);
ret |= cpufreq_sysfs_create_file(&temp_adjust_threshold_hi.attr);
ret |= cpufreq_sysfs_create_file(&temp_out.attr);
ret |= cpufreq_sysfs_create_file(&cpufreq_force.attr);
#else
ret = sysfs_create_file(cpufreq_global_kobject, &cpufreq_testout.attr);
ret |= sysfs_create_file(cpufreq_global_kobject, &temp_adjust_threshold_lo.attr);
ret |= sysfs_create_file(cpufreq_global_kobject, &temp_adjust_threshold_hi.attr);
ret |= sysfs_create_file(cpufreq_global_kobject, &temp_out.attr);
ret |= sysfs_create_file(cpufreq_global_kobject, &cpufreq_force.attr);
#endif
if (ret)
{
pr_err("[%s] create file fail\n", __func__);
}
return cpufreq_register_driver(&ms_cpufreq_driver);
}
static int ms_cpufreq_remove(struct platform_device *pdev)
{
return cpufreq_unregister_driver(&ms_cpufreq_driver);
}
static const struct of_device_id ms_cpufreq_of_match_table[] = {
{ .compatible = "sstar,infinity-cpufreq" },
{}
};
MODULE_DEVICE_TABLE(of, ms_cpufreq_of_match_table);
static struct platform_driver ms_cpufreq_platdrv = {
.driver = {
.name = "ms_cpufreq",
.owner = THIS_MODULE,
.of_match_table = ms_cpufreq_of_match_table,
},
.probe = ms_cpufreq_probe,
.remove = ms_cpufreq_remove,
};
module_platform_driver(ms_cpufreq_platdrv);