blob: 38ccf94a09f6d94bdd826c9e5823af9631392f0e [file] [log] [blame]
/*
* Copyright (C) 2018 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/platform_device.h>
#ifdef CONFIG_MTK_DRAMC
#include <mtk_dramc.h>
#endif
#ifdef CONFIG_MTK_EMI
#include <mt_emi_api.h>
#endif
#include <mt-plat/upmu_common.h>
#include "helio-dvfsrc-ip-v2.h"
#include <helio-dvfsrc-opp.h>
#include <helio-dvfsrc-mt6785.h>
#include <mt-plat/mtk_devinfo.h>
#include <linux/regulator/consumer.h>
#include "mmdvfs_pmqos.h"
#include <ext_wd_drv.h>
#define DVFSRC_1600_FLOOR
#define AUTOK_ENABLE
static struct reg_config dvfsrc_init_configs[][128] = {
{
{ DVFSRC_TIMEOUT_NEXTREQ, 0x0000002B },
{ DVFSRC_INT_EN, 0x00000002 },
{ DVFSRC_QOS_EN, 0x0000407C },
{ DVFSRC_VCORE_REQUEST4, 0x21110000 },
#ifdef DVFSRC_1600_FLOOR
{ DVFSRC_DDR_REQUEST, 0x00004322 },
#else
{ DVFSRC_DDR_REQUEST, 0x00004321 },
#endif
{ DVFSRC_DDR_REQUEST3, 0x00000055 },
#ifdef DVFSRC_1600_FLOOR
{ DVFSRC_DDR_REQUEST5, 0x00322000 },
#else
{ DVFSRC_DDR_REQUEST5, 0x00321000 },
#endif
{ DVFSRC_DDR_REQUEST7, 0x54000000 },
{ DVFSRC_DDR_REQUEST8, 0x55000000 },
{ DVFSRC_DDR_QOS0, 0x00000019 },
{ DVFSRC_DDR_QOS1, 0x00000026 },
{ DVFSRC_DDR_QOS2, 0x00000033 },
{ DVFSRC_DDR_QOS3, 0x0000004C },
{ DVFSRC_DDR_QOS4, 0x00000066 },
{ DVFSRC_DDR_QOS5, 0x00000077 },
{ DVFSRC_DDR_QOS6, 0x00000077 },
{ DVFSRC_95MD_SCEN_BW0_T, 0x22222220 },
{ DVFSRC_95MD_SCEN_BW1_T, 0x22222222 },
{ DVFSRC_95MD_SCEN_BW2_T, 0x22222222 },
{ DVFSRC_95MD_SCEN_BW3_T, 0x52222222 },
{ DVFSRC_95MD_SCEN_BW4, 0x00000005 },
{ DVFSRC_HRT_REQ_UNIT, 0x0000011E },
{ DVSFRC_HRT_REQ_MD_URG, 0x0000D3D3 },
{ DVFSRC_HRT_REQ_MD_BW_0, 0x00200802 },
{ DVFSRC_HRT_REQ_MD_BW_1, 0x00200800 },
{ DVFSRC_HRT_REQ_MD_BW_2, 0x00200002 },
{ DVFSRC_HRT_REQ_MD_BW_3, 0x00200802 },
{ DVFSRC_HRT_REQ_MD_BW_4, 0x00400802 },
{ DVFSRC_HRT_REQ_MD_BW_5, 0x00601404 },
{ DVFSRC_HRT_REQ_MD_BW_6, 0x00902008 },
{ DVFSRC_HRT_REQ_MD_BW_7, 0x00E0380E },
{ DVFSRC_HRT_REQ_MD_BW_8, 0x00000000 },
{ DVFSRC_HRT_REQ_MD_BW_9, 0x00000000 },
{ DVFSRC_HRT_REQ_MD_BW_10, 0x00034C00 },
{ DVFSRC_HRT1_REQ_MD_BW_0, 0x0360D836 },
{ DVFSRC_HRT1_REQ_MD_BW_1, 0x0360D800 },
{ DVFSRC_HRT1_REQ_MD_BW_2, 0x03600036 },
{ DVFSRC_HRT1_REQ_MD_BW_3, 0x0360D836 },
{ DVFSRC_HRT1_REQ_MD_BW_4, 0x0360D836 },
{ DVFSRC_HRT1_REQ_MD_BW_5, 0x0360D836 },
{ DVFSRC_HRT1_REQ_MD_BW_6, 0x0360D836 },
{ DVFSRC_HRT1_REQ_MD_BW_7, 0x0360D836 },
{ DVFSRC_HRT1_REQ_MD_BW_8, 0x00000000 },
{ DVFSRC_HRT1_REQ_MD_BW_9, 0x00000000 },
{ DVFSRC_HRT1_REQ_MD_BW_10, 0x00034C00 },
{ DVFSRC_HRT_HIGH, 0x070804B0 },
{ DVFSRC_HRT_HIGH_1, 0x11830B80 },
{ DVFSRC_HRT_HIGH_2, 0x18A618A6 },
{ DVFSRC_HRT_HIGH_3, 0x000018A6 },
{ DVFSRC_HRT_LOW, 0x070704AF },
{ DVFSRC_HRT_LOW_1, 0x11820B7F },
{ DVFSRC_HRT_LOW_2, 0x18A518A5 },
{ DVFSRC_HRT_LOW_3, 0x000018A5 },
#ifdef DVFSRC_1600_FLOOR
{ DVFSRC_HRT_REQUEST, 0x05554322 },
#else
{ DVFSRC_HRT_REQUEST, 0x05554321 },
#endif
{ DVFSRC_EMI_MON_DEBOUNCE_TIME, 0x4C4C0AB0 },
{ DVFSRC_DDR_ADD_REQUEST, 0x05543210 },
{ DVFSRC_EMI_ADD_REQUEST, 0x03333210 },
{ DVFSRC_LEVEL_LABEL_0_1, 0x40225032 },
{ DVFSRC_LEVEL_LABEL_2_3, 0x20223012 },
{ DVFSRC_LEVEL_LABEL_4_5, 0x40211012 },
{ DVFSRC_LEVEL_LABEL_6_7, 0x20213011 },
{ DVFSRC_LEVEL_LABEL_8_9, 0x30101011 },
{ DVFSRC_LEVEL_LABEL_10_11, 0x10102000 },
{ DVFSRC_LEVEL_LABEL_12_13, 0x00000000 },
{ DVFSRC_LEVEL_LABEL_14_15, 0x00000000 },
{ DVFSRC_CURRENT_FORCE, 0x00000001 },
{ DVFSRC_BASIC_CONTROL, 0x7599404B },
{ DVFSRC_BASIC_CONTROL, 0x7599014B },
{ DVFSRC_CURRENT_FORCE, 0x00000000 },
{ -1, 0 },
},
/* NULL */
{
{ -1, 0 },
},
};
#define dvfsrc_rmw(offset, val, mask, shift) \
dvfsrc_write(offset, (dvfsrc_read(offset) & ~(mask << shift)) \
| (val << shift))
u32 dvfsrc_get_ddr_qos(void)
{
unsigned int qos_total_bw = dvfsrc_read(DVFSRC_SW_BW_0) +
dvfsrc_read(DVFSRC_SW_BW_1) +
dvfsrc_read(DVFSRC_SW_BW_2) +
dvfsrc_read(DVFSRC_SW_BW_3) +
dvfsrc_read(DVFSRC_SW_BW_4);
if (qos_total_bw < 0x19)
return 0;
#ifdef DVFSRC_1600_FLOOR
else if (qos_total_bw < 0x26)
return 2;
#else
else if (qos_total_bw < 0x26)
return 1;
#endif
else if (qos_total_bw < 0x33)
return 2;
else if (qos_total_bw < 0x4c)
return 3;
else if (qos_total_bw < 0x66)
return 4;
else
return 5;
}
static int dvfsrc_get_emi_mon_gear(void)
{
unsigned int total_bw_status;
int i;
total_bw_status = vcorefs_get_total_emi_status() & 0x1F;
for (i = 4; i >= 0 ; i--) {
if ((total_bw_status >> i) > 0) {
#ifdef DVFSRC_1600_FLOOR
if (i == 0)
return i + 2;
else
return i + 1;
#else
return i + 1;
#endif
}
}
return 0;
}
static u32 dvfsrc_calc_hrt_opp(int data)
{
if (data < 0x04B0)
return DDR_OPP_5;
#ifdef DVFSRC_1600_FLOOR
else if (data < 0x0708)
return DDR_OPP_3;
#else
else if (data < 0x0708)
return DDR_OPP_4;
#endif
else if (data < 0x0B80)
return DDR_OPP_3;
else if (data < 0x1183)
return DDR_OPP_2;
else if (data < 0x18A6)
return DDR_OPP_1;
else
return DDR_OPP_0;
}
int dvfsrc_latch_register(int enable)
{
return mtk_rgu_cfg_dvfsrc(enable);
}
void dvfsrc_set_isp_hrt_bw(int data)
{
data = (data + 29) / 30;
if (data > 0x3FF)
data = 0x3FF;
dvfsrc_write(DVFSRC_ISP_HRT, data);
}
u32 dvfsrc_calc_isp_hrt_opp(int data)
{
return dvfsrc_calc_hrt_opp(((data + 29) / 30) * 30);
}
struct regulator *dvfsrc_vcore_requlator(struct device *dev)
{
return regulator_get(dev, "vcore");
}
#ifdef AUTOK_ENABLE
__weak int emmc_autok(void)
{
pr_info("NOT SUPPORT EMMC AUTOK\n");
return 0;
}
__weak int sd_autok(void)
{
pr_info("NOT SUPPORT SD AUTOK\n");
return 0;
}
__weak int sdio_autok(void)
{
pr_info("NOT SUPPORT SDIO AUTOK\n");
return 0;
}
void begin_autok_task(void)
{
/* notify MM DVFS for msdc autok start */
mmdvfs_prepare_action(MMDVFS_PREPARE_CALIBRATION_START);
}
void finish_autok_task(void)
{
/* check if dvfs force is released */
int force = pm_qos_request(PM_QOS_VCORE_DVFS_FORCE_OPP);
/* notify MM DVFS for msdc autok finish */
mmdvfs_prepare_action(MMDVFS_PREPARE_CALIBRATION_END);
if (force >= 0 && force < 13)
pr_info("autok task not release force opp: %d\n", force);
}
static void dvfsrc_autok_manager(void)
{
int r = 0;
begin_autok_task();
r = emmc_autok();
pr_info("EMMC autok done: %s\n", (r == 0) ? "Yes" : "No");
r = sd_autok();
pr_info("SD autok done: %s\n", (r == 0) ? "Yes" : "No");
r = sdio_autok();
pr_info("SDIO autok done: %s\n", (r == 0) ? "Yes" : "No");
finish_autok_task();
}
#endif
void helio_dvfsrc_platform_pre_init(struct helio_dvfsrc *dvfsrc)
{
}
void helio_dvfsrc_platform_init(struct helio_dvfsrc *dvfsrc)
{
int spmfw_idx = 0;
struct reg_config *config;
int idx = 0;
config = dvfsrc_init_configs[spmfw_idx];
while (config[idx].offset != -1) {
dvfsrc_write(config[idx].offset, config[idx].val);
idx++;
}
#ifdef AUTOK_ENABLE
dvfsrc_autok_manager();
#endif
}
int vcore_pmic_to_uv(int pmic_val)
{
return __vcore_pmic_to_uv(pmic_val);
}
int vcore_uv_to_pmic(int vcore_uv)
{
return __vcore_uv_to_pmic(vcore_uv);
}
void get_opp_info(char *p)
{
#if defined(CONFIG_FPGA_EARLY_PORTING) || !defined(CONFIG_MTK_PMIC_COMMON)
int pmic_val = 0;
int vsram_val = 0;
#else
int pmic_val = pmic_get_register_value(PMIC_VCORE_ADDR);
int vsram_val = pmic_get_register_value(PMIC_VSRAM_OTHERS_ADDR);
#endif
#ifdef CONFIG_MTK_DRAMC
int ddr_khz = get_dram_data_rate() * 1000;
#else
int ddr_khz = 0;
#endif
int vcore_uv = vcore_pmic_to_uv(pmic_val);
int vsram_uv = vsram_pmic_to_uv(vsram_val);
p += sprintf(p, "%-10s: %-8u uv (PMIC: 0x%x)\n",
"Vcore", vcore_uv, vcore_uv_to_pmic(vcore_uv));
p += sprintf(p, "%-10s: %-8u uv (PMIC: 0x%x)\n",
"Vsram", vsram_uv, vsram_uv_to_pmic(vsram_uv));
p += sprintf(p, "%-10s: %-8u khz\n", "DDR", ddr_khz);
}
/* met profile table */
static unsigned int met_vcorefs_src[SRC_MAX];
static char *met_src_name[SRC_MAX] = {
"MD2SPM",
"SRC_DDR_OPP",
"DDR__SW_REQ1_SPM",
"DDR__SW_REQ2_CM",
"DDR__SW_REQ3_PMQOS",
"DDR__SW_REQ4_MD",
"DDR__SW_REQ8_MCUSYS",
"DDR__QOS_BW",
"DDR__EMI_TOTAL",
"DDR__HRT_BW",
"DDR__HIFI",
"DDR__HIFI_LATENCY",
"DDR__MD_LATENCY",
"DDR__MD_DDR",
"SRC_VCORE_OPP",
"VCORE__SW_REQ3_PMQOS",
"VCORE__SCP",
"VCORE__HIFI",
"SCP_REQ",
"PMQOS_TATOL",
"PMQOS_BW0",
"PMQOS_BW1",
"PMQOS_BW2",
"PMQOS_BW3",
"PMQOS_BW4",
"TOTAL_EMI_BW",
"HRT_MD_BW",
"HRT_DISP_BW",
"HRT_ISP_BW",
"MD_SCENARIO",
"HIFI_SCENARIO_IDX",
"MD_EMI_LATENCY",
};
/* met profile function */
int vcorefs_get_src_req_num(void)
{
return SRC_MAX;
}
EXPORT_SYMBOL(vcorefs_get_src_req_num);
char **vcorefs_get_src_req_name(void)
{
return met_src_name;
}
EXPORT_SYMBOL(vcorefs_get_src_req_name);
static void vcorefs_get_src_ddr_req(void)
{
unsigned int sw_req;
met_vcorefs_src[DDR_OPP_IDX] =
get_cur_ddr_opp();
sw_req = dvfsrc_read(DVFSRC_SW_REQ1);
met_vcorefs_src[DDR_SW_REQ1_SPM_IDX] =
(sw_req >> DDR_SW_AP_SHIFT) & DDR_SW_AP_MASK;
sw_req = dvfsrc_read(DVFSRC_SW_REQ2);
met_vcorefs_src[DDR_SW_REQ2_CM_IDX] =
(sw_req >> DDR_SW_AP_SHIFT) & DDR_SW_AP_MASK;
sw_req = dvfsrc_read(DVFSRC_SW_REQ3);
met_vcorefs_src[DDR_SW_REQ3_PMQOS_IDX] =
(sw_req >> DDR_SW_AP_SHIFT) & DDR_SW_AP_MASK;
sw_req = dvfsrc_read(DVFSRC_SW_REQ4);
met_vcorefs_src[DDR_SW_REQ4_MD_IDX] =
(sw_req >> DDR_SW_AP_SHIFT) & DDR_SW_AP_MASK;
sw_req = dvfsrc_read(DVFSRC_SW_REQ8);
met_vcorefs_src[DDR_SW_REQ8_MCUSYS_IDX] =
(sw_req >> DDR_SW_AP_SHIFT) & DDR_SW_AP_MASK;
met_vcorefs_src[DDR_QOS_BW_IDX] =
dvfsrc_get_ddr_qos();
met_vcorefs_src[DDR_EMI_TOTAL_IDX] =
dvfsrc_get_emi_mon_gear();
met_vcorefs_src[DDR_HRT_BW_IDX] =
vcorefs_get_hrt_bw_ddr();
met_vcorefs_src[DDR_HIFI_IDX] =
vcorefs_get_hifi_ddr_status();
met_vcorefs_src[DDR_HIFI_LATENCY_IDX] =
vcorefs_get_hifi_rising_ddr();
met_vcorefs_src[DDR_MD_LATENCY_IDX] =
vcorefs_get_md_rising_ddr();
met_vcorefs_src[DDR_MD_DDR_IDX] =
vcorefs_get_md_scenario_ddr();
}
static void vcorefs_get_src_vcore_req(void)
{
u32 sw_req;
u32 scp_en;
scp_en = vcorefs_get_scp_req_status();
met_vcorefs_src[VCORE_OPP_IDX] =
get_cur_vcore_opp();
sw_req = dvfsrc_read(DVFSRC_SW_REQ3);
met_vcorefs_src[VCORE_SW_REQ3_PMQOS_IDX] =
(sw_req >> VCORE_SW_AP_SHIFT) & VCORE_SW_AP_MASK;
if (scp_en) {
sw_req = dvfsrc_read(DVFSRC_VCORE_REQUEST);
met_vcorefs_src[VCORE_SCP_IDX] =
(sw_req >> VCORE_SCP_GEAR_SHIFT) & VCORE_SCP_GEAR_MASK;
} else
met_vcorefs_src[VCORE_SCP_IDX] = 0;
met_vcorefs_src[VCORE_HIFI_IDX] =
vcorefs_get_hifi_vcore_status();
}
static void vcorefs_get_src_misc_info(void)
{
#ifdef CONFIG_MTK_EMI
unsigned int total_bw_last = (get_emi_bwvl(0) & 0x7F) * 813;
#endif
u32 qos_bw0, qos_bw1, qos_bw2, qos_bw3, qos_bw4;
qos_bw0 = dvfsrc_read(DVFSRC_SW_BW_0);
qos_bw1 = dvfsrc_read(DVFSRC_SW_BW_1);
qos_bw2 = dvfsrc_read(DVFSRC_SW_BW_2);
qos_bw3 = dvfsrc_read(DVFSRC_SW_BW_3);
qos_bw4 = dvfsrc_read(DVFSRC_SW_BW_4);
met_vcorefs_src[SRC_MD2SPM_IDX] =
vcorefs_get_md_scenario();
met_vcorefs_src[SRC_SCP_REQ_IDX] =
vcorefs_get_scp_req_status();
met_vcorefs_src[SRC_PMQOS_TATOL_IDX] =
qos_bw0 + qos_bw1 + qos_bw2 + qos_bw3 + qos_bw4;
met_vcorefs_src[SRC_PMQOS_BW0_IDX] =
qos_bw0;
met_vcorefs_src[SRC_PMQOS_BW1_IDX] =
qos_bw1;
met_vcorefs_src[SRC_PMQOS_BW2_IDX] =
qos_bw2;
met_vcorefs_src[SRC_PMQOS_BW3_IDX] =
qos_bw3;
met_vcorefs_src[SRC_PMQOS_BW4_IDX] =
qos_bw4;
#ifdef CONFIG_MTK_EMI
met_vcorefs_src[SRC_TOTAL_EMI_BW_IDX] =
total_bw_last;
#endif
met_vcorefs_src[SRC_HRT_MD_BW_IDX] =
dvfsrc_get_md_bw();
met_vcorefs_src[SRC_HRT_ISP_BW_IDX] =
dvfsrc_read(DVFSRC_ISP_HRT);
met_vcorefs_src[SRC_MD_SCENARIO_IDX] =
vcorefs_get_md_scenario();
met_vcorefs_src[SRC_HIFI_SCENARIO_IDX] =
vcorefs_get_hifi_scenario();
met_vcorefs_src[SRC_MD_EMI_LATENCY_IDX] =
vcorefs_get_md_emi_latency_status();
}
unsigned int *vcorefs_get_src_req(void)
{
vcorefs_get_src_ddr_req();
vcorefs_get_src_vcore_req();
vcorefs_get_src_misc_info();
vcorefs_trace_qos();
return met_vcorefs_src;
}
EXPORT_SYMBOL(vcorefs_get_src_req);
int get_cur_ddr_ratio(void)
{
int idx;
if (!is_dvfsrc_enabled())
return 0;
idx = get_cur_vcore_dvfs_opp();
if (idx >= VCORE_DVFS_OPP_NUM)
return 0;
if ((get_ddr_opp(idx) < DDR_OPP_3) || (idx == 10))
return 8;
else
return 4;
}
EXPORT_SYMBOL(get_cur_ddr_ratio);