6db4831e98
Android 14
1381 lines
37 KiB
C
1381 lines
37 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
#include <linux/slab.h>
|
|
#include <linux/arm-smccc.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/soc/mediatek/mtk_dvfsrc.h>
|
|
#include <linux/soc/mediatek/mtk-pm-qos.h>
|
|
#include <linux/soc/mediatek/mtk_sip_svc.h>
|
|
#include "mtk-scpsys.h"
|
|
#include <linux/regulator/consumer.h>
|
|
#ifdef CONFIG_MTK_AEE_FEATURE
|
|
#include <mt-plat/aee.h>
|
|
#endif
|
|
|
|
#define MTK_SIP_GET_SPM_INFO 0x3
|
|
#define DVFSRC_IDLE 0x00
|
|
#define DVFSRC_GET_TARGET_LEVEL(x) (((x) >> 0) & 0x0000ffff)
|
|
#define DVFSRC_GET_CURRENT_LEVEL(x) (((x) >> 16) & 0x0000ffff)
|
|
#define kBps_to_MBps(x) (div_u64(x, 1000))
|
|
#define MBps_to_kBps(x) ((x) * 1000)
|
|
|
|
#define MT8183_DVFSRC_OPP_LP4 0
|
|
#define MT8183_DVFSRC_OPP_LP4X 1
|
|
#define MT8183_DVFSRC_OPP_LP3 2
|
|
|
|
#define POLL_TIMEOUT 1000
|
|
#define STARTUP_TIME 1
|
|
|
|
/** VCORE UV table */
|
|
static u32 num_vopp;
|
|
static u32 *vopp_uv_tlb;
|
|
#define MTK_SIP_VCOREFS_VCORE_NUM 0x6
|
|
#define MTK_SIP_VCOREFS_VCORE_UV 0x4
|
|
|
|
/* Group of bits used for function enable */
|
|
#define HAS_CAP(_c, _x) (((_c) & (_x)) == (_x))
|
|
#define DVFSRC_CAP_CLK_INIT BIT(0)
|
|
#define DVFSRC_CAP_V_OPP_INIT BIT(1)
|
|
#define DVFSRC_CAP_V_CHECKER BIT(2)
|
|
#define DVFSRC_CAP_MTKQOS BIT(3)
|
|
|
|
|
|
struct dvfsrc_opp {
|
|
u32 vcore_opp;
|
|
u32 dram_opp;
|
|
};
|
|
|
|
struct dvfsrc_setting {
|
|
u32 offset;
|
|
u32 val;
|
|
};
|
|
|
|
struct dvfsrc_setting_desc {
|
|
const struct dvfsrc_setting *setting;
|
|
u32 size;
|
|
};
|
|
|
|
struct dvfsrc_domain {
|
|
u32 id;
|
|
u32 state;
|
|
};
|
|
|
|
struct mtk_dvfsrc;
|
|
|
|
struct dvfsrc_qos_data {
|
|
struct device *dev;
|
|
int max_vcore_opp;
|
|
int max_dram_opp;
|
|
void (*pm_qos_init)(struct mtk_dvfsrc *dvfsrc);
|
|
struct notifier_block pm_qos_bw_notify;
|
|
struct notifier_block pm_qos_ext_bw_notify;
|
|
struct notifier_block pm_qos_hrtbw_notify;
|
|
struct notifier_block pm_qos_ddr_notify;
|
|
struct notifier_block pm_qos_vcore_notify;
|
|
struct notifier_block pm_qos_scp_notify;
|
|
};
|
|
|
|
struct dvfsrc_soc_data {
|
|
const int *regs;
|
|
u32 num_opp;
|
|
const struct dvfsrc_opp **opps;
|
|
u32 num_setting;
|
|
const struct dvfsrc_setting_desc *init_setting;
|
|
struct dvfsrc_qos_data *qos_data;
|
|
u32 caps;
|
|
int (*get_target_level)(struct mtk_dvfsrc *dvfsrc);
|
|
int (*get_current_level)(struct mtk_dvfsrc *dvfsrc);
|
|
u32 (*get_vcore_level)(struct mtk_dvfsrc *dvfsrc);
|
|
u32 (*get_vcp_level)(struct mtk_dvfsrc *dvfsrc);
|
|
void (*set_dram_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw);
|
|
void (*set_dram_ext_bw)(struct mtk_dvfsrc *dvfsrc, u64 bw);
|
|
void (*set_opp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
void (*set_dram_hrtbw)(struct mtk_dvfsrc *dvfsrc, u64 bw);
|
|
void (*set_dram_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
void (*set_vcore_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
void (*set_vscp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
void (*set_ext_dram_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
int (*set_force_opp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
int (*wait_for_dram_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
int (*wait_for_vcore_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
int (*wait_for_opp_level)(struct mtk_dvfsrc *dvfsrc, u32 level);
|
|
int (*is_dvfsrc_enabled)(struct mtk_dvfsrc *dvfsrc);
|
|
};
|
|
|
|
struct mtk_dvfsrc {
|
|
struct device *dev;
|
|
struct clk *clk_dvfsrc;
|
|
const struct dvfsrc_soc_data *dvd;
|
|
int dram_type;
|
|
u32 vmode;
|
|
u32 flag;
|
|
int num_domains;
|
|
struct dvfsrc_domain *domains;
|
|
void __iomem *regs;
|
|
struct mutex lock;
|
|
struct mutex sw_req_lock;
|
|
struct notifier_block scpsys_notifier;
|
|
struct regulator *vcore_power;
|
|
bool opp_forced;
|
|
};
|
|
|
|
static DEFINE_MUTEX(pstate_lock);
|
|
static DEFINE_SPINLOCK(force_req_lock);
|
|
|
|
static bool is_dvfsrc_init_complete;
|
|
|
|
static BLOCKING_NOTIFIER_HEAD(dvfsrc_vchk_notifier);
|
|
int register_dvfsrc_vchk_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_register(&dvfsrc_vchk_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(register_dvfsrc_vchk_notifier);
|
|
|
|
int unregister_dvfsrc_vchk_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_unregister(&dvfsrc_vchk_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(unregister_dvfsrc_vchk_notifier);
|
|
|
|
static BLOCKING_NOTIFIER_HEAD(dvfsrc_dump_notifier);
|
|
int register_dvfsrc_dump_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_register(&dvfsrc_dump_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(register_dvfsrc_dump_notifier);
|
|
|
|
int unregister_dvfsrc_dump_notifier(struct notifier_block *nb)
|
|
{
|
|
return blocking_notifier_chain_unregister(&dvfsrc_dump_notifier, nb);
|
|
}
|
|
EXPORT_SYMBOL_GPL(unregister_dvfsrc_dump_notifier);
|
|
|
|
int mtk_dvfsrc_vcore_uv_table(u32 opp)
|
|
{
|
|
if ((!vopp_uv_tlb) || (opp >= num_vopp))
|
|
return 0;
|
|
|
|
return vopp_uv_tlb[num_vopp - opp - 1];
|
|
}
|
|
EXPORT_SYMBOL(mtk_dvfsrc_vcore_uv_table);
|
|
|
|
int mtk_dvfsrc_vcore_opp_count(void)
|
|
{
|
|
return num_vopp;
|
|
}
|
|
EXPORT_SYMBOL(mtk_dvfsrc_vcore_opp_count);
|
|
|
|
static void mtk_dvfsrc_setup_vopp_table(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
int i;
|
|
struct arm_smccc_res ares;
|
|
u32 num_opp = dvfsrc->dvd->num_opp;
|
|
|
|
num_vopp =
|
|
dvfsrc->dvd->opps[dvfsrc->dram_type][num_opp - 1].vcore_opp + 1;
|
|
vopp_uv_tlb = kcalloc(num_vopp, sizeof(u32), GFP_KERNEL);
|
|
|
|
if (!vopp_uv_tlb)
|
|
return;
|
|
|
|
for (i = 0; i < num_vopp; i++) {
|
|
arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL,
|
|
MTK_SIP_VCOREFS_VCORE_UV,
|
|
i, 0, 0, 0, 0, 0,
|
|
&ares);
|
|
|
|
if (!ares.a0)
|
|
vopp_uv_tlb[i] = ares.a1;
|
|
else {
|
|
kfree(vopp_uv_tlb);
|
|
vopp_uv_tlb = NULL;
|
|
break;
|
|
}
|
|
}
|
|
for (i = 0; i < num_vopp; i++)
|
|
dev_info(dvfsrc->dev, "dvfsrc vopp[%d] = %d\n",
|
|
i, mtk_dvfsrc_vcore_uv_table(i));
|
|
|
|
}
|
|
|
|
static void mtk_dvfsrc_dump_info(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
blocking_notifier_call_chain(&dvfsrc_dump_notifier,
|
|
0, NULL);
|
|
}
|
|
|
|
static void mtk_dvfsrc_vcore_check(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
int ret;
|
|
|
|
ret = blocking_notifier_call_chain(&dvfsrc_vchk_notifier,
|
|
level, NULL);
|
|
|
|
if (ret == NOTIFY_BAD) {
|
|
dev_info(dvfsrc->dev,
|
|
"DVFS FAIL= %d, 0x%08x 0x%08x\n",
|
|
level,
|
|
dvfsrc->dvd->get_current_level(dvfsrc),
|
|
dvfsrc->dvd->get_target_level(dvfsrc));
|
|
mtk_dvfsrc_dump_info(dvfsrc);
|
|
#ifdef CONFIG_MTK_AEE_FEATURE
|
|
aee_kernel_warning("DVFSRC", "VCORE fail");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static u32 dvfsrc_read(struct mtk_dvfsrc *dvfs, u32 offset)
|
|
{
|
|
return readl(dvfs->regs + dvfs->dvd->regs[offset]);
|
|
}
|
|
|
|
static void dvfsrc_write(struct mtk_dvfsrc *dvfs, u32 offset, u32 val)
|
|
{
|
|
writel(val, dvfs->regs + dvfs->dvd->regs[offset]);
|
|
}
|
|
|
|
#define dvfsrc_rmw(dvfs, offset, val, mask, shift) \
|
|
dvfsrc_write(dvfs, offset, \
|
|
(dvfsrc_read(dvfs, offset) & ~(mask << shift)) | (val << shift))
|
|
|
|
enum dvfsrc_regs {
|
|
DVFSRC_SW_REQ,
|
|
DVFSRC_EXT_SW_REQ,
|
|
DVFSRC_LEVEL,
|
|
DVFSRC_SW_BW,
|
|
DVFSRC_SW_EXT_BW,
|
|
DVFSRC_SW_HRT_BW,
|
|
DVFSRC_TARGET_LEVEL,
|
|
DVFSRC_VCORE_REQUEST,
|
|
DVFSRC_BASIC_CONTROL,
|
|
DVFSRC_TARGET_FORCE,
|
|
};
|
|
|
|
static const int mt8183_regs[] = {
|
|
[DVFSRC_SW_REQ] = 0x4,
|
|
[DVFSRC_LEVEL] = 0xDC,
|
|
[DVFSRC_SW_BW] = 0x160,
|
|
};
|
|
|
|
static const int mt6779_regs[] = {
|
|
[DVFSRC_SW_REQ] = 0xC,
|
|
[DVFSRC_LEVEL] = 0xD44,
|
|
[DVFSRC_SW_BW] = 0x26C,
|
|
[DVFSRC_SW_EXT_BW] = 0x260,
|
|
[DVFSRC_SW_HRT_BW] = 0x290,
|
|
[DVFSRC_TARGET_LEVEL] = 0xD48,
|
|
[DVFSRC_VCORE_REQUEST] = 0x6C,
|
|
[DVFSRC_BASIC_CONTROL] = 0x0,
|
|
[DVFSRC_TARGET_FORCE] = 0xD70,
|
|
};
|
|
|
|
static const int mt6761_regs[] = {
|
|
[DVFSRC_SW_REQ] = 0x4,
|
|
[DVFSRC_EXT_SW_REQ] = 0x8,
|
|
[DVFSRC_LEVEL] = 0xDC,
|
|
[DVFSRC_SW_BW] = 0x16C,
|
|
[DVFSRC_SW_EXT_BW] = 0x160,
|
|
[DVFSRC_VCORE_REQUEST] = 0x48,
|
|
[DVFSRC_BASIC_CONTROL] = 0x0,
|
|
[DVFSRC_TARGET_FORCE] = 0x300,
|
|
};
|
|
|
|
static const struct dvfsrc_opp *get_current_opp(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
int level;
|
|
|
|
level = dvfsrc->dvd->get_current_level(dvfsrc);
|
|
return &dvfsrc->dvd->opps[dvfsrc->dram_type][level];
|
|
}
|
|
|
|
static bool dvfsrc_is_idle(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
if (!dvfsrc->dvd->get_target_level)
|
|
return true;
|
|
|
|
return dvfsrc->dvd->get_target_level(dvfsrc);
|
|
}
|
|
|
|
static int dvfsrc_wait_for_idle(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
int state;
|
|
|
|
return readx_poll_timeout_atomic(dvfsrc_is_idle, dvfsrc,
|
|
state, state == DVFSRC_IDLE,
|
|
STARTUP_TIME, POLL_TIMEOUT);
|
|
}
|
|
|
|
static int dvfsrc_wait_for_opp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
const struct dvfsrc_opp *target, *curr;
|
|
|
|
target = &dvfsrc->dvd->opps[dvfsrc->dram_type][level];
|
|
|
|
return readx_poll_timeout_atomic(get_current_opp, dvfsrc, curr,
|
|
curr->dram_opp >= target->dram_opp,
|
|
STARTUP_TIME, POLL_TIMEOUT);
|
|
}
|
|
|
|
static int dvfsrc_wait_for_vcore_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
const struct dvfsrc_opp *curr;
|
|
|
|
return readx_poll_timeout_atomic(get_current_opp, dvfsrc, curr,
|
|
curr->vcore_opp >= level,
|
|
STARTUP_TIME, POLL_TIMEOUT);
|
|
}
|
|
|
|
static int dvfsrc_wait_for_dram_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
const struct dvfsrc_opp *curr;
|
|
|
|
return readx_poll_timeout_atomic(get_current_opp, dvfsrc, curr,
|
|
curr->dram_opp >= level,
|
|
STARTUP_TIME, POLL_TIMEOUT);
|
|
}
|
|
|
|
static int mt6779_dvfsrc_enabled(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return dvfsrc_read(dvfsrc, DVFSRC_BASIC_CONTROL) & 0x1;
|
|
}
|
|
|
|
static int mt6779_get_target_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return dvfsrc_read(dvfsrc, DVFSRC_TARGET_LEVEL);
|
|
}
|
|
|
|
static int mt6779_get_current_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
u32 curr_level;
|
|
|
|
curr_level = ffs(dvfsrc_read(dvfsrc, DVFSRC_LEVEL));
|
|
if (curr_level > dvfsrc->dvd->num_opp)
|
|
curr_level = 0;
|
|
else
|
|
curr_level = dvfsrc->dvd->num_opp - curr_level;
|
|
|
|
return curr_level;
|
|
}
|
|
|
|
static u32 mt6779_get_vcore_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return (dvfsrc_read(dvfsrc, DVFSRC_SW_REQ) >> 4) & 0x7;
|
|
}
|
|
|
|
static u32 mt6779_get_vcp_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return (dvfsrc_read(dvfsrc, DVFSRC_VCORE_REQUEST) >> 12) & 0x7;
|
|
}
|
|
|
|
static void mt6779_set_dram_bw(struct mtk_dvfsrc *dvfsrc, u64 bw)
|
|
{
|
|
bw = div_u64(kBps_to_MBps(bw), 100);
|
|
bw = (bw < 0xFF) ? bw : 0xff;
|
|
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_BW, bw);
|
|
}
|
|
|
|
static void mt6779_set_dram_ext_bw(struct mtk_dvfsrc *dvfsrc, u64 bw)
|
|
{
|
|
bw = div_u64(kBps_to_MBps(bw), 100);
|
|
bw = (bw < 0xFF) ? bw : 0xff;
|
|
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_EXT_BW, bw);
|
|
}
|
|
|
|
/* bw unit 30MBps */
|
|
static void mt6779_set_dram_hrtbw(struct mtk_dvfsrc *dvfsrc, u64 bw)
|
|
{
|
|
bw = div_u64((kBps_to_MBps(bw) + 29), 30);
|
|
if (bw > 0x3FF)
|
|
bw = 0x3FF;
|
|
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_HRT_BW, bw);
|
|
}
|
|
|
|
static void mt6779_set_vcore_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
mutex_lock(&dvfsrc->sw_req_lock);
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_SW_REQ, level, 0x7, 4);
|
|
mutex_unlock(&dvfsrc->sw_req_lock);
|
|
}
|
|
|
|
static void mt6779_set_vscp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_VCORE_REQUEST, level, 0x7, 12);
|
|
}
|
|
|
|
static void mt6779_set_dram_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
mutex_lock(&dvfsrc->sw_req_lock);
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_SW_REQ, level, 0x7, 12);
|
|
mutex_unlock(&dvfsrc->sw_req_lock);
|
|
}
|
|
|
|
static void mt6779_set_opp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
const struct dvfsrc_opp *opp;
|
|
|
|
opp = &dvfsrc->dvd->opps[dvfsrc->dram_type][level];
|
|
|
|
mt6779_set_dram_level(dvfsrc, opp->dram_opp);
|
|
}
|
|
|
|
static int mt6779_set_force_opp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
unsigned long flags;
|
|
int val;
|
|
int ret = 0;
|
|
|
|
if (level > dvfsrc->dvd->num_opp - 1) {
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_BASIC_CONTROL, 0, 0x1, 15);
|
|
dvfsrc_write(dvfsrc, DVFSRC_TARGET_FORCE, 0);
|
|
dvfsrc->opp_forced = false;
|
|
return 0;
|
|
}
|
|
|
|
dvfsrc->opp_forced = true;
|
|
spin_lock_irqsave(&force_req_lock, flags);
|
|
dvfsrc_write(dvfsrc, DVFSRC_TARGET_FORCE, 1 << level);
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_BASIC_CONTROL, 1, 0x1, 15);
|
|
ret = readl_poll_timeout_atomic(
|
|
dvfsrc->regs + dvfsrc->dvd->regs[DVFSRC_LEVEL],
|
|
val, val == (1 << level), STARTUP_TIME, POLL_TIMEOUT);
|
|
|
|
if (ret < 0) {
|
|
dev_info(dvfsrc->dev,
|
|
"[%s] wait idle, level: %d, last: %d -> %x\n",
|
|
__func__, level,
|
|
dvfsrc->dvd->get_current_level(dvfsrc),
|
|
dvfsrc->dvd->get_target_level(dvfsrc));
|
|
mtk_dvfsrc_dump_info(dvfsrc);
|
|
#ifdef CONFIG_MTK_AEE_FEATURE
|
|
aee_kernel_warning("DVFSRC", "FORCE OPP fail");
|
|
#endif
|
|
}
|
|
dvfsrc_write(dvfsrc, DVFSRC_TARGET_FORCE, 0);
|
|
spin_unlock_irqrestore(&force_req_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mt6761_dvfsrc_enabled(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return dvfsrc_read(dvfsrc, DVFSRC_BASIC_CONTROL) & 0x1;
|
|
}
|
|
|
|
static int mt6761_get_target_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return DVFSRC_GET_TARGET_LEVEL(dvfsrc_read(dvfsrc, DVFSRC_LEVEL));
|
|
}
|
|
|
|
static int mt6761_get_current_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
u32 curr_level;
|
|
|
|
curr_level = dvfsrc_read(dvfsrc, DVFSRC_LEVEL);
|
|
curr_level = ffs(DVFSRC_GET_CURRENT_LEVEL(curr_level));
|
|
|
|
if ((curr_level > 0) && (curr_level <= dvfsrc->dvd->num_opp))
|
|
return curr_level - 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static u32 mt6761_get_vcore_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return (dvfsrc_read(dvfsrc, DVFSRC_SW_REQ) >> 2) & 0x3;
|
|
}
|
|
|
|
static u32 mt6761_get_vcp_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return (dvfsrc_read(dvfsrc, DVFSRC_VCORE_REQUEST) >> 30) & 0x3;
|
|
}
|
|
|
|
static void mt6761_set_dram_bw(struct mtk_dvfsrc *dvfsrc, u64 bw)
|
|
{
|
|
bw = div_u64(kBps_to_MBps(bw), 100);
|
|
bw = (bw < 0xFF) ? bw : 0xff;
|
|
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_BW, bw);
|
|
}
|
|
|
|
static void mt6761_set_dram_ext_bw(struct mtk_dvfsrc *dvfsrc, u64 bw)
|
|
{
|
|
bw = div_u64(kBps_to_MBps(bw), 100);
|
|
bw = (bw < 0xFF) ? bw : 0xff;
|
|
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_EXT_BW, bw);
|
|
}
|
|
|
|
static void mt6761_set_vcore_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
mutex_lock(&dvfsrc->sw_req_lock);
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_SW_REQ, level, 0x3, 2);
|
|
mutex_unlock(&dvfsrc->sw_req_lock);
|
|
}
|
|
|
|
static void mt6761_set_vscp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_VCORE_REQUEST, level, 0x3, 30);
|
|
}
|
|
|
|
static void mt6761_set_dram_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
mutex_lock(&dvfsrc->sw_req_lock);
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_SW_REQ, level, 0x3, 0);
|
|
mutex_unlock(&dvfsrc->sw_req_lock);
|
|
}
|
|
|
|
static void mt6761_set_ext_dram_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
const struct dvfsrc_soc_data *dvd = dvfsrc->dvd;
|
|
int max_dram_opp;
|
|
u32 opp = dvfsrc->dvd->num_opp;
|
|
u32 dram_type = dvfsrc->dram_type;
|
|
|
|
max_dram_opp =
|
|
dvd->opps[dram_type][opp - 1].dram_opp;
|
|
|
|
if (level > max_dram_opp)
|
|
level = 0;
|
|
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_EXT_SW_REQ, level, 0x3, 0);
|
|
}
|
|
|
|
static void mt6761_set_opp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
const struct dvfsrc_opp *opp;
|
|
|
|
opp = &dvfsrc->dvd->opps[dvfsrc->dram_type][level];
|
|
|
|
mt6761_set_dram_level(dvfsrc, opp->dram_opp);
|
|
}
|
|
|
|
static int mt6761_set_force_opp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
unsigned long flags;
|
|
int val;
|
|
int ret = 0;
|
|
|
|
if (level > dvfsrc->dvd->num_opp - 1) {
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_BASIC_CONTROL, 0, 0x1, 15);
|
|
dvfsrc_write(dvfsrc, DVFSRC_TARGET_FORCE, 0);
|
|
dvfsrc->opp_forced = false;
|
|
return 0;
|
|
}
|
|
|
|
dvfsrc->opp_forced = true;
|
|
spin_lock_irqsave(&force_req_lock, flags);
|
|
level = dvfsrc->dvd->num_opp - 1 - level;
|
|
dvfsrc_write(dvfsrc, DVFSRC_TARGET_FORCE, 1 << level);
|
|
dvfsrc_rmw(dvfsrc, DVFSRC_BASIC_CONTROL, 1, 0x1, 15);
|
|
ret = readl_poll_timeout_atomic(
|
|
dvfsrc->regs + dvfsrc->dvd->regs[DVFSRC_LEVEL],
|
|
val, DVFSRC_GET_CURRENT_LEVEL(val) == (1 << level),
|
|
STARTUP_TIME, POLL_TIMEOUT);
|
|
|
|
if (ret < 0) {
|
|
dev_info(dvfsrc->dev,
|
|
"[%s] wait idle, level: %d, last: %d -> %x\n",
|
|
__func__, level,
|
|
dvfsrc->dvd->get_current_level(dvfsrc),
|
|
dvfsrc->dvd->get_target_level(dvfsrc));
|
|
mtk_dvfsrc_dump_info(dvfsrc);
|
|
#ifdef CONFIG_MTK_AEE_FEATURE
|
|
aee_kernel_warning("DVFSRC", "FORCE OPP fail");
|
|
#endif
|
|
}
|
|
dvfsrc_write(dvfsrc, DVFSRC_TARGET_FORCE, 0);
|
|
spin_unlock_irqrestore(&force_req_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int mt8183_get_target_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return DVFSRC_GET_TARGET_LEVEL(dvfsrc_read(dvfsrc, DVFSRC_LEVEL));
|
|
}
|
|
|
|
static int mt8183_get_current_level(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
return ffs(DVFSRC_GET_CURRENT_LEVEL(dvfsrc_read(dvfsrc, DVFSRC_LEVEL)));
|
|
}
|
|
|
|
static void mt8183_set_dram_bw(struct mtk_dvfsrc *dvfsrc, u64 bw)
|
|
{
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_BW, div_u64(kBps_to_MBps(bw), 100));
|
|
}
|
|
|
|
static void mt8183_set_opp_level(struct mtk_dvfsrc *dvfsrc, u32 level)
|
|
{
|
|
int vcore_opp, dram_opp;
|
|
const struct dvfsrc_opp *opp;
|
|
|
|
/* translate pstate to dvfsrc level, and set it to DVFSRC HW */
|
|
opp = &dvfsrc->dvd->opps[dvfsrc->dram_type][level];
|
|
vcore_opp = opp->vcore_opp;
|
|
dram_opp = opp->dram_opp;
|
|
|
|
dev_dbg(dvfsrc->dev, "vcore_opp: %d, dram_opp: %d\n",
|
|
vcore_opp, dram_opp);
|
|
dvfsrc_write(dvfsrc, DVFSRC_SW_REQ, dram_opp | vcore_opp << 2);
|
|
}
|
|
|
|
static int dvfsrc_pm_qos_bw_notify(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
struct dvfsrc_qos_data *qos;
|
|
|
|
qos = container_of(b, struct dvfsrc_qos_data, pm_qos_bw_notify);
|
|
mtk_dvfsrc_send_request(qos->dev,
|
|
MTK_DVFSRC_CMD_BW_REQUEST, MBps_to_kBps(l));
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int dvfsrc_pm_qos_ext_bw_notify(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
struct dvfsrc_qos_data *qos;
|
|
|
|
qos = container_of(b, struct dvfsrc_qos_data, pm_qos_ext_bw_notify);
|
|
mtk_dvfsrc_send_request(qos->dev,
|
|
MTK_DVFSRC_CMD_EXT_BW_REQUEST, MBps_to_kBps(l));
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int dvfsrc_pm_qos_hrtbw_notify(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
struct dvfsrc_qos_data *qos;
|
|
|
|
qos = container_of(b, struct dvfsrc_qos_data, pm_qos_hrtbw_notify);
|
|
mtk_dvfsrc_send_request(qos->dev,
|
|
MTK_DVFSRC_CMD_HRTBW_REQUEST, MBps_to_kBps(l));
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
static int dvfsrc_pm_qos_ddr_notify(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
struct dvfsrc_qos_data *qos;
|
|
int level;
|
|
|
|
qos = container_of(b, struct dvfsrc_qos_data, pm_qos_ddr_notify);
|
|
if (l > qos->max_dram_opp || l < 0)
|
|
level = 0;
|
|
else
|
|
level = qos->max_dram_opp - l;
|
|
|
|
mtk_dvfsrc_send_request(qos->dev,
|
|
MTK_DVFSRC_CMD_DRAM_REQUEST, level);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
static int dvfsrc_pm_qos_vcore_notify(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
struct dvfsrc_qos_data *qos;
|
|
int level;
|
|
|
|
qos = container_of(b, struct dvfsrc_qos_data, pm_qos_vcore_notify);
|
|
|
|
if (l > qos->max_vcore_opp || l < 0)
|
|
level = 0;
|
|
else
|
|
level = qos->max_vcore_opp - l;
|
|
|
|
mtk_dvfsrc_send_request(qos->dev,
|
|
MTK_DVFSRC_CMD_VCORE_REQUEST, level);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
static int dvfsrc_pm_qos_scp_notify(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
struct dvfsrc_qos_data *qos;
|
|
int level;
|
|
|
|
qos = container_of(b, struct dvfsrc_qos_data, pm_qos_scp_notify);
|
|
if (l > qos->max_vcore_opp || l < 0)
|
|
level = 0;
|
|
else
|
|
level = l;
|
|
|
|
mtk_dvfsrc_send_request(qos->dev,
|
|
MTK_DVFSRC_CMD_VSCP_REQUEST, level);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static void dvfsrc_qos_init(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
u32 opp = dvfsrc->dvd->num_opp;
|
|
u32 dram_type = dvfsrc->dram_type;
|
|
|
|
const struct dvfsrc_soc_data *dvd = dvfsrc->dvd;
|
|
struct dvfsrc_qos_data *qos = dvd->qos_data;
|
|
|
|
qos->dev = dvfsrc->dev;
|
|
|
|
qos->max_vcore_opp =
|
|
dvd->opps[dram_type][opp - 1].vcore_opp;
|
|
|
|
qos->max_dram_opp =
|
|
dvd->opps[dram_type][opp - 1].dram_opp;
|
|
|
|
mtk_pm_qos_add_notifier(MTK_PM_QOS_MEMORY_BANDWIDTH,
|
|
&qos->pm_qos_bw_notify);
|
|
mtk_pm_qos_add_notifier(MTK_PM_QOS_MEMORY_EXT_BANDWIDTH,
|
|
&qos->pm_qos_ext_bw_notify);
|
|
mtk_pm_qos_add_notifier(MTK_PM_QOS_HRT_BANDWIDTH,
|
|
&qos->pm_qos_hrtbw_notify);
|
|
mtk_pm_qos_add_notifier(MTK_PM_QOS_DDR_OPP,
|
|
&qos->pm_qos_ddr_notify);
|
|
mtk_pm_qos_add_notifier(MTK_PM_QOS_VCORE_OPP,
|
|
&qos->pm_qos_vcore_notify);
|
|
mtk_pm_qos_add_notifier(MTK_PM_QOS_SCP_VCORE_REQUEST,
|
|
&qos->pm_qos_scp_notify);
|
|
}
|
|
|
|
static struct dvfsrc_qos_data qos_data_dvfsrc = {
|
|
.pm_qos_init = dvfsrc_qos_init,
|
|
.pm_qos_bw_notify.notifier_call = dvfsrc_pm_qos_bw_notify,
|
|
.pm_qos_ext_bw_notify.notifier_call = dvfsrc_pm_qos_ext_bw_notify,
|
|
.pm_qos_hrtbw_notify.notifier_call = dvfsrc_pm_qos_hrtbw_notify,
|
|
.pm_qos_ddr_notify.notifier_call = dvfsrc_pm_qos_ddr_notify,
|
|
.pm_qos_vcore_notify.notifier_call = dvfsrc_pm_qos_vcore_notify,
|
|
.pm_qos_scp_notify.notifier_call = dvfsrc_pm_qos_scp_notify,
|
|
};
|
|
|
|
void mtk_dvfsrc_send_request(const struct device *dev, u32 cmd, u64 data)
|
|
{
|
|
struct mtk_dvfsrc *dvfsrc = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
if (cmd == MTK_DVFSRC_CMD_FORCE_OPP_REQUEST) {
|
|
if (dvfsrc->dvd->set_force_opp_level)
|
|
dvfsrc->dvd->set_force_opp_level(dvfsrc, data);
|
|
return;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case MTK_DVFSRC_CMD_BW_REQUEST:
|
|
dvfsrc->dvd->set_dram_bw(dvfsrc, data);
|
|
goto out;
|
|
case MTK_DVFSRC_CMD_EXT_BW_REQUEST:
|
|
if (dvfsrc->dvd->set_dram_ext_bw)
|
|
dvfsrc->dvd->set_dram_ext_bw(dvfsrc, data);
|
|
goto out;
|
|
case MTK_DVFSRC_CMD_OPP_REQUEST:
|
|
dvfsrc->dvd->set_opp_level(dvfsrc, data);
|
|
break;
|
|
case MTK_DVFSRC_CMD_DRAM_REQUEST:
|
|
dvfsrc->dvd->set_dram_level(dvfsrc, data);
|
|
break;
|
|
case MTK_DVFSRC_CMD_VCORE_REQUEST:
|
|
dvfsrc->dvd->set_vcore_level(dvfsrc, data);
|
|
break;
|
|
case MTK_DVFSRC_CMD_HRTBW_REQUEST:
|
|
if (dvfsrc->dvd->set_dram_hrtbw)
|
|
dvfsrc->dvd->set_dram_hrtbw(dvfsrc, data);
|
|
else
|
|
goto out;
|
|
break;
|
|
case MTK_DVFSRC_CMD_VSCP_REQUEST:
|
|
dvfsrc->dvd->set_vscp_level(dvfsrc, data);
|
|
break;
|
|
case MTK_DVFSRC_CMD_EXT_DRAM_REQUEST:
|
|
if (dvfsrc->dvd->set_ext_dram_level)
|
|
dvfsrc->dvd->set_ext_dram_level(dvfsrc, data);
|
|
goto out;
|
|
default:
|
|
dev_err(dvfsrc->dev, "unknown command: %d\n", cmd);
|
|
goto out;
|
|
break;
|
|
}
|
|
|
|
if (dvfsrc->opp_forced)
|
|
goto out;
|
|
|
|
if (dvfsrc->dvd->is_dvfsrc_enabled
|
|
&& !dvfsrc->dvd->is_dvfsrc_enabled(dvfsrc))
|
|
goto out;
|
|
|
|
udelay(STARTUP_TIME);
|
|
dvfsrc_wait_for_idle(dvfsrc);
|
|
udelay(STARTUP_TIME);
|
|
|
|
switch (cmd) {
|
|
case MTK_DVFSRC_CMD_OPP_REQUEST:
|
|
if (dvfsrc->dvd->wait_for_opp_level)
|
|
ret = dvfsrc->dvd->wait_for_opp_level(dvfsrc, data);
|
|
break;
|
|
case MTK_DVFSRC_CMD_DRAM_REQUEST:
|
|
if (dvfsrc->dvd->wait_for_dram_level)
|
|
ret = dvfsrc->dvd->wait_for_dram_level(dvfsrc, data);
|
|
break;
|
|
case MTK_DVFSRC_CMD_VCORE_REQUEST:
|
|
case MTK_DVFSRC_CMD_VSCP_REQUEST:
|
|
if (dvfsrc->dvd->wait_for_vcore_level) {
|
|
ret = dvfsrc->dvd->wait_for_vcore_level(dvfsrc, data);
|
|
if (HAS_CAP(dvfsrc->dvd->caps, DVFSRC_CAP_V_CHECKER))
|
|
mtk_dvfsrc_vcore_check(dvfsrc, data);
|
|
}
|
|
break;
|
|
}
|
|
out:
|
|
if (ret) {
|
|
dev_warn(dvfsrc->dev,
|
|
"[%s][%d] wait idle, level: %llu, last: %d -> %x\n",
|
|
__func__, cmd, data,
|
|
dvfsrc->dvd->get_current_level(dvfsrc),
|
|
dvfsrc->dvd->get_target_level(dvfsrc));
|
|
mtk_dvfsrc_dump_info(dvfsrc);
|
|
#ifdef CONFIG_MTK_AEE_FEATURE
|
|
aee_kernel_warning("DVFSRC", "LEVEL CHANGE fail");
|
|
#endif
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(mtk_dvfsrc_send_request);
|
|
|
|
int mtk_dvfsrc_query_info(const struct device *dev, u32 cmd, int *data)
|
|
{
|
|
struct mtk_dvfsrc *dvfsrc = dev_get_drvdata(dev);
|
|
|
|
switch (cmd) {
|
|
case MTK_DVFSRC_CMD_VCORE_QUERY:
|
|
*data = dvfsrc->dvd->get_vcore_level(dvfsrc);
|
|
break;
|
|
case MTK_DVFSRC_CMD_VCP_QUERY:
|
|
*data = dvfsrc->dvd->get_vcp_level(dvfsrc);
|
|
break;
|
|
case MTK_DVFSRC_CMD_CURR_LEVEL_QUERY:
|
|
*data = dvfsrc->dvd->get_current_level(dvfsrc);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mtk_dvfsrc_query_info);
|
|
|
|
static int dvfsrc_set_performance(struct notifier_block *b,
|
|
unsigned long l, void *v)
|
|
{
|
|
int i;
|
|
bool match = false;
|
|
u32 highest;
|
|
struct mtk_dvfsrc *dvfsrc;
|
|
struct scp_event_data *sc = v;
|
|
struct dvfsrc_domain *d;
|
|
|
|
if (sc->event_type != MTK_SCPSYS_PSTATE)
|
|
return 0;
|
|
|
|
dvfsrc = container_of(b, struct mtk_dvfsrc, scpsys_notifier);
|
|
|
|
d = dvfsrc->domains;
|
|
|
|
if (l > dvfsrc->dvd->num_opp) {
|
|
dev_err(dvfsrc->dev, "pstate out of range = %ld\n", l);
|
|
goto out;
|
|
}
|
|
|
|
mutex_lock(&pstate_lock);
|
|
for (i = 0, highest = 0; i < dvfsrc->num_domains; i++, d++) {
|
|
if (sc->domain_id == d->id) {
|
|
d->state = l;
|
|
match = true;
|
|
}
|
|
highest = max(highest, d->state);
|
|
}
|
|
|
|
if (!match)
|
|
goto out;
|
|
|
|
if (highest != 0)
|
|
highest = highest - 1;
|
|
|
|
mtk_dvfsrc_send_request(dvfsrc->dev, MTK_DVFSRC_CMD_OPP_REQUEST,
|
|
highest);
|
|
|
|
out:
|
|
mutex_unlock(&pstate_lock);
|
|
return 0;
|
|
}
|
|
|
|
static void pstate_notifier_register(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
dvfsrc->scpsys_notifier.notifier_call = dvfsrc_set_performance;
|
|
register_scpsys_notifier(&dvfsrc->scpsys_notifier);
|
|
}
|
|
|
|
static void dvfsrc_init_settings(struct mtk_dvfsrc *dvfsrc)
|
|
{
|
|
int i;
|
|
const struct dvfsrc_setting_desc *desc;
|
|
const struct dvfsrc_setting *setting;
|
|
|
|
desc = &dvfsrc->dvd->init_setting[dvfsrc->dram_type];
|
|
setting = desc->setting;
|
|
for (i = 0; i < desc->size; i++)
|
|
writel(setting[i].val, dvfsrc->regs + setting[i].offset);
|
|
}
|
|
|
|
static int mtk_dvfsrc_probe(struct platform_device *pdev)
|
|
{
|
|
struct arm_smccc_res ares;
|
|
struct resource *res;
|
|
struct mtk_dvfsrc *dvfsrc;
|
|
struct device_node *node = pdev->dev.of_node;
|
|
int i;
|
|
int ret;
|
|
|
|
dvfsrc = devm_kzalloc(&pdev->dev, sizeof(*dvfsrc), GFP_KERNEL);
|
|
if (!dvfsrc)
|
|
return -ENOMEM;
|
|
|
|
dvfsrc->dvd = of_device_get_match_data(&pdev->dev);
|
|
dvfsrc->dev = &pdev->dev;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
dvfsrc->regs = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(dvfsrc->regs))
|
|
return PTR_ERR(dvfsrc->regs);
|
|
|
|
dvfsrc->num_domains = of_count_phandle_with_args(node,
|
|
"perf-domains", NULL);
|
|
|
|
if (dvfsrc->num_domains > 0) {
|
|
dvfsrc->domains = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct dvfsrc_domain) * dvfsrc->num_domains,
|
|
GFP_KERNEL);
|
|
|
|
if (!dvfsrc->domains)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < dvfsrc->num_domains; i++) {
|
|
ret = of_property_read_u32_index(node, "perf-domains",
|
|
i, &dvfsrc->domains[i].id);
|
|
if (ret)
|
|
dev_info(dvfsrc->dev,
|
|
"Invalid favor domain idx = %d\n", i);
|
|
}
|
|
} else
|
|
dvfsrc->num_domains = 0;
|
|
|
|
if (HAS_CAP(dvfsrc->dvd->caps, DVFSRC_CAP_CLK_INIT)) {
|
|
dvfsrc->clk_dvfsrc = devm_clk_get(dvfsrc->dev, "dvfsrc");
|
|
if (IS_ERR(dvfsrc->clk_dvfsrc)) {
|
|
dev_err(dvfsrc->dev, "failed to get clock: %ld\n",
|
|
PTR_ERR(dvfsrc->clk_dvfsrc));
|
|
return PTR_ERR(dvfsrc->clk_dvfsrc);
|
|
}
|
|
|
|
ret = clk_prepare_enable(dvfsrc->clk_dvfsrc);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
of_property_read_u32(node, "dvfsrc,mode", &dvfsrc->flag);
|
|
of_property_read_u32(node, "dvfsrc_flag", &dvfsrc->flag);
|
|
of_property_read_u32(node, "dvfsrc_vmode", &dvfsrc->vmode);
|
|
|
|
|
|
mutex_init(&dvfsrc->lock);
|
|
mutex_init(&dvfsrc->sw_req_lock);
|
|
|
|
arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL, MTK_SIP_GET_SPM_INFO,
|
|
0, 0, 0, 0, 0, 0,
|
|
&ares);
|
|
|
|
if (!ares.a0) {
|
|
if (ares.a1 < dvfsrc->dvd->num_setting)
|
|
dvfsrc->dram_type = ares.a1;
|
|
else
|
|
return -EINVAL;
|
|
} else
|
|
return -ENODEV;
|
|
|
|
dev_info(dvfsrc->dev, "dram_type: %d\n", dvfsrc->dram_type);
|
|
arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL, MTK_SIP_SPM_DVFSRC_INIT,
|
|
dvfsrc->flag, dvfsrc->vmode, 0, 0, 0, 0,
|
|
&ares);
|
|
|
|
if (!ares.a0)
|
|
dvfsrc_init_settings(dvfsrc);
|
|
else
|
|
dev_info(dvfsrc->dev, "spm init fails: %lx\n", ares.a0);
|
|
|
|
platform_set_drvdata(pdev, dvfsrc);
|
|
pstate_notifier_register(dvfsrc);
|
|
|
|
if (HAS_CAP(dvfsrc->dvd->caps, DVFSRC_CAP_V_OPP_INIT) ||
|
|
HAS_CAP(dvfsrc->dvd->caps, DVFSRC_CAP_V_CHECKER))
|
|
mtk_dvfsrc_setup_vopp_table(dvfsrc);
|
|
|
|
ret = devm_of_platform_populate(&pdev->dev);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "Failed to populate dvfsrc context\n");
|
|
|
|
if (HAS_CAP(dvfsrc->dvd->caps, DVFSRC_CAP_MTKQOS))
|
|
dvfsrc->dvd->qos_data->pm_qos_init(dvfsrc);
|
|
|
|
is_dvfsrc_init_complete = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dvfsrc_opp dvfsrc_opp_mt8183_lp4[] = {
|
|
{0, 0}, {0, 1}, {0, 2}, {1, 2},
|
|
};
|
|
|
|
static const struct dvfsrc_opp dvfsrc_opp_mt8183_lp3[] = {
|
|
{0, 0}, {0, 1}, {1, 1}, {1, 2},
|
|
};
|
|
|
|
static const struct dvfsrc_opp *dvfsrc_opp_mt8183[] = {
|
|
[MT8183_DVFSRC_OPP_LP4] = dvfsrc_opp_mt8183_lp4,
|
|
[MT8183_DVFSRC_OPP_LP4X] = dvfsrc_opp_mt8183_lp3,
|
|
[MT8183_DVFSRC_OPP_LP3] = dvfsrc_opp_mt8183_lp3,
|
|
};
|
|
|
|
|
|
static const struct dvfsrc_soc_data mt8183_data = {
|
|
.opps = dvfsrc_opp_mt8183,
|
|
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt8183_lp4),
|
|
.regs = mt8183_regs,
|
|
.get_target_level = mt8183_get_target_level,
|
|
.get_current_level = mt8183_get_current_level,
|
|
.set_dram_bw = mt8183_set_dram_bw,
|
|
.set_opp_level = mt8183_set_opp_level,
|
|
};
|
|
|
|
static const struct dvfsrc_setting dvfsrc_mt6779_setting_0[] = {
|
|
{0xF8, 0x0000002B}, {0xC8, 0x00000002},
|
|
{0x280, 0x0000407C}, {0x78, 0x21110000},
|
|
{0xA00, 0x00004322}, {0xA08, 0x00000055},
|
|
{0xA10, 0x00322000}, {0xA18, 0x54000000},
|
|
{0xCF4, 0x55000000}, {0xA34, 0x00000019},
|
|
{0xA38, 0x00000026}, {0xA3C, 0x00000033},
|
|
{0xA40, 0x0000004C}, {0xA44, 0x00000066},
|
|
{0xD18, 0x00000077}, {0xD1C, 0x00000077},
|
|
{0xA84, 0x0000011E}, {0xA88, 0x0000D3D3},
|
|
{0xA8C, 0x00200802}, {0xA90, 0x00200800},
|
|
{0xA94, 0x00200002}, {0xA98, 0x00200802},
|
|
{0xA9C, 0x00400802}, {0xAA0, 0x00601404},
|
|
{0xAA4, 0x00902008}, {0xAA8, 0x00E0380E},
|
|
{0xACC, 0x00000000}, {0xAD0, 0x00000000},
|
|
{0xAD4, 0x00034C00}, {0xAAC, 0x0360D836},
|
|
{0xAB0, 0x0360D800}, {0xAB4, 0x03600036},
|
|
{0xAB8, 0x0360D836}, {0xABC, 0x0360D836},
|
|
{0xAC0, 0x0360D836}, {0xAC4, 0x0360D836},
|
|
{0xAC8, 0x0360D836}, {0xAD8, 0x00000000},
|
|
{0xADC, 0x00000000}, {0xAE0, 0x00034C00},
|
|
{0xAF4, 0x070804B0}, {0xAF0, 0x11830B80},
|
|
{0xAEC, 0x18A618A6}, {0xD38, 0x000018A6},
|
|
{0xB00, 0x070704AF}, {0xAFC, 0x11820B7F},
|
|
{0xAF8, 0x18A518A5}, {0xD3C, 0x000018A5},
|
|
{0xAE8, 0x05554322}, {0x308, 0x4C4C0AB0},
|
|
{0xB04, 0x05543210}, {0xD74, 0x03333210},
|
|
{0x100, 0x40225032}, {0x104, 0x20223012},
|
|
{0x108, 0x40211012}, {0x10C, 0x20213011},
|
|
{0x110, 0x30101011}, {0x114, 0x10102000},
|
|
{0x118, 0x00000000}, {0x11C, 0x00000000},
|
|
{0xD6C, 0x00000001}, {0x0, 0x3599404B},
|
|
{0x0, 0x3599014B}, {0xD6C, 0x00000000},
|
|
};
|
|
|
|
static const struct dvfsrc_setting_desc dvfsrc_setting_mt6779[] = {
|
|
{
|
|
.setting = dvfsrc_mt6779_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6779_setting_0),
|
|
}
|
|
};
|
|
|
|
static const struct dvfsrc_opp dvfsrc_opp_mt6779_lp4[] = {
|
|
{0, 0}, {0, 1}, {0, 2}, {0, 3},
|
|
{1, 1}, {1, 2}, {1, 3}, {1, 4},
|
|
{2, 1}, {2, 2}, {2, 3}, {2, 4}, {2, 5},
|
|
};
|
|
|
|
static const struct dvfsrc_opp *dvfsrc_opp_mt6779[] = {
|
|
[0] = dvfsrc_opp_mt6779_lp4,
|
|
};
|
|
|
|
static const struct dvfsrc_soc_data mt6779_data = {
|
|
.opps = dvfsrc_opp_mt6779,
|
|
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt6779_lp4),
|
|
.init_setting = dvfsrc_setting_mt6779,
|
|
.num_setting = ARRAY_SIZE(dvfsrc_setting_mt6779),
|
|
.regs = mt6779_regs,
|
|
.caps = DVFSRC_CAP_CLK_INIT |
|
|
DVFSRC_CAP_MTKQOS |
|
|
DVFSRC_CAP_V_OPP_INIT |
|
|
DVFSRC_CAP_V_CHECKER,
|
|
.qos_data = &qos_data_dvfsrc,
|
|
.get_target_level = mt6779_get_target_level,
|
|
.get_current_level = mt6779_get_current_level,
|
|
.get_vcore_level = mt6779_get_vcore_level,
|
|
.get_vcp_level = mt6779_get_vcp_level,
|
|
.wait_for_vcore_level = dvfsrc_wait_for_vcore_level,
|
|
.wait_for_dram_level = dvfsrc_wait_for_dram_level,
|
|
.wait_for_opp_level = dvfsrc_wait_for_opp_level,
|
|
.set_dram_bw = mt6779_set_dram_bw,
|
|
.set_dram_ext_bw = mt6779_set_dram_ext_bw,
|
|
.set_dram_level = mt6779_set_dram_level,
|
|
.set_opp_level = mt6779_set_opp_level,
|
|
.set_dram_hrtbw = mt6779_set_dram_hrtbw,
|
|
.set_vcore_level = mt6779_set_vcore_level,
|
|
.set_vscp_level = mt6779_set_vscp_level,
|
|
.set_force_opp_level = mt6779_set_force_opp_level,
|
|
.is_dvfsrc_enabled = mt6779_dvfsrc_enabled,
|
|
};
|
|
|
|
static const struct dvfsrc_setting dvfsrc_mt6761_setting_0[] = {
|
|
{0xC, 0x00240009}, {0x14, 0x09000000},
|
|
{0x18, 0x003E362C}, {0x24, 0x00000033},
|
|
{0x28, 0x0000004C}, {0x3C, 0x00000007},
|
|
{0x40, 0x00000038}, {0x44, 0x000080C0},
|
|
{0x78, 0x000080C0}, {0x48, 0x000C0000},
|
|
{0x50, 0x00000036}, {0x84, 0x20000000},
|
|
{0xD8, 0x00000014}, {0x9C, 0x00000003},
|
|
{0xE0, 0x00010000}, {0xE4, 0x00020101},
|
|
{0xE8, 0x01020012}, {0xEC, 0x02120112},
|
|
{0xF0, 0x00230013}, {0xF4, 0x01230113},
|
|
{0xF8, 0x02230213}, {0xFC, 0x03230323},
|
|
{0x300, 0x20000000}, {0x604, 0x0000000C},
|
|
{0x180, 0x0000407F}, {0x0, 0x0000407B},
|
|
{0x0, 0x0000017B}, {0x300, 0x00000000},
|
|
};
|
|
|
|
static const struct dvfsrc_setting dvfsrc_mt6761_setting_1[] = {
|
|
{0xC, 0x00240009 }, {0x14, 0x09000000},
|
|
{0x18, 0x00000020 }, {0x24, 0x00000026},
|
|
{0x28, 0x00000033 }, {0x3C, 0x00000007},
|
|
{0x40, 0x00000038 }, {0x44, 0x000080C0},
|
|
{0x50, 0x00000020 }, {0x84, 0x20000000},
|
|
{0xD8, 0x00000014 }, {0x9C, 0x00000003},
|
|
{0xE0, 0x00010000 }, {0xE4, 0x00020101},
|
|
{0xE8, 0x01020012 }, {0xEC, 0x02120112},
|
|
{0xF0, 0x00230013 }, {0xF4, 0x01230113},
|
|
{0xF8, 0x02230213 }, {0xFC, 0x03230323},
|
|
{0x300, 0x20000000 }, {0x604, 0x0000000C},
|
|
{0x180, 0x0000407F }, {0x0, 0x0000407B},
|
|
{0x0, 0x0000017B}, {0x300, 0x00000000},
|
|
};
|
|
|
|
static const struct dvfsrc_setting_desc dvfsrc_setting_mt6761[] = {
|
|
{
|
|
.setting = dvfsrc_mt6761_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6761_setting_0),
|
|
}, {
|
|
.setting = dvfsrc_mt6761_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6761_setting_0),
|
|
}, {
|
|
.setting = dvfsrc_mt6761_setting_1,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6761_setting_1),
|
|
}, {
|
|
.setting = dvfsrc_mt6761_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6761_setting_0),
|
|
}, {
|
|
.setting = dvfsrc_mt6761_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6761_setting_0),
|
|
}
|
|
};
|
|
|
|
static const struct dvfsrc_opp dvfsrc_opp_mt6761_0[] = {
|
|
{0, 0}, {1, 0}, {1, 0}, {2, 0},
|
|
{2, 1}, {2, 0}, {2, 1}, {2, 1},
|
|
{3, 1}, {3, 2}, {3, 1}, {3, 2},
|
|
{3, 1}, {3, 2}, {3, 2}, {3, 2},
|
|
};
|
|
|
|
static const struct dvfsrc_opp *dvfsrc_opps_mt6761[] = {
|
|
[0] = dvfsrc_opp_mt6761_0,
|
|
[1] = dvfsrc_opp_mt6761_0,
|
|
[2] = dvfsrc_opp_mt6761_0,
|
|
[3] = dvfsrc_opp_mt6761_0,
|
|
[4] = dvfsrc_opp_mt6761_0,
|
|
};
|
|
|
|
static const struct dvfsrc_soc_data mt6761_data = {
|
|
.opps = dvfsrc_opps_mt6761,
|
|
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt6761_0),
|
|
.init_setting = dvfsrc_setting_mt6761,
|
|
.num_setting = ARRAY_SIZE(dvfsrc_setting_mt6761),
|
|
.regs = mt6761_regs,
|
|
.caps = DVFSRC_CAP_V_OPP_INIT |
|
|
DVFSRC_CAP_V_CHECKER |
|
|
DVFSRC_CAP_MTKQOS,
|
|
.qos_data = &qos_data_dvfsrc,
|
|
.get_target_level = mt6761_get_target_level,
|
|
.get_current_level = mt6761_get_current_level,
|
|
.get_vcore_level = mt6761_get_vcore_level,
|
|
.get_vcp_level = mt6761_get_vcp_level,
|
|
.wait_for_dram_level = dvfsrc_wait_for_dram_level,
|
|
.wait_for_vcore_level = dvfsrc_wait_for_vcore_level,
|
|
.wait_for_opp_level = dvfsrc_wait_for_opp_level,
|
|
.set_dram_bw = mt6761_set_dram_bw,
|
|
.set_dram_ext_bw = mt6761_set_dram_ext_bw,
|
|
.set_dram_level = mt6761_set_dram_level,
|
|
.set_vcore_level = mt6761_set_vcore_level,
|
|
.set_opp_level = mt6761_set_opp_level,
|
|
.set_vscp_level = mt6761_set_vscp_level,
|
|
.set_ext_dram_level = mt6761_set_ext_dram_level,
|
|
.set_force_opp_level = mt6761_set_force_opp_level,
|
|
.is_dvfsrc_enabled = mt6761_dvfsrc_enabled,
|
|
};
|
|
|
|
static const struct dvfsrc_setting dvfsrc_mt6765_setting_0[] = {
|
|
{0xC, 0x00240009}, {0x14, 0x09000000},
|
|
{0x18, 0x003E362C}, {0x24, 0x00000033},
|
|
{0x28, 0x0000004C}, {0x3C, 0x00000007},
|
|
{0x40, 0x00000038}, {0x44, 0x000080C0},
|
|
{0x78, 0x000080C0}, {0x48, 0x000C0000},
|
|
{0x50, 0x00000036}, {0x84, 0x20000000},
|
|
{0xD8, 0x00000014}, {0x9C, 0x00000003},
|
|
{0xE0, 0x00010000}, {0xE4, 0x00020101},
|
|
{0xE8, 0x01020012}, {0xEC, 0x02120112},
|
|
{0xF0, 0x00230013}, {0xF4, 0x01230113},
|
|
{0xF8, 0x02230213}, {0xFC, 0x03230323},
|
|
{0x300, 0x20000000}, {0x604, 0x0000000C},
|
|
{0x180, 0x0000407F}, {0x0, 0x0000407B},
|
|
{0x0, 0x0000017B}, {0x300, 0x00000000},
|
|
};
|
|
|
|
static const struct dvfsrc_setting dvfsrc_mt6765_setting_1[] = {
|
|
{0xC, 0x00240009 }, {0x14, 0x09000000},
|
|
{0x18, 0x00000020 }, {0x24, 0x00000026},
|
|
{0x28, 0x00000033 }, {0x3C, 0x00000007},
|
|
{0x40, 0x00000038 }, {0x44, 0x000080C0},
|
|
{0x50, 0x00000020 }, {0x84, 0x20000000},
|
|
{0xD8, 0x00000014 }, {0x9C, 0x00000003},
|
|
{0xE0, 0x00010000 }, {0xE4, 0x00020101},
|
|
{0xE8, 0x01020012 }, {0xEC, 0x02120112},
|
|
{0xF0, 0x00230013 }, {0xF4, 0x01230113},
|
|
{0xF8, 0x02230213 }, {0xFC, 0x03230323},
|
|
{0x300, 0x20000000 }, {0x604, 0x0000000C},
|
|
{0x180, 0x0000407F }, {0x0, 0x0000407B},
|
|
{0x0, 0x0000017B}, {0x300, 0x00000000},
|
|
};
|
|
|
|
static const struct dvfsrc_setting_desc dvfsrc_setting_mt6765[] = {
|
|
{
|
|
.setting = dvfsrc_mt6765_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6765_setting_0),
|
|
}, {
|
|
.setting = dvfsrc_mt6765_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6765_setting_0),
|
|
}, {
|
|
.setting = dvfsrc_mt6765_setting_1,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6765_setting_1),
|
|
}, {
|
|
.setting = dvfsrc_mt6765_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6765_setting_0),
|
|
}, {
|
|
.setting = dvfsrc_mt6765_setting_0,
|
|
.size = ARRAY_SIZE(dvfsrc_mt6765_setting_0),
|
|
}
|
|
};
|
|
|
|
static const struct dvfsrc_opp dvfsrc_opp_mt6765_0[] = {
|
|
{0, 0}, {1, 0}, {1, 0}, {2, 0},
|
|
{2, 1}, {2, 0}, {2, 1}, {2, 1},
|
|
{3, 1}, {3, 2}, {3, 1}, {3, 2},
|
|
{3, 1}, {3, 2}, {3, 2}, {3, 2},
|
|
};
|
|
|
|
static const struct dvfsrc_opp *dvfsrc_opps_mt6765[] = {
|
|
[0] = dvfsrc_opp_mt6765_0,
|
|
[1] = dvfsrc_opp_mt6765_0,
|
|
[2] = dvfsrc_opp_mt6765_0,
|
|
[3] = dvfsrc_opp_mt6765_0,
|
|
[4] = dvfsrc_opp_mt6765_0,
|
|
};
|
|
|
|
static const struct dvfsrc_soc_data mt6765_data = {
|
|
.opps = dvfsrc_opps_mt6765,
|
|
.num_opp = ARRAY_SIZE(dvfsrc_opp_mt6765_0),
|
|
.init_setting = dvfsrc_setting_mt6765,
|
|
.num_setting = ARRAY_SIZE(dvfsrc_setting_mt6765),
|
|
.regs = mt6761_regs,
|
|
.caps = DVFSRC_CAP_V_OPP_INIT |
|
|
DVFSRC_CAP_V_CHECKER |
|
|
DVFSRC_CAP_MTKQOS,
|
|
.qos_data = &qos_data_dvfsrc,
|
|
.get_target_level = mt6761_get_target_level,
|
|
.get_current_level = mt6761_get_current_level,
|
|
.get_vcore_level = mt6761_get_vcore_level,
|
|
.get_vcp_level = mt6761_get_vcp_level,
|
|
.wait_for_dram_level = dvfsrc_wait_for_dram_level,
|
|
.wait_for_vcore_level = dvfsrc_wait_for_vcore_level,
|
|
.wait_for_opp_level = dvfsrc_wait_for_opp_level,
|
|
.set_dram_bw = mt6761_set_dram_bw,
|
|
.set_dram_ext_bw = mt6761_set_dram_ext_bw,
|
|
.set_dram_level = mt6761_set_dram_level,
|
|
.set_vcore_level = mt6761_set_vcore_level,
|
|
.set_opp_level = mt6761_set_opp_level,
|
|
.set_vscp_level = mt6761_set_vscp_level,
|
|
.set_ext_dram_level = mt6761_set_ext_dram_level,
|
|
.set_force_opp_level = mt6761_set_force_opp_level,
|
|
.is_dvfsrc_enabled = mt6761_dvfsrc_enabled,
|
|
};
|
|
|
|
static int mtk_dvfsrc_remove(struct platform_device *pdev)
|
|
{
|
|
struct mtk_dvfsrc *dvfsrc = platform_get_drvdata(pdev);
|
|
|
|
if (HAS_CAP(dvfsrc->dvd->caps, DVFSRC_CAP_CLK_INIT))
|
|
clk_disable_unprepare(dvfsrc->clk_dvfsrc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id mtk_dvfsrc_of_match[] = {
|
|
{
|
|
.compatible = "mediatek,mt8183-dvfsrc",
|
|
.data = &mt8183_data,
|
|
}, {
|
|
.compatible = "mediatek,mt6779-dvfsrc",
|
|
.data = &mt6779_data,
|
|
}, {
|
|
.compatible = "mediatek,mt6761-dvfsrc",
|
|
.data = &mt6761_data,
|
|
}, {
|
|
.compatible = "mediatek,mt6765-dvfsrc",
|
|
.data = &mt6765_data,
|
|
}, {
|
|
/* sentinel */
|
|
},
|
|
};
|
|
|
|
static struct platform_driver mtk_dvfsrc_driver = {
|
|
.probe = mtk_dvfsrc_probe,
|
|
.remove = mtk_dvfsrc_remove,
|
|
.driver = {
|
|
.name = "mtk-dvfsrc",
|
|
.of_match_table = of_match_ptr(mtk_dvfsrc_of_match),
|
|
},
|
|
};
|
|
|
|
builtin_platform_driver(mtk_dvfsrc_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MTK DVFSRC driver");
|
|
|