kernel_samsung_a34x-permissive/drivers/misc/mediatek/scp/rv/scp_dvfs.c
2024-04-28 15:51:13 +02:00

2268 lines
54 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/arm-smccc.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/ktime.h>
#include <linux/mfd/syscon.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeup.h>
#include <linux/printk.h>
#include <linux/proc_fs.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/soc/mediatek/mtk_sip_svc.h>
#include <linux/soc/mediatek/mtk-pm-qos.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/suspend.h>
#include <linux/uaccess.h>
#include <linux/bits.h>
#include <linux/build_bug.h>
#include <clk-fmeter.h>
#if IS_ENABLED(CONFIG_OF)
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#endif /* IS_ENABLED(CONFIG_OF) */
#include "scp_ipi.h"
#include "scp_helper.h"
#include "scp_excep.h"
#include "scp_dvfs.h"
#include "scp.h"
#include "mtk_pmic_info.h"
#include "mtk_pmic_api_buck.h"
#include <linux/pm_qos.h>
#include "helio-dvfsrc-opp.h"
#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) "[scp_dvfs]: " fmt
#define DRV_Reg32(addr) readl(addr)
#define DRV_WriteReg32(addr, val) writel(val, addr)
#define DRV_SetReg32(addr, val) DRV_WriteReg32(addr, DRV_Reg32(addr) | (val))
#define DRV_ClrReg32(addr, val) DRV_WriteReg32(addr, DRV_Reg32(addr) & ~(val))
#define SCP_CLK_CTRL_PHANDLE_NAME "scp_clk_ctrl"
#define ULPOSC_CLK_PHANDLE_NAME "ulposc_clksys"
#define FM_CNT2FREQ(cnt) (cnt * 26 / CALI_DIV_VAL)
#define FM_FREQ2CNT(freq) (freq * CALI_DIV_VAL / 26)
unsigned int scp_ipi_ackdata0, scp_ipi_ackdata1;
struct ipi_tx_data_t {
unsigned int arg1;
unsigned int arg2;
};
/*
* -1 : SCP Debug CMD: off,
* >=0 : SCP DVFS Debug OPP.
*/
static int scp_dvfs_debug_flag = -1;
/* used to replace 'SCP_DVFS_INIT_ENABLE' compile flag */
static int scp_dvfs_enable;
/* -1:SCP DVFS OFF, 1:SCP DVFS ON */
static int scp_dvfs_flag = 1;
/*
* 0: SCP Sleep: OFF,
* 1: SCP Sleep: ON,
* 2: SCP Sleep: sleep without wakeup,
* 3: SCP Sleep: force to sleep
*/
static int scp_resrc_current_req = -1;
static struct mt_scp_pll_t mt_scp_pll;
static struct wakeup_source *scp_suspend_lock;
static int g_scp_dvfs_init_flag = -1;
static struct mtk_pm_qos_request dvfsrc_scp_vcore_req;
static struct scp_dvfs_hw dvfs;
const char *ulposc_ver[MAX_ULPOSC_VERSION] __initconst = {
[ULPOSC_VER_1] = "v1",
};
const char *clk_dbg_ver[MAX_CLK_DBG_VERSION] __initconst = {
[CLK_DBG_VER_1] = "v1",
};
const char *scp_clk_ver[MAX_SCP_CLK_VERSION] __initconst = {
[SCP_CLK_VER_1] = "v1",
};
struct ulposc_cali_regs cali_regs[MAX_ULPOSC_VERSION] __initdata = {
[ULPOSC_VER_1] = {
REG_DEFINE(con0, 0x2C0, REG_MAX_MASK, 0)
REG_DEFINE(cali, 0x2C0, GENMASK(CAL_BITS, 0), 0)
REG_DEFINE(con1, 0x2C4, REG_MAX_MASK, 0)
REG_DEFINE(con2, 0x2C8, REG_MAX_MASK, 0)
},
};
struct clk_cali_regs clk_dbg_reg[MAX_CLK_DBG_VERSION] __initdata = {
[CLK_DBG_VER_1] = {
REG_DEFINE(clk_misc_cfg0, 0x140, REG_MAX_MASK, 0)
REG_DEFINE_WITH_INIT(meter_div, 0x140, 0xFF, 24, 0, 0)
REG_DEFINE(clk_dbg_cfg, 0x17C, 0x3, 0)
REG_DEFINE_WITH_INIT(fmeter_ck_sel, 0x17C, 0x3F, 16, 36, 0)
REG_DEFINE_WITH_INIT(abist_clk, 0x17C, 0x3, 0, 0, 0)
REG_DEFINE(clk26cali_0, 0x220, REG_MAX_MASK, 0)
REG_DEFINE_WITH_INIT(fmeter_en, 0x220, 0x1, 12, 1, 0)
REG_DEFINE_WITH_INIT(trigger_cal, 0x220, 0x1, 4, 1, 0)
REG_DEFINE(clk26cali_1, 0x224, REG_MAX_MASK, 0)
REG_DEFINE(cal_cnt, 0x224, 0xFFFF, 0)
REG_DEFINE_WITH_INIT(load_cnt, 0x224, 0x3FF, 16, 0x1FF, 0)
},
};
struct scp_clk_hw scp_clk_hw_regs[MAX_SCP_CLK_VERSION] = {
[SCP_CLK_VER_1] = {
REG_DEFINE(clk_high_en, 0x4, 0x1, 1)
REG_DEFINE(ulposc2_en, 0x6C, 0x1, 5)
REG_DEFINE(ulposc2_cg, 0x5C, 0x1, 1)
REG_DEFINE(sel_clk, 0x0, 0xF, 8)
}
};
static void slp_ipi_init(void)
{
int ret;
ret = mtk_ipi_register(&scp_ipidev, IPI_OUT_C_SLEEP_0,
NULL, NULL, &scp_ipi_ackdata0);
if (ret) {
pr_notice("scp0 slp ipi register failed\n");
WARN_ON(1);
}
if (dvfs.core_nums == 2) {
ret = mtk_ipi_register(&scp_ipidev, IPI_OUT_C_SLEEP_1,
NULL, NULL, &scp_ipi_ackdata1);
if (ret)
pr_notice("scp1 slp ipi register failed\n");
}
if (!ret)
dvfs.ipi_init_done = true;
}
#if 0
static int scp_get_vcore_table(unsigned int gear)
{
struct arm_smccc_res res;
arm_smccc_smc(MTK_SIP_KERNEL_SCP_DVFS_CTRL, VCORE_ACQUIRE,
gear, 0, 0, 0, 0, 0, &res);
if (!(res.a0))
return res.a1;
pr_notice("[%s]: should not end here\n", __func__);
return -1;
}
#endif
int scp_resource_req(unsigned int req_type)
{
struct arm_smccc_res res;
if (req_type < 0 || req_type >= SCP_REQ_MAX)
return 0;
#if defined(CONFIG_MACH_MT6853) || defined(CONFIG_MACH_MT6893)
arm_smccc_smc(MTK_SIP_KERNEL_SCP_DVFS_CTRL, RESOURCE_REQ,
req_type, 0, 0, 0, 0, 0, &res);
#else
arm_smccc_smc(MTK_SIP_KERNEL_SCP_DVFS_CTRL, req_type,
0, 0, 0, 0, 0, 0, &res);
#endif
if (!res.a0)
scp_resrc_current_req = req_type;
else
pr_notice("[%s]: resource request failed, req: %u\n",
__func__, req_type);
return res.a0;
}
static int scp_reg_update(struct regmap *regmap, struct reg_info *reg, u32 val)
{
u32 mask;
int ret = 0;
if (!reg->msk) {
pr_notice("[%s]: reg not support\n", __func__);
return -ESCP_REG_NOT_SUPPORTED;
}
mask = reg->msk << reg->bit;
val = (val & reg->msk) << reg->bit;
if (reg->setclr) {
ret = regmap_write(regmap, reg->ofs + 4, mask);
ret = regmap_write(regmap, reg->ofs + 2, val);
} else {
ret = regmap_update_bits(regmap, reg->ofs, mask, val);
}
return ret;
}
static int scp_reg_read(struct regmap *regmap, struct reg_info *reg, u32 *val)
{
int ret = 0;
if (!reg->msk) {
pr_notice("[%s]: reg not support\n", __func__);
return -ESCP_REG_NOT_SUPPORTED;
}
ret = regmap_read(regmap, reg->ofs, val);
if (!ret)
*val = (*val >> reg->bit) & reg->msk;
return ret;
}
static inline int scp_reg_init(struct regmap *regmap, struct reg_info *reg)
{
return scp_reg_update(regmap, reg, reg->init_config);
}
static int scp_get_freq_idx(unsigned int clk_opp)
{
int i;
for (i = 0; i < dvfs.scp_opp_nums; i++)
if (clk_opp == dvfs.opp[i].freq)
break;
if (i == dvfs.scp_opp_nums) {
pr_notice("no available opp, freq: %u\n", clk_opp);
return -EINVAL;
}
return i;
}
static int scp_update_pmic_vow_lp_mode(bool on)
{
int ret = 0;
if (dvfs.vlp_support) {
pr_notice("[%s]: VCORE DVS is not supported\n", __func__);
WARN_ON(1);
return -ESCP_DVFS_DVS_SHOULD_BE_BYPASSED;
}
if (on)
/* enable VOW low power mode */
pmic_buck_vgpu11_lp(SRCLKEN11, 0, 1, HW_LP);
else
/* disable VOW low power mode */
pmic_buck_vgpu11_lp(SRCLKEN11, 0, 1, HW_OFF);
return ret;
}
static int scp_set_pmic_vcore(unsigned int cur_freq)
{
int ret = 0;
int idx = 0;
if (dvfs.vlp_support) {
pr_notice("[%s]: VCORE DVS is not supported\n", __func__);
return -ESCP_DVFS_DVS_SHOULD_BE_BYPASSED;
}
idx = scp_get_freq_idx(cur_freq);
if (idx >= 0 && idx < dvfs.scp_opp_nums) {
if (dvfs.pmic_sshub_en) {
unsigned int ret_vc = 0;
ret = get_vcore_uv_table(dvfs.opp[idx].uv_idx);
if (ret > 0)
dvfs.opp[idx].tuned_vcore = ret;
ret_vc = pmic_scp_set_vcore(dvfs.opp[idx].tuned_vcore);
if (ret_vc) {
ret = -1;
pr_err("ERROR: %s: scp vcore setting error, (%d)\n",
__func__, ret_vc);
WARN_ON(1);
}
}
if (dvfs.vow_lp_en_gear != -1) {
/* vcore > 0.6v cannot hold pmic/vcore in lp mode */
if (idx < dvfs.vow_lp_en_gear)
/* enable VOW low power mode */
scp_update_pmic_vow_lp_mode(true);
else
/* disable VOW low power mode */
scp_update_pmic_vow_lp_mode(false);
}
} else {
ret = -ESCP_DVFS_OPP_OUT_OF_BOUND;
pr_notice("[%s]: cur_freq=%d is not supported\n",
__func__, cur_freq);
WARN_ON(1);
}
return ret;
}
static uint32_t sum_required_freq(uint32_t core_id)
{
uint32_t i = 0;
uint32_t sum = 0;
if (core_id >= dvfs.core_nums) {
pr_notice("[%s]: ERROR: core_id is invalid: %u\n",
__func__, core_id);
WARN_ON(1);
core_id = SCPSYS_CORE0;
}
/*
* calculate scp frequence for core_id
*/
for (i = 0; i < NUM_FEATURE_ID; i++) {
if (i != VCORE_TEST_FEATURE_ID &&
feature_table[i].enable == 1 &&
feature_table[i].sys_id == core_id)
sum += feature_table[i].freq;
}
/*
* calculate scp sensor frequence (core0 only)
*/
if (core_id == SCPSYS_CORE0)
for (i = 0; i < NUM_SENSOR_TYPE; i++)
if (sensor_type_table[i].enable == 1)
sum += sensor_type_table[i].freq;
return sum;
}
static uint32_t _mt_scp_dvfs_set_test_freq(uint32_t sum)
{
uint32_t freq = 0, added_freq = 0, i = 0;
if (scp_dvfs_debug_flag == -1)
return 0;
pr_info("manually set opp = %d\n", scp_dvfs_debug_flag);
for (i = 0; i < dvfs.scp_opp_nums; i++) {
freq = dvfs.opp[i].freq;
if (scp_dvfs_debug_flag == i && sum < freq) {
added_freq = freq - sum;
break;
}
}
feature_table[VCORE_TEST_FEATURE_ID].freq = added_freq;
pr_notice("[%s]test freq: %d + %d = %d (MHz)\n",
__func__,
sum,
added_freq,
sum + added_freq);
return added_freq;
}
uint32_t scp_get_freq(void)
{
uint32_t i;
uint32_t sum_core0 = 0;
uint32_t sum_core1 = 0;
uint32_t sum = 0;
uint32_t return_freq = 0;
/*
* calculate scp frequence requirement
*/
sum_core0 += sum_required_freq(SCPSYS_CORE0);
if (dvfs.core_nums > SCPSYS_CORE1)
sum_core1 = sum_required_freq(SCPSYS_CORE1);
if (sum_core0 > sum_core1) {
sum = sum_core0;
feature_table[VCORE_TEST_FEATURE_ID].sys_id = SCPSYS_CORE0;
} else {
sum = sum_core1;
feature_table[VCORE_TEST_FEATURE_ID].sys_id = SCPSYS_CORE1;
}
/*
* add scp test cmd frequence
*/
sum += _mt_scp_dvfs_set_test_freq(sum);
for (i = 0; i < dvfs.scp_opp_nums; i++) {
if (sum <= dvfs.opp[i].freq) {
return_freq = dvfs.opp[i].freq;
break;
}
}
if (i == dvfs.scp_opp_nums) {
return_freq = dvfs.opp[dvfs.scp_opp_nums - 1].freq;
pr_notice("warning: request freq %d > max opp %d\n",
sum, return_freq);
}
return return_freq;
}
static void scp_vcore_request(unsigned int clk_opp)
{
int idx;
int ret = 0;
pr_debug("[%s]: opp(%d)\n", __func__, clk_opp);
if (dvfs.vlp_support)
return;
/* SCP vcore request to PMIC */
if (dvfs.pmic_sshub_en)
ret = scp_set_pmic_vcore(clk_opp);
idx = scp_get_freq_idx(clk_opp);
if (idx < 0) {
pr_notice("[%s]: invalid clk_opp %d\n", __func__, clk_opp);
WARN_ON(1);
return;
}
/* SCP vcore request to DVFSRC
* min & max set to requested Vcore value
* DVFSRC SW will find corresponding idx to process
* if opp[idx].dvfsrc_opp == 0xff, means that
* opp[idx] is not supported by DVFSRC
*/
mtk_pm_qos_update_request(&dvfsrc_scp_vcore_req, dvfs.opp[idx].dvfsrc_opp);
/* SCP vcore request to SPM */
DRV_WriteReg32(SCP_SCP2SPM_VOL_LV, dvfs.opp[idx].spm_opp);
}
void scp_init_vcore_request(void)
{
if (scp_dvfs_flag != 1)
scp_vcore_request(dvfs.opp[0].freq);
}
int scp_request_freq_vcore(void)
{
int timeout = 50;
int ret = 0;
unsigned long spin_flags;
int is_increasing_freq = 0;
int opp_idx;
if (scp_dvfs_flag != 1) {
pr_debug("[%s]: warning: SCP DVFS is OFF\n", __func__);
return 0;
}
/* because we are waiting for scp to update register:scp_current_freq
* use wake lock to prevent AP from entering suspend state
*/
__pm_stay_awake(scp_suspend_lock);
if (scp_current_freq != scp_expected_freq) {
scp_awake_lock((void *)SCP_A_ID);
/* do DVS before DFS if increasing frequency */
if (scp_current_freq < scp_expected_freq) {
scp_vcore_request(scp_expected_freq);
is_increasing_freq = 1;
}
/* Request SPM not to turn off mainpll/26M/infra */
/* because SCP may park in it during DFS process */
scp_resource_req(SCP_REQ_26M |
SCP_REQ_INFRA |
SCP_REQ_SYSPLL);
/* turn on PLL if necessary */
if (!dvfs.vlp_support) /* no parking needed for vlp */
scp_pll_ctrl_set(PLL_ENABLE, scp_expected_freq);
do {
ret = mtk_ipi_send(&scp_ipidev,
IPI_OUT_DVFS_SET_FREQ_0,
IPI_SEND_WAIT, &scp_expected_freq,
PIN_OUT_SIZE_DVFS_SET_FREQ_0, 0);
if (ret != IPI_ACTION_DONE)
pr_notice("SCP send IPI fail - %d\n", ret);
mdelay(2);
timeout -= 1; /*try 50 times, total about 100ms*/
if (timeout <= 0) {
pr_notice("set freq fail, current(%d) != expect(%d)\n",
scp_current_freq, scp_expected_freq);
__pm_relax(scp_suspend_lock);
WARN_ON(1);
return -ESCP_DVFS_IPI_FAILED;
}
/* read scp_current_freq again */
spin_lock_irqsave(&scp_awake_spinlock, spin_flags);
scp_current_freq = readl(CURRENT_FREQ_REG);
spin_unlock_irqrestore(&scp_awake_spinlock, spin_flags);
} while (scp_current_freq != scp_expected_freq);
/* turn off PLL if necessary */
if (!dvfs.vlp_support) /* no parking needed for vlp */
scp_pll_ctrl_set(PLL_DISABLE, scp_expected_freq);
/* do DVS after DFS if decreasing frequency */
if (is_increasing_freq == 0)
scp_vcore_request(scp_expected_freq);
scp_awake_unlock((void *)SCP_A_ID);
opp_idx = scp_get_freq_idx(scp_current_freq);
if (dvfs.opp[opp_idx].resource_req)
scp_resource_req(dvfs.opp[opp_idx].resource_req);
else
scp_resource_req(SCP_REQ_RELEASE);
}
__pm_relax(scp_suspend_lock);
pr_debug("[SCP] succeed to set freq, expect=%d, cur=%d\n",
scp_expected_freq, scp_current_freq);
return 0;
}
int scp_request_freq_vlp(void)
{
int timeout = 50;
int ret = 0;
unsigned long spin_flags;
if (scp_dvfs_flag != 1) {
pr_debug("[%s]: warning: SCP DVFS is OFF\n", __func__);
return 0;
}
if (!dvfs.vlp_support) {
pr_notice("[%s]: should not end here: vlp not supported!\n", __func__);
return 0;
}
/*
* In order to prevent sending the same freq request from kernel repeatedly,
* we used last_scp_expected_freq to record last freq request.
*/
if (last_scp_expected_freq == scp_expected_freq) {
pr_debug("[SCP] Skip DFS: resending the same freq request: %dMhz\n",
last_scp_expected_freq);
return 0;
}
/* because we are waiting for scp to update register:scp_current_freq
* use wake lock to prevent AP from entering suspend state
*/
__pm_stay_awake(scp_suspend_lock);
if (scp_current_freq != scp_expected_freq) {
scp_awake_lock((void *)SCP_A_ID);
/* Request SPM not to turn off mainpll/26M/infra */
/* because SCP may park in it during DFS process */
scp_resource_req(SCP_REQ_26M |
SCP_REQ_INFRA |
SCP_REQ_SYSPLL);
do {
ret = mtk_ipi_send(&scp_ipidev,
IPI_OUT_DVFS_SET_FREQ_0,
IPI_SEND_WAIT, &scp_expected_freq,
PIN_OUT_SIZE_DVFS_SET_FREQ_0, 0);
mdelay(2);
if (ret != IPI_ACTION_DONE) {
pr_notice("SCP send IPI fail - %d\n", ret);
} else {
last_scp_expected_freq = scp_expected_freq;
break;
}
timeout -= 1; /*try 50 times, total about 100ms*/
} while (timeout > 0);
/* if ipi send fail 50(=timeout) times */
if (timeout <= 0) {
/* to check scp_current_freq */
spin_lock_irqsave(&scp_awake_spinlock, spin_flags);
scp_current_freq = readl(CURRENT_FREQ_REG);
spin_unlock_irqrestore(&scp_awake_spinlock, spin_flags);
pr_notice("set expected_freq(%d) fail, current is %d\n",
scp_expected_freq, scp_current_freq);
__pm_relax(scp_suspend_lock);
WARN_ON(1);
return -ESCP_DVFS_IPI_FAILED;
}
scp_awake_unlock((void *)SCP_A_ID);
scp_resource_req(SCP_REQ_RELEASE);
}
__pm_relax(scp_suspend_lock);
pr_debug("[SCP] succeed to set freq, expect=%d, cur=%d\n",
scp_expected_freq, scp_current_freq);
return 0;
}
/* scp_request_freq
* return :-1 means the scp request freq. error
* return :0 means the request freq. finished
*/
int scp_request_freq(void)
{
if (dvfs.vlp_support)
return scp_request_freq_vlp();
else
return scp_request_freq_vcore();
}
void wait_scp_dvfs_init_done(void)
{
int count = 0;
while (g_scp_dvfs_init_flag != 1) {
mdelay(1);
count++;
if (count > 3000) {
pr_notice("SCP dvfs driver init fail\n");
WARN_ON(1);
}
}
}
static int set_scp_clk_mux(unsigned int pll_ctrl_flag)
{
int ret = 0;
if (pll_ctrl_flag == PLL_ENABLE) {
if (!dvfs.pre_mux_en) {
ret = clk_prepare_enable(mt_scp_pll.clk_mux);
if (ret) {
pr_notice("[%s]: clk_prepare_enable failed\n",
__func__);
WARN_ON(1);
return -1;
}
dvfs.pre_mux_en = true;
}
} else if (pll_ctrl_flag == PLL_DISABLE) {
clk_disable_unprepare(mt_scp_pll.clk_mux);
dvfs.pre_mux_en = false;
}
return 0;
}
static int __scp_pll_sel_26M(unsigned int pll_ctrl_flag, unsigned int pll_sel)
{
int ret = 0;
if (pll_sel != CLK_26M)
return -EINVAL;
ret = set_scp_clk_mux(pll_ctrl_flag);
if (ret)
return ret;
if (pll_ctrl_flag == PLL_ENABLE) {
ret = clk_set_parent(mt_scp_pll.clk_mux, mt_scp_pll.clk_pll[0]);
if (ret) {
pr_notice("[%s]: clk_set_parent() failed for 26M\n", __func__);
WARN_ON(1);
}
}
return ret;
}
int scp_pll_ctrl_set(unsigned int pll_ctrl_flag, unsigned int pll_sel)
{
int idx;
int mux_idx = 0;
int ret = 0;
pr_debug("[%s]: (%d, %d)\n", __func__, pll_ctrl_flag, pll_sel);
if (pll_sel == CLK_26M)
return __scp_pll_sel_26M(pll_ctrl_flag, pll_sel);
idx = scp_get_freq_idx(pll_sel);
if (idx < 0) {
pr_notice("invalid idx %d\n", idx);
WARN_ON(1);
return -EINVAL;
}
mux_idx = dvfs.opp[idx].clk_mux;
if (mux_idx < 0) {
pr_notice("invalid mux_idx %d\n", mux_idx);
WARN_ON(1);
return -EINVAL;
}
if (pll_ctrl_flag == PLL_ENABLE) {
ret = set_scp_clk_mux(pll_ctrl_flag);
if (ret)
return ret;
if (idx >= 0 && idx < dvfs.scp_opp_nums
&& mux_idx < mt_scp_pll.pll_num)
ret = clk_set_parent(mt_scp_pll.clk_mux,
mt_scp_pll.clk_pll[mux_idx]);
else {
pr_notice("[%s]: not support opp freq %d\n",
__func__, pll_sel);
WARN_ON(1);
}
if (ret) {
pr_notice("[%s]: clk_set_parent() failed, opp=%d\n",
__func__, pll_sel);
WARN_ON(1);
}
} else if ((pll_ctrl_flag == PLL_DISABLE) &&
(dvfs.opp[idx].resource_req == 0)) {
set_scp_clk_mux(pll_ctrl_flag);
}
return ret;
}
void mt_scp_dvfs_state_dump(void)
{
unsigned int scp_state, slp_pwr_ctrl, power_status;
char *scp_status = 0;
scp_state = readl(SCP_A_SLEEP_DEBUG_REG);
scp_status = ((scp_state & IN_DEBUG_IDLE) == IN_DEBUG_IDLE) ? "idle mode"
: ((scp_state & ENTERING_SLEEP) == ENTERING_SLEEP) ?
"enter sleep"
: ((scp_state & IN_SLEEP) == IN_SLEEP) ?
"sleep mode"
: ((scp_state & ENTERING_ACTIVE) == ENTERING_ACTIVE) ?
"enter active"
: ((scp_state & IN_ACTIVE) == IN_ACTIVE) ?
"active mode" : "none of state";
if (dvfs.vlp_support) {
slp_pwr_ctrl = readl(SCP_SLP_PWR_CTRL);
power_status = readl(SCP_POWER_STATUS);
pr_info("scp status: %s, cpu-off config: %s, power status: %s\n",
scp_status,
((slp_pwr_ctrl & R_CPU_OFF) == R_CPU_OFF) ? "enable" : "disable",
((power_status & POW_ON) == POW_ON) ? "on" : "off");
} else {
pr_info("scp status: %s\n", scp_status);
}
}
#ifdef CONFIG_PROC_FS
/*
* PROC
*/
/****************************
* show SCP state
*****************************/
static int mt_scp_dvfs_state_proc_show(struct seq_file *m, void *v)
{
unsigned int scp_state, slp_pwr_ctrl, power_status;
scp_state = readl(SCP_A_SLEEP_DEBUG_REG);
seq_printf(m, "scp status: %s\n",
((scp_state & IN_DEBUG_IDLE) == IN_DEBUG_IDLE) ? "idle mode"
: ((scp_state & ENTERING_SLEEP) == ENTERING_SLEEP) ?
"enter sleep"
: ((scp_state & IN_SLEEP) == IN_SLEEP) ?
"sleep mode"
: ((scp_state & ENTERING_ACTIVE) == ENTERING_ACTIVE) ?
"enter active"
: ((scp_state & IN_ACTIVE) == IN_ACTIVE) ?
"active mode" : "none of state");
seq_printf(m, "current debug scp core: %d\n",
dvfs.cur_dbg_core);
if (dvfs.vlp_support) {
slp_pwr_ctrl = readl(SCP_SLP_PWR_CTRL);
power_status = readl(SCP_POWER_STATUS);
seq_printf(m, "cpu-off config: %s, power status: %s\n",
((slp_pwr_ctrl & R_CPU_OFF) == R_CPU_OFF) ? "enable" : "disable",
((power_status & POW_ON) == POW_ON) ? "on" : "off");
}
return 0;
}
/****************************
* show SCP count
*****************************/
static int mt_scp_dvfs_sleep_cnt_proc_show(struct seq_file *m, void *v)
{
struct ipi_tx_data_t ipi_data;
unsigned int *scp_ack_data = NULL;
int ret;
if (!dvfs.ipi_init_done)
slp_ipi_init();
ipi_data.arg1 = SCP_SLEEP_GET_COUNT;
if (dvfs.cur_dbg_core == SCP_CORE_0) {
ret = mtk_ipi_send_compl(&scp_ipidev, IPI_OUT_C_SLEEP_0,
IPI_SEND_WAIT, &ipi_data, PIN_OUT_C_SIZE_SLEEP_0, 500);
scp_ack_data = &scp_ipi_ackdata0;
} else if (dvfs.cur_dbg_core == SCP_CORE_1) {
ret = mtk_ipi_send_compl(&scp_ipidev, IPI_OUT_C_SLEEP_1,
IPI_SEND_WAIT, &ipi_data, PIN_OUT_C_SIZE_SLEEP_1, 500);
scp_ack_data = &scp_ipi_ackdata1;
} else {
pr_notice("[%s]: invalid scp core num: %d\n",
__func__, dvfs.cur_dbg_core);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
if (ret != IPI_ACTION_DONE) {
pr_notice("[%s] ipi send failed with error: %d\n",
__func__, ret);
return -ESCP_DVFS_IPI_FAILED;
}
seq_printf(m, "scp core%d sleep count: %d\n",
dvfs.cur_dbg_core, *scp_ack_data);
pr_notice("[%s]: scp core%d sleep count :%d\n",
__func__, dvfs.cur_dbg_core, *scp_ack_data);
return 0;
}
/**********************************
* write scp dvfs sleep
***********************************/
static ssize_t mt_scp_dvfs_sleep_cnt_proc_write(
struct file *file,
const char __user *buffer,
size_t count,
loff_t *data)
{
char desc[64], cmd[32];
unsigned int len = 0;
unsigned int ipi_cmd = -1;
unsigned int ipi_cmd_size = -1;
int ret = 0;
int n = 0;
struct ipi_tx_data_t ipi_data;
if (count <= 0)
return 0;
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
if (copy_from_user(desc, buffer, len))
return 0;
desc[len] = '\0';
if (!dvfs.ipi_init_done)
slp_ipi_init();
n = sscanf(desc, "%31s", cmd);
if (n != 1) {
pr_notice("[%s]: invalid command", __func__);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
if (!strcmp(cmd, "reset")) {
ipi_data.arg1 = SCP_SLEEP_RESET;
if (dvfs.cur_dbg_core == SCP_CORE_0) {
ipi_cmd = IPI_OUT_C_SLEEP_0;
ipi_cmd_size = PIN_OUT_C_SIZE_SLEEP_0;
} else if (dvfs.cur_dbg_core == SCP_CORE_1) {
ipi_cmd = IPI_OUT_C_SLEEP_1;
ipi_cmd_size = PIN_OUT_C_SIZE_SLEEP_1;
} else {
pr_notice("[%s]: invalid core index: %d\n",
__func__, dvfs.cur_dbg_core);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
ret = mtk_ipi_send(&scp_ipidev, ipi_cmd, IPI_SEND_WAIT,
&ipi_data, ipi_cmd_size, 500);
if (ret != SCP_IPI_DONE) {
pr_info("[%s]: SCP send IPI failed - %d\n",
__func__, ret);
return -ESCP_DVFS_IPI_FAILED;
}
} else {
pr_notice("[%s]: invalid command: %s\n", __func__, cmd);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
return count;
}
/****************************
* show scp dvfs sleep
*****************************/
static int mt_scp_dvfs_sleep_proc_show(struct seq_file *m, void *v)
{
struct ipi_tx_data_t ipi_data;
unsigned int *scp_ack_data = NULL;
int ret = 0;
if (!dvfs.ipi_init_done)
slp_ipi_init();
ipi_data.arg1 = SCP_SLEEP_GET_DBG_FLAG;
if (dvfs.cur_dbg_core == SCP_CORE_0) {
ret = mtk_ipi_send_compl(&scp_ipidev, IPI_OUT_C_SLEEP_0,
IPI_SEND_WAIT, &ipi_data, PIN_OUT_C_SIZE_SLEEP_0, 500);
scp_ack_data = &scp_ipi_ackdata0;
} else if (dvfs.cur_dbg_core == SCP_CORE_1) {
ret = mtk_ipi_send_compl(&scp_ipidev, IPI_OUT_C_SLEEP_1,
IPI_SEND_WAIT, &ipi_data, PIN_OUT_C_SIZE_SLEEP_1, 500);
scp_ack_data = &scp_ipi_ackdata1;
} else {
pr_notice("[%s]: invalid scp core index: %d\n",
__func__, dvfs.cur_dbg_core);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
if (ret != IPI_ACTION_DONE) {
pr_notice("[%s] ipi send failed with error: %d\n",
__func__, ret);
} else {
if (*scp_ack_data >= SCP_SLEEP_OFF &&
*scp_ack_data <= SCP_SLEEP_NO_CONDITION)
seq_printf(m, "scp sleep flag: %d\n",
*scp_ack_data);
else
seq_printf(m, "invalid sleep flag: %d\n",
*scp_ack_data);
}
return 0;
}
/**********************************
* write scp dvfs sleep
***********************************/
static ssize_t mt_scp_dvfs_sleep_proc_write(
struct file *file,
const char __user *buffer,
size_t count,
loff_t *data)
{
char desc[64], cmd[32];
unsigned int len = 0;
unsigned int ipi_cmd = -1;
unsigned int ipi_cmd_size = -1;
int ret = 0;
int n = 0;
int dbg_core = -1, slp_cmd = -1;
struct ipi_tx_data_t ipi_data;
if (count <= 0)
return 0;
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
if (copy_from_user(desc, buffer, len))
return 0;
desc[len] = '\0';
if (!dvfs.ipi_init_done)
slp_ipi_init();
n = sscanf(desc, "%31s %d", cmd, &slp_cmd);
if (n != 2) {
pr_notice("[%s]: invalid command", __func__);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
if (!strcmp(cmd, "sleep")) {
if (slp_cmd < SCP_SLEEP_OFF
|| slp_cmd > SCP_SLEEP_NO_CONDITION) {
pr_info("[%s]: invalid slp cmd: %d\n",
__func__, slp_cmd);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
ipi_data.arg1 = slp_cmd;
if (dvfs.cur_dbg_core == SCP_CORE_0) {
ipi_cmd = IPI_OUT_C_SLEEP_0;
ipi_cmd_size = PIN_OUT_C_SIZE_SLEEP_0;
} else if (dvfs.cur_dbg_core == SCP_CORE_1) {
ipi_cmd = IPI_OUT_C_SLEEP_1;
ipi_cmd_size = PIN_OUT_C_SIZE_SLEEP_1;
} else {
pr_notice("[%s]: invalid debug core: %d\n",
__func__, dvfs.cur_dbg_core);
}
ret = mtk_ipi_send(&scp_ipidev, ipi_cmd, IPI_SEND_WAIT,
&ipi_data, ipi_cmd_size, 500);
if (ret != SCP_IPI_DONE) {
pr_info("%s: SCP send IPI fail - %d\n",
__func__, ret);
return -ESCP_DVFS_IPI_FAILED;
}
} else if (!strcmp(cmd, "dbg_core")) {
dbg_core = slp_cmd;
if (dbg_core >= SCP_CORE_0 && dbg_core < SCP_MAX_CORE_NUM)
dvfs.cur_dbg_core = dbg_core;
} else {
pr_notice("[%s]: invalid command: %s\n", __func__, cmd);
return -ESCP_DVFS_DBG_INVALID_CMD;
}
return count;
}
/****************************
* show scp dvfs ctrl
*****************************/
static int mt_scp_dvfs_ctrl_proc_show(struct seq_file *m, void *v)
{
unsigned long spin_flags;
unsigned int scp_expected_freq_reg;
int i;
spin_lock_irqsave(&scp_awake_spinlock, spin_flags);
scp_current_freq = readl(CURRENT_FREQ_REG);
scp_expected_freq_reg = readl(EXPECTED_FREQ_REG);
spin_unlock_irqrestore(&scp_awake_spinlock, spin_flags);
seq_printf(m, "SCP DVFS: %s\n", (scp_dvfs_flag == 1)?"ON":"OFF");
seq_printf(m, "SCP frequency: cur=%dMHz, expect=%dMHz, kernel=%dMHz\n",
scp_current_freq, scp_expected_freq_reg, scp_expected_freq);
for (i = 0; i < NUM_FEATURE_ID; i++)
seq_printf(m, "feature=%d, freq=%d, enable=%d\n",
feature_table[i].feature, feature_table[i].freq,
feature_table[i].enable);
for (i = 0; i < NUM_SENSOR_TYPE; i++)
seq_printf(m, "sensor id=%d, freq=%d, enable=%d\n",
sensor_type_table[i].feature, sensor_type_table[i].freq,
sensor_type_table[i].enable);
return 0;
}
/**********************************
* write scp dvfs ctrl
***********************************/
static ssize_t mt_scp_dvfs_ctrl_proc_write(
struct file *file,
const char __user *buffer,
size_t count,
loff_t *data)
{
char desc[64], cmd[32];
unsigned int len = 0;
int dvfs_opp;
int n;
if (count <= 0)
return 0;
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
if (copy_from_user(desc, buffer, len))
return 0;
desc[len] = '\0';
n = sscanf(desc, "%31s %d", cmd, &dvfs_opp);
if (n == 1 || n == 2) {
if (!strcmp(cmd, "on")) {
scp_dvfs_flag = 1;
pr_info("SCP DVFS: ON\n");
} else if (!strcmp(cmd, "off")) {
scp_dvfs_flag = -1;
pr_info("SCP DVFS: OFF\n");
} else if (!strcmp(cmd, "opp")) {
if (dvfs_opp == -1) {
/* deregister dvfs debug feature */
pr_info("remove the opp setting of command\n");
feature_table[VCORE_TEST_FEATURE_ID].freq = 0;
scp_deregister_feature(
VCORE_TEST_FEATURE_ID);
scp_dvfs_debug_flag = dvfs_opp;
} else if (dvfs_opp >= 0 &&
dvfs_opp < dvfs.scp_opp_nums) {
/* register dvfs debug feature */
scp_dvfs_debug_flag = dvfs_opp;
scp_register_feature(
VCORE_TEST_FEATURE_ID);
} else {
pr_info("invalid opp value %d\n", dvfs_opp);
}
} else {
pr_info("invalid command %s\n", cmd);
}
} else {
pr_info("invalid length %d\n", n);
}
return count;
}
#define PROC_FOPS_RW(name) \
static int mt_ ## name ## _proc_open(\
struct inode *inode, \
struct file *file) \
{ \
return single_open(file, \
mt_ ## name ## _proc_show, \
PDE_DATA(inode)); \
} \
static const struct file_operations \
mt_ ## name ## _proc_fops = {\
.owner = THIS_MODULE, \
.open = mt_ ## name ## _proc_open, \
.read = seq_read, \
.llseek = seq_lseek, \
.release = single_release, \
.write = mt_ ## name ## _proc_write, \
}
#define PROC_FOPS_RO(name) \
static int mt_ ## name ## _proc_open(\
struct inode *inode,\
struct file *file)\
{\
return single_open(file, \
mt_ ## name ## _proc_show, \
PDE_DATA(inode)); \
} \
static const struct file_operations mt_ ## name ## _proc_fops = {\
.owner = THIS_MODULE,\
.open = mt_ ## name ## _proc_open,\
.read = seq_read,\
.llseek = seq_lseek,\
.release = single_release,\
}
#define PROC_ENTRY(name) {__stringify(name), &mt_ ## name ## _proc_fops}
PROC_FOPS_RO(scp_dvfs_state);
PROC_FOPS_RW(scp_dvfs_sleep_cnt);
PROC_FOPS_RW(scp_dvfs_sleep);
PROC_FOPS_RW(scp_dvfs_ctrl);
static int mt_scp_dvfs_create_procfs(void)
{
struct proc_dir_entry *dir = NULL;
int i, ret = 0;
struct pentry {
const char *name;
const struct file_operations *fops;
};
const struct pentry entries[] = {
PROC_ENTRY(scp_dvfs_state),
PROC_ENTRY(scp_dvfs_sleep_cnt),
PROC_ENTRY(scp_dvfs_sleep),
PROC_ENTRY(scp_dvfs_ctrl)
};
dir = proc_mkdir("scp_dvfs", NULL);
if (!dir) {
pr_notice("fail to create /proc/scp_dvfs @ %s()\n", __func__);
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(entries); i++) {
if (!proc_create(entries[i].name,
0664,
dir,
entries[i].fops)) {
pr_notice("ERROR: %s: create /proc/scp_dvfs/%s failed\n",
__func__, entries[i].name);
ret = -ENOMEM;
}
}
return ret;
}
#endif /* CONFIG_PROC_FS */
static const struct of_device_id scpdvfs_of_ids[] = {
{.compatible = "mediatek,scp_dvfs",},
{}
};
static void __init mt_pmic_sshub_init(void)
{
#if defined(CONFIG_MACH_MT6885) || \
defined(CONFIG_MACH_MT6873) || \
defined(CONFIG_MACH_MT6893) || \
defined(CONFIG_MACH_MT6877)
if (dvfs.pmic_sshub_en) {
/* set SCP VCORE voltage */
if (pmic_scp_set_vcore(dvfs.opp[0].tuned_vcore) != 0) {
pr_notice("Set wrong vcore voltage\n");
WARN_ON(1);
}
if (dvfs.vow_lp_en_gear != -1)
/* enable VOW low power mode */
pmic_buck_vgpu11_lp(SRCLKEN11, 0, 1, HW_LP);
else
/* disable VOW low power mode */
pmic_buck_vgpu11_lp(SRCLKEN11, 0, 1, HW_OFF);
/* BUCK_VCORE_SSHUB_EN: ON */
/* LDO_VSRAM_OTHERS_SSHUB_EN: OFF */
/* pmrc_mode: OFF */
pmic_scp_ctrl_enable(true, false, false);
} else {
pr_notice("shouldn't be here\n");
WARN_ON(1);
}
#elif defined(CONFIG_MACH_MT6853) || defined(CONFIG_MACH_MT6833)
if (!dvfs.pmic_sshub_en)
pmic_buck_vcore_lp(SRCLKEN11, 0, 1, HW_OFF);
else {
pr_notice("shouldn't be here\n");
WARN_ON(1);
}
#else
#error "platform not support"
#endif
}
/*static_assert(CAL_BITS + CAL_EXT_BITS <= 8 * sizeof(unsigned short),
"error: there are only 16bits available in IPI\n");*/
void sync_ulposc_cali_data_to_scp(void)
{
unsigned int sel_clk = 0;
unsigned int ipi_data[2];
unsigned short *p = (unsigned short *)&ipi_data[1];
int i, ret;
if (!dvfs.ulposc_hw.do_ulposc_cali) {
pr_notice("[%s]: ulposc2 calibration is not done by AP\n",
__func__);
return;
}
if (dvfs.ulposc_hw.cali_failed) {
pr_notice("[%s]: ulposc2 calibration failed\n", __func__);
return;
}
if (!dvfs.ipi_init_done)
slp_ipi_init();
ipi_data[0] = SCP_SYNC_ULPOSC_CALI;
for (i = 0; i < dvfs.ulposc_hw.cali_nums; i++) {
*p = dvfs.ulposc_hw.cali_freq[i];
if ((!dvfs.vlpck_support) || dvfs.vlpck_bypass_phase1)
*(p + 1) = dvfs.ulposc_hw.cali_val[i];
else
*(p + 1) = dvfs.ulposc_hw.cali_val[i] |
(dvfs.ulposc_hw.cali_val_ext[i] << CAL_BITS);
pr_notice("[%s]: ipi to scp: freq=%d, cali_val=0x%x\n",
__func__,
dvfs.ulposc_hw.cali_freq[i],
*(p + 1));
ret = mtk_ipi_send_compl(&scp_ipidev,
IPI_OUT_C_SLEEP_0,
IPI_SEND_WAIT,
&ipi_data[0],
PIN_OUT_C_SIZE_SLEEP_0,
500);
if (ret != IPI_ACTION_DONE) {
pr_notice("[%s]: ipi send ulposc cali val(%d, 0x%x) fail\n",
__func__,
dvfs.ulposc_hw.cali_freq[i],
*(p + 1));
WARN_ON(1);
}
}
scp_reg_read(dvfs.clk_hw->scp_clk_regmap,
&dvfs.clk_hw->_sel_clk, &sel_clk);
if ((sel_clk & (SCP_ULPOSC_SEL_CORE | SCP_ULPOSC_SEL_PERI)) == 0) {
pr_notice("[%s]:ERROR scp is not switched to ULPOSC, CLK_SW_SEL=0x%x\n",
__func__, sel_clk);
WARN_ON(1);
}
}
static inline bool __init is_ulposc_cali_pass(unsigned int cur,
unsigned int target)
{
if (cur > (target * (1000 - CALI_MIS_RATE) / 1000)
&& cur < (target * (1000 + CALI_MIS_RATE) / 1000))
return 1;
/* calibrated failed here */
pr_notice("[%s]: cur: %dMHz, target: %dMHz calibrate failed\n",
__func__,
FM_CNT2FREQ(cur),
FM_CNT2FREQ(target));
return 0;
}
static unsigned int __init get_ulposc_clk_by_fmeter(void)
{
if (dvfs.ccf_fmeter_support)
return mt_get_abist_freq(dvfs.ulposc_hw.fmeter_id_ulposc2);
else {
pr_notice("shouldn't be here\n");
WARN_ON(1);
}
return 0;
}
static void __init set_ulposc_cali_value(unsigned int cali_val)
{
int ret = 0;
ret = scp_reg_update(dvfs.ulposc_hw.ulposc_regmap,
&dvfs.ulposc_hw.ulposc_regs->_cali,
cali_val);
udelay(50);
}
static int __init ulposc_cali_process(unsigned int cali_idx,
unsigned short *cali_res)
{
unsigned int target_val = 0, current_val = 0;
unsigned int min = CAL_MIN_VAL, max = CAL_MAX_VAL, mid;
unsigned int diff_by_min = 0, diff_by_max = 0xffff;
target_val = dvfs.ulposc_hw.cali_freq[cali_idx]*1000;
do {
mid = (min + max) / 2;
if (mid == min) {
pr_debug("mid(%u) == min(%u)\n", mid, min);
break;
}
set_ulposc_cali_value(mid);
current_val = get_ulposc_clk_by_fmeter();
if (current_val > target_val)
max = mid;
else
min = mid;
} while (min <= max);
set_ulposc_cali_value(min);
current_val = get_ulposc_clk_by_fmeter();
diff_by_min = (current_val > target_val) ?
(current_val - target_val):(target_val - current_val);
set_ulposc_cali_value(max);
current_val = get_ulposc_clk_by_fmeter();
diff_by_max = (current_val > target_val) ?
(current_val - target_val):(target_val - current_val);
*cali_res = (diff_by_min < diff_by_max) ? min : max;
set_ulposc_cali_value(*cali_res);
current_val = get_ulposc_clk_by_fmeter();
if (!is_ulposc_cali_pass(current_val, target_val)) {
pr_notice("[%s]: calibration failed for: %dMHz\n",
__func__,
dvfs.ulposc_hw.cali_freq[cali_idx]);
*cali_res = 0;
WARN_ON(1);
return -ESCP_DVFS_CALI_FAILED;
}
pr_notice("[%s]: target: %uMhz, calibrated = %uMHz\n",
__func__, target_val/1000, current_val/1000);
return 0;
}
#if 0
static int smc_turn_on_ulposc2(void)
{
struct arm_smccc_res res;
arm_smccc_smc(MTK_SIP_KERNEL_SCP_DVFS_CTRL, ULPOSC2_TURN_ON,
0, 0, 0, 0, 0, 0, &res);
return res.a0;
}
static int smc_turn_off_ulposc2(void)
{
struct arm_smccc_res res;
arm_smccc_smc(MTK_SIP_KERNEL_SCP_DVFS_CTRL, ULPOSC2_TURN_OFF,
0, 0, 0, 0, 0, 0, &res);
return res.a0;
}
#endif
static void turn_onoff_ulposc2(enum ulposc_onoff_enum on)
{
if (on) {
/* turn on ulposc */
if (dvfs.vlp_support) {
pr_notice("shouldn't be here\n");
WARN_ON(1);
} else {
scp_reg_update(dvfs.clk_hw->scp_clk_regmap,
&dvfs.clk_hw->_clk_high_en, on);
scp_reg_update(dvfs.clk_hw->scp_clk_regmap,
&dvfs.clk_hw->_ulposc2_en, !on);
/* wait settle time */
udelay(150);
scp_reg_update(dvfs.clk_hw->scp_clk_regmap,
&dvfs.clk_hw->_ulposc2_cg, on);
}
} else {
/* turn off ulposc */
if (dvfs.vlp_support) {
pr_notice("shouldn't be here\n");
WARN_ON(1);
} else {
scp_reg_update(dvfs.clk_hw->scp_clk_regmap,
&dvfs.clk_hw->_ulposc2_cg, on);
udelay(50);
scp_reg_update(dvfs.clk_hw->scp_clk_regmap,
&dvfs.clk_hw->_ulposc2_en, !on);
}
}
udelay(50);
}
static int __init mt_scp_dvfs_do_ulposc_cali_process(void)
{
int ret = 0;
unsigned int i;
if (!dvfs.ulposc_hw.do_ulposc_cali) {
pr_notice("[%s]: ulposc2 calibration is not done by AP\n",
__func__);
return 0;
}
for (i = 0; i < dvfs.ulposc_hw.cali_nums; i++) {
turn_onoff_ulposc2(ULPOSC_OFF);
ret += scp_reg_update(dvfs.ulposc_hw.ulposc_regmap,
&dvfs.ulposc_hw.ulposc_regs->_con0,
dvfs.ulposc_hw.cali_configs[i].con0_val);
ret += scp_reg_update(dvfs.ulposc_hw.ulposc_regmap,
&dvfs.ulposc_hw.ulposc_regs->_con1,
dvfs.ulposc_hw.cali_configs[i].con1_val);
ret += scp_reg_update(dvfs.ulposc_hw.ulposc_regmap,
&dvfs.ulposc_hw.ulposc_regs->_con2,
dvfs.ulposc_hw.cali_configs[i].con2_val);
if (ret) {
pr_notice("[%s]: config ulposc register failed\n",
__func__);
return ret;
}
turn_onoff_ulposc2(ULPOSC_ON);
ret = ulposc_cali_process(i, &dvfs.ulposc_hw.cali_val[i]);
if (ret) {
pr_notice("[%s]: cali %uMHz ulposc failed\n",
__func__, dvfs.ulposc_hw.cali_freq[i]);
dvfs.ulposc_hw.cali_failed = true;
return -ESCP_DVFS_CALI_FAILED;
}
}
turn_onoff_ulposc2(ULPOSC_OFF);
pr_notice("[%s]: ulposc calibration all done\n", __func__);
return ret;
}
static int __init mt_scp_dts_get_cali_hw_setting(struct device_node *node,
struct ulposc_cali_hw *cali_hw)
{
unsigned int i;
int ret = 0;
/* find hw calibration configuration data */
/* update clk_dbg or ulposc_cali data if there is minor change by hw */
ret = of_property_count_u32_elems(node, "ulposc-cali-config");
if ((ret / CALI_CONFIG_ELEM_CNT) <= 0
|| (ret % CALI_CONFIG_ELEM_CNT) != 0) {
pr_notice("[%s]: cali config count does not equal to cali nums\n",
__func__);
return ret;
}
cali_hw->cali_configs = kcalloc(cali_hw->cali_nums,
sizeof(struct ulposc_cali_config),
GFP_KERNEL);
if (!(cali_hw->cali_configs))
return -ENOMEM;
for (i = 0; i < cali_hw->cali_nums; i++) {
ret = of_property_read_u32_index(node, "ulposc-cali-config",
(i * CALI_CONFIG_ELEM_CNT),
&(cali_hw->cali_configs[i].con0_val));
if (ret) {
pr_notice("[%s]: get con0 setting failed\n", __func__);
goto CALI_DATA_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "ulposc-cali-config",
(i * CALI_CONFIG_ELEM_CNT + 1),
&(cali_hw->cali_configs[i].con1_val));
if (ret) {
pr_notice("[%s]: get con1 setting failed\n", __func__);
goto CALI_DATA_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "ulposc-cali-config",
(i * CALI_CONFIG_ELEM_CNT + 2),
&(cali_hw->cali_configs[i].con2_val));
if (ret) {
pr_notice("[%s]: get con2 setting failed\n", __func__);
goto CALI_DATA_INIT_FAILED;
}
}
return 0;
CALI_DATA_INIT_FAILED:
kfree(cali_hw->cali_configs);
return ret;
}
static int __init mt_scp_dts_get_cali_target(struct device_node *node,
struct ulposc_cali_hw *cali_hw)
{
int ret = 0;
unsigned int i;
unsigned int tmp;
/* find number of ulposc need to do calibration */
ret = of_property_read_u32(node, "ulposc-cali-num",
&cali_hw->cali_nums);
if (ret) {
pr_notice("[%s]: find ulposc calibration numbers failed\n",
__func__);
return ret;
}
ret = of_property_count_u32_elems(node, "ulposc-cali-target");
if (ret != cali_hw->cali_nums) {
pr_notice("[%s]: target nums does not equals to ulposc-cali-num\n",
__func__);
return ret;
}
if (dvfs.vlpck_support) {
cali_hw->cali_val_ext = kcalloc(cali_hw->cali_nums, sizeof(unsigned short),
GFP_KERNEL);
if (!cali_hw->cali_val_ext)
return -ENOMEM;
} else {
cali_hw->cali_val_ext = NULL;
}
cali_hw->cali_val = kcalloc(cali_hw->cali_nums, sizeof(unsigned short),
GFP_KERNEL);
if (!cali_hw->cali_val) {
ret = -ENOMEM;
goto CALI_EXT_TARGET_ALLOC_FAILED;
}
cali_hw->cali_freq = kcalloc(cali_hw->cali_nums, sizeof(unsigned short),
GFP_KERNEL);
if (!cali_hw->cali_freq) {
ret = -ENOMEM;
goto CALI_TARGET_ALLOC_FAILED;
}
for (i = 0; i < cali_hw->cali_nums; i++) {
ret = of_property_read_u32_index(node, "ulposc-cali-target",
i, &tmp);
if (ret) {
pr_notice("[%s]: find cali target failed, idx: %d\n",
__func__, i);
goto FIND_TARGET_FAILED;
}
cali_hw->cali_freq[i] = (unsigned short) tmp;
}
return ret;
FIND_TARGET_FAILED:
kfree(cali_hw->cali_freq);
CALI_TARGET_ALLOC_FAILED:
kfree(cali_hw->cali_val);
CALI_EXT_TARGET_ALLOC_FAILED:
kfree(cali_hw->cali_val_ext);
return ret;
}
static int __init mt_scp_dts_get_cali_hw_regs(struct device_node *node,
struct ulposc_cali_hw *cali_hw)
{
const char *str = NULL;
int ret = 0;
int i;
/* find ulposc register hw version */
ret = of_property_read_string(node, "ulposc-cali-ver", &str);
if (ret) {
pr_notice("[%s]: find ulposc-cali-ver failed with err: %d\n",
__func__, ret);
return ret;
}
for (i = 0; i < MAX_ULPOSC_VERSION; i++)
if (!strcmp(ulposc_ver[i], str))
cali_hw->ulposc_regs = &cali_regs[i];
if (!cali_hw->ulposc_regs) {
pr_notice("[%s]: no ulposc cali reg found\n", __func__);
return -ESCP_DVFS_NO_CALI_HW_FOUND;
}
/* find clk dbg register hw version */
if (!dvfs.ccf_fmeter_support) {
ret = of_property_read_string(node, "clk-dbg-ver", &str);
if (ret) {
pr_notice("[%s]: find clk-dbg-ver failed with err: %d\n",
__func__, ret);
return ret;
}
for (i = 0; i < MAX_CLK_DBG_VERSION; i++)
if (!strcmp(clk_dbg_ver[i], str))
cali_hw->clkdbg_regs = &clk_dbg_reg[i];
if (!cali_hw->clkdbg_regs) {
pr_notice("[%s]: no clkfbg regs found\n",
__func__);
return -ESCP_DVFS_NO_CALI_HW_FOUND;
}
}
return ret;
}
#if IS_ENABLED(CONFIG_PM)
static int mt_scp_dump_sleep_count(void)
{
int ret = 0;
struct ipi_tx_data_t ipi_data;
if (!dvfs.ipi_init_done)
slp_ipi_init();
ipi_data.arg1 = SCP_SLEEP_GET_COUNT;
ret = mtk_ipi_send_compl(&scp_ipidev, IPI_OUT_C_SLEEP_0,
IPI_SEND_WAIT, &ipi_data, PIN_OUT_C_SIZE_SLEEP_0, 500);
if (ret != IPI_ACTION_DONE) {
pr_notice("[SCP] [%s:%d] - scp ipi failed, ret = %d\n",
__func__, __LINE__, ret);
goto FINISH;
}
if (dvfs.core_nums < 2) {
pr_notice("[SCP] [%s:%d] - scp_sleep_cnt_0 = %d\n",
__func__, __LINE__, scp_ipi_ackdata0);
goto FINISH;
}
/* if there are 2 cores */
ret = mtk_ipi_send_compl(&scp_ipidev, IPI_OUT_C_SLEEP_1,
IPI_SEND_WAIT, &ipi_data, PIN_OUT_C_SIZE_SLEEP_1, 500);
if (ret != IPI_ACTION_DONE) {
pr_notice("[SCP] [%s:%d] - scp ipi failed, ret = %d\n",
__func__, __LINE__, ret);
goto FINISH;
}
pr_notice("[SCP] [%s:%d] - scp_sleep_cnt_0 = %d, scp_sleep_cnt_1 = %d\n",
__func__, __LINE__, scp_ipi_ackdata0, scp_ipi_ackdata1);
FINISH:
return 0;
}
static int scp_pm_event(struct notifier_block *notifier,
unsigned long pm_event, void *unused)
{
switch (pm_event) {
case PM_HIBERNATION_PREPARE:
return NOTIFY_DONE;
case PM_RESTORE_PREPARE:
return NOTIFY_DONE;
case PM_POST_HIBERNATION:
return NOTIFY_DONE;
case PM_SUSPEND_PREPARE:
case PM_POST_SUSPEND:
mt_scp_dvfs_state_dump();
mt_scp_dump_sleep_count();
return NOTIFY_DONE;
}
return NOTIFY_OK;
}
static struct notifier_block scp_pm_notifier_func = {
.notifier_call = scp_pm_event,
};
#endif /* CONFIG_PM */
static int __init mt_scp_dts_init_scp_clk_hw(struct device_node *node)
{
const char *str = NULL;
unsigned int i;
int ret = 0;
ret = of_property_read_string(node, "scp-clk-hw-ver", &str);
if (ret) {
pr_notice("[%s]: find scp-clk-hw-ver failed with err: %d\n",
__func__, ret);
return ret;
}
for (i = 0; i < MAX_SCP_CLK_VERSION; i++) {
if (!strcmp(scp_clk_ver[i], str)) {
dvfs.clk_hw = &(scp_clk_hw_regs[i]);
return 0;
}
}
pr_notice("[%s]: no scp clk hw reg found\n", __func__);
return -ESCP_DVFS_NO_CALI_HW_FOUND;
}
static int __init mt_scp_dts_init_cali_regmap(struct device_node *node,
struct ulposc_cali_hw *cali_hw)
{
/* init regmap for calibration process */
cali_hw->ulposc_regmap = syscon_regmap_lookup_by_phandle(node,
ULPOSC_CLK_PHANDLE_NAME);
if (IS_ERR(cali_hw->ulposc_regmap)) {
pr_notice("ulposc regmap init failed: %d\n",
PTR_ERR(cali_hw->ulposc_regmap));
return PTR_ERR(cali_hw->ulposc_regmap);
}
return 0;
}
static int __init mt_scp_dts_ulposc_cali_init(struct device_node *node,
struct ulposc_cali_hw *cali_hw)
{
int ret = 0;
unsigned int val;
if (!cali_hw) {
pr_notice("[%s]: ulposc calibration hw is NULL\n",
__func__);
WARN_ON(1);
return -ESCP_DVFS_NO_CALI_HW_FOUND;
}
/* check if current platform need to do calibration */
dvfs.ulposc_hw.do_ulposc_cali = of_property_read_bool(node,
"do-ulposc-cali");
if (!dvfs.ulposc_hw.do_ulposc_cali) {
pr_notice("[%s]: skip ulposc calibration process\n", __func__);
return 0;
}
ret = mt_scp_dts_init_cali_regmap(node, cali_hw);
if (ret)
return ret;
ret = mt_scp_dts_init_scp_clk_hw(node);
if (ret)
return ret;
ret = mt_scp_dts_get_cali_hw_regs(node, cali_hw);
if (ret)
return ret;
dvfs.clk_hw->scp_clk_regmap = syscon_regmap_lookup_by_phandle(node,
SCP_CLK_CTRL_PHANDLE_NAME);
if (!dvfs.clk_hw->scp_clk_regmap) {
pr_notice("[%s]: get scp clk regmap failed\n", __func__);
return ret;
}
ret = mt_scp_dts_get_cali_target(node, cali_hw);
if (ret)
return ret;
ret = mt_scp_dts_get_cali_hw_setting(node, cali_hw);
if (ret)
return ret;
/* get ulposc2 fmeter id */
ret = of_property_read_u32(node, "fmeter-id-ulposc2",
&dvfs.ulposc_hw.fmeter_id_ulposc2);
if (ret) {
pr_notice("[%s]: ulposc2-fmeter-id not config in dts\n", __func__);
WARN_ON(1);
}
/* get 26M fmeter id */
ret = of_property_read_u32(node, "fmeter-id-26M",
&val);
if (!ret)
/* read 26M by freq-meter to check if freq-meter API is correct */
pr_notice("freq-meter 26M(id=%d) = %u Hz\n", val, mt_get_abist_freq(val));
else
pr_notice("[%s]: fmeter-id-26M not config in dts\n", __func__);
return ret;
}
static int __init mt_scp_dts_clk_init(struct platform_device *pdev)
{
char buf[15];
int ret = 0;
int i;
mt_scp_pll.clk_mux = devm_clk_get(&pdev->dev, "clk_mux");
if (IS_ERR(mt_scp_pll.clk_mux)) {
dev_notice(&pdev->dev, "cannot get clock mux\n");
WARN_ON(1);
return PTR_ERR(mt_scp_pll.clk_mux);
}
/* scp_sel has most 9 member of clk source */
mt_scp_pll.pll_num = MAX_SUPPORTED_PLL_NUM;
for (i = 0; i < MAX_SUPPORTED_PLL_NUM; i++) {
ret = snprintf(buf, 15, "clk_pll_%d", i);
if (ret < 0 || ret >= 15) {
pr_notice("[%s]: clk name buf len: %d\n",
__func__, ret);
return ret;
}
mt_scp_pll.clk_pll[i] = devm_clk_get(&pdev->dev, buf);
if (IS_ERR(mt_scp_pll.clk_pll[i])) {
dev_notice(&pdev->dev,
"cannot get %dst clock parent\n",
i);
mt_scp_pll.pll_num = i;
break;
}
}
return 0;
}
static int __init mt_scp_dts_init_dvfs_data(struct device_node *node,
struct dvfs_opp **opp)
{
int ret = 0;
int i;
if (*opp) {
pr_notice("[%s]: opp is initialized\n", __func__);
return -ESCP_DVFS_DATA_RE_INIT;
}
/* get scp dvfs opp count */
ret = of_property_count_u32_elems(node, "dvfs-opp");
if ((ret / OPP_ELEM_CNT) <= 0 || (ret % OPP_ELEM_CNT) != 0) {
pr_notice("[%s]: get dvfs opp count failed, count: %d\n",
__func__, ret);
return ret;
}
dvfs.scp_opp_nums = ret / 7;
*opp = kcalloc(dvfs.scp_opp_nums, sizeof(struct dvfs_opp), GFP_KERNEL);
if (!(*opp))
return -ENOMEM;
/* get each dvfs opp data from dts node */
for (i = 0; i < dvfs.scp_opp_nums; i++) {
ret = of_property_read_u32_index(node, "dvfs-opp",
i * OPP_ELEM_CNT,
&(*opp)[i].vcore);
if (ret) {
pr_notice("Cannot get property vcore(%d)\n", ret);
goto OPP_INIT_FAILED;
}
(*opp)[i].tuned_vcore = (*opp)[i].vcore;
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 1,
&(*opp)[i].vsram);
if (ret) {
pr_notice("Cannot get property vsram(%d)\n", ret);
goto OPP_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 2,
&(*opp)[i].dvfsrc_opp);
if (ret) {
pr_notice("Cannot get property dvfsrc opp(%d)\n", ret);
goto OPP_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 3,
&(*opp)[i].spm_opp);
if (ret) {
pr_notice("Cannot get property spm opp(%d)\n", ret);
goto OPP_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 4,
&(*opp)[i].freq);
if (ret) {
pr_notice("Cannot get property freq(%d)\n", ret);
goto OPP_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 5,
&(*opp)[i].clk_mux);
if (ret) {
pr_notice("Cannot get property clk mux(%d)\n", ret);
goto OPP_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 6,
&(*opp)[i].resource_req);
if (ret) {
pr_notice("Cannot get property opp resource(%d)\n", ret);
goto OPP_INIT_FAILED;
}
ret = of_property_read_u32_index(node, "dvfs-opp",
(i * OPP_ELEM_CNT) + 7,
&(*opp)[i].uv_idx);
if (ret) {
pr_notice("Cannot get property vcore opp(%d)\n", ret);
goto OPP_INIT_FAILED;
}
}
return ret;
OPP_INIT_FAILED:
WARN_ON(1);
kfree(*opp);
return ret;
}
static int __init mt_scp_dts_init_pmic_data(void)
{
return 0;
}
static int __init mt_scp_dts_regmap_init(struct platform_device *pdev,
struct device_node *node)
{
return 0;
}
static int __init mt_scp_dts_init(struct platform_device *pdev)
{
struct device_node *node;
int ret = 0;
const char *str = NULL;
/* find device tree node of scp_dvfs */
node = pdev->dev.of_node;
if (!node) {
dev_notice(&pdev->dev, "fail to find SCPDVFS node\n");
return -ENODEV;
}
/* used to replace 'SCP_DVFS_INIT_ENABLE' compile flag */
of_property_read_string(node, "scp-dvfs-feature", &str);
if (str) {
if (strcmp(str, "enable") == 0) {
scp_dvfs_enable = 1;
pr_notice("SCP DVFS feature: enable\n");
} else {
scp_dvfs_enable = 0;
pr_notice("SCP DVFS is disabled, so bypass its init\n");
return 0;
}
} else {
scp_dvfs_enable = 0;
pr_notice("SCP DVFS is disabled, so bypass its init\n");
return 0;
}
/*
* if set, no VCORE DVS is needed & PMIC setting should
* be done in SCP side.
*/
dvfs.vlp_support = of_property_read_bool(node, "vlp-support");
if (dvfs.vlp_support) {
pr_notice("%s: vlp not support\n", __func__);
WARN_ON(1);
}
if (dvfs.vlp_support) {
dvfs.vow_lp_en_gear = -1;
} else {
ret = of_property_read_u32(node, "vow-lp-en-gear",
&dvfs.vow_lp_en_gear);
if (ret) {
pr_notice("[%s]: no vow-lp-enable-gear property, set gear to -1\n",
__func__);
dvfs.vow_lp_en_gear = -1;
}
}
dvfs.vlpck_support = of_property_read_bool(node, "vlpck-support");
if (dvfs.vlpck_support) {
pr_notice("%s: vlpck not support\n", __func__);
WARN_ON(1);
} else {
dvfs.vlpck_bypass_phase1 = false;
}
ret = of_property_read_u32(node, "scp-cores",
&dvfs.core_nums);
if (ret || dvfs.core_nums > SCP_MAX_CORE_NUM) {
pr_notice("[%s]: find invalid core numbers, set to 1\n",
__func__);
dvfs.core_nums = 1;
}
if (dvfs.vlp_support)
dvfs.pmic_sshub_en = false;
else
dvfs.pmic_sshub_en = of_property_read_bool(node, "pmic-sshub-support");
ret = mt_scp_dts_regmap_init(pdev, node);
if (ret) {
pr_notice("[%s]: scp regmap init failed with err: %d\n",
__func__, ret);
goto DTS_FAILED;
}
if (!dvfs.vlp_support) {
ret = mt_scp_dts_init_pmic_data();
if (ret)
goto DTS_FAILED;
}
/*
* 1. If "ccf-fmeter-support" was set, it means common clock framework has provided API
* to use fmeter. And we should use mt_get_fmeter_freq(id, type) defined in clk-fmeter.h,
* instead of using get_ulposc_clk_by_fmeter*().
*
* 2. Only wehn mt_get_fmeter_freq havn't been provide, get_ulposc_clk_by_fmeter*() can
* be used temporarily.
*/
dvfs.ccf_fmeter_support = of_property_read_bool(node, "ccf-fmeter-support");
if (!dvfs.ccf_fmeter_support) {
pr_notice("[%s]: fmeter api havn't been provided, use legacy one\n", __func__);
WARN_ON(1);
}
/* init dvfs data */
ret = mt_scp_dts_init_dvfs_data(node, &dvfs.opp);
if (ret) {
pr_notice("[%s]: scp dvfs opp data init failed with err: %d\n",
__func__, ret);
goto DTS_FAILED;
}
/* init clock mux/pll */
ret = mt_scp_dts_clk_init(pdev);
if (ret) {
pr_notice("[%s]: init scp clk failed with err: %d\n",
__func__, ret);
goto DTS_FAILED_FREE_RES;
}
/* init ulposc cali dts data */
ret = mt_scp_dts_ulposc_cali_init(node, &dvfs.ulposc_hw);
if (ret) {
pr_notice("[%s]: init scp ulposc cali data with err: %d\n",
__func__, ret);
goto DTS_FAILED_FREE_RES;
}
return 0;
DTS_FAILED_FREE_RES:
kfree(dvfs.opp);
DTS_FAILED:
return ret;
}
/* used to replace 'SCP_DVFS_INIT_ENABLE' compile flag */
int scp_dvfs_feature_enable(void)
{
return scp_dvfs_enable;
}
static int __init mt_scp_dvfs_pdrv_probe(struct platform_device *pdev)
{
int ret = 0;
ret = mt_scp_dts_init(pdev);
if (ret) {
pr_notice("[%s]: dts init fail, %d\n", __func__, ret);
goto err;
}
if (!scp_dvfs_enable) {
g_scp_dvfs_init_flag = 1;
pr_notice("bypass scp dvfs init\n");
return 0;
}
/* turn on scp_sel mux before access any scp reg/sram */
scp_pll_ctrl_set(PLL_ENABLE, CLK_26M);
/* request 26M resource */
scp_resource_req(SCP_REQ_26M);
/* do ulposc calibration */
mt_scp_dvfs_do_ulposc_cali_process();
kfree(dvfs.ulposc_hw.cali_configs);
/* init sshub */
if (!dvfs.vlp_support)
mt_pmic_sshub_init();
mtk_pm_qos_add_request(&dvfsrc_scp_vcore_req,
MTK_PM_QOS_SCP_VCORE_REQUEST,
MTK_PM_QOS_SCP_VCORE_REQUEST_DEFAULT_VALUE);
scp_suspend_lock = wakeup_source_register(NULL, "scp wakelock");
#if IS_ENABLED(CONFIG_PM)
ret = register_pm_notifier(&scp_pm_notifier_func);
if (ret) {
pr_notice("[%s]: failed to register PM notifier.\n", __func__);
goto err;
}
#endif
#ifdef CONFIG_PROC_FS
/* init proc */
if (mt_scp_dvfs_create_procfs()) {
pr_notice("mt_scp_dvfs_create_procfs fail..\n");
goto err;
}
#endif
g_scp_dvfs_init_flag = 1;
pr_notice("[%s]: scp_dvfs probe done\n", __func__);
return 0;
err:
WARN_ON(1);
return -ESCP_DVFS_INIT_FAILED;
}
/***************************************
* this function should never be called
****************************************/
static int mt_scp_dvfs_pdrv_remove(struct platform_device *pdev)
{
if (!scp_dvfs_enable) {
pr_notice("bypass scp dvfs pdrv remove\n");
return 0;
}
kfree(dvfs.opp);
kfree(dvfs.ulposc_hw.cali_val_ext);
kfree(dvfs.ulposc_hw.cali_val);
kfree(dvfs.ulposc_hw.cali_freq);
kfree(dvfs.ulposc_hw.cali_configs);
return 0;
}
static struct platform_driver mt_scp_dvfs_pdrv __refdata = {
.probe = mt_scp_dvfs_pdrv_probe,
.remove = mt_scp_dvfs_pdrv_remove,
.driver = {
.name = "scp_dvfs",
.owner = THIS_MODULE,
.of_match_table = scpdvfs_of_ids,
},
};
/**********************************
* mediatek scp dvfs initialization
***********************************/
int __init scp_dvfs_init(void)
{
int ret = 0;
pr_debug("%s\n", __func__);
ret = platform_driver_register(&mt_scp_dvfs_pdrv);
if (ret) {
pr_notice("fail to register scp dvfs driver @ %s()\n", __func__);
goto fail;
}
return 0;
fail:
WARN_ON(1);
return -ESCP_DVFS_INIT_FAILED;
}
void __exit scp_dvfs_exit(void)
{
unregister_pm_notifier(&scp_pm_notifier_func);
platform_driver_unregister(&mt_scp_dvfs_pdrv);
}