soc: mediatek: SVS: add debug commands

The purpose of SVS is to help find the suitable voltages
for DVFS. Therefore, if SVS bank voltages are concerned
to be wrong, we can show/disable SVS bank voltages by
this patch.

Signed-off-by: Roger Lu <roger.lu@mediatek.com>
Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Reviewed-by: Kevin Hilman <khilman@baylibre.com>
Link: https://lore.kernel.org/r/20220516004311.18358-6-roger.lu@mediatek.com
Signed-off-by: Matthias Brugger <matthias.bgg@gmail.com>
This commit is contained in:
Roger Lu 2022-05-16 08:43:09 +08:00 committed by Matthias Brugger
parent 6c69271926
commit 13f1bbcfb5

View File

@ -7,6 +7,7 @@
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/cpuidle.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
@ -23,6 +24,7 @@
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/thermal.h>
@ -69,6 +71,39 @@
static DEFINE_SPINLOCK(svs_lock);
#define debug_fops_ro(name) \
static int svs_##name##_debug_open(struct inode *inode, \
struct file *filp) \
{ \
return single_open(filp, svs_##name##_debug_show, \
inode->i_private); \
} \
static const struct file_operations svs_##name##_debug_fops = { \
.owner = THIS_MODULE, \
.open = svs_##name##_debug_open, \
.read = seq_read, \
.llseek = seq_lseek, \
.release = single_release, \
}
#define debug_fops_rw(name) \
static int svs_##name##_debug_open(struct inode *inode, \
struct file *filp) \
{ \
return single_open(filp, svs_##name##_debug_show, \
inode->i_private); \
} \
static const struct file_operations svs_##name##_debug_fops = { \
.owner = THIS_MODULE, \
.open = svs_##name##_debug_open, \
.read = seq_read, \
.write = svs_##name##_debug_write, \
.llseek = seq_lseek, \
.release = single_release, \
}
#define svs_dentry_data(name) {__stringify(name), &svs_##name##_debug_fops}
/**
* enum svsb_phase - svs bank phase enumeration
* @SVSB_PHASE_ERROR: svs bank encounters unexpected condition
@ -268,6 +303,7 @@ struct svs_platform_data {
* @tzone_name: thermal zone name
* @phase: bank current phase
* @volt_od: bank voltage overdrive
* @reg_data: bank register data in different phase for debug purpose
* @pm_runtime_enabled_count: bank pm runtime enabled count
* @mode_support: bank mode support.
* @freq_base: reference frequency for bank init
@ -326,6 +362,7 @@ struct svs_bank {
char *tzone_name;
enum svsb_phase phase;
s32 volt_od;
u32 reg_data[SVSB_PHASE_MAX][SVS_REG_MAX];
u32 pm_runtime_enabled_count;
u32 mode_support;
u32 freq_base;
@ -467,6 +504,220 @@ unlock_mutex:
return ret;
}
static int svs_dump_debug_show(struct seq_file *m, void *p)
{
struct svs_platform *svsp = (struct svs_platform *)m->private;
struct svs_bank *svsb;
unsigned long svs_reg_addr;
u32 idx, i, j, bank_id;
for (i = 0; i < svsp->efuse_max; i++)
if (svsp->efuse && svsp->efuse[i])
seq_printf(m, "M_HW_RES%d = 0x%08x\n",
i, svsp->efuse[i]);
for (i = 0; i < svsp->tefuse_max; i++)
if (svsp->tefuse)
seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n",
i, svsp->tefuse[i]);
for (bank_id = 0, idx = 0; idx < svsp->bank_max; idx++, bank_id++) {
svsb = &svsp->banks[idx];
for (i = SVSB_PHASE_INIT01; i <= SVSB_PHASE_MON; i++) {
seq_printf(m, "Bank_number = %u\n", bank_id);
if (i == SVSB_PHASE_INIT01 || i == SVSB_PHASE_INIT02)
seq_printf(m, "mode = init%d\n", i);
else if (i == SVSB_PHASE_MON)
seq_puts(m, "mode = mon\n");
else
seq_puts(m, "mode = error\n");
for (j = DESCHAR; j < SVS_REG_MAX; j++) {
svs_reg_addr = (unsigned long)(svsp->base +
svsp->regs[j]);
seq_printf(m, "0x%08lx = 0x%08x\n",
svs_reg_addr, svsb->reg_data[i][j]);
}
}
}
return 0;
}
debug_fops_ro(dump);
static int svs_enable_debug_show(struct seq_file *m, void *v)
{
struct svs_bank *svsb = (struct svs_bank *)m->private;
switch (svsb->phase) {
case SVSB_PHASE_ERROR:
seq_puts(m, "disabled\n");
break;
case SVSB_PHASE_INIT01:
seq_puts(m, "init1\n");
break;
case SVSB_PHASE_INIT02:
seq_puts(m, "init2\n");
break;
case SVSB_PHASE_MON:
seq_puts(m, "mon mode\n");
break;
default:
seq_puts(m, "unknown\n");
break;
}
return 0;
}
static ssize_t svs_enable_debug_write(struct file *filp,
const char __user *buffer,
size_t count, loff_t *pos)
{
struct svs_bank *svsb = file_inode(filp)->i_private;
struct svs_platform *svsp = dev_get_drvdata(svsb->dev);
unsigned long flags;
int enabled, ret;
char *buf = NULL;
if (count >= PAGE_SIZE)
return -EINVAL;
buf = (char *)memdup_user_nul(buffer, count);
if (IS_ERR(buf))
return PTR_ERR(buf);
ret = kstrtoint(buf, 10, &enabled);
if (ret)
return ret;
if (!enabled) {
spin_lock_irqsave(&svs_lock, flags);
svsp->pbank = svsb;
svsb->mode_support = SVSB_MODE_ALL_DISABLE;
svs_switch_bank(svsp);
svs_writel_relaxed(svsp, SVSB_EN_OFF, SVSEN);
svs_writel_relaxed(svsp, SVSB_INTSTS_CLEAN, INTSTS);
spin_unlock_irqrestore(&svs_lock, flags);
svsb->phase = SVSB_PHASE_ERROR;
svs_adjust_pm_opp_volts(svsb);
}
kfree(buf);
return count;
}
debug_fops_rw(enable);
static int svs_status_debug_show(struct seq_file *m, void *v)
{
struct svs_bank *svsb = (struct svs_bank *)m->private;
struct dev_pm_opp *opp;
int tzone_temp = 0, ret;
u32 i;
ret = thermal_zone_get_temp(svsb->tzd, &tzone_temp);
if (ret)
seq_printf(m, "%s: temperature ignore\n", svsb->name);
else
seq_printf(m, "%s: temperature = %d\n", svsb->name, tzone_temp);
for (i = 0; i < svsb->opp_count; i++) {
opp = dev_pm_opp_find_freq_exact(svsb->opp_dev,
svsb->opp_dfreq[i], true);
if (IS_ERR(opp)) {
seq_printf(m, "%s: cannot find freq = %u (%ld)\n",
svsb->name, svsb->opp_dfreq[i],
PTR_ERR(opp));
return PTR_ERR(opp);
}
seq_printf(m, "opp_freq[%02u]: %u, opp_volt[%02u]: %lu, ",
i, svsb->opp_dfreq[i], i,
dev_pm_opp_get_voltage(opp));
seq_printf(m, "svsb_volt[%02u]: 0x%x, freq_pct[%02u]: %u\n",
i, svsb->volt[i], i, svsb->freq_pct[i]);
dev_pm_opp_put(opp);
}
return 0;
}
debug_fops_ro(status);
static int svs_create_debug_cmds(struct svs_platform *svsp)
{
struct svs_bank *svsb;
struct dentry *svs_dir, *svsb_dir, *file_entry;
const char *d = "/sys/kernel/debug/svs";
u32 i, idx;
struct svs_dentry {
const char *name;
const struct file_operations *fops;
};
struct svs_dentry svs_entries[] = {
svs_dentry_data(dump),
};
struct svs_dentry svsb_entries[] = {
svs_dentry_data(enable),
svs_dentry_data(status),
};
svs_dir = debugfs_create_dir("svs", NULL);
if (IS_ERR(svs_dir)) {
dev_err(svsp->dev, "cannot create %s: %ld\n",
d, PTR_ERR(svs_dir));
return PTR_ERR(svs_dir);
}
for (i = 0; i < ARRAY_SIZE(svs_entries); i++) {
file_entry = debugfs_create_file(svs_entries[i].name, 0664,
svs_dir, svsp,
svs_entries[i].fops);
if (IS_ERR(file_entry)) {
dev_err(svsp->dev, "cannot create %s/%s: %ld\n",
d, svs_entries[i].name, PTR_ERR(file_entry));
return PTR_ERR(file_entry);
}
}
for (idx = 0; idx < svsp->bank_max; idx++) {
svsb = &svsp->banks[idx];
if (svsb->mode_support == SVSB_MODE_ALL_DISABLE)
continue;
svsb_dir = debugfs_create_dir(svsb->name, svs_dir);
if (IS_ERR(svsb_dir)) {
dev_err(svsp->dev, "cannot create %s/%s: %ld\n",
d, svsb->name, PTR_ERR(svsb_dir));
return PTR_ERR(svsb_dir);
}
for (i = 0; i < ARRAY_SIZE(svsb_entries); i++) {
file_entry = debugfs_create_file(svsb_entries[i].name,
0664, svsb_dir, svsb,
svsb_entries[i].fops);
if (IS_ERR(file_entry)) {
dev_err(svsp->dev, "no %s/%s/%s?: %ld\n",
d, svsb->name, svsb_entries[i].name,
PTR_ERR(file_entry));
return PTR_ERR(file_entry);
}
}
}
return 0;
}
static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx)
{
u32 vx;
@ -591,6 +842,16 @@ static void svs_set_bank_phase(struct svs_platform *svsp,
}
}
static inline void svs_save_bank_register_data(struct svs_platform *svsp,
enum svsb_phase phase)
{
struct svs_bank *svsb = svsp->pbank;
enum svs_reg_index rg_i;
for (rg_i = DESCHAR; rg_i < SVS_REG_MAX; rg_i++)
svsb->reg_data[phase][rg_i] = svs_readl_relaxed(svsp, rg_i);
}
static inline void svs_error_isr_handler(struct svs_platform *svsp)
{
struct svs_bank *svsb = svsp->pbank;
@ -605,6 +866,8 @@ static inline void svs_error_isr_handler(struct svs_platform *svsp)
svs_readl_relaxed(svsp, SMSTATE1));
dev_err(svsb->dev, "TEMP = 0x%08x\n", svs_readl_relaxed(svsp, TEMP));
svs_save_bank_register_data(svsp, SVSB_PHASE_ERROR);
svsb->phase = SVSB_PHASE_ERROR;
svs_writel_relaxed(svsp, SVSB_EN_OFF, SVSEN);
svs_writel_relaxed(svsp, SVSB_INTSTS_CLEAN, INTSTS);
@ -619,6 +882,8 @@ static inline void svs_init01_isr_handler(struct svs_platform *svsp)
svs_readl_relaxed(svsp, VDESIGN30),
svs_readl_relaxed(svsp, DCVALUES));
svs_save_bank_register_data(svsp, SVSB_PHASE_INIT01);
svsb->phase = SVSB_PHASE_INIT01;
svsb->dc_voffset_in = ~(svs_readl_relaxed(svsp, DCVALUES) &
GENMASK(15, 0)) + 1;
@ -644,6 +909,8 @@ static inline void svs_init02_isr_handler(struct svs_platform *svsp)
svs_readl_relaxed(svsp, VOP30),
svs_readl_relaxed(svsp, DCVALUES));
svs_save_bank_register_data(svsp, SVSB_PHASE_INIT02);
svsb->phase = SVSB_PHASE_INIT02;
svsb->get_volts(svsp);
@ -655,6 +922,8 @@ static inline void svs_mon_mode_isr_handler(struct svs_platform *svsp)
{
struct svs_bank *svsb = svsp->pbank;
svs_save_bank_register_data(svsp, SVSB_PHASE_MON);
svsb->phase = SVSB_PHASE_MON;
svsb->get_volts(svsp);
@ -1626,6 +1895,12 @@ static int svs_probe(struct platform_device *pdev)
goto svs_probe_iounmap;
}
ret = svs_create_debug_cmds(svsp);
if (ret) {
dev_err(svsp->dev, "svs create debug cmds fail: %d\n", ret);
goto svs_probe_iounmap;
}
return 0;
svs_probe_iounmap: