soc: mediatek: SVS: add debug commands
authorRoger Lu <roger.lu@mediatek.com>
Mon, 16 May 2022 00:43:09 +0000 (08:43 +0800)
committerMatthias Brugger <matthias.bgg@gmail.com>
Fri, 17 Jun 2022 08:50:07 +0000 (10:50 +0200)
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>
drivers/soc/mediatek/mtk-svs.c

index 44ccad1e33276dea387a0446ea195d794e3e7c16..aa06657253393030a370cad1670879fd161df294 100644 (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>
 
 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: