The SVS (Smart Voltage Scaling) engine is a piece of hardware which is
used to calculate optimized voltage values of several power domains, e.g.
CPU/GPU/CCI, according to chip process corner, temperatures, and other
factors. Then DVFS driver could apply those optimized voltage values to
reduce power consumption.

Signed-off-by: Roger Lu <roger...@mediatek.com>
---
 drivers/power/avs/Kconfig     |   10 +
 drivers/power/avs/Makefile    |    1 +
 drivers/power/avs/mtk_svs.c   | 2075 +++++++++++++++++++++++++++++++++
 include/linux/power/mtk_svs.h |   23 +
 4 files changed, 2109 insertions(+)
 create mode 100644 drivers/power/avs/mtk_svs.c
 create mode 100644 include/linux/power/mtk_svs.h

diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig
index b5a217b828dc..7c72504d5593 100644
--- a/drivers/power/avs/Kconfig
+++ b/drivers/power/avs/Kconfig
@@ -19,3 +19,13 @@ config ROCKCHIP_IODOMAIN
           Say y here to enable support io domains on Rockchip SoCs. It is
           necessary for the io domain setting of the SoC to match the
           voltage supplied by the regulators.
+
+config MTK_SVS
+       bool "MediaTek Smart Voltage Scaling(SVS)"
+       depends on POWER_AVS && MTK_EFUSE
+       help
+         The SVS engine is a piece of hardware which is used to calculate
+         optimized voltage values of several power domains, e.g.
+         CPU clusters/GPU/CCI, according to chip process corner, temperatures,
+         and other factors. Then DVFS driver could apply those optimized 
voltage
+         values to reduce power consumption.
diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile
index a1b8cd453f19..57246b977a93 100644
--- a/drivers/power/avs/Makefile
+++ b/drivers/power/avs/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-$(CONFIG_POWER_AVS_OMAP)           += smartreflex.o
 obj-$(CONFIG_ROCKCHIP_IODOMAIN)                += rockchip-io-domain.o
