blob: 4d76442f58cfe3e1bd5d051ef81270c93f02022d [file] [log] [blame]
/*
* Copyright (C) 2017 MediaTek Inc.
*
* 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 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
*/
#include <linux/device.h>
#include <linux/seq_file.h>
#include <linux/file.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <helio-dvfsrc.h>
#include <helio-dvfsrc-opp.h>
#include <mtk_spm_vcore_dvfs.h>
#include <mt-plat/mtk_devinfo.h>
#if defined(CONFIG_MTK_DRAMC)
#include <mtk_dramc.h>
#endif
__weak int __spm_get_dram_type(void) { return 0; }
#define VCORE_OPP_EFUSE_NUM (2)
__weak int spm_vcorefs_pwarp_cmd(void) { return 0; }
/* SOC v1 Voltage (10uv)*/
static unsigned int
vcore_opp_L4_2CH[VCORE_DVFS_OPP_NUM][VCORE_OPP_EFUSE_NUM] = {
{ 800000, 800000 },
{ 725000, 700000 },
{ 725000, 700000 },
{ 725000, 700000 },
};
static unsigned int
vcore_opp_L4_2CH_CASE2[VCORE_DVFS_OPP_NUM][VCORE_OPP_EFUSE_NUM] = {
{ 800000, 800000 },
{ 800000, 800000 },
{ 725000, 700000 },
{ 725000, 700000 },
};
static unsigned int
vcore_opp_L3_1CH[VCORE_DVFS_OPP_NUM][VCORE_OPP_EFUSE_NUM] = {
{ 800000, 800000 },
{ 800000, 800000 },
{ 725000, 700000 },
{ 725000, 700000 },
};
int vcore_dvfs_to_vcore_opp[] = {
VCORE_OPP_0, VCORE_OPP_1, VCORE_OPP_1, VCORE_OPP_1, VCORE_OPP_NUM
};
int vcore_dvfs_to_ddr_opp[] = {
DDR_OPP_0, DDR_OPP_0, DDR_OPP_1, DDR_OPP_2, DDR_OPP_NUM
};
/* ptr that points to v1 or v2 opp table */
unsigned int (*vcore_opp)[VCORE_OPP_EFUSE_NUM];
/* final vcore opp table */
unsigned int vcore_opp_table[VCORE_DVFS_OPP_NUM];
/* record index for vcore opp table from efuse */
unsigned int vcore_opp_efuse_idx[VCORE_DVFS_OPP_NUM] = { 0 };
unsigned int get_cur_vcore_opp(void)
{
return vcore_dvfs_to_vcore_opp[spm_vcorefs_get_dvfs_opp()];
}
unsigned int get_cur_ddr_opp(void)
{
return vcore_dvfs_to_ddr_opp[spm_vcorefs_get_dvfs_opp()];
}
unsigned int get_min_opp_for_vcore(int vcore_opp)
{
int i = VCORE_DVFS_OPP_NUM;
for (i = VCORE_DVFS_OPP_NUM; i >= 0 ; i--) {
if (vcore_dvfs_to_vcore_opp[i] == vcore_opp)
break;
}
return i;
}
unsigned int get_min_opp_for_ddr(int ddr_opp)
{
int i = VCORE_DVFS_OPP_NUM;
for (i = VCORE_DVFS_OPP_NUM; i >= 0; i--) {
if (vcore_dvfs_to_ddr_opp[i] == ddr_opp)
break;
}
return i;
}
unsigned int get_vcore_opp_volt(unsigned int opp)
{
if (opp >= VCORE_DVFS_OPP_NUM) {
pr_info("WRONG OPP: %u\n", opp);
return 0;
}
return vcore_opp_table[opp];
}
unsigned int update_vcore_opp_uv(unsigned int opp, unsigned int vcore_uv)
{
unsigned int ret = 0;
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
int i;
#endif
if ((opp < VCORE_DVFS_OPP_NUM) && (opp >= 0))
vcore_opp_table[opp] = vcore_uv;
#ifdef CONFIG_MTK_TINYSYS_SSPM_SUPPORT
for (i = 0; i < VCORE_DVFS_OPP_NUM; i++)
dvfsrc_update_sspm_vcore_opp_table(i, get_vcore_opp_volt(i));
#endif
ret = spm_vcorefs_pwarp_cmd();
return ret;
}
static int get_soc_efuse(void)
{
pr_info("[VcoreFS]efuse=0x%x soc_efuse=0x%x\n",
get_devinfo_with_index(65), ((get_devinfo_with_index(65) >> 12) & 0x3));
return ((get_devinfo_with_index(65) >> 12) & 0x3);
}
static void build_vcore_opp_table(unsigned int ddr_type, unsigned int soc_efuse)
{
int i, mask = 0x3;
if (soc_efuse > 1) {
pr_info("WRONG VCORE EFUSE(%d)\n", soc_efuse);
/* set to default table */
for (i = 0; i < VCORE_DVFS_OPP_NUM; i++)
vcore_opp_table[i] = *(vcore_opp[i]);
return;
}
if (ddr_type == SPMFW_LP4X_2CH_3200) {
vcore_opp = &vcore_opp_L4_2CH[0];
vcore_opp_efuse_idx[0] = 0; /* 0.8V, no corner tightening*/
vcore_opp_efuse_idx[1] = soc_efuse & mask; /* 0.7V */
vcore_opp_efuse_idx[2] = soc_efuse & mask; /* 0.7V */
vcore_opp_efuse_idx[3] = soc_efuse & mask; /* 0.7V */
vcore_dvfs_to_vcore_opp[0] = VCORE_OPP_0;
vcore_dvfs_to_vcore_opp[1] = VCORE_OPP_1;
vcore_dvfs_to_vcore_opp[2] = VCORE_OPP_1;
vcore_dvfs_to_vcore_opp[3] = VCORE_OPP_1;
vcore_dvfs_to_ddr_opp[0] = DDR_OPP_0;
vcore_dvfs_to_ddr_opp[1] = DDR_OPP_0;
vcore_dvfs_to_ddr_opp[2] = DDR_OPP_1;
vcore_dvfs_to_ddr_opp[3] = DDR_OPP_2;
} else if (ddr_type == SPMFW_LP4X_2CH_3733 ||
ddr_type == SPMFW_LP4_2CH_2400) {
vcore_opp = &vcore_opp_L4_2CH_CASE2[0];
vcore_opp_efuse_idx[0] = 0; /* 0.8V, no corner tightening*/
vcore_opp_efuse_idx[1] = 0; /* 0.8V, no corner tightening*/
vcore_opp_efuse_idx[2] = soc_efuse & mask; /* 0.7V */
vcore_opp_efuse_idx[3] = soc_efuse & mask; /* 0.7V */
vcore_dvfs_to_vcore_opp[0] = VCORE_OPP_0;
vcore_dvfs_to_vcore_opp[1] = VCORE_OPP_0;
vcore_dvfs_to_vcore_opp[2] = VCORE_OPP_1;
vcore_dvfs_to_vcore_opp[3] = VCORE_OPP_1;
vcore_dvfs_to_ddr_opp[0] = DDR_OPP_0;
vcore_dvfs_to_ddr_opp[1] = DDR_OPP_1;
vcore_dvfs_to_ddr_opp[2] = DDR_OPP_1;
vcore_dvfs_to_ddr_opp[3] = DDR_OPP_2;
} else if (ddr_type == SPMFW_LP3_1CH_1866) {
vcore_opp = &vcore_opp_L3_1CH[0];
vcore_opp_efuse_idx[0] = 0; /* 0.8V, no corner tightening*/
vcore_opp_efuse_idx[1] = 0; /* 0.8V, no corner tightening*/
vcore_opp_efuse_idx[2] = soc_efuse & mask; /* 0.7V */
vcore_opp_efuse_idx[3] = soc_efuse & mask; /* 0.7V */
vcore_dvfs_to_vcore_opp[0] = VCORE_OPP_0;
vcore_dvfs_to_vcore_opp[1] = VCORE_OPP_0;
vcore_dvfs_to_vcore_opp[2] = VCORE_OPP_1;
vcore_dvfs_to_vcore_opp[3] = VCORE_OPP_1;
vcore_dvfs_to_ddr_opp[0] = DDR_OPP_0;
vcore_dvfs_to_ddr_opp[1] = DDR_OPP_1;
vcore_dvfs_to_ddr_opp[2] = DDR_OPP_1;
vcore_dvfs_to_ddr_opp[3] = DDR_OPP_2;
} else {
pr_info("WRONG SPM DRAM TYPE: %d\n", ddr_type);
return;
}
for (i = 0; i < VCORE_DVFS_OPP_NUM; i++)
vcore_opp_table[i] = *(vcore_opp[i] + vcore_opp_efuse_idx[i]);
for (i = VCORE_DVFS_OPP_NUM - 2; i >= 0; i--)
vcore_opp_table[i] =
max(vcore_opp_table[i], vcore_opp_table[i + 1]);
pr_info("[VcoreFS]table(d=%d, ef=%d): %d, %d, %d, %d\n",
ddr_type, soc_efuse, vcore_opp_table[0], vcore_opp_table[1],
vcore_opp_table[2], vcore_opp_table[3]);
pr_info("[VcoreFS]vcore opp tbl: %d, %d, %d, %d\n",
vcore_dvfs_to_vcore_opp[0], vcore_dvfs_to_vcore_opp[1],
vcore_dvfs_to_vcore_opp[2], vcore_dvfs_to_vcore_opp[3]);
pr_info("[VcoreFS]ddr opp tbl: %d, %d, %d, %d\n",
vcore_dvfs_to_ddr_opp[0], vcore_dvfs_to_ddr_opp[1],
vcore_dvfs_to_ddr_opp[2], vcore_dvfs_to_ddr_opp[3]);
}
static char *_copy_from_user_for_proc(const char __user *buffer, size_t count)
{
char *buf = (char *)__get_free_page(GFP_USER);
if (!buf)
return NULL;
if (count >= PAGE_SIZE)
goto out;
if (copy_from_user(buf, buffer, count))
goto out;
buf[count] = '\0';
return buf;
out:
free_page((unsigned long)buf);
return NULL;
}
static int vcore_opp_proc_show(struct seq_file *m, void *v)
{
unsigned int i = 0;
for (i = 0; i < VCORE_DVFS_OPP_NUM; i++)
seq_printf(m, "%d ", get_vcore_opp_volt(i));
seq_puts(m, "\n");
return 0;
}
static ssize_t vcore_opp_proc_write(struct file *file,
const char __user *buffer, size_t count, loff_t *pos)
{
s32 opp, vcore_uv;
char *buf = _copy_from_user_for_proc(buffer, count);
if (!buf)
return -EINVAL;
if (sscanf(buf, "%d %d", &opp, &vcore_uv) != 2)
goto err;
if (opp < 0 || opp >= VCORE_DVFS_OPP_NUM || vcore_uv < 0)
goto err;
update_vcore_opp_uv(opp, vcore_uv);
free_page((unsigned long)buf);
return count;
err:
free_page((unsigned long)buf);
return -EINVAL;
}
#define PROC_FOPS_RW(name) \
static int name ## _proc_open(struct inode *inode, \
struct file *file) \
{ \
return single_open(file, name ## _proc_show, \
PDE_DATA(inode)); \
} \
static const struct file_operations name ## _proc_fops = { \
.owner = THIS_MODULE, \
.open = name ## _proc_open, \
.read = seq_read, \
.llseek = seq_lseek, \
.release = single_release, \
.write = name ## _proc_write, \
}
#define PROC_ENTRY(name) {__stringify(name), &name ## _proc_fops}
PROC_FOPS_RW(vcore_opp);
static int vcore_opp_procfs_init(void)
{
struct proc_dir_entry *dir = NULL;
int ret = 0;
int i;
struct pentry {
const char *name;
const struct file_operations *fops;
};
struct pentry det_entries_vcore[] = {
PROC_ENTRY(vcore_opp),
};
dir = proc_mkdir("vcore_opp", NULL);
if (!dir) {
pr_info("%s: Failed to create /proc/vcore_opp dir\n", __func__);
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(det_entries_vcore); i++) {
if (!proc_create(det_entries_vcore[i].name,
0644, dir,
det_entries_vcore[i].fops)) {
pr_info("%s: Failed to create /proc/vcore_opp/%s\n",
__func__, det_entries_vcore[i].name);
return -ENOMEM;
}
}
return ret;
}
int vcore_opp_init(void)
{
int ret = 0;
ret = vcore_opp_procfs_init();
if (ret)
return ret;
build_vcore_opp_table(__spm_get_dram_type(), get_soc_efuse());
return 0;
}