blob: b4a630ccb6adc7c27c0d3f0ddcfe8cf84279ee0b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 MediaTek Inc.
*/
#include <dt-bindings/clock/mmdvfs-clk.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/regulator/consumer.h>
#include <linux/soc/mediatek/mtk_mmdvfs.h>
#include <soc/mediatek/mmdvfs_v3.h>
#include <mt-plat/mrdump.h>
#define CREATE_TRACE_POINTS
#include "mmdvfs_events.h"
#define MMDVFS_DBG_VER1 BIT(0)
#define MMDVFS_DBG_VER3 BIT(1)
#define MMDVFS_DBG(fmt, args...) \
pr_notice("[mmdvfs_dbg][dbg]%s: "fmt"\n", __func__, ##args)
#define MMDVFS_RECORD_OBJ (4)
#define MMDVFS_RECORD_NUM (16)
struct mmdvfs_record {
u64 sec;
u64 nsec;
u8 opp;
};
struct mmdvfs_debug {
struct device *dev;
struct proc_dir_entry *proc;
u32 debug_version;
/* MMDVFS_DBG_VER1 */
struct regulator *reg;
u32 reg_cnt_vol;
u32 force_step0;
u32 release_step0;
spinlock_t lock;
u8 rec_cnt;
struct mmdvfs_record rec[MMDVFS_RECORD_NUM];
/* MMDVFS_DBG_VER3 */
void *base;
u32 use_v3_pwr;
struct clk *aov_clk;
};
static struct mmdvfs_debug *g_mmdvfs;
static bool ftrace_v1_ena, ftrace_v3_ena;
void mtk_mmdvfs_debug_release_step0(void)
{
if (!g_mmdvfs || (g_mmdvfs->use_v3_pwr & (1 << PWR_MMDVFS_VCORE))
|| !g_mmdvfs->release_step0)
return;
if (!IS_ERR_OR_NULL(g_mmdvfs->reg))
regulator_set_voltage(g_mmdvfs->reg, 0, INT_MAX);
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_debug_release_step0);
static int mmdvfs_debug_set_force_step(const char *val,
const struct kernel_param *kp)
{
u8 idx = 0, opp = 0;
int ret;
ret = sscanf(val, "%hhu %hhu", &idx, &opp);
if (ret != 2 || idx >= PWR_MMDVFS_NUM) {
MMDVFS_DBG("failed:%d idx:%hhu opp:%hhu", ret, idx, opp);
return -EINVAL;
}
if (idx == PWR_MMDVFS_VCORE && (!g_mmdvfs->debug_version ||
g_mmdvfs->debug_version & MMDVFS_DBG_VER1))
mmdvfs_set_force_step(opp);
if (g_mmdvfs->debug_version & MMDVFS_DBG_VER3)
mtk_mmdvfs_v3_set_force_step(idx, opp);
return 0;
}
static struct kernel_param_ops mmdvfs_debug_set_force_step_ops = {
.set = mmdvfs_debug_set_force_step,
};
module_param_cb(force_step, &mmdvfs_debug_set_force_step_ops, NULL, 0644);
MODULE_PARM_DESC(force_step, "force mmdvfs to specified step");
static int mmdvfs_debug_set_vote_step(const char *val,
const struct kernel_param *kp)
{
u8 idx = 0, opp = 0;
int ret;
ret = sscanf(val, "%hhu %hhu", &idx, &opp);
if (ret != 2 || idx >= PWR_MMDVFS_NUM) {
MMDVFS_DBG("failed:%d idx:%hhu opp:%hhu", ret, idx, opp);
return -EINVAL;
}
if (idx == PWR_MMDVFS_VCORE && (!g_mmdvfs->debug_version ||
g_mmdvfs->debug_version & MMDVFS_DBG_VER1))
mmdvfs_set_vote_step(opp);
if (g_mmdvfs->debug_version & MMDVFS_DBG_VER3)
mtk_mmdvfs_v3_set_vote_step(idx, opp);
return 0;
}
static struct kernel_param_ops mmdvfs_debug_set_vote_step_ops = {
.set = mmdvfs_debug_set_vote_step,
};
module_param_cb(vote_step, &mmdvfs_debug_set_vote_step_ops, NULL, 0644);
MODULE_PARM_DESC(vote_step, "vote mmdvfs to specified step");
static void mmdvfs_debug_record_opp(const u8 opp)
{
struct mmdvfs_record *rec;
unsigned long flags;
if (!g_mmdvfs)
return;
spin_lock_irqsave(&g_mmdvfs->lock, flags);
rec = &g_mmdvfs->rec[g_mmdvfs->rec_cnt];
rec->sec = sched_clock();
rec->nsec = do_div(rec->sec, 1000000000);
rec->opp = opp;
g_mmdvfs->rec_cnt =
(g_mmdvfs->rec_cnt + 1) % ARRAY_SIZE(g_mmdvfs->rec);
spin_unlock_irqrestore(&g_mmdvfs->lock, flags);
}
static int mmdvfs_debug_opp_show(struct seq_file *file, void *data)
{
unsigned long cnt, mem[MMDVFS_RECORD_OBJ], flags;
s32 i, j;
/* MMDVFS_DBG_VER1 */
seq_puts(file, "VER1: mux controlled by vcore regulator:\n");
spin_lock_irqsave(&g_mmdvfs->lock, flags);
if (g_mmdvfs->rec[g_mmdvfs->rec_cnt].sec)
for (i = g_mmdvfs->rec_cnt; i < ARRAY_SIZE(g_mmdvfs->rec); i++)
seq_printf(file, "[%5llu.%06llu] opp:%u\n",
g_mmdvfs->rec[i].sec, g_mmdvfs->rec[i].nsec,
g_mmdvfs->rec[i].opp);
for (i = 0; i < g_mmdvfs->rec_cnt; i++)
seq_printf(file, "[%5llu.%06llu] opp:%u\n",
g_mmdvfs->rec[i].sec, g_mmdvfs->rec[i].nsec,
g_mmdvfs->rec[i].opp);
spin_unlock_irqrestore(&g_mmdvfs->lock, flags);
/* MMDVFS_DBG_VER3 */
if (!g_mmdvfs->base)
return 0;
seq_puts(file, "VER3: mux controlled by vcp:\n");
cnt = readl(g_mmdvfs->base);
if (readl(g_mmdvfs->base + (((cnt + 1) * MMDVFS_RECORD_OBJ) << 2)))
for (i = cnt; i < MMDVFS_RECORD_NUM; i++) {
for (j = 0; j < ARRAY_SIZE(mem); j++)
mem[j] = readl(g_mmdvfs->base + (((i + 1) *
MMDVFS_RECORD_OBJ + j) << 2));
seq_printf(file, "[%5lu.%3lu] rec:%lu opp:%lu\n",
mem[0], mem[1], mem[2], mem[3]);
}
for (i = 0; i < cnt; i++) {
for (j = 0; j < ARRAY_SIZE(mem); j++)
mem[j] = readl(g_mmdvfs->base + (((i + 1) *
MMDVFS_RECORD_OBJ + j) << 2));
seq_printf(file, "[%5lu.%3lu] rec:%lu opp:%lu\n",
mem[0], mem[1], mem[2], mem[3]);
}
return 0;
}
static int mmdvfs_debug_opp_open(struct inode *inode, struct file *file)
{
return single_open(file, mmdvfs_debug_opp_show, inode->i_private);
}
static const struct proc_ops mmdvfs_debug_opp_fops = {
.proc_open = mmdvfs_debug_opp_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int mmdvfs_v3_debug_thread(void *data)
{
phys_addr_t pa = 0ULL;
int ret = 0, retry = 0;
while (!mtk_is_mmdvfs_init_done()) {
if (++retry > 50) {
MMDVFS_DBG("mmdvfs not ready");
goto err;
}
msleep(2000);
}
if (g_mmdvfs->use_v3_pwr & (1 << PWR_MMDVFS_VCORE))
mtk_mmdvfs_v3_set_vote_step(PWR_MMDVFS_VCORE, g_mmdvfs->force_step0);
if (g_mmdvfs->use_v3_pwr & (1 << PWR_MMDVFS_VMM))
mtk_mmdvfs_v3_set_vote_step(PWR_MMDVFS_VMM, g_mmdvfs->force_step0);
if (!g_mmdvfs->release_step0)
goto err;
if (!IS_ERR_OR_NULL(g_mmdvfs->reg))
regulator_set_voltage(g_mmdvfs->reg, 0, INT_MAX);
if (g_mmdvfs->use_v3_pwr & (1 << PWR_MMDVFS_VCORE))
mtk_mmdvfs_v3_set_vote_step(PWR_MMDVFS_VCORE, -1);
if (g_mmdvfs->use_v3_pwr & (1 << PWR_MMDVFS_VMM))
mtk_mmdvfs_v3_set_vote_step(PWR_MMDVFS_VMM, -1);
if (g_mmdvfs->aov_clk) {
ret = clk_set_rate(g_mmdvfs->aov_clk, 1);
if (ret)
MMDVFS_DBG("clk_set_rate failed:%d", ret);
}
g_mmdvfs->base = mtk_mmdvfs_vcp_get_base(&pa);
if (g_mmdvfs->base && pa)
ret = mrdump_mini_add_extra_file(
(unsigned long)(unsigned long *)g_mmdvfs->base,
pa, PAGE_SIZE, "MMDVFS_OPP");
MMDVFS_DBG("ret:%d pa:%pa va:%p", ret, &pa, g_mmdvfs->base);
err:
mtk_mmdvfs_enable_vcp(false);
return ret;
}
static int mmdvfs_v1_dbg_ftrace_thread(void *data)
{
static u8 old_cnt;
unsigned long flags;
int ret = 0;
s32 i;
while (!kthread_should_stop()) {
spin_lock_irqsave(&g_mmdvfs->lock, flags);
if (g_mmdvfs->rec_cnt != old_cnt) {
if (g_mmdvfs->rec_cnt > old_cnt) {
for (i = old_cnt; i < g_mmdvfs->rec_cnt; i++)
trace_mmdvfs__record_opp_v1(1, g_mmdvfs->rec[i].opp);
} else {
for (i = old_cnt; i < ARRAY_SIZE(g_mmdvfs->rec); i++)
trace_mmdvfs__record_opp_v1(1, g_mmdvfs->rec[i].opp);
for (i = 0; i < g_mmdvfs->rec_cnt; i++)
trace_mmdvfs__record_opp_v1(1, g_mmdvfs->rec[i].opp);
}
old_cnt = g_mmdvfs->rec_cnt;
}
spin_unlock_irqrestore(&g_mmdvfs->lock, flags);
msleep(20);
}
ftrace_v1_ena = false;
MMDVFS_DBG("kthread mmdvfs-dbg-ftrace-v1 end");
return ret;
}
static int mmdvfs_v3_dbg_ftrace_thread(void *data)
{
phys_addr_t pa;
static unsigned long old_cnt;
unsigned long cnt, mem[MMDVFS_RECORD_OBJ];
int ret = 0, retry = 0;
s32 i, j;
while (!mtk_is_mmdvfs_init_done() && !kthread_should_stop()) {
if (++retry > 50) {
MMDVFS_DBG("mmdvfs not ready");
goto err;
}
msleep(2000);
}
if (!g_mmdvfs->base) {
g_mmdvfs->base = mtk_mmdvfs_vcp_get_base(&pa);
if (!g_mmdvfs->base) {
ftrace_v3_ena = false;
MMDVFS_DBG("kthread mmdvfs-dbg-ftrace-v3 end");
return 0;
}
}
while (!kthread_should_stop()) {
cnt = readl(g_mmdvfs->base);
if (cnt != old_cnt) {
if (cnt > old_cnt) {
for (i = old_cnt; i < cnt; i++) {
for (j = 0; j < ARRAY_SIZE(mem); j++)
mem[j] = readl(g_mmdvfs->base + (((i + 1) *
MMDVFS_RECORD_OBJ + j) << 2));
trace_mmdvfs__record_opp_v3(mem[2], mem[3]);
}
} else {
for (i = old_cnt; i < MMDVFS_RECORD_NUM; i++) {
for (j = 0; j < ARRAY_SIZE(mem); j++)
mem[j] = readl(g_mmdvfs->base + (((i + 1) *
MMDVFS_RECORD_OBJ + j) << 2));
trace_mmdvfs__record_opp_v3(mem[2], mem[3]);
}
for (i = 0; i < cnt; i++) {
for (j = 0; j < ARRAY_SIZE(mem); j++)
mem[j] = readl(g_mmdvfs->base + (((i + 1) *
MMDVFS_RECORD_OBJ + j) << 2));
trace_mmdvfs__record_opp_v3(mem[2], mem[3]);
}
}
old_cnt = cnt;
}
msleep(20);
}
err:
mtk_mmdvfs_enable_vcp(false);
ftrace_v3_ena = false;
MMDVFS_DBG("kthread mmdvfs-dbg-ftrace-v3 end");
return ret;
}
static int mmdvfs_debug_set_ftrace(const char *val,
const struct kernel_param *kp)
{
static struct task_struct *kthr_v1, *kthr_v3;
u32 ver = 0, ena = 0;
int ret;
ret = sscanf(val, "%hhu %hhu", &ver, &ena);
if (ret != 2) {
MMDVFS_DBG("failed:%d ver:%hhu ena:%hhu", ret, ver, ena);
return -EINVAL;
}
if (ver & MMDVFS_DBG_VER1) {
if (ena) {
if (ftrace_v1_ena)
MMDVFS_DBG("kthread mmdvfs-dbg-ftrace-v1 already created");
else {
kthr_v1 = kthread_run(
mmdvfs_v1_dbg_ftrace_thread, NULL, "mmdvfs-dbg-ftrace-v1");
if (IS_ERR(kthr_v1))
MMDVFS_DBG("create kthread mmdvfs-dbg-ftrace-v1 failed");
else
ftrace_v1_ena = true;
}
} else {
if (ftrace_v1_ena) {
ret = kthread_stop(kthr_v1);
if (!ret) {
MMDVFS_DBG("stop kthread mmdvfs-dbg-ftrace-v1");
ftrace_v1_ena = false;
}
}
}
}
if (ver & MMDVFS_DBG_VER3) {
if (ena) {
if (ftrace_v3_ena)
MMDVFS_DBG("kthread mmdvfs-dbg-ftrace-v3 already created");
else {
kthr_v3 = kthread_run(
mmdvfs_v3_dbg_ftrace_thread, NULL, "mmdvfs-dbg-ftrace-v3");
if (IS_ERR(kthr_v3))
MMDVFS_DBG("create kthread mmdvfs-dbg-ftrace-v3 failed");
else
ftrace_v3_ena = true;
}
} else {
if (ftrace_v3_ena) {
ret = kthread_stop(kthr_v3);
if (!ret) {
MMDVFS_DBG("stop kthread mmdvfs-dbg-ftrace-v3");
ftrace_v3_ena = false;
}
}
}
}
return 0;
}
static struct kernel_param_ops mmdvfs_debug_set_ftrace_ops = {
.set = mmdvfs_debug_set_ftrace,
};
module_param_cb(ftrace, &mmdvfs_debug_set_ftrace_ops, NULL, 0644);
MODULE_PARM_DESC(ftrace, "mmdvfs ftrace log");
void mtk_mmdvfs_debug_ulposc_enable(const bool enable)
{
int ret = 0;
if (!g_mmdvfs)
return;
if (g_mmdvfs->aov_clk) {
ret = clk_set_rate(g_mmdvfs->aov_clk, enable ? 0 : 1);
if (ret)
MMDVFS_DBG("clk_set_rate failed:%d", ret);
MMDVFS_DBG("enable=%d", enable);
}
}
EXPORT_SYMBOL_GPL(mtk_mmdvfs_debug_ulposc_enable);
static int mmdvfs_debug_probe(struct platform_device *pdev)
{
struct proc_dir_entry *dir, *proc;
struct task_struct *kthr;
struct regulator *reg;
int ret;
g_mmdvfs = kzalloc(sizeof(*g_mmdvfs), GFP_KERNEL);
if (!g_mmdvfs) {
MMDVFS_DBG("kzalloc: g_mmdvfs no memory");
return -ENOMEM;
}
g_mmdvfs->dev = &pdev->dev;
dir = proc_mkdir("mmdvfs", NULL);
if (IS_ERR_OR_NULL(dir))
MMDVFS_DBG("proc_mkdir failed:%ld", PTR_ERR(dir));
proc = proc_create("mmdvfs_opp", 0444, dir, &mmdvfs_debug_opp_fops);
if (IS_ERR_OR_NULL(proc))
MMDVFS_DBG("proc_create failed:%ld", PTR_ERR(proc));
else
g_mmdvfs->proc = proc;
ret = of_property_read_u32(g_mmdvfs->dev->of_node,
"debug-version", &g_mmdvfs->debug_version);
if (ret)
MMDVFS_DBG("debug_version:%u failed:%d",
g_mmdvfs->debug_version, ret);
/* MMDVFS_DBG_VER1 */
reg = devm_regulator_get(g_mmdvfs->dev, "dvfsrc-vcore");
if (IS_ERR_OR_NULL(reg)) {
MMDVFS_DBG("devm_regulator_get failed:%d", PTR_ERR(reg));
return PTR_ERR(reg);
}
g_mmdvfs->reg = reg;
ret = regulator_count_voltages(reg);
if (ret < 0) {
MMDVFS_DBG("regulator_count_voltages failed:%d", ret);
return ret;
}
g_mmdvfs->reg_cnt_vol = (u32)ret;
ret = of_property_read_u32(g_mmdvfs->dev->of_node,
"force-step0", &g_mmdvfs->force_step0);
if (ret) {
MMDVFS_DBG("force_step0:%u failed:%d",
g_mmdvfs->force_step0, ret);
return ret;
}
if (g_mmdvfs->force_step0 >= g_mmdvfs->reg_cnt_vol) {
MMDVFS_DBG("force_step0:%u cannot larger reg_cnt_vol:%u",
g_mmdvfs->force_step0, g_mmdvfs->reg_cnt_vol);
return -EINVAL;
}
spin_lock_init(&g_mmdvfs->lock);
mmdvfs_debug_record_opp_set_fp(mmdvfs_debug_record_opp);
ret = regulator_list_voltage(
reg, g_mmdvfs->reg_cnt_vol - 1 - g_mmdvfs->force_step0);
regulator_set_voltage(reg, ret, INT_MAX);
ret = of_property_read_u32(g_mmdvfs->dev->of_node,
"release-step0", &g_mmdvfs->release_step0);
if (ret)
MMDVFS_DBG("release_step0:%u failed:%d",
g_mmdvfs->release_step0, ret);
/* MMDVFS_DBG_VER3 */
g_mmdvfs->aov_clk = devm_clk_get(g_mmdvfs->dev, "aov_clk");
if (IS_ERR(g_mmdvfs->aov_clk))
g_mmdvfs->aov_clk = NULL;
ret = of_property_read_u32(g_mmdvfs->dev->of_node,
"use-v3-pwr", &g_mmdvfs->use_v3_pwr);
if (g_mmdvfs->use_v3_pwr)
kthr = kthread_run(
mmdvfs_v3_debug_thread, NULL, "mmdvfs-dbg-vcp");
return 0;
}
static int mmdvfs_debug_remove(struct platform_device *pdev)
{
devm_regulator_put(g_mmdvfs->reg);
kfree(g_mmdvfs);
return 0;
}
static const struct of_device_id of_mmdvfs_debug_match_tbl[] = {
{
.compatible = "mediatek,mmdvfs-debug",
},
{}
};
static struct platform_driver mmdvfs_debug_drv = {
.probe = mmdvfs_debug_probe,
.remove = mmdvfs_debug_remove,
.driver = {
.name = "mtk-mmdvfs-debug",
.of_match_table = of_mmdvfs_debug_match_tbl,
},
};
static int __init mmdvfs_debug_init(void)
{
int ret;
ret = platform_driver_register(&mmdvfs_debug_drv);
if (ret)
MMDVFS_DBG("failed:%d", ret);
return ret;
}
static void __exit mmdvfs_debug_exit(void)
{
platform_driver_unregister(&mmdvfs_debug_drv);
}
module_init(mmdvfs_debug_init);
module_exit(mmdvfs_debug_exit);
MODULE_DESCRIPTION("MMDVFS Debug Driver");
MODULE_AUTHOR("Anthony Huang<anthony.huang@mediatek.com>");
MODULE_LICENSE("GPL");