+obj-$(CONFIG_MTK_SVS)                  += mtk_svs.o
diff --git a/drivers/power/avs/mtk_svs.c b/drivers/power/avs/mtk_svs.c
new file mode 100644
index 000000000000..78ec93c3a4a5
--- /dev/null
+++ b/drivers/power/avs/mtk_svs.c
@@ -0,0 +1,2075 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 MediaTek Inc.
+ */
+
+#define pr_fmt(fmt)    "[mtk_svs] " fmt
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/pm_runtime.h>
+#include <linux/power/mtk_svs.h>
+#include <linux/proc_fs.h>
+#include <linux/regulator/consumer.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/thermal.h>
+#include <linux/uaccess.h>
+
+#define SVS_INIT01_VOLT_IGNORE         1
+#define SVS_INIT01_VOLT_INC_ONLY       2
+
+#define SVS_PHASE_INIT01               0
+#define SVS_PHASE_INIT02               1
+#define SVS_PHASE_MON                  2
+#define SVS_PHASE_ERROR                        3
+
+#define SVS_CPU_LITTLE                 1
+#define SVS_CPU_BIG                    2
+#define SVS_CCI                                3
+#define SVS_GPU                                4
+
+#define proc_fops_rw(name) \
+       static int name ## _proc_open(struct inode *inode,      \
+               struct file *file)                              \
+       {                                                       \
+               return single_open(file, name ## _proc_show,    \
+                       PDE_DATA(inode));                       \
+       }                                                       \
+       static const struct file_operations name ## _proc_fops = {      \
+               .owner          = THIS_MODULE,                          \
+               .open           = name ## _proc_open,                   \
+               .read           = seq_read,                             \
+               .llseek         = seq_lseek,                            \
+               .release        = single_release,                       \
+               .write          = name ## _proc_write,                  \
+       }
+
+#define proc_fops_ro(name) \
+       static int name ## _proc_open(struct inode *inode,      \
+               struct file *file)                              \
+       {                                                       \
+               return single_open(file, name ## _proc_show,    \
+                       PDE_DATA(inode));                       \
+       }                                                       \
+       static const struct file_operations name ## _proc_fops = {      \
+               .owner          = THIS_MODULE,                          \
+               .open           = name ## _proc_open,                   \
+               .read           = seq_read,                             \
+               .llseek         = seq_lseek,                            \
+               .release        = single_release,                       \
+       }
+
+#define proc_entry(name)       {__stringify(name), &name ## _proc_fops}
+
+static DEFINE_SPINLOCK(mtk_svs_lock);
+struct mtk_svs;
+
+enum reg_index {
+       TEMPMONCTL0 = 0,
+       TEMPMONCTL1,
+       TEMPMONCTL2,
+       TEMPMONINT,
+       TEMPMONINTSTS,
+       TEMPMONIDET0,
+       TEMPMONIDET1,
+       TEMPMONIDET2,
+       TEMPH2NTHRE,
+       TEMPHTHRE,
+       TEMPCTHRE,
+       TEMPOFFSETH,
+       TEMPOFFSETL,
+       TEMPMSRCTL0,
+       TEMPMSRCTL1,
+       TEMPAHBPOLL,
+       TEMPAHBTO,
+       TEMPADCPNP0,
+       TEMPADCPNP1,
+       TEMPADCPNP2,
+       TEMPADCMUX,
+       TEMPADCEXT,
+       TEMPADCEXT1,
+       TEMPADCEN,
+       TEMPPNPMUXADDR,
+       TEMPADCMUXADDR,
+       TEMPADCEXTADDR,
+       TEMPADCEXT1ADDR,
+       TEMPADCENADDR,
+       TEMPADCVALIDADDR,
+       TEMPADCVOLTADDR,
+       TEMPRDCTRL,
+       TEMPADCVALIDMASK,
+       TEMPADCVOLTAGESHIFT,
+       TEMPADCWRITECTRL,
+       TEMPMSR0,
+       TEMPMSR1,
+       TEMPMSR2,
+       TEMPADCHADDR,
+       TEMPIMMD0,
+       TEMPIMMD1,
+       TEMPIMMD2,
+       TEMPMONIDET3,
+       TEMPADCPNP3,
+       TEMPMSR3,
+       TEMPIMMD3,
+       TEMPPROTCTL,
+       TEMPPROTTA,
+       TEMPPROTTB,
+       TEMPPROTTC,
+       TEMPSPARE0,
+       TEMPSPARE1,
+       TEMPSPARE2,
+       TEMPSPARE3,
+       TEMPMSR0_1,
+       TEMPMSR1_1,
+       TEMPMSR2_1,
+       TEMPMSR3_1,
+       DESCHAR,
+       TEMPCHAR,
+       DETCHAR,
+       AGECHAR,
+       DCCONFIG,
+       AGECONFIG,
+       FREQPCT30,
+       FREQPCT74,
+       LIMITVALS,
+       VBOOT,
+       DETWINDOW,
+       CONFIG,
+       TSCALCS,
+       RUNCONFIG,
+       SVSEN,
+       INIT2VALS,
+       DCVALUES,
+       AGEVALUES,
+       VOP30,
+       VOP74,
+       TEMP,
+       INTSTS,
+       INTSTSRAW,
+       INTEN,
+       CHKINT,
+       CHKSHIFT,
+       STATUS,
+       VDESIGN30,
+       VDESIGN74,
+       DVT30,
+       DVT74,
+       AGECOUNT,
+       SMSTATE0,
+       SMSTATE1,
+       CTL0,
+       DESDETSEC,
+       TEMPAGESEC,
+       CTRLSPARE0,
+       CTRLSPARE1,
+       CTRLSPARE2,
+       CTRLSPARE3,
+       CORESEL,
+       THERMINTST,
+       INTST,
+       THSTAGE0ST,
+       THSTAGE1ST,
+       THSTAGE2ST,
+       THAHBST0,
+       THAHBST1,
+       SPARE0,
+       SPARE1,
+       SPARE2,
+       SPARE3,
+       THSLPEVEB,
+       reg_num,
+};
+
+static const u32 svs_regs_v2[] = {
+       [TEMPMONCTL0]           = 0x000,
+       [TEMPMONCTL1]           = 0x004,
+       [TEMPMONCTL2]           = 0x008,
+       [TEMPMONINT]            = 0x00c,
+       [TEMPMONINTSTS]         = 0x010,
+       [TEMPMONIDET0]          = 0x014,
+       [TEMPMONIDET1]          = 0x018,
+       [TEMPMONIDET2]          = 0x01c,
+       [TEMPH2NTHRE]           = 0x024,
+       [TEMPHTHRE]             = 0x028,
+       [TEMPCTHRE]             = 0x02c,
+       [TEMPOFFSETH]           = 0x030,
+       [TEMPOFFSETL]           = 0x034,
+       [TEMPMSRCTL0]           = 0x038,
+       [TEMPMSRCTL1]           = 0x03c,
+       [TEMPAHBPOLL]           = 0x040,
+       [TEMPAHBTO]             = 0x044,
+       [TEMPADCPNP0]           = 0x048,
+       [TEMPADCPNP1]           = 0x04c,
+       [TEMPADCPNP2]           = 0x050,
+       [TEMPADCMUX]            = 0x054,
+       [TEMPADCEXT]            = 0x058,
+       [TEMPADCEXT1]           = 0x05c,
+       [TEMPADCEN]             = 0x060,
+       [TEMPPNPMUXADDR]        = 0x064,
+       [TEMPADCMUXADDR]        = 0x068,
+       [TEMPADCEXTADDR]        = 0x06c,
+       [TEMPADCEXT1ADDR]       = 0x070,
+       [TEMPADCENADDR]         = 0x074,
+       [TEMPADCVALIDADDR]      = 0x078,
+       [TEMPADCVOLTADDR]       = 0x07c,
+       [TEMPRDCTRL]            = 0x080,
+       [TEMPADCVALIDMASK]      = 0x084,
+       [TEMPADCVOLTAGESHIFT]   = 0x088,
+       [TEMPADCWRITECTRL]      = 0x08c,
+       [TEMPMSR0]              = 0x090,
+       [TEMPMSR1]              = 0x094,
+       [TEMPMSR2]              = 0x098,
+       [TEMPADCHADDR]          = 0x09c,
+       [TEMPIMMD0]             = 0x0a0,
+       [TEMPIMMD1]             = 0x0a4,
+       [TEMPIMMD2]             = 0x0a8,
+       [TEMPMONIDET3]          = 0x0b0,
+       [TEMPADCPNP3]           = 0x0b4,
+       [TEMPMSR3]              = 0x0b8,
+       [TEMPIMMD3]             = 0x0bc,
+       [TEMPPROTCTL]           = 0x0c0,
+       [TEMPPROTTA]            = 0x0c4,
+       [TEMPPROTTB]            = 0x0c8,
+       [TEMPPROTTC]            = 0x0cc,
+       [TEMPSPARE0]            = 0x0f0,
+       [TEMPSPARE1]            = 0x0f4,
+       [TEMPSPARE2]            = 0x0f8,
+       [TEMPSPARE3]            = 0x0fc,
+       [TEMPMSR0_1]            = 0x190,
+       [TEMPMSR1_1]            = 0x194,
+       [TEMPMSR2_1]            = 0x198,
+       [TEMPMSR3_1]            = 0x1b8,
+       [DESCHAR]               = 0xc00,
+       [TEMPCHAR]              = 0xc04,
+       [DETCHAR]               = 0xc08,
+       [AGECHAR]               = 0xc0c,
+       [DCCONFIG]              = 0xc10,
+       [AGECONFIG]             = 0xc14,
+       [FREQPCT30]             = 0xc18,
+       [FREQPCT74]             = 0xc1c,
+       [LIMITVALS]             = 0xc20,
+       [VBOOT]                 = 0xc24,
+       [DETWINDOW]             = 0xc28,
+       [CONFIG]                = 0xc2c,
+       [TSCALCS]               = 0xc30,
+       [RUNCONFIG]             = 0xc34,
+       [SVSEN]                 = 0xc38,
+       [INIT2VALS]             = 0xc3c,
+       [DCVALUES]              = 0xc40,
+       [AGEVALUES]             = 0xc44,
+       [VOP30]                 = 0xc48,
+       [VOP74]                 = 0xc4c,
+       [TEMP]                  = 0xc50,
+       [INTSTS]                = 0xc54,
+       [INTSTSRAW]             = 0xc58,
+       [INTEN]                 = 0xc5c,
+       [CHKINT]                = 0xc60,
+       [CHKSHIFT]              = 0xc64,
+       [STATUS]                = 0xc68,
+       [VDESIGN30]             = 0xc6c,
+       [VDESIGN74]             = 0xc70,
+       [DVT30]                 = 0xc74,
+       [DVT74]                 = 0xc78,
+       [AGECOUNT]              = 0xc7c,
+       [SMSTATE0]              = 0xc80,
+       [SMSTATE1]              = 0xc84,
+       [CTL0]                  = 0xc88,
+       [DESDETSEC]             = 0xce0,
+       [TEMPAGESEC]            = 0xce4,
+       [CTRLSPARE0]            = 0xcf0,
+       [CTRLSPARE1]            = 0xcf4,
+       [CTRLSPARE2]            = 0xcf8,
+       [CTRLSPARE3]            = 0xcfc,
+       [CORESEL]               = 0xf00,
+       [THERMINTST]            = 0xf04,
+       [INTST]                 = 0xf08,
+       [THSTAGE0ST]            = 0xf0c,
+       [THSTAGE1ST]            = 0xf10,
+       [THSTAGE2ST]            = 0xf14,
+       [THAHBST0]              = 0xf18,
+       [THAHBST1]              = 0xf1c,
+       [SPARE0]                = 0xf20,
+       [SPARE1]                = 0xf24,
+       [SPARE2]                = 0xf28,
+       [SPARE3]                = 0xf2c,
+       [THSLPEVEB]             = 0xf30,
+};
+
+struct thermal_parameter {
+       int adc_ge_t;
+       int adc_oe_t;
+       int ge;
+       int oe;
+       int gain;
+       int o_vtsabb;
+       int o_vtsmcu1;
+       int o_vtsmcu2;
+       int o_vtsmcu3;
+       int o_vtsmcu4;
+       int o_vtsmcu5;
+       int degc_cali;
+       int adc_cali_en_t;
+       int o_slope;
+       int o_slope_sign;
+       int ts_id;
+};
+
+struct svs_bank_ops {
+       void (*set_freqs_pct)(struct mtk_svs *svs);
+       void (*get_vops)(struct mtk_svs *svs);
+};
+
+struct svs_bank {
+       struct svs_bank_ops *ops;
+       struct completion init_completion;
+       struct device *dev;
+       struct regulator *buck;
+       struct mutex lock;      /* lock to protect update voltage process */
+       bool suspended;
+       bool mtcmos_request;
+       bool init01_support;
+       bool init02_support;
+       bool mon_mode_support;
+       s32 volt_offset;
+       u32 *opp_freqs;
+       u32 *freqs_pct;
+       u32 *opp_volts;
+       u32 *init02_volts;
+       u32 *volts;
+       u32 reg_data[3][reg_num];
+       u32 freq_base;
+       u32 vboot;
+       u32 volt_step;
+       u32 volt_base;
+       u32 init01_volt_flag;
+       u32 phase;
+       u32 vmax;
+       u32 vmin;
+       u32 bts;
+       u32 mts;
+       u32 bdes;
+       u32 mdes;
+       u32 mtdes;
+       u32 dcbdet;
+       u32 dcmdet;
+       u32 dthi;
+       u32 dtlo;
+       u32 det_window;
+       u32 det_max;
+       u32 age_config;
+       u32 age_voffset_in;
+       u32 agem;
+       u32 dc_config;
+       u32 dc_voffset_in;
+       u32 dvt_fixed;
+       u32 vco;
+       u32 chkshift;
+       u32 svs_temp;
+       u32 upper_temp_bound;
+       u32 lower_temp_bound;
+       u32 low_temp_threashold;
+       u32 low_temp_offset;
+       u32 coresel;
+       u32 opp_count;
+       u32 intst;
+       u32 systemclk_en;
+       u32 sw_id;
+       u32 hw_id;
+       u32 ctl0;
+       u8 *of_compatible;
+       u8 *name;
+       u8 *zone_name;
+       u8 *buck_name;
+};
+
+struct svs_platform {
+       struct svs_bank *banks;
+       int (*efuse_parsing)(struct mtk_svs *svs);
+       bool fake_efuse;
+       const u32 *regs;
+       u32 bank_num;
+       u32 efuse_num;
+       u32 efuse_check;
+       u32 thermal_efuse_num;
+       u8 *name;
+};
+
+struct mtk_svs {
+       const struct svs_platform *platform;
+       struct svs_bank *bank;
+       struct device *dev;
+       void __iomem *base;
+       struct clk *main_clk;
+       u32 *efuse;
+       u32 *thermal_efuse;
+};
+
+unsigned long claim_mtk_svs_lock(void)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&mtk_svs_lock, flags);
+
+       return flags;
+}
+EXPORT_SYMBOL_GPL(claim_mtk_svs_lock);
+
+void release_mtk_svs_lock(unsigned long flags)
+{
+       spin_unlock_irqrestore(&mtk_svs_lock, flags);
+}
+EXPORT_SYMBOL_GPL(release_mtk_svs_lock);
+
+static u32 percent(u32 numerator, u32 denominator)
+{
+       u32 percent;
+
+       /* If not divide 1000, "numerator * 100" would be data overflow. */
+       numerator /= 1000;
+       denominator /= 1000;
+       percent = ((numerator * 100) + denominator - 1) / denominator;
+
+       return percent;
+}
+
+static u32 svs_readl(struct mtk_svs *svs, enum reg_index i)
+{
+       return readl(svs->base + svs->platform->regs[i]);
+}
+
+static void svs_writel(struct mtk_svs *svs, u32 val, enum reg_index i)
+{
+       writel(val, svs->base + svs->platform->regs[i]);
+}
+
+static void svs_switch_bank(struct mtk_svs *svs)
+{
+       struct svs_bank *svsb = svs->bank;
+
+       svs_writel(svs, svsb->coresel, CORESEL);
+}
+
+static u32 svs_volt_to_opp_volt(u32 svsb_volt,
+                               u32 svsb_volt_step, u32 svsb_volt_base)
+{
+       u32 u_volt;
+
+       u_volt = (svsb_volt * svsb_volt_step) + svsb_volt_base;
+
+       return u_volt;
+}
+
+static int svs_get_zone_temperature(struct svs_bank *svsb, int *zone_temp)
+{
+       struct thermal_zone_device *tzd;
+       int ret;
+
+       tzd = thermal_zone_get_zone_by_name(svsb->zone_name);
+       ret = thermal_zone_get_temp(tzd, zone_temp);
+
+       return ret;
+}
+
+static int svs_set_volts(struct svs_bank *svsb, bool force_update)
+{
+       u32 i, svsb_volt, opp_volt, low_temp_offset = 0;
+       int zone_temp, ret;
+
+       mutex_lock(&svsb->lock);
+
+       /* If bank is suspended, it means init02 voltage is applied.
+        * Don't need to update opp voltage anymore.
+        */
+       if (svsb->suspended && !force_update) {
+               pr_notice("%s: bank is suspended\n", svsb->name);
+               mutex_unlock(&svsb->lock);
+               return -EPERM;
+       }
+
+       /* get thermal effect */
+       if (svsb->phase == SVS_PHASE_MON) {
+               if (svsb->svs_temp > svsb->upper_temp_bound &&
+                   svsb->svs_temp < svsb->lower_temp_bound) {
+                       pr_err("%s: svs_temp is abnormal (0x%x)?\n",
+                              svsb->name, svsb->svs_temp);
+                       mutex_unlock(&svsb->lock);
+                       return -EINVAL;
+               }
+
+               ret = svs_get_zone_temperature(svsb, &zone_temp);
+               if (ret) {
+                       pr_err("%s: cannot get zone \"%s\" temperature\n",
+                              svsb->name, svsb->zone_name);
+                       pr_err("%s: add low_temp_offset = %u\n",
+                              svsb->name, svsb->low_temp_offset);
+                       zone_temp = svsb->low_temp_threashold;
+               }
+
+               if (zone_temp <= svsb->low_temp_threashold)
+                       low_temp_offset = svsb->low_temp_offset;
+       }
+
+       /* vmin <= svsb_volt (opp_volt) <= signed-off voltage */
+       for (i = 0; i < svsb->opp_count; i++) {
+               if (svsb->phase == SVS_PHASE_MON) {
+                       svsb_volt = max((svsb->volts[i] + svsb->volt_offset +
+                                        low_temp_offset), svsb->vmin);
+                       opp_volt = svs_volt_to_opp_volt(svsb_volt,
+                                                       svsb->volt_step,
+                                                       svsb->volt_base);
+               } else if (svsb->phase == SVS_PHASE_INIT02) {
+                       svsb_volt = max((svsb->init02_volts[i] +
+                                        svsb->volt_offset), svsb->vmin);
+                       opp_volt = svs_volt_to_opp_volt(svsb_volt,
+                                                       svsb->volt_step,
+                                                       svsb->volt_base);
+               } else if (svsb->phase == SVS_PHASE_ERROR) {
+                       opp_volt = svsb->opp_volts[i];
+               } else {
+                       pr_err("%s: unknown phase: %u?\n",
+                              svsb->name, svsb->phase);
+                       mutex_unlock(&svsb->lock);
+                       return -EINVAL;
+               }
+
+               opp_volt = min(opp_volt, svsb->opp_volts[i]);
+               ret = dev_pm_opp_adjust_voltage(svsb->dev, svsb->opp_freqs[i],
+                                               opp_volt);
+               if (ret) {
+                       pr_err("%s: set voltage failed: %d\n", svsb->name, ret);
+                       mutex_unlock(&svsb->lock);
+                       return ret;
+               }
+       }
+
+       mutex_unlock(&svsb->lock);
+
+       return 0;
+}
+
+static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx)
+{
+       u32 vy;
+
+       if (v0 == v1 || f0 == f1)
+               return v0;
+
+       /* *100 to have decimal fraction factor, +99 for rounding up. */
+       vy = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx));
+       vy = (vy + 99) / 100;
+
+       return vy;
+}
+
+static void svs_get_vops_v2(struct mtk_svs *svs)
+{
+       struct svs_bank *svsb = svs->bank;
+       u32 temp, i;
+
+       temp = svs_readl(svs, VOP30);
+       svsb->volts[6] = (temp >> 24) & 0xff;
+       svsb->volts[4] = (temp >> 16) & 0xff;
+       svsb->volts[2] = (temp >> 8)  & 0xff;
+       svsb->volts[0] = (temp & 0xff);
+
+       temp = svs_readl(svs, VOP74);
+       svsb->volts[14] = (temp >> 24) & 0xff;
+       svsb->volts[12] = (temp >> 16) & 0xff;
+       svsb->volts[10] = (temp >> 8)  & 0xff;
+       svsb->volts[8] = (temp & 0xff);
+
+       for (i = 0; i <= 7; i++) {
+               if (i < 7) {
+                       svsb->volts[(i * 2) + 1] =
+                               interpolate(svsb->freqs_pct[i * 2],
+                                           svsb->freqs_pct[(i + 1) * 2],
+                                           svsb->volts[i * 2],
+                                           svsb->volts[(i + 1) * 2],
+                                           svsb->freqs_pct[(i * 2) + 1]);
+               } else if (i == 7) {
+                       svsb->volts[(i * 2) + 1] =
+                               interpolate(svsb->freqs_pct[(i - 1) * 2],
+                                           svsb->freqs_pct[i * 2],
+                                           svsb->volts[(i - 1) * 2],
+                                           svsb->volts[i * 2],
+                                           svsb->freqs_pct[(i * 2) + 1]);
+               }
+       }
+}
+
+static void svs_set_freqs_pct_v2(struct mtk_svs *svs)
+{
+       struct svs_bank *svsb = svs->bank;
+
+       svs_writel(svs,
+                  ((svsb->freqs_pct[6] << 24) & 0xff000000) |
+                  ((svsb->freqs_pct[4] << 16) & 0xff0000) |
+                  ((svsb->freqs_pct[2] << 8) & 0xff00) |
+                  (svsb->freqs_pct[0] & 0xff),
+                  FREQPCT30);
+       svs_writel(svs,
+                  ((svsb->freqs_pct[14] << 24) & 0xff000000) |
+                  ((svsb->freqs_pct[12] << 16) & 0xff0000) |
+                  ((svsb->freqs_pct[10] << 8) & 0xff00) |
+                  ((svsb->freqs_pct[8]) & 0xff),
+                  FREQPCT74);
+}
+
+static void svs_set_phase(struct mtk_svs *svs, u32 target_phase)
+{
+       struct svs_bank *svsb = svs->bank;
+       u32 des_char, temp_char, det_char, limit_vals;
+       u32 init2vals, ts_calcs, val, filter, i;
+
+       svs_switch_bank(svs);
+
+       des_char = ((svsb->bdes << 8) & 0xff00) | (svsb->mdes & 0xff);
+       svs_writel(svs, des_char, DESCHAR);
+
+       temp_char = ((svsb->vco << 16) & 0xff0000) |
+                   ((svsb->mtdes << 8) & 0xff00) |
+                   (svsb->dvt_fixed & 0xff);
+       svs_writel(svs, temp_char, TEMPCHAR);
+
+       det_char = ((svsb->dcbdet << 8) & 0xff00) | (svsb->dcmdet & 0xff);
+       svs_writel(svs, det_char, DETCHAR);
+
+       svs_writel(svs, svsb->dc_config, DCCONFIG);
+       svs_writel(svs, svsb->age_config, AGECONFIG);
+
+       if (svsb->agem == 0x0) {
+               svs_writel(svs, 0x80000000, RUNCONFIG);
+       } else {
+               val = 0x0;
+
+               for (i = 0; i < 24; i += 2) {
+                       filter = 0x3 << i;
+
+                       if ((svsb->age_config & filter) == 0x0)
+                               val |= (0x1 << i);
+                       else
+                               val |= (svsb->age_config & filter);
+               }
+               svs_writel(svs, val, RUNCONFIG);
+       }
+
+       svsb->ops->set_freqs_pct(svs);
+
+       limit_vals = ((svsb->vmax << 24) & 0xff000000) |
+                    ((svsb->vmin << 16) & 0xff0000) |
+                    ((svsb->dthi << 8) & 0xff00) |
+                    (svsb->dtlo & 0xff);
+       svs_writel(svs, limit_vals, LIMITVALS);
+       svs_writel(svs, (svsb->vboot & 0xff), VBOOT);
+       svs_writel(svs, (svsb->det_window & 0xffff), DETWINDOW);
+       svs_writel(svs, (svsb->det_max & 0xffff), CONFIG);
+
+       if (svsb->chkshift != 0)
+               svs_writel(svs, (svsb->chkshift & 0xff), CHKSHIFT);
+
+       if (svsb->ctl0 != 0)
+               svs_writel(svs, svsb->ctl0, CTL0);
+
+       svs_writel(svs, 0x00ffffff, INTSTS);
+
+       switch (target_phase) {
+       case SVS_PHASE_INIT01:
+               svs_writel(svs, 0x00005f01, INTEN);
+               svs_writel(svs, 0x00000001, SVSEN);
+               break;
+       case SVS_PHASE_INIT02:
+               svs_writel(svs, 0x00005f01, INTEN);
+               init2vals = ((svsb->age_voffset_in << 16) & 0xffff0000) |
+                           (svsb->dc_voffset_in & 0xffff);
+               svs_writel(svs, init2vals, INIT2VALS);
+               svs_writel(svs, 0x00000005, SVSEN);
+               break;
+       case SVS_PHASE_MON:
+               ts_calcs = ((svsb->bts << 12) & 0xfff000) | (svsb->mts & 0xfff);
+               svs_writel(svs, ts_calcs, TSCALCS);
+               svs_writel(svs, 0x00FF0000, INTEN);
+               svs_writel(svs, 0x00000002, SVSEN);
+               break;
+       default:
+               WARN_ON(1);
+               break;
+       }
+}
+
+static inline void svs_init01_isr_handler(struct mtk_svs *svs)
+{
+       struct svs_bank *svsb = svs->bank;
+       enum reg_index rg_i;
+
+       pr_notice("%s: %s: VDN74:0x%08x, VDN30:0x%08x, DCVALUES:0x%08x\n",
+                 svsb->name, __func__, svs_readl(svs, VDESIGN74),
+                 svs_readl(svs, VDESIGN30), svs_readl(svs, DCVALUES));
+
+       for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+               svsb->reg_data[SVS_PHASE_INIT01][rg_i] = svs_readl(svs, rg_i);
+
+       svsb->dc_voffset_in = ~(svs_readl(svs, DCVALUES) & 0xffff) + 1;
+       if (svsb->init01_volt_flag == SVS_INIT01_VOLT_IGNORE)
+               svsb->dc_voffset_in = 0;
+       else if ((svsb->dc_voffset_in & 0x8000) &&
+                (svsb->init01_volt_flag == SVS_INIT01_VOLT_INC_ONLY))
+               svsb->dc_voffset_in = 0;
+
+       svsb->age_voffset_in = svs_readl(svs, AGEVALUES) & 0xffff;
+
+       svs_writel(svs, 0x0, SVSEN);
+       svs_writel(svs, 0x1, INTSTS);
+
+       /* svs init01 clock gating */
+       svsb->coresel &= ~svsb->systemclk_en;
+
+       svsb->phase = SVS_PHASE_INIT01;
+       complete(&svsb->init_completion);
+}
+
+static inline void svs_init02_isr_handler(struct mtk_svs *svs)
+{
+       struct svs_bank *svsb = svs->bank;
+       enum reg_index rg_i;
+
+       pr_notice("%s: %s: VOP74:0x%08x, VOP30:0x%08x, DCVALUES:0x%08x\n",
+                 svsb->name, __func__, svs_readl(svs, VOP74),
+                 svs_readl(svs, VOP30), svs_readl(svs, DCVALUES));
+
+       for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+               svsb->reg_data[SVS_PHASE_INIT02][rg_i] = svs_readl(svs, rg_i);
+
+       svsb->ops->get_vops(svs);
+       memcpy(svsb->init02_volts, svsb->volts, 4 * svsb->opp_count);
+       svsb->phase = SVS_PHASE_INIT02;
+
+       svs_writel(svs, 0x0, SVSEN);
+       svs_writel(svs, 0x1, INTSTS);
+
+       complete(&svsb->init_completion);
+}
+
+static inline void svs_mon_mode_isr_handler(struct mtk_svs *svs)
+{
+       struct svs_bank *svsb = svs->bank;
+       enum reg_index rg_i;
+
+       for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+               svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+       svsb->svs_temp = svs_readl(svs, TEMP) & 0xff;
+
+       svsb->ops->get_vops(svs);
+       svsb->phase = SVS_PHASE_MON;
+
+       svs_writel(svs, 0x00ff0000, INTSTS);
+}
+
+static inline void svs_error_isr_handler(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb = svs->bank;
+       enum reg_index rg_i;
+
+       pr_err("%s(): %s(%s)", __func__, svsp->name, svsb->name);
+       pr_err("CORESEL(0x%x) = 0x%08x\n",
+              svsp->regs[CORESEL], svs_readl(svs, CORESEL)),
+       pr_err("SVSEN(0x%x) = 0x%08x, INTSTS(0x%x) = 0x%08x\n",
+              svsp->regs[SVSEN], svs_readl(svs, SVSEN),
+              svsp->regs[INTSTS], svs_readl(svs, INTSTS));
+       pr_err("SMSTATE0(0x%x) = 0x%08x, SMSTATE1(0x%x) = 0x%08x\n",
+              svsp->regs[SMSTATE0], svs_readl(svs, SMSTATE0),
+              svsp->regs[SMSTATE1], svs_readl(svs, SMSTATE1));
+
+       for (rg_i = TEMPMONCTL0; rg_i < reg_num; rg_i++)
+               svsb->reg_data[SVS_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+       svsb->init01_support = false;
+       svsb->init02_support = false;
+       svsb->mon_mode_support = false;
+
+       if (svsb->phase == SVS_PHASE_MON)
+               svsb->phase = SVS_PHASE_INIT02;
+
+       svs_writel(svs, 0x0, SVSEN);
+       svs_writel(svs, 0x00ffffff, INTSTS);
+}
+
+static inline void svs_isr_handler(struct mtk_svs *svs)
+{
+       u32 intsts, svsen;
+
+       svs_switch_bank(svs);
+
+       intsts = svs_readl(svs, INTSTS);
+       svsen = svs_readl(svs, SVSEN);
+
+       if (intsts == 0x1 && ((svsen & 0x7) == 0x1))
+               svs_init01_isr_handler(svs);
+       else if ((intsts == 0x1) && ((svsen & 0x7) == 0x5))
+               svs_init02_isr_handler(svs);
+       else if ((intsts & 0x00ff0000) != 0x0)
+               svs_mon_mode_isr_handler(svs);
+       else
+               svs_error_isr_handler(svs);
+}
+
+static irqreturn_t svs_isr(int irq, void *data)
+{
+       struct mtk_svs *svs = (struct mtk_svs *)data;
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb = NULL;
+       unsigned long flags;
+       u32 idx;
+
+       flags = claim_mtk_svs_lock();
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svs->bank = svsb;
+
+               if (svsb->suspended)
+                       continue;
+               else if (svsb->intst & svs_readl(svs, INTST))
+                       continue;
+
+               svs_isr_handler(svs);
+               break;
+       }
+       release_mtk_svs_lock(flags);
+
+       if (svsb->phase != SVS_PHASE_INIT01)
+               svs_set_volts(svsb, false);
+
+       return IRQ_HANDLED;
+}
+
+static void svs_mon_mode(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       unsigned long flags;
+       u32 idx;
+
+       flags = claim_mtk_svs_lock();
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svs->bank = svsb;
+
+               if (!svsb->mon_mode_support)
+                       continue;
+
+               svs_set_phase(svs, SVS_PHASE_MON);
+       }
+       release_mtk_svs_lock(flags);
+}
+
+static int svs_init02(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       unsigned long flags, time_left;
+       u32 idx;
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svs->bank = svsb;
+
+               if (!svsb->init02_support)
+                       continue;
+
+               reinit_completion(&svsb->init_completion);
+               flags = claim_mtk_svs_lock();
+               svs_set_phase(svs, SVS_PHASE_INIT02);
+               release_mtk_svs_lock(flags);
+               time_left =
+                       wait_for_completion_timeout(&svsb->init_completion,
+                                                   msecs_to_jiffies(2000));
+               if (time_left == 0) {
+                       pr_err("%s: init02 completion timeout\n", svsb->name);
+                       return -EBUSY;
+               }
+       }
+
+       return 0;
+}
+
+static int svs_init01(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       struct pm_qos_request qos_request = { {0} };
+       unsigned long flags, time_left;
+       bool search_done;
+       int ret = -EINVAL;
+       u32 opp_freqs, opp_vboot, buck_volt, idx, i;
+
+       /* Let CPUs leave idle-off state for initializing svs_init01. */
+       pm_qos_add_request(&qos_request, PM_QOS_CPU_DMA_LATENCY, 0);
+
+       /* Sometimes two svs_bank use the same buck.
+        * Therefore, we set each svs_bank to vboot voltage first.
+        */
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               search_done = false;
+
+               if (!svsb->init01_support)
+                       continue;
+
+               ret = regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST);
+               if (ret)
+                       pr_notice("%s: fail to set fast mode: %d\n",
+                                 svsb->name, ret);
+
+               if (svsb->mtcmos_request) {
+                       ret = regulator_enable(svsb->buck);
+                       if (ret) {
+                               pr_err("%s: fail to enable %s power: %d\n",
+                                      svsb->name, svsb->buck_name, ret);
+                               goto init01_finish;
+                       }
+
+                       ret = dev_pm_domain_attach(svsb->dev, false);
+                       if (ret) {
+                               pr_err("%s: attach pm domain fail: %d\n",
+                                      svsb->name, ret);
+                               goto init01_finish;
+                       }
+
+                       pm_runtime_enable(svsb->dev);
+                       ret = pm_runtime_get_sync(svsb->dev);
+                       if (ret < 0) {
+                               pr_err("%s: turn mtcmos on fail: %d\n",
+                                      svsb->name, ret);
+                               goto init01_finish;
+                       }
+               }
+
+               /* Find the fastest freq that can be run at vboot and
+                * fix to that freq until svs_init01 is done.
+                */
+               opp_vboot = svs_volt_to_opp_volt(svsb->vboot,
+                                                svsb->volt_step,
+                                                svsb->volt_base);
+
+               for (i = 0; i < svsb->opp_count; i++) {
+                       opp_freqs = svsb->opp_freqs[i];
+                       if (!search_done && svsb->opp_volts[i] <= opp_vboot) {
+                               ret = dev_pm_opp_adjust_voltage(svsb->dev,
+                                                               opp_freqs,
+                                                               opp_vboot);
+                               if (ret) {
+                                       pr_err("%s: set voltage failed: %d\n",
+                                              svsb->name, ret);
+                                       goto init01_finish;
+                               }
+
+                               search_done = true;
+                       } else {
+                               dev_pm_opp_disable(svsb->dev,
+                                                  svsb->opp_freqs[i]);
+                       }
+               }
+       }
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svs->bank = svsb;
+
+               if (!svsb->init01_support)
+                       continue;
+
+               opp_vboot = svs_volt_to_opp_volt(svsb->vboot,
+                                                svsb->volt_step,
+                                                svsb->volt_base);
+
+               buck_volt = regulator_get_voltage(svsb->buck);
+               if (buck_volt != opp_vboot) {
+                       pr_err("%s: buck voltage: %u, expected vboot: %u\n",
+                              svsb->name, buck_volt, opp_vboot);
+                       ret = -EPERM;
+                       goto init01_finish;
+               }
+
+               init_completion(&svsb->init_completion);
+               flags = claim_mtk_svs_lock();
+               svs_set_phase(svs, SVS_PHASE_INIT01);
+               release_mtk_svs_lock(flags);
+               time_left =
+                       wait_for_completion_timeout(&svsb->init_completion,
+                                                   msecs_to_jiffies(2000));
+               if (time_left == 0) {
+                       pr_err("%s: init01 completion timeout\n", svsb->name);
+                       ret = -EBUSY;
+                       goto init01_finish;
+               }
+       }
+
+init01_finish:
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+
+               if (!svsb->init01_support)
+                       continue;
+
+               for (i = 0; i < svsb->opp_count; i++)
+                       dev_pm_opp_enable(svsb->dev, svsb->opp_freqs[i]);
+
+               if (regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL))
+                       pr_notice("%s: fail to set normal mode: %d\n",
+                                 svsb->name, ret);
+
+               if (svsb->mtcmos_request) {
+                       if (pm_runtime_put_sync(svsb->dev))
+                               pr_err("%s: turn mtcmos off fail: %d\n",
+                                      svsb->name, ret);
+                       pm_runtime_disable(svsb->dev);
+                       dev_pm_domain_detach(svsb->dev, 0);
+                       if (regulator_disable(svsb->buck))
+                               pr_err("%s: fail to disable %s power: %d\n",
+                                      svsb->name, svsb->buck_name, ret);
+               }
+       }
+
+       pm_qos_remove_request(&qos_request);
+
+       return ret;
+}
+
+static int svs_start(struct mtk_svs *svs)
+{
+       int ret;
+
+       ret = svs_init01(svs);
+       if (ret)
+               return ret;
+
+       ret = svs_init02(svs);
+       if (ret)
+               return ret;
+
+       svs_mon_mode(svs);
+
+       return ret;
+}
+
+static int svs_mt8183_efuse_parsing(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct thermal_parameter tp;
+       struct svs_bank *svsb;
+       bool mon_mode_support = true;
+       int format[6], x_roomt[6], tb_roomt;
+       u32 idx, i, ft_pgm, mts, temp0, temp1, temp2;
+
+       if (svsp->fake_efuse) {
+               pr_notice("fake efuse\n");
+               svs->efuse[0] = 0x00310080;
+               svs->efuse[1] = 0xabfbf757;
+               svs->efuse[2] = 0x47c747c7;
+               svs->efuse[3] = 0xabfbf757;
+               svs->efuse[4] = 0xe7fca0ec;
+               svs->efuse[5] = 0x47bf4b88;
+               svs->efuse[6] = 0xabfb8fa5;
+               svs->efuse[7] = 0xabfb217b;
+               svs->efuse[8] = 0x4bf34be1;
+               svs->efuse[9] = 0xabfb670d;
+               svs->efuse[16] = 0xabfbc653;
+               svs->efuse[17] = 0x47f347e1;
+               svs->efuse[18] = 0xabfbd848;
+
+               svs->thermal_efuse[0] = 0x02873f69;
+               svs->thermal_efuse[1] = 0xa11d9142;
+               svs->thermal_efuse[2] = 0xa2526900;
+       }
+
+       /* svs efuse parsing */
+       ft_pgm = (svs->efuse[0] >> 4) & 0xf;
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               if (ft_pgm <= 1)
+                       svsb->init01_volt_flag = SVS_INIT01_VOLT_IGNORE;
+
+               switch (svsb->sw_id) {
+               case SVS_CPU_LITTLE:
+                       svsb->bdes = svs->efuse[16] & 0xff;
+                       svsb->mdes = (svs->efuse[16] >> 8) & 0xff;
+                       svsb->dcbdet = (svs->efuse[16] >> 16) & 0xff;
+                       svsb->dcmdet = (svs->efuse[16] >> 24) & 0xff;
+                       svsb->mtdes  = (svs->efuse[17] >> 16) & 0xff;
+
+                       if (ft_pgm <= 3)
+                               svsb->volt_offset += 10;
+                       else
+                               svsb->volt_offset += 2;
+                       break;
+               case SVS_CPU_BIG:
+                       svsb->bdes = svs->efuse[18] & 0xff;
+                       svsb->mdes = (svs->efuse[18] >> 8) & 0xff;
+                       svsb->dcbdet = (svs->efuse[18] >> 16) & 0xff;
+                       svsb->dcmdet = (svs->efuse[18] >> 24) & 0xff;
+                       svsb->mtdes  = svs->efuse[17] & 0xff;
+
+                       if (ft_pgm <= 3)
+                               svsb->volt_offset += 15;
+                       else
+                               svsb->volt_offset += 12;
+                       break;
+               case SVS_CCI:
+                       svsb->bdes = svs->efuse[4] & 0xff;
+                       svsb->mdes = (svs->efuse[4] >> 8) & 0xff;
+                       svsb->dcbdet = (svs->efuse[4] >> 16) & 0xff;
+                       svsb->dcmdet = (svs->efuse[4] >> 24) & 0xff;
+                       svsb->mtdes  = (svs->efuse[5] >> 16) & 0xff;
+
+                       if (ft_pgm <= 3)
+                               svsb->volt_offset += 10;
+                       else
+                               svsb->volt_offset += 2;
+                       break;
+               case SVS_GPU:
+                       svsb->bdes = svs->efuse[6] & 0xff;
+                       svsb->mdes = (svs->efuse[6] >> 8) & 0xff;
+                       svsb->dcbdet = (svs->efuse[6] >> 16) & 0xff;
+                       svsb->dcmdet = (svs->efuse[6] >> 24) & 0xff;
+                       svsb->mtdes  = svs->efuse[5] & 0xff;
+
+                       if (ft_pgm >= 2) {
+                               svsb->freq_base = 800000000; /* 800MHz */
+                               svsb->dvt_fixed = 2;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       for (i = 0; i < svsp->efuse_num; i++) {
+               if (svs->efuse[i])
+                       pr_notice("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]);
+       }
+
+       /* thermal efuse parsing */
+       if (!svs->thermal_efuse)
+               return 0;
+
+       tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & 0x3ff;
+       tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & 0x3ff;
+
+       tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & 0x1ff;
+       tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & 0x1ff;
+       tp.o_vtsmcu3 = svs->thermal_efuse[1] & 0x1ff;
+       tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & 0x1ff;
+       tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & 0x1ff;
+       tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & 0x1ff;
+
+       tp.degc_cali = (svs->thermal_efuse[0] >> 1) & 0x3f;
+       tp.adc_cali_en_t = svs->thermal_efuse[0] & BIT(0);
+       tp.o_slope_sign = (svs->thermal_efuse[0] >> 7) & BIT(0);
+
+       tp.ts_id = (svs->thermal_efuse[1] >> 9) & BIT(0);
+       tp.o_slope = (svs->thermal_efuse[0] >> 26) & 0x3f;
+
+       if (tp.adc_cali_en_t == 1) {
+               if (tp.ts_id == 0)
+                       tp.o_slope = 0;
+
+               if ((tp.adc_ge_t < 265 || tp.adc_ge_t > 758) ||
+                   (tp.adc_oe_t < 265 || tp.adc_oe_t > 758) ||
+                   (tp.o_vtsmcu1 < -8 || tp.o_vtsmcu1 > 484) ||
+                   (tp.o_vtsmcu2 < -8 || tp.o_vtsmcu2 > 484) ||
+                   (tp.o_vtsmcu3 < -8 || tp.o_vtsmcu3 > 484) ||
+                   (tp.o_vtsmcu4 < -8 || tp.o_vtsmcu4 > 484) ||
+                   (tp.o_vtsmcu5 < -8 || tp.o_vtsmcu5 > 484) ||
+                   (tp.o_vtsabb < -8 || tp.o_vtsabb > 484) ||
+                   (tp.degc_cali < 1 || tp.degc_cali > 63)) {
+                       pr_err("bad thermal efuse data. disable mon mode\n");
+                       mon_mode_support = false;
+               }
+       } else {
+               pr_err("no thermal efuse data. disable mon mode\n");
+               mon_mode_support = false;
+       }
+
+       if (!mon_mode_support) {
+               for (i = 0; i < svsp->thermal_efuse_num; i++)
+                       pr_err("thermal_efuse[%u] = 0x%08x\n",
+                              i, svs->thermal_efuse[i]);
+
+               for (idx = 0; idx < svsp->bank_num; idx++) {
+                       svsb = &svsp->banks[idx];
+                       svsb->mon_mode_support = false;
+               }
+
+               return 0;
+       }
+
+       tp.ge = ((tp.adc_ge_t - 512) * 10000) / 4096;
+       tp.oe = (tp.adc_oe_t - 512);
+       tp.gain = (10000 + tp.ge);
+
+       format[0] = (tp.o_vtsmcu1 + 3350 - tp.oe);
+       format[1] = (tp.o_vtsmcu2 + 3350 - tp.oe);
+       format[2] = (tp.o_vtsmcu3 + 3350 - tp.oe);
+       format[3] = (tp.o_vtsmcu4 + 3350 - tp.oe);
+       format[4] = (tp.o_vtsmcu5 + 3350 - tp.oe);
+       format[5] = (tp.o_vtsabb + 3350 - tp.oe);
+
+       for (i = 0; i < 6; i++)
+               x_roomt[i] = (((format[i] * 10000) / 4096) * 10000) / tp.gain;
+
+       temp0 = (10000 * 100000 / tp.gain) * 15 / 18;
+
+       if (tp.o_slope_sign == 0)
+               mts = (temp0 * 10) / (1534 + tp.o_slope * 10);
+       else
+               mts = (temp0 * 10) / (1534 - tp.o_slope * 10);
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svsb->mts = mts;
+
+               switch (svsb->sw_id) {
+               case SVS_CPU_LITTLE:
+                       tb_roomt = x_roomt[3];
+                       break;
+               case SVS_CPU_BIG:
+                       tb_roomt = x_roomt[4];
+                       break;
+               case SVS_CCI:
+                       tb_roomt = x_roomt[3];
+                       break;
+               case SVS_GPU:
+                       tb_roomt = x_roomt[1];
+                       break;
+               default:
+                       pr_err("unknown svsb_id = %u? disable svs\n",
+                              svsb->sw_id);
+                       return -EINVAL;
+               }
+
+               temp0 = (tp.degc_cali * 10 / 2);
+               temp1 = ((10000 * 100000 / 4096 / tp.gain) *
+                        tp.oe + tb_roomt * 10) * 15 / 18;
+
+               if (tp.o_slope_sign == 0)
+                       temp2 = temp1 * 100 / (1534 + tp.o_slope * 10);
+               else
+                       temp2 = temp1 * 100 / (1534 - tp.o_slope * 10);
+
+               svsb->bts = (temp0 + temp2 - 250) * 4 / 10;
+       }
+
+       return 0;
+}
+
+static int svs_is_support(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       struct nvmem_cell *cell;
+       size_t len;
+       int ret;
+       u32 idx, i;
+
+       if (svsp->fake_efuse) {
+               len = svsp->efuse_num * 4;
+               svs->efuse = kzalloc(len, GFP_KERNEL);
+               if (!svs->efuse)
+                       return -ENOMEM;
+
+               len = svsp->thermal_efuse_num * 4;
+               svs->thermal_efuse = kzalloc(len, GFP_KERNEL);
+               if (!svs->thermal_efuse)
+                       return -ENOMEM;
+
+               goto svsp_efuse_parsing;
+       }
+
+       /* get svs efuse by nvmem */
+       cell = nvmem_cell_get(svs->dev, "svs-calibration-data");
+       if (IS_ERR(cell)) {
+               pr_err("no \"svs-calibration-data\" from dts? disable svs\n");
+               return PTR_ERR(cell);
+       }
+
+       svs->efuse = (u32 *)nvmem_cell_read(cell, &len);
+       nvmem_cell_put(cell);
+
+       ret = (svs->efuse[svsp->efuse_check] == 0) ? -EPERM : 0;
+       if (ret) {
+               pr_err("no svs efuse. disable svs.\n");
+               for (i = 0; i < svsp->efuse_num; i++)
+                       pr_err("M_HW_RES%d: 0x%08x\n", i, svs->efuse[i]);
+               return ret;
+       }
+
+       /* get thermal efuse by nvmem */
+       cell = nvmem_cell_get(svs->dev, "calibration-data");
+       if (IS_ERR(cell)) {
+               pr_err("no \"calibration-data\" from dts? disable mon mode\n");
+               svs->thermal_efuse = NULL;
+               for (idx = 0; idx < svsp->bank_num; idx++) {
+                       svsb = &svsp->banks[idx];
+                       svsb->mon_mode_support = false;
+               }
+               goto svsp_efuse_parsing;
+       }
+
+       svs->thermal_efuse = (u32 *)nvmem_cell_read(cell, &len);
+       nvmem_cell_put(cell);
+
+svsp_efuse_parsing:
+       ret = svsp->efuse_parsing(svs);
+
+       return ret;
+}
+
+static int svs_resource_setup(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       struct platform_device *pdev;
+       struct device_node *np = NULL;
+       struct dev_pm_opp *opp;
+       unsigned long freq;
+       size_t opp_size;
+       int count, ret;
+       u32 idx, i;
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+
+               if (!svsb->init01_support)
+                       continue;
+
+               switch (svsb->sw_id) {
+               case SVS_CPU_LITTLE:
+                       svsb->name = "SVS_CPU_LITTLE";
+                       break;
+               case SVS_CPU_BIG:
+                       svsb->name = "SVS_CPU_BIG";
+                       break;
+               case SVS_CCI:
+                       svsb->name = "SVS_CCI";
+                       break;
+               case SVS_GPU:
+                       svsb->name = "SVS_GPU";
+                       break;
+               default:
+                       WARN_ON(1);
+                       return -EINVAL;
+               }
+
+               /* Add svs_bank device for opp-table/mtcmos/buck control */
+               pdev = platform_device_alloc(svsb->name, 0);
+               if (!pdev) {
+                       pr_err("%s: fail to alloc pdev for svs_bank\n",
+                              svsb->name);
+                       return -ENOMEM;
+               }
+
+               for_each_child_of_node(svs->dev->of_node, np) {
+                       if (of_device_is_compatible(np, svsb->of_compatible)) {
+                               pdev->dev.of_node = np;
+                               break;
+                       }
+               }
+
+               ret = platform_device_add(pdev);
+               if (ret) {
+                       pr_err("%s: fail to add svs_bank device: %d\n",
+                              svsb->name, ret);
+                       return ret;
+               }
+
+               svsb->dev = &pdev->dev;
+               dev_set_drvdata(svsb->dev, svs);
+               ret = dev_pm_opp_of_add_table(svsb->dev);
+               if (ret) {
+                       pr_err("%s: fail to add opp table: %d\n",
+                              svsb->name, ret);
+                       return ret;
+               }
+
+               mutex_init(&svsb->lock);
+
+               svsb->buck = devm_regulator_get_optional(svsb->dev,
+                                                        svsb->buck_name);
+               if (IS_ERR(svsb->buck)) {
+                       pr_err("%s: cannot get regulator \"%s-supply\"\n",
+                              svsb->name, svsb->buck_name);
+                       return PTR_ERR(svsb->buck);
+               }
+
+               count = dev_pm_opp_get_opp_count(svsb->dev);
+               if (svsb->opp_count != count) {
+                       pr_err("%s: opp_count not \"%u\" but get \"%d\"?\n",
+                              svsb->name, svsb->opp_count, count);
+                       return count;
+               }
+
+               opp_size = 4 * svsb->opp_count;
+               svsb->opp_volts = kmalloc(opp_size, GFP_KERNEL);
+               if (!svsb->opp_volts)
+                       return -ENOMEM;
+
+               svsb->init02_volts = kmalloc(opp_size, GFP_KERNEL);
+               if (!svsb->init02_volts)
+                       return -ENOMEM;
+
+               svsb->volts = kmalloc(opp_size, GFP_KERNEL);
+               if (!svsb->volts)
+                       return -ENOMEM;
+
+               svsb->opp_freqs = kmalloc(opp_size, GFP_KERNEL);
+               if (!svsb->opp_freqs)
+                       return -ENOMEM;
+
+               svsb->freqs_pct = kmalloc(opp_size, GFP_KERNEL);
+               if (!svsb->freqs_pct)
+                       return -ENOMEM;
+
+               for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
+                       opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq);
+                       if (IS_ERR(opp)) {
+                               pr_err("%s: error opp entry!!, err = %ld\n",
+                                      svsb->name, PTR_ERR(opp));
+                               return PTR_ERR(opp);
+                       }
+
+                       svsb->opp_freqs[i] = freq;
+                       svsb->opp_volts[i] = dev_pm_opp_get_voltage(opp);
+                       svsb->freqs_pct[i] = percent(svsb->opp_freqs[i],
+                                                    svsb->freq_base) & 0xff;
+               }
+       }
+
+       return 0;
+}
+
+static int svs_suspend(struct device *dev)
+{
+       struct mtk_svs *svs = dev_get_drvdata(dev);
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       unsigned long flags;
+       u32 idx;
+
+       /* Wait if there is processing svs_isr(). Suspend all banks. */
+       flags = claim_mtk_svs_lock();
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svs->bank = svsb;
+               svs_switch_bank(svs);
+               svs_writel(svs, 0x0, SVSEN);
+               svs_writel(svs, 0x00ffffff, INTSTS);
+               svsb->suspended = true;
+       }
+       release_mtk_svs_lock(flags);
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               if (svsb->phase == SVS_PHASE_MON) {
+                       svsb->phase = SVS_PHASE_INIT02;
+                       svs_set_volts(svsb, true);
+               }
+       }
+
+       clk_disable_unprepare(svs->main_clk);
+
+       return 0;
+}
+
+static int svs_resume(struct device *dev)
+{
+       struct mtk_svs *svs = dev_get_drvdata(dev);
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       int ret;
+       u32 idx;
+
+       ret = clk_prepare_enable(svs->main_clk);
+       if (ret)
+               pr_err("%s(): cannot enable main_clk\n", __func__);
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+               svsb->suspended = false;
+       }
+
+       ret = svs_init02(svs);
+       if (ret)
+               return ret;
+
+       svs_mon_mode(svs);
+
+       return 0;
+}
+
+static int svs_debug_proc_show(struct seq_file *m, void *v)
+{
+       struct svs_bank *svsb = (struct svs_bank *)m->private;
+
+       if (svsb->phase == SVS_PHASE_INIT01)
+               seq_puts(m, "init1\n");
+       else if (svsb->phase == SVS_PHASE_INIT02)
+               seq_puts(m, "init2\n");
+       else if (svsb->phase == SVS_PHASE_MON)
+               seq_puts(m, "mon mode\n");
+       else if (svsb->phase == SVS_PHASE_ERROR)
+               seq_puts(m, "disabled\n");
+       else
+               seq_puts(m, "unknown\n");
+
+       return 0;
+}
+
+static ssize_t svs_debug_proc_write(struct file *file,
+                                   const char __user *buffer,
+                                   size_t count, loff_t *pos)
+{
+       struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file));
+       struct mtk_svs *svs = dev_get_drvdata(svsb->dev);
+       char *buf = (char *)__get_free_page(GFP_USER);
+       unsigned long flags;
+       int enabled, ret;
+
+       if (svsb->phase == SVS_PHASE_ERROR)
+               return count;
+
+       if (!buf)
+               return -ENOMEM;
+
+       if (count >= PAGE_SIZE) {
+               free_page((unsigned long)buf);
+               return -EINVAL;
+       }
+
+       if (copy_from_user(buf, buffer, count)) {
+               free_page((unsigned long)buf);
+               return -EFAULT;
+       }
+
+       buf[count] = '\0';
+
+       ret = kstrtoint(buf, 10, &enabled);
+       if (ret)
+               return ret;
+
+       if (!enabled) {
+               flags = claim_mtk_svs_lock();
+               svs->bank = svsb;
+
+               svsb->init01_support = false;
+               svsb->init02_support = false;
+               svsb->mon_mode_support = false;
+
+               svs_switch_bank(svs);
+               svs_writel(svs, 0x0, SVSEN);
+               svs_writel(svs, 0x00ffffff, INTSTS);
+               release_mtk_svs_lock(flags);
+       }
+
+       svsb->phase = SVS_PHASE_ERROR;
+       svs_set_volts(svsb, true);
+
+       return count;
+}
+
+proc_fops_rw(svs_debug);
+
+static int svs_dump_proc_show(struct seq_file *m, void *v)
+{
+       struct mtk_svs *svs = (struct mtk_svs *)m->private;
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       unsigned long svs_reg_addr;
+       u32 idx, i, j;
+
+       for (i = 0; i < svsp->efuse_num; i++) {
+               if (svs->efuse[i])
+                       seq_printf(m, "M_HW_RES%d = 0x%08x\n",
+                                  i, svs->efuse[i]);
+       }
+
+       for (i = 0; i < svsp->thermal_efuse_num; i++) {
+               if (svs->thermal_efuse && svs->thermal_efuse[i])
+                       seq_printf(m, "THERMAL_EFUSE%d = 0x%08x\n",
+                                  i, svs->thermal_efuse[i]);
+       }
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+
+               if (!svsb->init01_support)
+                       continue;
+
+               for (i = SVS_PHASE_INIT01; i <= SVS_PHASE_MON; i++) {
+                       seq_printf(m, "Bank_number = %u\n", svsb->hw_id);
+
+                       if (i < SVS_PHASE_MON)
+                               seq_printf(m, "mode = init%d\n", i + 1);
+                       else
+                               seq_puts(m, "mode = mon\n");
+
+                       for (j = TEMPMONCTL0; j < reg_num; j++) {
+                               svs_reg_addr = (unsigned long)(svs->base +
+                                                              svsp->regs[j]);
+                               seq_printf(m, "0x%08lx = 0x%08x\n",
+                                          svs_reg_addr, svsb->reg_data[i][j]);
+                       }
+               }
+       }
+
+       return 0;
+}
+
+proc_fops_ro(svs_dump);
+
+static int svs_status_proc_show(struct seq_file *m, void *v)
+{
+       struct svs_bank *svsb = (struct svs_bank *)m->private;
+       struct dev_pm_opp *opp;
+       unsigned long freq;
+       int zone_temp, ret;
+       u32 i;
+
+       ret = svs_get_zone_temperature(svsb, &zone_temp);
+       if (ret)
+               seq_printf(m, "%s: cannot get zone \"%s\" temperature\n",
+                          svsb->name, svsb->zone_name);
+       else
+               seq_printf(m, "%s: temperature = %d\n", svsb->name, zone_temp);
+
+       for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
+               opp = dev_pm_opp_find_freq_floor(svsb->dev, &freq);
+               if (IS_ERR(opp)) {
+                       seq_printf(m, "%s: error opp entry!!, err = %ld\n",
+                                  svsb->name, PTR_ERR(opp));
+                       return PTR_ERR(opp);
+               }
+
+               seq_printf(m, "opp_freqs[%02u]: %lu, volts[%02u]: %lu, ",
+                          i, freq, i, dev_pm_opp_get_voltage(opp));
+               seq_printf(m, "svsb_volts[%02u]: 0x%x, freqs_pct[%02u]: %u\n",
+                          i, svsb->volts[i], i, svsb->freqs_pct[i]);
+       }
+
+       return 0;
+}
+
+proc_fops_ro(svs_status);
+
+static int svs_volt_offset_proc_show(struct seq_file *m, void *v)
+{
+       struct svs_bank *svsb = (struct svs_bank *)m->private;
+
+       seq_printf(m, "%d\n", svsb->volt_offset);
+
+       return 0;
+}
+
+static ssize_t svs_volt_offset_proc_write(struct file *file,
+                                         const char __user *buffer,
+                                         size_t count, loff_t *pos)
+{
+       struct svs_bank *svsb = (struct svs_bank *)PDE_DATA(file_inode(file));
+       char *buf = (char *)__get_free_page(GFP_USER);
+       int ret, volt_offset;
+
+       if (!buf)
+               return -ENOMEM;
+
+       if (count >= PAGE_SIZE) {
+               free_page((unsigned long)buf);
+               return -EINVAL;
+       }
+
+       if (copy_from_user(buf, buffer, count)) {
+               free_page((unsigned long)buf);
+               return -EFAULT;
+       }
+
+       buf[count] = '\0';
+
+       if (!kstrtoint(buf, 10, &volt_offset)) {
+               svsb->volt_offset = volt_offset;
+               ret = svs_set_volts(svsb, true);
+               if (ret)
+                       return ret;
+       }
+
+       return count;
+}
+
+proc_fops_rw(svs_volt_offset);
+
+static int svs_create_svs_procfs(struct mtk_svs *svs)
+{
+       const struct svs_platform *svsp = svs->platform;
+       struct svs_bank *svsb;
+       struct proc_dir_entry *svs_dir, *bank_dir;
+       u32 idx, i;
+
+       struct pentry {
+               const char *name;
+               const struct file_operations *fops;
+       };
+
+       struct pentry svs_entries[] = {
+               proc_entry(svs_dump),
+       };
+
+       struct pentry bank_entries[] = {
+               proc_entry(svs_debug),
+               proc_entry(svs_status),
+               proc_entry(svs_volt_offset),
+       };
+
+       svs_dir = proc_mkdir("svs", NULL);
+       if (!svs_dir) {
+               pr_err("mkdir /proc/svs failed\n");
+               return -EPERM;
+       }
+
+       for (i = 0; i < ARRAY_SIZE(svs_entries); i++) {
+               if (!proc_create_data(svs_entries[i].name, 0664,
+                                     svs_dir, svs_entries[i].fops, svs)) {
+                       pr_err("create /proc/svs/%s failed\n",
+                              svs_entries[i].name);
+                       return -EPERM;
+               }
+       }
+
+       for (idx = 0; idx < svsp->bank_num; idx++) {
+               svsb = &svsp->banks[idx];
+
+               if (!svsb->init01_support)
+                       continue;
+
+               bank_dir = proc_mkdir(svsb->name, svs_dir);
+               if (!bank_dir) {
+                       pr_err("mkdir /proc/svs/%s failed\n", svsb->name);
+                       return -EPERM;
+               }
+
+               for (i = 0; i < ARRAY_SIZE(bank_entries); i++) {
+                       if (!proc_create_data(bank_entries[i].name, 0664,
+                                             bank_dir, bank_entries[i].fops,
+                                             svsb)) {
+                               pr_err("create /proc/svs/%s/%s failed\n",
+                                      svsb->name, bank_entries[i].name);
+                               return -EPERM;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+struct svs_bank_ops svs_mt8183_banks_ops = {
+       .set_freqs_pct  = svs_set_freqs_pct_v2,
+       .get_vops       = svs_get_vops_v2,
+};
+
+struct svs_bank svs_mt8183_banks[4] = {
+       {
+               .of_compatible          = "mediatek,mt8183-svs-cpu-little",
+               .sw_id                  = SVS_CPU_LITTLE,
+               .hw_id                  = 0,
+               .ops                    = &svs_mt8183_banks_ops,
+               .zone_name              = "tzts4",
+               .buck_name              = "vcpu-little",
+               .mtcmos_request         = false,
+               .init01_volt_flag       = SVS_INIT01_VOLT_INC_ONLY,
+               .init01_support         = true,
+               .init02_support         = true,
+               .mon_mode_support       = false,
+               .opp_count              = 16,
+               .freq_base              = 1989000000,
+               .vboot                  = 0x30,
+               .volt_step              = 6250,
+               .volt_base              = 500000,
+               .volt_offset            = 0,
+               .vmax                   = 0x64,
+               .vmin                   = 0x18,
+               .dthi                   = 0x1,
+               .dtlo                   = 0xfe,
+               .det_window             = 0xa28,
+               .det_max                = 0xffff,
+               .age_config             = 0x555555,
+               .agem                   = 0x0,
+               .dc_config              = 0x555555,
+               .dvt_fixed              = 0x7,
+               .vco                    = 0x10,
+               .chkshift               = 0x77,
+               .upper_temp_bound       = 0x64,
+               .lower_temp_bound       = 0xb2,
+               .low_temp_threashold    = 25000,
+               .low_temp_offset        = 0,
+               .coresel                = 0x8fff0000,
+               .systemclk_en           = BIT(31),
+               .intst                  = BIT(0),
+               .ctl0                   = 0x00010001,
+       },
+       {
+               .of_compatible          = "mediatek,mt8183-svs-cpu-big",
+               .sw_id                  = SVS_CPU_BIG,
+               .hw_id                  = 1,
+               .ops                    = &svs_mt8183_banks_ops,
+               .zone_name              = "tzts5",
+               .buck_name              = "vcpu-big",
+               .mtcmos_request         = false,
+               .init01_volt_flag       = SVS_INIT01_VOLT_INC_ONLY,
+               .init01_support         = true,
+               .init02_support         = true,
+               .mon_mode_support       = false,
+               .opp_count              = 16,
+               .freq_base              = 1989000000,
+               .vboot                  = 0x30,
+               .volt_step              = 6250,
+               .volt_base              = 500000,
+               .volt_offset            = 0,
+               .vmax                   = 0x58,
+               .vmin                   = 0x10,
+               .dthi                   = 0x1,
+               .dtlo                   = 0xfe,
+               .det_window             = 0xa28,
+               .det_max                = 0xffff,
+               .age_config             = 0x555555,
+               .agem                   = 0x0,
+               .dc_config              = 0x555555,
+               .dvt_fixed              = 0x7,
+               .vco                    = 0x10,
+               .chkshift               = 0x77,
+               .upper_temp_bound       = 0x64,
+               .lower_temp_bound       = 0xb2,
+               .low_temp_threashold    = 25000,
+               .low_temp_offset        = 0,
+               .coresel                = 0x8fff0001,
+               .systemclk_en           = BIT(31),
+               .intst                  = BIT(1),
+               .ctl0                   = 0x00000001,
+       },
+       {
+               .of_compatible          = "mediatek,mt8183-svs-cci",
+               .sw_id                  = SVS_CCI,
+               .hw_id                  = 2,
+               .ops                    = &svs_mt8183_banks_ops,
+               .zone_name              = "tzts4",
+               .buck_name              = "vcci",
+               .mtcmos_request         = false,
+               .init01_volt_flag       = SVS_INIT01_VOLT_INC_ONLY,
+               .init01_support         = true,
+               .init02_support         = true,
+               .mon_mode_support       = false,
+               .opp_count              = 16,
+               .freq_base              = 1196000000,
+               .vboot                  = 0x30,
+               .volt_step              = 6250,
+               .volt_base              = 500000,
+               .volt_offset            = 0,
+               .vmax                   = 0x64,
+               .vmin                   = 0x18,
+               .dthi                   = 0x1,
+               .dtlo                   = 0xfe,
+               .det_window             = 0xa28,
+               .det_max                = 0xffff,
+               .age_config             = 0x555555,
+               .agem                   = 0x0,
+               .dc_config              = 0x555555,
+               .dvt_fixed              = 0x7,
+               .vco                    = 0x10,
+               .chkshift               = 0x77,
+               .upper_temp_bound       = 0x64,
+               .lower_temp_bound       = 0xb2,
+               .low_temp_threashold    = 25000,
+               .low_temp_offset        = 0,
+               .coresel                = 0x8fff0002,
+               .systemclk_en           = BIT(31),
+               .intst                  = BIT(2),
+               .ctl0                   = 0x00100003,
+       },
+       {
+               .of_compatible          = "mediatek,mt8183-svs-gpu",
+               .sw_id                  = SVS_GPU,
+               .hw_id                  = 3,
+               .ops                    = &svs_mt8183_banks_ops,
+               .zone_name              = "tzts2",
+               .buck_name              = "vgpu",
+               .mtcmos_request         = true,
+               .init01_volt_flag       = SVS_INIT01_VOLT_INC_ONLY,
+               .init01_support         = true,
+               .init02_support         = true,
+               .mon_mode_support       = true,
+               .opp_count              = 16,
+               .freq_base              = 900000000,
+               .vboot                  = 0x30,
+               .volt_step              = 6250,
+               .volt_base              = 500000,
+               .volt_offset            = 0,
+               .vmax                   = 0x40,
+               .vmin                   = 0x14,
+               .dthi                   = 0x1,
+               .dtlo                   = 0xfe,
+               .det_window             = 0xa28,
+               .det_max                = 0xffff,
+               .age_config             = 0x555555,
+               .agem                   = 0x0,
+               .dc_config              = 0x555555,
+               .dvt_fixed              = 0x3,
+               .vco                    = 0x10,
+               .chkshift               = 0x77,
+               .upper_temp_bound       = 0x64,
+               .lower_temp_bound       = 0xb2,
+               .low_temp_threashold    = 25000,
+               .low_temp_offset        = 3,
+               .coresel                = 0x8fff0003,
+               .systemclk_en           = BIT(31),
+               .intst                  = BIT(3),
+               .ctl0                   = 0x00050001,
+       },
+};
+
+static const struct svs_platform svs_mt8183_platform = {
+       .name           = "mt8183-svs",
+       .banks          = svs_mt8183_banks,
+       .efuse_parsing  = svs_mt8183_efuse_parsing,
+       .regs           = svs_regs_v2,
+       .fake_efuse     = false,
+       .bank_num       = 4,
+       .efuse_num      = 25,
+       .efuse_check    = 2,
+       .thermal_efuse_num = 3,
+};
+
+static const struct of_device_id mtk_svs_of_match[] = {
+       {
+               .compatible = "mediatek,mt8183-svs",
+               .data = &svs_mt8183_platform,
+       }, {
+               /* sentinel */
+       },
+};
+
+static int svs_probe(struct platform_device *pdev)
+{
+       const struct of_device_id *of_dev_id;
+       struct mtk_svs *svs;
+       int ret;
+       u32 svs_irq;
+
+       svs = devm_kzalloc(&pdev->dev, sizeof(*svs), GFP_KERNEL);
+       if (!svs)
+               return -ENOMEM;
+
+       svs->dev = &pdev->dev;
+       if (!svs->dev->of_node) {
+               pr_err("cannot find device node\n");
+               return -ENODEV;
+       }
+
+       svs->base = of_iomap(svs->dev->of_node, 0);
+       if (IS_ERR(svs->base)) {
+               pr_err("cannot find svs register base\n");
+               return PTR_ERR(svs->base);
+       }
+
+       svs_irq = irq_of_parse_and_map(svs->dev->of_node, 0);
+       ret = devm_request_threaded_irq(svs->dev, svs_irq, NULL, svs_isr,
+                                       IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+                                       "mtk-svs", svs);
+       if (ret) {
+               pr_err("register irq(%d) failed: %d\n", svs_irq, ret);
+               return ret;
+       }
+
+       of_dev_id = of_match_node(mtk_svs_of_match, svs->dev->of_node);
+       if (!of_dev_id || !of_dev_id->data)
+               return -EINVAL;
+
+       svs->platform = of_dev_id->data;
+       dev_set_drvdata(svs->dev, svs);
+
+       svs->main_clk = devm_clk_get(svs->dev, "main_clk");
+       if (IS_ERR(svs->main_clk)) {
+               pr_err("failed to get clock: %ld\n", PTR_ERR(svs->main_clk));
+               return PTR_ERR(svs->main_clk);
+       }
+
+       ret = clk_prepare_enable(svs->main_clk);
+       if (ret) {
+               pr_err("cannot enable main_clk: %d\n", ret);
+               return ret;
+       }
+
+       ret = svs_is_support(svs);
+       if (ret)
+               goto svs_probe_fail;
+
+       ret = svs_resource_setup(svs);
+       if (ret)
+               goto svs_probe_fail;
+
+       ret = svs_start(svs);
+       if (ret)
+               goto svs_probe_fail;
+
+       ret = svs_create_svs_procfs(svs);
+       if (ret)
+               goto svs_probe_fail;
+
+       return 0;
+
+svs_probe_fail:
+       clk_disable_unprepare(svs->main_clk);
+
+       return ret;
+}
+
+static const struct dev_pm_ops svs_pm_ops = {
+       .suspend        = svs_suspend,
+       .resume         = svs_resume,
+};
+
+static struct platform_driver svs_driver = {
+       .probe  = svs_probe,
+       .driver = {
+               .name           = "mtk-svs",
+               .pm             = &svs_pm_ops,
+               .of_match_table = of_match_ptr(mtk_svs_of_match),
+       },
+};
+
+static int __init svs_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&svs_driver);
+       if (ret) {
+               pr_err("svs platform driver register failed: %d\n", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+late_initcall_sync(svs_init);
+
+MODULE_DESCRIPTION("MediaTek SVS Driver v1.0");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/power/mtk_svs.h b/include/linux/power/mtk_svs.h
new file mode 100644
index 000000000000..d5efca8d9dca
--- /dev/null
+++ b/include/linux/power/mtk_svs.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2018 MediaTek Inc.
+ */
+
+#ifndef __MTK_SVS_H__
+#define __MTK_SVS_H__
+
+#if defined(CONFIG_MTK_SVS)
+unsigned long claim_mtk_svs_lock(void);
+void release_mtk_svs_lock(unsigned long flags);
+#else
+static inline unsigned long claim_mtk_svs_lock(void)
+{
+       return 0;
+}
+
+static inline void release_mtk_svs_lock(unsigned long flags)
+{
+}
+#endif /* CONFIG_MTK_SVS */
+
+#endif /* __MTK_SVS_H__ */
-- 
2.18.0

Reply via email to