Contributors: 11
Author Tokens Token Proportion Commits Commit Proportion
Roger Lu 9321 62.55% 12 25.53%
Angelo G. Del Regno 3336 22.39% 21 44.68%
Mark Tseng 2179 14.62% 4 8.51%
Matthias Brugger 33 0.22% 3 6.38%
Ricardo Ribalda Delgado 9 0.06% 1 2.13%
Peng Wu 9 0.06% 1 2.13%
Zeng Heng 7 0.05% 1 2.13%
Rob Herring 3 0.02% 1 2.13%
Nícolas F. R. A. Prado 3 0.02% 1 2.13%
Nathan Chancellor 1 0.01% 1 2.13%
Shang XiaoJing 1 0.01% 1 2.13%
Total 14902 47


// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2022 MediaTek Inc.
 * Copyright (C) 2022 Collabora Ltd.
 *                    AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
 */

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/cpu.h>
#include <linux/cpuidle.h>
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.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/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/thermal.h>

/* svs bank mode support */
#define SVSB_MODE_ALL_DISABLE		0
#define SVSB_MODE_INIT01		BIT(1)
#define SVSB_MODE_INIT02		BIT(2)
#define SVSB_MODE_MON			BIT(3)

/* svs bank volt flags */
#define SVSB_INIT01_PD_REQ		BIT(0)
#define SVSB_INIT01_VOLT_IGNORE		BIT(1)
#define SVSB_INIT01_VOLT_INC_ONLY	BIT(2)
#define SVSB_MON_VOLT_IGNORE		BIT(16)
#define SVSB_REMOVE_DVTFIXED_VOLT	BIT(24)

/* svs bank register fields and common configuration */
#define SVSB_PTPCONFIG_DETMAX		GENMASK(15, 0)
#define SVSB_DET_MAX			FIELD_PREP(SVSB_PTPCONFIG_DETMAX, 0xffff)
#define SVSB_DET_WINDOW			0xa28

/* DESCHAR */
#define SVSB_DESCHAR_FLD_MDES		GENMASK(7, 0)
#define SVSB_DESCHAR_FLD_BDES		GENMASK(15, 8)

/* TEMPCHAR */
#define SVSB_TEMPCHAR_FLD_DVT_FIXED	GENMASK(7, 0)
#define SVSB_TEMPCHAR_FLD_MTDES		GENMASK(15, 8)
#define SVSB_TEMPCHAR_FLD_VCO		GENMASK(23, 16)

/* DETCHAR */
#define SVSB_DETCHAR_FLD_DCMDET		GENMASK(7, 0)
#define SVSB_DETCHAR_FLD_DCBDET		GENMASK(15, 8)

/* SVSEN (PTPEN) */
#define SVSB_PTPEN_INIT01		BIT(0)
#define SVSB_PTPEN_MON			BIT(1)
#define SVSB_PTPEN_INIT02		(SVSB_PTPEN_INIT01 | BIT(2))
#define SVSB_PTPEN_OFF			0x0

/* FREQPCTS */
#define SVSB_FREQPCTS_FLD_PCT0_4	GENMASK(7, 0)
#define SVSB_FREQPCTS_FLD_PCT1_5	GENMASK(15, 8)
#define SVSB_FREQPCTS_FLD_PCT2_6	GENMASK(23, 16)
#define SVSB_FREQPCTS_FLD_PCT3_7	GENMASK(31, 24)

/* INTSTS */
#define SVSB_INTSTS_VAL_CLEAN		0x00ffffff
#define SVSB_INTSTS_F0_COMPLETE		BIT(0)
#define SVSB_INTSTS_FLD_MONVOP		GENMASK(23, 16)
#define SVSB_RUNCONFIG_DEFAULT		0x80000000

/* LIMITVALS */
#define SVSB_LIMITVALS_FLD_DTLO		GENMASK(7, 0)
#define SVSB_LIMITVALS_FLD_DTHI		GENMASK(15, 8)
#define SVSB_LIMITVALS_FLD_VMIN		GENMASK(23, 16)
#define SVSB_LIMITVALS_FLD_VMAX		GENMASK(31, 24)
#define SVSB_VAL_DTHI			0x1
#define SVSB_VAL_DTLO			0xfe

/* INTEN */
#define SVSB_INTEN_F0EN			BIT(0)
#define SVSB_INTEN_DACK0UPEN		BIT(8)
#define SVSB_INTEN_DC0EN		BIT(9)
#define SVSB_INTEN_DC1EN		BIT(10)
#define SVSB_INTEN_DACK0LOEN		BIT(11)
#define SVSB_INTEN_INITPROD_OVF_EN	BIT(12)
#define SVSB_INTEN_INITSUM_OVF_EN	BIT(14)
#define SVSB_INTEN_MONVOPEN		GENMASK(23, 16)
#define SVSB_INTEN_INIT0x		(SVSB_INTEN_F0EN | SVSB_INTEN_DACK0UPEN |	\
					 SVSB_INTEN_DC0EN | SVSB_INTEN_DC1EN |		\
					 SVSB_INTEN_DACK0LOEN |				\
					 SVSB_INTEN_INITPROD_OVF_EN |			\
					 SVSB_INTEN_INITSUM_OVF_EN)

/* TSCALCS */
#define SVSB_TSCALCS_FLD_MTS		GENMASK(11, 0)
#define SVSB_TSCALCS_FLD_BTS		GENMASK(23, 12)

/* INIT2VALS */
#define SVSB_INIT2VALS_FLD_DCVOFFSETIN	GENMASK(15, 0)
#define SVSB_INIT2VALS_FLD_AGEVOFFSETIN	GENMASK(31, 16)

/* VOPS */
#define SVSB_VOPS_FLD_VOP0_4		GENMASK(7, 0)
#define SVSB_VOPS_FLD_VOP1_5		GENMASK(15, 8)
#define SVSB_VOPS_FLD_VOP2_6		GENMASK(23, 16)
#define SVSB_VOPS_FLD_VOP3_7		GENMASK(31, 24)

/* SVS Thermal Coefficients */
#define SVSB_TS_COEFF_MT8195		250460
#define SVSB_TS_COEFF_MT8186		204650

/* Algo helpers */
#define FUSE_DATA_NOT_VALID		U32_MAX

/* svs bank related setting */
#define BITS8				8
#define MAX_OPP_ENTRIES			16
#define REG_BYTES			4
#define SVSB_DC_SIGNED_BIT		BIT(15)
#define SVSB_DET_CLK_EN			BIT(31)
#define SVSB_TEMP_LOWER_BOUND		0xb2
#define SVSB_TEMP_UPPER_BOUND		0x64

static DEFINE_SPINLOCK(svs_lock);

#ifdef CONFIG_DEBUG_FS
#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}
#endif

/**
 * enum svsb_sw_id - SVS Bank Software ID
 * @SVSB_SWID_CPU_LITTLE: CPU little cluster Bank
 * @SVSB_SWID_CPU_BIG:    CPU big cluster Bank
 * @SVSB_SWID_CCI:        Cache Coherent Interconnect Bank
 * @SVSB_SWID_GPU:        GPU Bank
 * @SVSB_SWID_MAX:        Total number of Banks
 */
enum svsb_sw_id {
	SVSB_SWID_CPU_LITTLE,
	SVSB_SWID_CPU_BIG,
	SVSB_SWID_CCI,
	SVSB_SWID_GPU,
	SVSB_SWID_MAX
};

/**
 * enum svsb_type - SVS Bank 2-line: Type and Role
 * @SVSB_TYPE_NONE: One-line type Bank - Global role
 * @SVSB_TYPE_LOW:  Two-line type Bank - Low bank role
 * @SVSB_TYPE_HIGH: Two-line type Bank - High bank role
 * @SVSB_TYPE_MAX:  Total number of bank types
 */
enum svsb_type {
	SVSB_TYPE_NONE,
	SVSB_TYPE_LOW,
	SVSB_TYPE_HIGH,
	SVSB_TYPE_MAX
};

/**
 * enum svsb_phase - svs bank phase enumeration
 * @SVSB_PHASE_ERROR: svs bank encounters unexpected condition
 * @SVSB_PHASE_INIT01: svs bank basic init for data calibration
 * @SVSB_PHASE_INIT02: svs bank can provide voltages to opp table
 * @SVSB_PHASE_MON: svs bank can provide voltages with thermal effect
 * @SVSB_PHASE_MAX: total number of svs bank phase (debug purpose)
 *
 * Each svs bank has its own independent phase and we enable each svs bank by
 * running their phase orderly. However, when svs bank encounters unexpected
 * condition, it will fire an irq (PHASE_ERROR) to inform svs software.
 *
 * svs bank general phase-enabled order:
 * SVSB_PHASE_INIT01 -> SVSB_PHASE_INIT02 -> SVSB_PHASE_MON
 */
enum svsb_phase {
	SVSB_PHASE_ERROR = 0,
	SVSB_PHASE_INIT01,
	SVSB_PHASE_INIT02,
	SVSB_PHASE_MON,
	SVSB_PHASE_MAX,
};

enum svs_reg_index {
	DESCHAR = 0,
	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,
	SVS_REG_MAX,
};

static const u32 svs_regs_v2[] = {
	[DESCHAR]		= 0x00,
	[TEMPCHAR]		= 0x04,
	[DETCHAR]		= 0x08,
	[AGECHAR]		= 0x0c,
	[DCCONFIG]		= 0x10,
	[AGECONFIG]		= 0x14,
	[FREQPCT30]		= 0x18,
	[FREQPCT74]		= 0x1c,
	[LIMITVALS]		= 0x20,
	[VBOOT]			= 0x24,
	[DETWINDOW]		= 0x28,
	[CONFIG]		= 0x2c,
	[TSCALCS]		= 0x30,
	[RUNCONFIG]		= 0x34,
	[SVSEN]			= 0x38,
	[INIT2VALS]		= 0x3c,
	[DCVALUES]		= 0x40,
	[AGEVALUES]		= 0x44,
	[VOP30]			= 0x48,
	[VOP74]			= 0x4c,
	[TEMP]			= 0x50,
	[INTSTS]		= 0x54,
	[INTSTSRAW]		= 0x58,
	[INTEN]			= 0x5c,
	[CHKINT]		= 0x60,
	[CHKSHIFT]		= 0x64,
	[STATUS]		= 0x68,
	[VDESIGN30]		= 0x6c,
	[VDESIGN74]		= 0x70,
	[DVT30]			= 0x74,
	[DVT74]			= 0x78,
	[AGECOUNT]		= 0x7c,
	[SMSTATE0]		= 0x80,
	[SMSTATE1]		= 0x84,
	[CTL0]			= 0x88,
	[DESDETSEC]		= 0xe0,
	[TEMPAGESEC]		= 0xe4,
	[CTRLSPARE0]		= 0xf0,
	[CTRLSPARE1]		= 0xf4,
	[CTRLSPARE2]		= 0xf8,
	[CTRLSPARE3]		= 0xfc,
	[CORESEL]		= 0x300,
	[THERMINTST]		= 0x304,
	[INTST]			= 0x308,
	[THSTAGE0ST]		= 0x30c,
	[THSTAGE1ST]		= 0x310,
	[THSTAGE2ST]		= 0x314,
	[THAHBST0]		= 0x318,
	[THAHBST1]		= 0x31c,
	[SPARE0]		= 0x320,
	[SPARE1]		= 0x324,
	[SPARE2]		= 0x328,
	[SPARE3]		= 0x32c,
	[THSLPEVEB]		= 0x330,
};

static const char * const svs_swid_names[SVSB_SWID_MAX] = {
	"SVSB_CPU_LITTLE", "SVSB_CPU_BIG", "SVSB_CCI", "SVSB_GPU"
};

static const char * const svs_type_names[SVSB_TYPE_MAX] = {
	"", "_LOW", "_HIGH"
};

enum svs_fusemap_dev {
	BDEV_BDES,
	BDEV_MDES,
	BDEV_MTDES,
	BDEV_DCBDET,
	BDEV_DCMDET,
	BDEV_MAX
};

enum svs_fusemap_glb {
	GLB_FT_PGM,
	GLB_VMIN,
	GLB_MAX
};

struct svs_fusemap {
	s8 index;
	u8 ofst;
};

/**
 * struct svs_platform - svs platform control
 * @base: svs platform register base
 * @dev: svs platform device
 * @main_clk: main clock for svs bank
 * @banks: svs banks that svs platform supports
 * @rst: svs platform reset control
 * @efuse_max: total number of svs efuse
 * @tefuse_max: total number of thermal efuse
 * @regs: svs platform registers map
 * @efuse: svs efuse data received from NVMEM framework
 * @tefuse: thermal efuse data received from NVMEM framework
 * @ts_coeff: thermal sensors coefficient
 * @bank_max: total number of svs banks
 */
struct svs_platform {
	void __iomem *base;
	struct device *dev;
	struct clk *main_clk;
	struct svs_bank *banks;
	struct reset_control *rst;
	size_t efuse_max;
	size_t tefuse_max;
	const u32 *regs;
	u32 *efuse;
	u32 *tefuse;
	u32 ts_coeff;
	u16 bank_max;
};

struct svs_platform_data {
	char *name;
	struct svs_bank *banks;
	bool (*efuse_parsing)(struct svs_platform *svsp, const struct svs_platform_data *pdata);
	int (*probe)(struct svs_platform *svsp);
	const struct svs_fusemap *glb_fuse_map;
	const u32 *regs;
	u32 ts_coeff;
	u16 bank_max;
};

/**
 * struct svs_bank_pdata - SVS Bank immutable config parameters
 * @dev_fuse_map: Bank fuse map data
 * @buck_name: Regulator name
 * @tzone_name: Thermal zone name
 * @age_config: Bank age configuration
 * @ctl0: TS-x selection
 * @dc_config: Bank dc configuration
 * @int_st: Bank interrupt identification
 * @turn_freq_base: Reference frequency for 2-line turn point
 * @tzone_htemp: Thermal zone high temperature threshold
 * @tzone_ltemp: Thermal zone low temperature threshold
 * @volt_step: Bank voltage step
 * @volt_base: Bank voltage base
 * @tzone_htemp_voffset: Thermal zone high temperature voltage offset
 * @tzone_ltemp_voffset: Thermal zone low temperature voltage offset
 * @chk_shift: Bank chicken shift
 * @cpu_id: CPU core ID for SVS CPU bank use only
 * @opp_count: Bank opp count
 * @vboot: Voltage request for bank init01 only
 * @vco: Bank VCO value
 * @sw_id: Bank software identification
 * @type: SVS Bank Type (1 or 2-line) and Role (high/low)
 * @set_freq_pct: function pointer to set bank frequency percent table
 * @get_volts: function pointer to get bank voltages
 */
struct svs_bank_pdata {
	const struct svs_fusemap *dev_fuse_map;
	char *buck_name;
	char *tzone_name;
	u32 age_config;
	u32 ctl0;
	u32 dc_config;
	u32 int_st;
	u32 turn_freq_base;
	u32 tzone_htemp;
	u32 tzone_ltemp;
	u32 volt_step;
	u32 volt_base;
	u16 tzone_htemp_voffset;
	u16 tzone_ltemp_voffset;
	u8 chk_shift;
	u8 cpu_id;
	u8 opp_count;
	u8 vboot;
	u8 vco;
	u8 sw_id;
	u8 type;

	/* Callbacks */
	void (*set_freq_pct)(struct svs_platform *svsp, struct svs_bank *svsb);
	void (*get_volts)(struct svs_platform *svsp, struct svs_bank *svsb);
};

/**
 * struct svs_bank - svs bank representation
 * @pdata: SVS Bank immutable config parameters
 * @dev: bank device
 * @opp_dev: device for opp table/buck control
 * @init_completion: the timeout completion for bank init
 * @buck: regulator used by opp_dev
 * @tzd: thermal zone device for getting temperature
 * @lock: mutex lock to protect voltage update process
 * @name: bank 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
 * @opp_dfreq: default opp frequency table
 * @opp_dvolt: default opp voltage table
 * @freq_pct: frequency percent table for bank init
 * @volt: bank voltage table
 * @volt_flags: bank voltage flags
 * @vmax: bank voltage maximum
 * @vmin: bank voltage minimum
 * @age_voffset_in: bank age voltage offset
 * @dc_voffset_in: bank dc voltage offset
 * @dvt_fixed: bank dvt fixed value
 * @core_sel: bank selection
 * @temp: bank temperature
 * @bts: svs efuse data
 * @mts: svs efuse data
 * @bdes: svs efuse data
 * @mdes: svs efuse data
 * @mtdes: svs efuse data
 * @dcbdet: svs efuse data
 * @dcmdet: svs efuse data
 * @turn_pt: 2-line turn point tells which opp_volt calculated by high/low bank
 * @vbin_turn_pt: voltage bin turn point helps know which svsb_volt should be overridden
 *
 * Svs bank will generate suitable voltages by below general math equation
 * and provide these voltages to opp voltage table.
 *
 * opp_volt[i] = (volt[i] * volt_step) + volt_base;
 */
struct svs_bank {
	const struct svs_bank_pdata pdata;
	struct device *dev;
	struct device *opp_dev;
	struct completion init_completion;
	struct regulator *buck;
	struct thermal_zone_device *tzd;
	struct mutex lock;
	int pm_runtime_enabled_count;
	short int volt_od;
	char *name;
	enum svsb_phase phase;
	u32 reg_data[SVSB_PHASE_MAX][SVS_REG_MAX];
	u8 mode_support;
	u32 opp_dfreq[MAX_OPP_ENTRIES];
	u32 opp_dvolt[MAX_OPP_ENTRIES];
	u32 freq_pct[MAX_OPP_ENTRIES];
	u32 volt[MAX_OPP_ENTRIES];
	u32 volt_flags;
	u32 freq_base;
	u32 turn_pt;
	u32 vbin_turn_pt;
	u32 core_sel;
	u32 temp;
	u16 age_voffset_in;
	u16 dc_voffset_in;
	u8 dvt_fixed;
	u8 vmax;
	u8 vmin;
	u16 bts;
	u16 mts;
	u16 bdes;
	u16 mdes;
	u8 mtdes;
	u8 dcbdet;
	u8 dcmdet;
};

static u32 percent(u32 numerator, u32 denominator)
{
	/* If not divide 1000, "numerator * 100" will have data overflow. */
	numerator /= 1000;
	denominator /= 1000;

	return DIV_ROUND_UP(numerator * 100, denominator);
}

static u32 svs_readl_relaxed(struct svs_platform *svsp, enum svs_reg_index rg_i)
{
	return readl_relaxed(svsp->base + svsp->regs[rg_i]);
}

static void svs_writel_relaxed(struct svs_platform *svsp, u32 val,
			       enum svs_reg_index rg_i)
{
	writel_relaxed(val, svsp->base + svsp->regs[rg_i]);
}

static void svs_switch_bank(struct svs_platform *svsp, struct svs_bank *svsb)
{
	svs_writel_relaxed(svsp, svsb->core_sel, CORESEL);
}

static u32 svs_bank_volt_to_opp_volt(u32 svsb_volt, u32 svsb_volt_step,
				     u32 svsb_volt_base)
{
	return (svsb_volt * svsb_volt_step) + svsb_volt_base;
}

static u32 svs_opp_volt_to_bank_volt(u32 opp_u_volt, u32 svsb_volt_step,
				     u32 svsb_volt_base)
{
	return (opp_u_volt - svsb_volt_base) / svsb_volt_step;
}

static int svs_sync_bank_volts_from_opp(struct svs_bank *svsb)
{
	const struct svs_bank_pdata *bdata = &svsb->pdata;
	struct dev_pm_opp *opp;
	u32 i, opp_u_volt;

	for (i = 0; i < bdata->opp_count; i++) {
		opp = dev_pm_opp_find_freq_exact(svsb->opp_dev,
						 svsb->opp_dfreq[i],
						 true);
		if (IS_ERR(opp)) {
			dev_err(svsb->dev, "cannot find freq = %u (%ld)\n",
				svsb->opp_dfreq[i], PTR_ERR(opp));
			return PTR_ERR(opp);
		}

		opp_u_volt = dev_pm_opp_get_voltage(opp);
		svsb->volt[i] = svs_opp_volt_to_bank_volt(opp_u_volt,
							  bdata->volt_step,
							  bdata->volt_base);
		dev_pm_opp_put(opp);
	}

	return 0;
}

static int svs_adjust_pm_opp_volts(struct svs_bank *svsb)
{
	int ret = -EPERM, tzone_temp = 0;
	const struct svs_bank_pdata *bdata = &svsb->pdata;
	u32 i, svsb_volt, opp_volt, temp_voffset = 0, opp_start, opp_stop;

	mutex_lock(&svsb->lock);

	/*
	 * 2-line bank updates its corresponding opp volts.
	 * 1-line bank updates all opp volts.
	 */
	if (bdata->type == SVSB_TYPE_HIGH) {
		opp_start = 0;
		opp_stop = svsb->turn_pt;
	} else if (bdata->type == SVSB_TYPE_LOW) {
		opp_start = svsb->turn_pt;
		opp_stop = bdata->opp_count;
	} else {
		opp_start = 0;
		opp_stop = bdata->opp_count;
	}

	/* Get thermal effect */
	if (!IS_ERR_OR_NULL(svsb->tzd)) {
		ret = thermal_zone_get_temp(svsb->tzd, &tzone_temp);
		if (ret || (svsb->temp > SVSB_TEMP_UPPER_BOUND &&
			    svsb->temp < SVSB_TEMP_LOWER_BOUND)) {
			dev_err(svsb->dev, "%s: %d (0x%x), run default volts\n",
				bdata->tzone_name, ret, svsb->temp);
			svsb->phase = SVSB_PHASE_ERROR;
		}

		if (tzone_temp >= bdata->tzone_htemp)
			temp_voffset += bdata->tzone_htemp_voffset;
		else if (tzone_temp <= bdata->tzone_ltemp)
			temp_voffset += bdata->tzone_ltemp_voffset;

		/* 2-line bank update all opp volts when running mon mode */
		if (svsb->phase == SVSB_PHASE_MON && (bdata->type == SVSB_TYPE_HIGH ||
						      bdata->type == SVSB_TYPE_LOW)) {
			opp_start = 0;
			opp_stop = bdata->opp_count;
		}
	}

	/* vmin <= svsb_volt (opp_volt) <= default opp voltage */
	for (i = opp_start; i < opp_stop; i++) {
		switch (svsb->phase) {
		case SVSB_PHASE_ERROR:
			opp_volt = svsb->opp_dvolt[i];
			break;
		case SVSB_PHASE_INIT01:
			/* do nothing */
			goto unlock_mutex;
		case SVSB_PHASE_INIT02:
		case SVSB_PHASE_MON:
			svsb_volt = max(svsb->volt[i] + temp_voffset, svsb->vmin);
			opp_volt = svs_bank_volt_to_opp_volt(svsb_volt,
							     bdata->volt_step,
							     bdata->volt_base);
			break;
		default:
			dev_err(svsb->dev, "unknown phase: %u\n", svsb->phase);
			ret = -EINVAL;
			goto unlock_mutex;
		}

		opp_volt = min(opp_volt, svsb->opp_dvolt[i]);
		ret = dev_pm_opp_adjust_voltage(svsb->opp_dev,
						svsb->opp_dfreq[i],
						opp_volt, opp_volt,
						svsb->opp_dvolt[i]);
		if (ret) {
			dev_err(svsb->dev, "set %uuV fail: %d\n",
				opp_volt, ret);
			goto unlock_mutex;
		}
	}

unlock_mutex:
	mutex_unlock(&svsb->lock);

	return ret;
}

static void svs_bank_disable_and_restore_default_volts(struct svs_platform *svsp,
						       struct svs_bank *svsb)
{
	unsigned long flags;

	if (svsb->mode_support == SVSB_MODE_ALL_DISABLE)
		return;

	spin_lock_irqsave(&svs_lock, flags);
	svs_switch_bank(svsp, svsb);
	svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
	svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS);
	spin_unlock_irqrestore(&svs_lock, flags);

	svsb->phase = SVSB_PHASE_ERROR;
	svs_adjust_pm_opp_volts(svsb);
}

#ifdef CONFIG_DEBUG_FS
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);
	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) {
		svs_bank_disable_and_restore_default_volts(svsp, svsb);
		svsb->mode_support = SVSB_MODE_ALL_DISABLE;
	}

	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, vbin_turn_pt = %u, turn_pt = %u\n",
			   svsb->name, svsb->vbin_turn_pt, svsb->turn_pt);
	else
		seq_printf(m, "%s: temperature = %d, vbin_turn_pt = %u, turn_pt = %u\n",
			   svsb->name, tzone_temp, svsb->vbin_turn_pt,
			   svsb->turn_pt);

	for (i = 0; i < svsb->pdata.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;
}
#endif /* CONFIG_DEBUG_FS */

static u32 interpolate(u32 f0, u32 f1, u32 v0, u32 v1, u32 fx)
{
	u32 vx;

	if (v0 == v1 || f0 == f1)
		return v0;

	/* *100 to have decimal fraction factor */
	vx = (v0 * 100) - ((((v0 - v1) * 100) / (f0 - f1)) * (f0 - fx));

	return DIV_ROUND_UP(vx, 100);
}

static void svs_get_bank_volts_v3(struct svs_platform *svsp, struct svs_bank *svsb)
{
	const struct svs_bank_pdata *bdata = &svsb->pdata;
	u32 i, j, *vop, vop74, vop30, turn_pt = svsb->turn_pt;
	u32 b_sft, shift_byte = 0, opp_start = 0, opp_stop = 0;
	u32 middle_index = (bdata->opp_count / 2);

	if (svsb->phase == SVSB_PHASE_MON &&
	    svsb->volt_flags & SVSB_MON_VOLT_IGNORE)
		return;

	vop74 = svs_readl_relaxed(svsp, VOP74);
	vop30 = svs_readl_relaxed(svsp, VOP30);

	/* Target is to set svsb->volt[] by algorithm */
	if (turn_pt < middle_index) {
		if (bdata->type == SVSB_TYPE_HIGH) {
			/* volt[0] ~ volt[turn_pt - 1] */
			for (i = 0; i < turn_pt; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				vop = (shift_byte < REG_BYTES) ? &vop30 :
								 &vop74;
				svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
				shift_byte++;
			}
		} else if (bdata->type == SVSB_TYPE_LOW) {
			/* volt[turn_pt] + volt[j] ~ volt[opp_count - 1] */
			j = bdata->opp_count - 7;
			svsb->volt[turn_pt] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, vop30);
			shift_byte++;
			for (i = j; i < bdata->opp_count; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				vop = (shift_byte < REG_BYTES) ? &vop30 :
								 &vop74;
				svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
				shift_byte++;
			}

			/* volt[turn_pt + 1] ~ volt[j - 1] by interpolate */
			for (i = turn_pt + 1; i < j; i++)
				svsb->volt[i] = interpolate(svsb->freq_pct[turn_pt],
							    svsb->freq_pct[j],
							    svsb->volt[turn_pt],
							    svsb->volt[j],
							    svsb->freq_pct[i]);
		}
	} else {
		if (bdata->type == SVSB_TYPE_HIGH) {
			/* volt[0] + volt[j] ~ volt[turn_pt - 1] */
			j = turn_pt - 7;
			svsb->volt[0] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, vop30);
			shift_byte++;
			for (i = j; i < turn_pt; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				vop = (shift_byte < REG_BYTES) ? &vop30 :
								 &vop74;
				svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
				shift_byte++;
			}

			/* volt[1] ~ volt[j - 1] by interpolate */
			for (i = 1; i < j; i++)
				svsb->volt[i] = interpolate(svsb->freq_pct[0],
							    svsb->freq_pct[j],
							    svsb->volt[0],
							    svsb->volt[j],
							    svsb->freq_pct[i]);
		} else if (bdata->type == SVSB_TYPE_LOW) {
			/* volt[turn_pt] ~ volt[opp_count - 1] */
			for (i = turn_pt; i < bdata->opp_count; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				vop = (shift_byte < REG_BYTES) ? &vop30 :
								 &vop74;
				svsb->volt[i] = (*vop >> b_sft) & GENMASK(7, 0);
				shift_byte++;
			}
		}
	}

	if (bdata->type == SVSB_TYPE_HIGH) {
		opp_start = 0;
		opp_stop = svsb->turn_pt;
	} else if (bdata->type == SVSB_TYPE_LOW) {
		opp_start = svsb->turn_pt;
		opp_stop = bdata->opp_count;
	}

	for (i = opp_start; i < opp_stop; i++)
		if (svsb->volt_flags & SVSB_REMOVE_DVTFIXED_VOLT)
			svsb->volt[i] -= svsb->dvt_fixed;

	/* For voltage bin support */
	if (svsb->opp_dfreq[0] > svsb->freq_base) {
		svsb->volt[0] = svs_opp_volt_to_bank_volt(svsb->opp_dvolt[0],
							  bdata->volt_step,
							  bdata->volt_base);

		/* Find voltage bin turn point */
		for (i = 0; i < bdata->opp_count; i++) {
			if (svsb->opp_dfreq[i] <= svsb->freq_base) {
				svsb->vbin_turn_pt = i;
				break;
			}
		}

		/* Override svs bank voltages */
		for (i = 1; i < svsb->vbin_turn_pt; i++)
			svsb->volt[i] = interpolate(svsb->freq_pct[0],
						    svsb->freq_pct[svsb->vbin_turn_pt],
						    svsb->volt[0],
						    svsb->volt[svsb->vbin_turn_pt],
						    svsb->freq_pct[i]);
	}
}

static void svs_set_bank_freq_pct_v3(struct svs_platform *svsp, struct svs_bank *svsb)
{
	const struct svs_bank_pdata *bdata = &svsb->pdata;
	u32 i, j, *freq_pct, freq_pct74 = 0, freq_pct30 = 0;
	u32 b_sft, shift_byte = 0, turn_pt;
	u32 middle_index = (bdata->opp_count / 2);

	for (i = 0; i < bdata->opp_count; i++) {
		if (svsb->opp_dfreq[i] <= bdata->turn_freq_base) {
			svsb->turn_pt = i;
			break;
		}
	}

	turn_pt = svsb->turn_pt;

	/* Target is to fill out freq_pct74 / freq_pct30 by algorithm */
	if (turn_pt < middle_index) {
		if (bdata->type == SVSB_TYPE_HIGH) {
			/*
			 * If we don't handle this situation,
			 * SVSB_TYPE_HIGH's FREQPCT74 / FREQPCT30 would keep "0"
			 * and this leads SVSB_TYPE_LOW to work abnormally.
			 */
			if (turn_pt == 0)
				freq_pct30 = svsb->freq_pct[0];

			/* freq_pct[0] ~ freq_pct[turn_pt - 1] */
			for (i = 0; i < turn_pt; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				freq_pct = (shift_byte < REG_BYTES) ?
					   &freq_pct30 : &freq_pct74;
				*freq_pct |= (svsb->freq_pct[i] << b_sft);
				shift_byte++;
			}
		} else if (bdata->type == SVSB_TYPE_LOW) {
			/*
			 * freq_pct[turn_pt] +
			 * freq_pct[opp_count - 7] ~ freq_pct[opp_count -1]
			 */
			freq_pct30 = svsb->freq_pct[turn_pt];
			shift_byte++;
			j = bdata->opp_count - 7;
			for (i = j; i < bdata->opp_count; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				freq_pct = (shift_byte < REG_BYTES) ?
					   &freq_pct30 : &freq_pct74;
				*freq_pct |= (svsb->freq_pct[i] << b_sft);
				shift_byte++;
			}
		}
	} else {
		if (bdata->type == SVSB_TYPE_HIGH) {
			/*
			 * freq_pct[0] +
			 * freq_pct[turn_pt - 7] ~ freq_pct[turn_pt - 1]
			 */
			freq_pct30 = svsb->freq_pct[0];
			shift_byte++;
			j = turn_pt - 7;
			for (i = j; i < turn_pt; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				freq_pct = (shift_byte < REG_BYTES) ?
					   &freq_pct30 : &freq_pct74;
				*freq_pct |= (svsb->freq_pct[i] << b_sft);
				shift_byte++;
			}
		} else if (bdata->type == SVSB_TYPE_LOW) {
			/* freq_pct[turn_pt] ~ freq_pct[opp_count - 1] */
			for (i = turn_pt; i < bdata->opp_count; i++) {
				b_sft = BITS8 * (shift_byte % REG_BYTES);
				freq_pct = (shift_byte < REG_BYTES) ?
					   &freq_pct30 : &freq_pct74;
				*freq_pct |= (svsb->freq_pct[i] << b_sft);
				shift_byte++;
			}
		}
	}

	svs_writel_relaxed(svsp, freq_pct74, FREQPCT74);
	svs_writel_relaxed(svsp, freq_pct30, FREQPCT30);
}

static void svs_get_bank_volts_v2(struct svs_platform *svsp, struct svs_bank *svsb)
{
	const struct svs_bank_pdata *bdata = &svsb->pdata;
	u32 temp, i;

	temp = svs_readl_relaxed(svsp, VOP74);
	svsb->volt[14] = FIELD_GET(SVSB_VOPS_FLD_VOP3_7, temp);
	svsb->volt[12] = FIELD_GET(SVSB_VOPS_FLD_VOP2_6, temp);
	svsb->volt[10] = FIELD_GET(SVSB_VOPS_FLD_VOP1_5, temp);
	svsb->volt[8] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, temp);

	temp = svs_readl_relaxed(svsp, VOP30);
	svsb->volt[6] = FIELD_GET(SVSB_VOPS_FLD_VOP3_7, temp);
	svsb->volt[4] = FIELD_GET(SVSB_VOPS_FLD_VOP2_6, temp);
	svsb->volt[2] = FIELD_GET(SVSB_VOPS_FLD_VOP1_5, temp);
	svsb->volt[0] = FIELD_GET(SVSB_VOPS_FLD_VOP0_4, temp);

	for (i = 0; i <= 12; i += 2)
		svsb->volt[i + 1] = interpolate(svsb->freq_pct[i],
						svsb->freq_pct[i + 2],
						svsb->volt[i],
						svsb->volt[i + 2],
						svsb->freq_pct[i + 1]);

	svsb->volt[15] = interpolate(svsb->freq_pct[12],
				     svsb->freq_pct[14],
				     svsb->volt[12],
				     svsb->volt[14],
				     svsb->freq_pct[15]);

	for (i = 0; i < bdata->opp_count; i++)
		svsb->volt[i] += svsb->volt_od;

	/* For voltage bin support */
	if (svsb->opp_dfreq[0] > svsb->freq_base) {
		svsb->volt[0] = svs_opp_volt_to_bank_volt(svsb->opp_dvolt[0],
							  bdata->volt_step,
							  bdata->volt_base);

		/* Find voltage bin turn point */
		for (i = 0; i < bdata->opp_count; i++) {
			if (svsb->opp_dfreq[i] <= svsb->freq_base) {
				svsb->vbin_turn_pt = i;
				break;
			}
		}

		/* Override svs bank voltages */
		for (i = 1; i < svsb->vbin_turn_pt; i++)
			svsb->volt[i] = interpolate(svsb->freq_pct[0],
						    svsb->freq_pct[svsb->vbin_turn_pt],
						    svsb->volt[0],
						    svsb->volt[svsb->vbin_turn_pt],
						    svsb->freq_pct[i]);
	}
}

static void svs_set_bank_freq_pct_v2(struct svs_platform *svsp, struct svs_bank *svsb)
{
	u32 freqpct74_val, freqpct30_val;

	freqpct74_val = FIELD_PREP(SVSB_FREQPCTS_FLD_PCT0_4, svsb->freq_pct[8]) |
			FIELD_PREP(SVSB_FREQPCTS_FLD_PCT1_5, svsb->freq_pct[10]) |
			FIELD_PREP(SVSB_FREQPCTS_FLD_PCT2_6, svsb->freq_pct[12]) |
			FIELD_PREP(SVSB_FREQPCTS_FLD_PCT3_7, svsb->freq_pct[14]);

	freqpct30_val = FIELD_PREP(SVSB_FREQPCTS_FLD_PCT0_4, svsb->freq_pct[0]) |
			FIELD_PREP(SVSB_FREQPCTS_FLD_PCT1_5, svsb->freq_pct[2]) |
			FIELD_PREP(SVSB_FREQPCTS_FLD_PCT2_6, svsb->freq_pct[4]) |
			FIELD_PREP(SVSB_FREQPCTS_FLD_PCT3_7, svsb->freq_pct[6]);

	svs_writel_relaxed(svsp, freqpct74_val, FREQPCT74);
	svs_writel_relaxed(svsp, freqpct30_val, FREQPCT30);
}

static void svs_set_bank_phase(struct svs_platform *svsp,
			       unsigned int bank_idx,
			       enum svsb_phase target_phase)
{
	struct svs_bank *svsb = &svsp->banks[bank_idx];
	const struct svs_bank_pdata *bdata = &svsb->pdata;
	u32 des_char, temp_char, det_char, limit_vals, init2vals, ts_calcs;

	svs_switch_bank(svsp, svsb);

	des_char = FIELD_PREP(SVSB_DESCHAR_FLD_BDES, svsb->bdes) |
		   FIELD_PREP(SVSB_DESCHAR_FLD_MDES, svsb->mdes);
	svs_writel_relaxed(svsp, des_char, DESCHAR);

	temp_char = FIELD_PREP(SVSB_TEMPCHAR_FLD_VCO, bdata->vco) |
		    FIELD_PREP(SVSB_TEMPCHAR_FLD_MTDES, svsb->mtdes) |
		    FIELD_PREP(SVSB_TEMPCHAR_FLD_DVT_FIXED, svsb->dvt_fixed);
	svs_writel_relaxed(svsp, temp_char, TEMPCHAR);

	det_char = FIELD_PREP(SVSB_DETCHAR_FLD_DCBDET, svsb->dcbdet) |
		   FIELD_PREP(SVSB_DETCHAR_FLD_DCMDET, svsb->dcmdet);
	svs_writel_relaxed(svsp, det_char, DETCHAR);

	svs_writel_relaxed(svsp, bdata->dc_config, DCCONFIG);
	svs_writel_relaxed(svsp, bdata->age_config, AGECONFIG);
	svs_writel_relaxed(svsp, SVSB_RUNCONFIG_DEFAULT, RUNCONFIG);

	bdata->set_freq_pct(svsp, svsb);

	limit_vals = FIELD_PREP(SVSB_LIMITVALS_FLD_DTLO, SVSB_VAL_DTLO) |
		     FIELD_PREP(SVSB_LIMITVALS_FLD_DTHI, SVSB_VAL_DTHI) |
		     FIELD_PREP(SVSB_LIMITVALS_FLD_VMIN, svsb->vmin) |
		     FIELD_PREP(SVSB_LIMITVALS_FLD_VMAX, svsb->vmax);
	svs_writel_relaxed(svsp, limit_vals, LIMITVALS);

	svs_writel_relaxed(svsp, SVSB_DET_WINDOW, DETWINDOW);
	svs_writel_relaxed(svsp, SVSB_DET_MAX, CONFIG);
	svs_writel_relaxed(svsp, bdata->chk_shift, CHKSHIFT);
	svs_writel_relaxed(svsp, bdata->ctl0, CTL0);
	svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS);

	switch (target_phase) {
	case SVSB_PHASE_INIT01:
		svs_writel_relaxed(svsp, bdata->vboot, VBOOT);
		svs_writel_relaxed(svsp, SVSB_INTEN_INIT0x, INTEN);
		svs_writel_relaxed(svsp, SVSB_PTPEN_INIT01, SVSEN);
		break;
	case SVSB_PHASE_INIT02:
		init2vals = FIELD_PREP(SVSB_INIT2VALS_FLD_AGEVOFFSETIN, svsb->age_voffset_in) |
			    FIELD_PREP(SVSB_INIT2VALS_FLD_DCVOFFSETIN, svsb->dc_voffset_in);
		svs_writel_relaxed(svsp, SVSB_INTEN_INIT0x, INTEN);
		svs_writel_relaxed(svsp, init2vals, INIT2VALS);
		svs_writel_relaxed(svsp, SVSB_PTPEN_INIT02, SVSEN);
		break;
	case SVSB_PHASE_MON:
		ts_calcs = FIELD_PREP(SVSB_TSCALCS_FLD_BTS, svsb->bts) |
			   FIELD_PREP(SVSB_TSCALCS_FLD_MTS, svsb->mts);
		svs_writel_relaxed(svsp, ts_calcs, TSCALCS);
		svs_writel_relaxed(svsp, SVSB_INTEN_MONVOPEN, INTEN);
		svs_writel_relaxed(svsp, SVSB_PTPEN_MON, SVSEN);
		break;
	default:
		dev_err(svsb->dev, "requested unknown target phase: %u\n",
			target_phase);
		break;
	}
}

static inline void svs_save_bank_register_data(struct svs_platform *svsp,
					       unsigned short bank_idx,
					       enum svsb_phase phase)
{
	struct svs_bank *svsb = &svsp->banks[bank_idx];
	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,
					 unsigned short bank_idx)
{
	struct svs_bank *svsb = &svsp->banks[bank_idx];

	dev_err(svsb->dev, "%s: CORESEL = 0x%08x\n",
		__func__, svs_readl_relaxed(svsp, CORESEL));
	dev_err(svsb->dev, "SVSEN = 0x%08x, INTSTS = 0x%08x\n",
		svs_readl_relaxed(svsp, SVSEN),
		svs_readl_relaxed(svsp, INTSTS));
	dev_err(svsb->dev, "SMSTATE0 = 0x%08x, SMSTATE1 = 0x%08x\n",
		svs_readl_relaxed(svsp, SMSTATE0),
		svs_readl_relaxed(svsp, SMSTATE1));
	dev_err(svsb->dev, "TEMP = 0x%08x\n", svs_readl_relaxed(svsp, TEMP));

	svs_save_bank_register_data(svsp, bank_idx, SVSB_PHASE_ERROR);

	svsb->phase = SVSB_PHASE_ERROR;
	svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
	svs_writel_relaxed(svsp, SVSB_INTSTS_VAL_CLEAN, INTSTS);
}

static inline void svs_init01_isr_handler(struct svs_platform *svsp,
					  unsigned short bank_idx)
{
	struct svs_bank *svsb = &svsp->banks[bank_idx];
	u32 val;

	dev_info(svsb->dev, "%s: VDN74~30:0x%08x~0x%08x, DC:0x%08x\n",
		 __func__, svs_readl_relaxed(svsp, VDESIGN74),
		 svs_readl_relaxed(svsp, VDESIGN30),
		 svs_readl_relaxed(svsp, DCVALUES));

	svs_save_bank_register_data(svsp, bank_idx, SVSB_PHASE_INIT01);

	svsb->phase = SVSB_PHASE_INIT01;
	val = ~(svs_readl_relaxed(svsp, DCVALUES) & GENMASK(15, 0)) + 1;
	svsb->dc_voffset_in = val & GENMASK(15, 0);
	if (svsb->volt_flags & SVSB_INIT01_VOLT_IGNORE ||
	    (svsb->dc_voffset_in & SVSB_DC_SIGNED_BIT &&
	     svsb->volt_flags & SVSB_INIT01_VOLT_INC_ONLY))
		svsb->dc_voffset_in = 0;

	svsb->age_voffset_in = svs_readl_relaxed(svsp, AGEVALUES) &
			       GENMASK(15, 0);

	svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
	svs_writel_relaxed(svsp, SVSB_INTSTS_F0_COMPLETE, INTSTS);
	svsb->core_sel &= ~SVSB_DET_CLK_EN;
}

static inline void svs_init02_isr_handler(struct svs_platform *svsp,
					  unsigned short bank_idx)
{
	struct svs_bank *svsb = &svsp->banks[bank_idx];
	const struct svs_bank_pdata *bdata = &svsb->pdata;

	dev_info(svsb->dev, "%s: VOP74~30:0x%08x~0x%08x, DC:0x%08x\n",
		 __func__, svs_readl_relaxed(svsp, VOP74),
		 svs_readl_relaxed(svsp, VOP30),
		 svs_readl_relaxed(svsp, DCVALUES));

	svs_save_bank_register_data(svsp, bank_idx, SVSB_PHASE_INIT02);

	svsb->phase = SVSB_PHASE_INIT02;
	bdata->get_volts(svsp, svsb);

	svs_writel_relaxed(svsp, SVSB_PTPEN_OFF, SVSEN);
	svs_writel_relaxed(svsp, SVSB_INTSTS_F0_COMPLETE, INTSTS);
}

static inline void svs_mon_mode_isr_handler(struct svs_platform *svsp,
					    unsigned short bank_idx)
{
	struct svs_bank *svsb = &svsp->banks[bank_idx];
	const struct svs_bank_pdata *bdata = &svsb->pdata;

	svs_save_bank_register_data(svsp, bank_idx, SVSB_PHASE_MON);

	svsb->phase = SVSB_PHASE_MON;
	bdata->get_volts(svsp, svsb);

	svsb->temp = svs_readl_relaxed(svsp, TEMP) & GENMASK(7, 0);
	svs_writel_relaxed(svsp, SVSB_INTSTS_FLD_MONVOP, INTSTS);
}

static irqreturn_t svs_isr(int irq, void *data)
{
	struct svs_platform *svsp = data;
	const struct svs_bank_pdata *bdata;
	struct svs_bank *svsb = NULL;
	unsigned long flags;
	u32 idx, int_sts, svs_en;

	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;
		WARN(!svsb, "%s: svsb(%s) is null", __func__, svsb->name);

		spin_lock_irqsave(&svs_lock, flags);

		/* Find out which svs bank fires interrupt */
		if (bdata->int_st & svs_readl_relaxed(svsp, INTST)) {
			spin_unlock_irqrestore(&svs_lock, flags);
			continue;
		}

		svs_switch_bank(svsp, svsb);
		int_sts = svs_readl_relaxed(svsp, INTSTS);
		svs_en = svs_readl_relaxed(svsp, SVSEN);

		if (int_sts == SVSB_INTSTS_F0_COMPLETE &&
		    svs_en == SVSB_PTPEN_INIT01)
			svs_init01_isr_handler(svsp, idx);
		else if (int_sts == SVSB_INTSTS_F0_COMPLETE &&
			 svs_en == SVSB_PTPEN_INIT02)
			svs_init02_isr_handler(svsp, idx);
		else if (int_sts & SVSB_INTSTS_FLD_MONVOP)
			svs_mon_mode_isr_handler(svsp, idx);
		else
			svs_error_isr_handler(svsp, idx);

		spin_unlock_irqrestore(&svs_lock, flags);
		break;
	}

	svs_adjust_pm_opp_volts(svsb);

	if (svsb->phase == SVSB_PHASE_INIT01 ||
	    svsb->phase == SVSB_PHASE_INIT02)
		complete(&svsb->init_completion);

	return IRQ_HANDLED;
}

static bool svs_mode_available(struct svs_platform *svsp, u8 mode)
{
	int i;

	for (i = 0; i < svsp->bank_max; i++)
		if (svsp->banks[i].mode_support & mode)
			return true;
	return false;
}

static int svs_init01(struct svs_platform *svsp)
{
	const struct svs_bank_pdata *bdata;
	struct svs_bank *svsb;
	unsigned long flags, time_left;
	bool search_done;
	int ret = 0, r;
	u32 opp_freq, opp_vboot, buck_volt, idx, i;

	if (!svs_mode_available(svsp, SVSB_MODE_INIT01))
		return 0;

	/* Keep CPUs' core power on for svs_init01 initialization */
	cpuidle_pause_and_lock();

	 /* Svs bank init01 preparation - power enable */
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;

		if (!(svsb->mode_support & SVSB_MODE_INIT01))
			continue;

		ret = regulator_enable(svsb->buck);
		if (ret) {
			dev_err(svsb->dev, "%s enable fail: %d\n",
				bdata->buck_name, ret);
			goto svs_init01_resume_cpuidle;
		}

		/* Some buck doesn't support mode change. Show fail msg only */
		ret = regulator_set_mode(svsb->buck, REGULATOR_MODE_FAST);
		if (ret)
			dev_notice(svsb->dev, "set fast mode fail: %d\n", ret);

		if (svsb->volt_flags & SVSB_INIT01_PD_REQ) {
			if (!pm_runtime_enabled(svsb->opp_dev)) {
				pm_runtime_enable(svsb->opp_dev);
				svsb->pm_runtime_enabled_count++;
			}

			ret = pm_runtime_resume_and_get(svsb->opp_dev);
			if (ret < 0) {
				dev_err(svsb->dev, "mtcmos on fail: %d\n", ret);
				goto svs_init01_resume_cpuidle;
			}
		}
	}

	/*
	 * Svs bank init01 preparation - vboot voltage adjustment
	 * Sometimes two svs banks use the same buck. Therefore,
	 * we have to set each svs bank to target voltage(vboot) first.
	 */
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;

		if (!(svsb->mode_support & SVSB_MODE_INIT01))
			continue;

		/*
		 * Find the fastest freq that can be run at vboot and
		 * fix to that freq until svs_init01 is done.
		 */
		search_done = false;
		opp_vboot = svs_bank_volt_to_opp_volt(bdata->vboot,
						      bdata->volt_step,
						      bdata->volt_base);

		for (i = 0; i < bdata->opp_count; i++) {
			opp_freq = svsb->opp_dfreq[i];
			if (!search_done && svsb->opp_dvolt[i] <= opp_vboot) {
				ret = dev_pm_opp_adjust_voltage(svsb->opp_dev,
								opp_freq,
								opp_vboot,
								opp_vboot,
								opp_vboot);
				if (ret) {
					dev_err(svsb->dev,
						"set opp %uuV vboot fail: %d\n",
						opp_vboot, ret);
					goto svs_init01_finish;
				}

				search_done = true;
			} else {
				ret = dev_pm_opp_disable(svsb->opp_dev,
							 svsb->opp_dfreq[i]);
				if (ret) {
					dev_err(svsb->dev,
						"opp %uHz disable fail: %d\n",
						svsb->opp_dfreq[i], ret);
					goto svs_init01_finish;
				}
			}
		}
	}

	/* Svs bank init01 begins */
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;

		if (!(svsb->mode_support & SVSB_MODE_INIT01))
			continue;

		opp_vboot = svs_bank_volt_to_opp_volt(bdata->vboot,
						      bdata->volt_step,
						      bdata->volt_base);

		buck_volt = regulator_get_voltage(svsb->buck);
		if (buck_volt != opp_vboot) {
			dev_err(svsb->dev,
				"buck voltage: %uuV, expected vboot: %uuV\n",
				buck_volt, opp_vboot);
			ret = -EPERM;
			goto svs_init01_finish;
		}

		spin_lock_irqsave(&svs_lock, flags);
		svs_set_bank_phase(svsp, idx, SVSB_PHASE_INIT01);
		spin_unlock_irqrestore(&svs_lock, flags);

		time_left = wait_for_completion_timeout(&svsb->init_completion,
							msecs_to_jiffies(5000));
		if (!time_left) {
			dev_err(svsb->dev, "init01 completion timeout\n");
			ret = -EBUSY;
			goto svs_init01_finish;
		}
	}

svs_init01_finish:
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;

		if (!(svsb->mode_support & SVSB_MODE_INIT01))
			continue;

		for (i = 0; i < bdata->opp_count; i++) {
			r = dev_pm_opp_enable(svsb->opp_dev,
					      svsb->opp_dfreq[i]);
			if (r)
				dev_err(svsb->dev, "opp %uHz enable fail: %d\n",
					svsb->opp_dfreq[i], r);
		}

		if (svsb->volt_flags & SVSB_INIT01_PD_REQ) {
			r = pm_runtime_put_sync(svsb->opp_dev);
			if (r)
				dev_err(svsb->dev, "mtcmos off fail: %d\n", r);

			if (svsb->pm_runtime_enabled_count > 0) {
				pm_runtime_disable(svsb->opp_dev);
				svsb->pm_runtime_enabled_count--;
			}
		}

		r = regulator_set_mode(svsb->buck, REGULATOR_MODE_NORMAL);
		if (r)
			dev_notice(svsb->dev, "set normal mode fail: %d\n", r);

		r = regulator_disable(svsb->buck);
		if (r)
			dev_err(svsb->dev, "%s disable fail: %d\n",
				bdata->buck_name, r);
	}

svs_init01_resume_cpuidle:
	cpuidle_resume_and_unlock();

	return ret;
}

static int svs_init02(struct svs_platform *svsp)
{
	const struct svs_bank_pdata *bdata;
	struct svs_bank *svsb;
	unsigned long flags, time_left;
	int ret;
	u32 idx;

	if (!svs_mode_available(svsp, SVSB_MODE_INIT02))
		return 0;

	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];

		if (!(svsb->mode_support & SVSB_MODE_INIT02))
			continue;

		reinit_completion(&svsb->init_completion);
		spin_lock_irqsave(&svs_lock, flags);
		svs_set_bank_phase(svsp, idx, SVSB_PHASE_INIT02);
		spin_unlock_irqrestore(&svs_lock, flags);

		time_left = wait_for_completion_timeout(&svsb->init_completion,
							msecs_to_jiffies(5000));
		if (!time_left) {
			dev_err(svsb->dev, "init02 completion timeout\n");
			ret = -EBUSY;
			goto out_of_init02;
		}
	}

	/*
	 * 2-line high/low bank update its corresponding opp voltages only.
	 * Therefore, we sync voltages from opp for high/low bank voltages
	 * consistency.
	 */
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;

		if (!(svsb->mode_support & SVSB_MODE_INIT02))
			continue;

		if (bdata->type == SVSB_TYPE_HIGH || bdata->type == SVSB_TYPE_LOW) {
			if (svs_sync_bank_volts_from_opp(svsb)) {
				dev_err(svsb->dev, "sync volt fail\n");
				ret = -EPERM;
				goto out_of_init02;
			}
		}
	}

	return 0;

out_of_init02:
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		svs_bank_disable_and_restore_default_volts(svsp, svsb);
	}

	return ret;
}

static void svs_mon_mode(struct svs_platform *svsp)
{
	struct svs_bank *svsb;
	unsigned long flags;
	u32 idx;

	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];

		if (!(svsb->mode_support & SVSB_MODE_MON))
			continue;

		spin_lock_irqsave(&svs_lock, flags);
		svs_set_bank_phase(svsp, idx, SVSB_PHASE_MON);
		spin_unlock_irqrestore(&svs_lock, flags);
	}
}

static int svs_start(struct svs_platform *svsp)
{
	int ret;

	ret = svs_init01(svsp);
	if (ret)
		return ret;

	ret = svs_init02(svsp);
	if (ret)
		return ret;

	svs_mon_mode(svsp);

	return 0;
}

static int svs_suspend(struct device *dev)
{
	struct svs_platform *svsp = dev_get_drvdata(dev);
	int ret;
	u32 idx;

	for (idx = 0; idx < svsp->bank_max; idx++) {
		struct svs_bank *svsb = &svsp->banks[idx];

		svs_bank_disable_and_restore_default_volts(svsp, svsb);
	}

	ret = reset_control_assert(svsp->rst);
	if (ret) {
		dev_err(svsp->dev, "cannot assert reset %d\n", ret);
		return ret;
	}

	clk_disable_unprepare(svsp->main_clk);

	return 0;
}

static int svs_resume(struct device *dev)
{
	struct svs_platform *svsp = dev_get_drvdata(dev);
	int ret;

	ret = clk_prepare_enable(svsp->main_clk);
	if (ret) {
		dev_err(svsp->dev, "cannot enable main_clk, disable svs\n");
		return ret;
	}

	ret = reset_control_deassert(svsp->rst);
	if (ret) {
		dev_err(svsp->dev, "cannot deassert reset %d\n", ret);
		goto out_of_resume;
	}

	ret = svs_init02(svsp);
	if (ret)
		goto svs_resume_reset_assert;

	svs_mon_mode(svsp);

	return 0;

svs_resume_reset_assert:
	dev_err(svsp->dev, "assert reset: %d\n",
		reset_control_assert(svsp->rst));

out_of_resume:
	clk_disable_unprepare(svsp->main_clk);
	return ret;
}

static int svs_bank_resource_setup(struct svs_platform *svsp)
{
	const struct svs_bank_pdata *bdata;
	struct svs_bank *svsb;
	struct dev_pm_opp *opp;
	char tz_name_buf[20];
	unsigned long freq;
	int count, ret;
	u32 idx, i;

	dev_set_drvdata(svsp->dev, svsp);

	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;

		if (bdata->sw_id >= SVSB_SWID_MAX || bdata->type >= SVSB_TYPE_MAX) {
			dev_err(svsb->dev, "unknown bank sw_id or type\n");
			return -EINVAL;
		}

		svsb->dev = devm_kzalloc(svsp->dev, sizeof(*svsb->dev), GFP_KERNEL);
		if (!svsb->dev)
			return -ENOMEM;

		svsb->name = devm_kasprintf(svsp->dev, GFP_KERNEL, "%s%s",
					    svs_swid_names[bdata->sw_id],
					    svs_type_names[bdata->type]);
		if (!svsb->name)
			return -ENOMEM;

		ret = dev_set_name(svsb->dev, "%s", svsb->name);
		if (ret)
			return ret;

		dev_set_drvdata(svsb->dev, svsp);

		ret = devm_pm_opp_of_add_table(svsb->opp_dev);
		if (ret) {
			dev_err(svsb->dev, "add opp table fail: %d\n", ret);
			return ret;
		}

		mutex_init(&svsb->lock);
		init_completion(&svsb->init_completion);

		if (svsb->mode_support & SVSB_MODE_INIT01) {
			svsb->buck = devm_regulator_get_optional(svsb->opp_dev,
								 bdata->buck_name);
			if (IS_ERR(svsb->buck)) {
				dev_err(svsb->dev, "cannot get \"%s-supply\"\n",
					bdata->buck_name);
				return PTR_ERR(svsb->buck);
			}
		}

		if (!IS_ERR_OR_NULL(bdata->tzone_name)) {
			snprintf(tz_name_buf, ARRAY_SIZE(tz_name_buf),
				 "%s-thermal", bdata->tzone_name);
			svsb->tzd = thermal_zone_get_zone_by_name(tz_name_buf);
			if (IS_ERR(svsb->tzd)) {
				dev_err(svsb->dev, "cannot get \"%s\" thermal zone\n",
					tz_name_buf);
				return PTR_ERR(svsb->tzd);
			}
		}

		count = dev_pm_opp_get_opp_count(svsb->opp_dev);
		if (bdata->opp_count != count) {
			dev_err(svsb->dev,
				"opp_count not \"%u\" but get \"%d\"?\n",
				bdata->opp_count, count);
			return count;
		}

		for (i = 0, freq = ULONG_MAX; i < bdata->opp_count; i++, freq--) {
			opp = dev_pm_opp_find_freq_floor(svsb->opp_dev, &freq);
			if (IS_ERR(opp)) {
				dev_err(svsb->dev, "cannot find freq = %ld\n",
					PTR_ERR(opp));
				return PTR_ERR(opp);
			}

			svsb->opp_dfreq[i] = freq;
			svsb->opp_dvolt[i] = dev_pm_opp_get_voltage(opp);
			svsb->freq_pct[i] = percent(svsb->opp_dfreq[i],
						    svsb->freq_base);
			dev_pm_opp_put(opp);
		}
	}

	return 0;
}

static int svs_get_efuse_data(struct svs_platform *svsp,
			      const char *nvmem_cell_name,
			      u32 **svsp_efuse, size_t *svsp_efuse_max)
{
	struct nvmem_cell *cell;

	cell = nvmem_cell_get(svsp->dev, nvmem_cell_name);
	if (IS_ERR(cell)) {
		dev_err(svsp->dev, "no \"%s\"? %ld\n",
			nvmem_cell_name, PTR_ERR(cell));
		return PTR_ERR(cell);
	}

	*svsp_efuse = nvmem_cell_read(cell, svsp_efuse_max);
	if (IS_ERR(*svsp_efuse)) {
		nvmem_cell_put(cell);
		return PTR_ERR(*svsp_efuse);
	}

	*svsp_efuse_max /= sizeof(u32);
	nvmem_cell_put(cell);

	return 0;
}

static u32 svs_get_fuse_val(u32 *fuse_array, const struct svs_fusemap *fmap, u8 nbits)
{
	u32 val;

	if (fmap->index < 0)
		return FUSE_DATA_NOT_VALID;

	val = fuse_array[fmap->index] >> fmap->ofst;
	val &= GENMASK(nbits - 1, 0);

	return val;
}

static bool svs_is_available(struct svs_platform *svsp)
{
	int i, num_populated = 0;

	/* If at least two fuse arrays are populated, SVS is calibrated */
	for (i = 0; i < svsp->efuse_max; i++) {
		if (svsp->efuse[i])
			num_populated++;

		if (num_populated > 1)
			return true;
	}

	return false;
}

static bool svs_common_parse_efuse(struct svs_platform *svsp,
				   const struct svs_platform_data *pdata)
{
	const struct svs_fusemap *gfmap = pdata->glb_fuse_map;
	struct svs_fusemap tfm = { 0, 24 };
	u32 golden_temp, val;
	u8 ft_pgm, vmin;
	int i;

	if (!svs_is_available(svsp))
		return false;

	/* Get golden temperature from SVS-Thermal calibration */
	val = svs_get_fuse_val(svsp->tefuse, &tfm, 8);

	/* If golden temp is not programmed, use the default of 50 */
	golden_temp = val ? val : 50;

	/* Parse fused SVS calibration */
	ft_pgm = svs_get_fuse_val(svsp->efuse, &gfmap[GLB_FT_PGM], 8);
	vmin = svs_get_fuse_val(svsp->efuse, &gfmap[GLB_VMIN], 2);

	for (i = 0; i < svsp->bank_max; i++) {
		struct svs_bank *svsb = &svsp->banks[i];
		const struct svs_bank_pdata *bdata = &svsb->pdata;
		const struct svs_fusemap *dfmap = bdata->dev_fuse_map;

		if (vmin == 1)
			svsb->vmin = 0x1e;

		if (ft_pgm == 0)
			svsb->volt_flags |= SVSB_INIT01_VOLT_IGNORE;

		svsb->mtdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MTDES], 8);
		svsb->bdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_BDES], 8);
		svsb->mdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MDES], 8);
		svsb->dcbdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCBDET], 8);
		svsb->dcmdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCMDET], 8);
		svsb->vmax += svsb->dvt_fixed;

		svsb->mts = (svsp->ts_coeff * 2) / 1000;
		svsb->bts = (((500 * golden_temp + svsp->ts_coeff) / 1000) - 25) * 4;
	}

	return true;
}

static bool svs_mt8183_efuse_parsing(struct svs_platform *svsp,
				     const struct svs_platform_data *pdata)
{
	struct svs_bank *svsb;
	const struct svs_bank_pdata *bdata;
	int format[6], x_roomt[6], o_vtsmcu[5], o_vtsabb, tb_roomt = 0;
	int adc_ge_t, adc_oe_t, ge, oe, gain, degc_cali, adc_cali_en_t;
	int o_slope, o_slope_sign, ts_id;
	u32 idx, i, ft_pgm, mts, temp0, temp1, temp2;

	for (i = 0; i < svsp->efuse_max; i++)
		if (svsp->efuse[i])
			dev_info(svsp->dev, "M_HW_RES%d: 0x%08x\n",
				 i, svsp->efuse[i]);

	if (!svsp->efuse[2]) {
		dev_notice(svsp->dev, "svs_efuse[2] = 0x0?\n");
		return false;
	}

	/* Svs efuse parsing */
	ft_pgm = svs_get_fuse_val(svsp->efuse, &pdata->glb_fuse_map[GLB_FT_PGM], 4);

	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;
		const struct svs_fusemap *dfmap = bdata->dev_fuse_map;

		if (ft_pgm <= 1)
			svsb->volt_flags |= SVSB_INIT01_VOLT_IGNORE;

		svsb->mtdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MTDES], 8);
		svsb->bdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_BDES], 8);
		svsb->mdes = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_MDES], 8);
		svsb->dcbdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCBDET], 8);
		svsb->dcmdet = svs_get_fuse_val(svsp->efuse, &dfmap[BDEV_DCMDET], 8);

		switch (bdata->sw_id) {
		case SVSB_SWID_CPU_LITTLE:
		case SVSB_SWID_CCI:
			if (ft_pgm <= 3)
				svsb->volt_od += 10;
			else
				svsb->volt_od += 2;
			break;
		case SVSB_SWID_CPU_BIG:
			if (ft_pgm <= 3)
				svsb->volt_od += 15;
			else
				svsb->volt_od += 12;
			break;
		case SVSB_SWID_GPU:
			if (ft_pgm != FUSE_DATA_NOT_VALID && ft_pgm >= 2) {
				svsb->freq_base = 800000000; /* 800MHz */
				svsb->dvt_fixed = 2;
			}
			break;
		default:
			dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->sw_id);
			return false;
		}
	}

	/* Thermal efuse parsing */
	adc_ge_t = (svsp->tefuse[1] >> 22) & GENMASK(9, 0);
	adc_oe_t = (svsp->tefuse[1] >> 12) & GENMASK(9, 0);

	o_vtsmcu[0] = (svsp->tefuse[0] >> 17) & GENMASK(8, 0);
	o_vtsmcu[1] = (svsp->tefuse[0] >> 8) & GENMASK(8, 0);
	o_vtsmcu[2] = svsp->tefuse[1] & GENMASK(8, 0);
	o_vtsmcu[3] = (svsp->tefuse[2] >> 23) & GENMASK(8, 0);
	o_vtsmcu[4] = (svsp->tefuse[2] >> 5) & GENMASK(8, 0);
	o_vtsabb = (svsp->tefuse[2] >> 14) & GENMASK(8, 0);

	degc_cali = (svsp->tefuse[0] >> 1) & GENMASK(5, 0);
	adc_cali_en_t = svsp->tefuse[0] & BIT(0);
	o_slope_sign = (svsp->tefuse[0] >> 7) & BIT(0);

	ts_id = (svsp->tefuse[1] >> 9) & BIT(0);
	if (!ts_id) {
		o_slope = 1534;
	} else {
		o_slope = (svsp->tefuse[0] >> 26) & GENMASK(5, 0);
		if (!o_slope_sign)
			o_slope = 1534 + o_slope * 10;
		else
			o_slope = 1534 - o_slope * 10;
	}

	if (adc_cali_en_t == 0 ||
	    adc_ge_t < 265 || adc_ge_t > 758 ||
	    adc_oe_t < 265 || adc_oe_t > 758 ||
	    o_vtsmcu[0] < -8 || o_vtsmcu[0] > 484 ||
	    o_vtsmcu[1] < -8 || o_vtsmcu[1] > 484 ||
	    o_vtsmcu[2] < -8 || o_vtsmcu[2] > 484 ||
	    o_vtsmcu[3] < -8 || o_vtsmcu[3] > 484 ||
	    o_vtsmcu[4] < -8 || o_vtsmcu[4] > 484 ||
	    o_vtsabb < -8 || o_vtsabb > 484 ||
	    degc_cali < 1 || degc_cali > 63) {
		dev_err(svsp->dev, "bad thermal efuse, no mon mode\n");
		goto remove_mt8183_svsb_mon_mode;
	}

	ge = ((adc_ge_t - 512) * 10000) / 4096;
	oe = (adc_oe_t - 512);
	gain = (10000 + ge);

	format[0] = (o_vtsmcu[0] + 3350 - oe);
	format[1] = (o_vtsmcu[1] + 3350 - oe);
	format[2] = (o_vtsmcu[2] + 3350 - oe);
	format[3] = (o_vtsmcu[3] + 3350 - oe);
	format[4] = (o_vtsmcu[4] + 3350 - oe);
	format[5] = (o_vtsabb + 3350 - oe);

	for (i = 0; i < 6; i++)
		x_roomt[i] = (((format[i] * 10000) / 4096) * 10000) / gain;

	temp0 = (10000 * 100000 / gain) * 15 / 18;
	mts = (temp0 * 10) / o_slope;

	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		bdata = &svsb->pdata;
		svsb->mts = mts;

		switch (bdata->sw_id) {
		case SVSB_SWID_CPU_LITTLE:
			tb_roomt = x_roomt[3];
			break;
		case SVSB_SWID_CPU_BIG:
			tb_roomt = x_roomt[4];
			break;
		case SVSB_SWID_CCI:
			tb_roomt = x_roomt[3];
			break;
		case SVSB_SWID_GPU:
			tb_roomt = x_roomt[1];
			break;
		default:
			dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->sw_id);
			goto remove_mt8183_svsb_mon_mode;
		}

		temp0 = (degc_cali * 10 / 2);
		temp1 = ((10000 * 100000 / 4096 / gain) *
			 oe + tb_roomt * 10) * 15 / 18;
		temp2 = temp1 * 100 / o_slope;

		svsb->bts = (temp0 + temp2 - 250) * 4 / 10;
	}

	return true;

remove_mt8183_svsb_mon_mode:
	for (idx = 0; idx < svsp->bank_max; idx++) {
		svsb = &svsp->banks[idx];
		svsb->mode_support &= ~SVSB_MODE_MON;
	}

	return true;
}

static struct device *svs_get_subsys_device(struct svs_platform *svsp,
					    const char *node_name)
{
	struct platform_device *pdev;
	struct device_node *np;

	np = of_find_node_by_name(NULL, node_name);
	if (!np) {
		dev_err(svsp->dev, "cannot find %s node\n", node_name);
		return ERR_PTR(-ENODEV);
	}

	pdev = of_find_device_by_node(np);
	if (!pdev) {
		of_node_put(np);
		dev_err(svsp->dev, "cannot find pdev by %s\n", node_name);
		return ERR_PTR(-ENXIO);
	}

	of_node_put(np);

	return &pdev->dev;
}

static struct device *svs_add_device_link(struct svs_platform *svsp,
					  const char *node_name)
{
	struct device *dev;
	struct device_link *sup_link;

	dev = svs_get_subsys_device(svsp, node_name);
	if (IS_ERR(dev))
		return dev;

	sup_link = device_link_add(svsp->dev, dev,
				   DL_FLAG_AUTOREMOVE_CONSUMER);
	if (!sup_link) {
		dev_err(svsp->dev, "sup_link is NULL\n");
		return ERR_PTR(-EINVAL);
	}

	if (sup_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
		return ERR_PTR(-EPROBE_DEFER);

	return dev;
}

static int svs_mt8192_platform_probe(struct svs_platform *svsp)
{
	struct device *dev;
	u32 idx;

	svsp->rst = devm_reset_control_get_optional(svsp->dev, "svs_rst");
	if (IS_ERR(svsp->rst))
		return dev_err_probe(svsp->dev, PTR_ERR(svsp->rst),
				     "cannot get svs reset control\n");

	dev = svs_add_device_link(svsp, "thermal-sensor");
	if (IS_ERR(dev))
		return dev_err_probe(svsp->dev, PTR_ERR(dev),
				     "failed to get lvts device\n");

	for (idx = 0; idx < svsp->bank_max; idx++) {
		struct svs_bank *svsb = &svsp->banks[idx];
		const struct svs_bank_pdata *bdata = &svsb->pdata;

		switch (bdata->sw_id) {
		case SVSB_SWID_CPU_LITTLE:
		case SVSB_SWID_CPU_BIG:
			svsb->opp_dev = get_cpu_device(bdata->cpu_id);
			break;
		case SVSB_SWID_CCI:
			svsb->opp_dev = svs_add_device_link(svsp, "cci");
			break;
		case SVSB_SWID_GPU:
			if (bdata->type == SVSB_TYPE_LOW)
				svsb->opp_dev = svs_get_subsys_device(svsp, "gpu");
			else
				svsb->opp_dev = svs_add_device_link(svsp, "gpu");
			break;
		default:
			dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->sw_id);
			return -EINVAL;
		}

		if (IS_ERR(svsb->opp_dev))
			return dev_err_probe(svsp->dev, PTR_ERR(svsb->opp_dev),
					     "failed to get OPP device for bank %d\n",
					     idx);
	}

	return 0;
}

static int svs_mt8183_platform_probe(struct svs_platform *svsp)
{
	struct device *dev;
	u32 idx;

	dev = svs_add_device_link(svsp, "thermal-sensor");
	if (IS_ERR(dev))
		return dev_err_probe(svsp->dev, PTR_ERR(dev),
				     "failed to get thermal device\n");

	for (idx = 0; idx < svsp->bank_max; idx++) {
		struct svs_bank *svsb = &svsp->banks[idx];
		const struct svs_bank_pdata *bdata = &svsb->pdata;

		switch (bdata->sw_id) {
		case SVSB_SWID_CPU_LITTLE:
		case SVSB_SWID_CPU_BIG:
			svsb->opp_dev = get_cpu_device(bdata->cpu_id);
			break;
		case SVSB_SWID_CCI:
			svsb->opp_dev = svs_add_device_link(svsp, "cci");
			break;
		case SVSB_SWID_GPU:
			svsb->opp_dev = svs_add_device_link(svsp, "gpu");
			break;
		default:
			dev_err(svsb->dev, "unknown sw_id: %u\n", bdata->sw_id);
			return -EINVAL;
		}

		if (IS_ERR(svsb->opp_dev))
			return dev_err_probe(svsp->dev, PTR_ERR(svsb->opp_dev),
					     "failed to get OPP device for bank %d\n",
					     idx);
	}

	return 0;
}

static struct svs_bank svs_mt8195_banks[] = {
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.type			= SVSB_TYPE_LOW,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 640000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x1,
			.vco			= 0x18,
			.chk_shift		= 0x87,
			.int_st			= BIT(0),
			.ctl0			= 0x00540003,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 10, 16 }, { 10, 24 }, { 10, 0 }, { 8, 0 }, { 8, 8 }
			}
		},
		.mode_support	= SVSB_MODE_INIT02,
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT,
		.freq_base	= 640000000,
		.core_sel	= 0x0fff0100,
		.dvt_fixed	= 0x1,
		.vmax		= 0x38,
		.vmin		= 0x14,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.type			= SVSB_TYPE_HIGH,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.tzone_name		= "gpu",
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 640000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x1,
			.vco			= 0x18,
			.chk_shift		= 0x87,
			.int_st			= BIT(1),
			.ctl0			= 0x00540003,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 0,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 7,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 9, 16 }, { 9, 24 }, { 9, 0 }, { 8, 0 }, { 8, 8 }
			},
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 880000000,
		.core_sel	= 0x0fff0101,
		.dvt_fixed	= 0x6,
		.vmax		= 0x38,
		.vmin		= 0x14,
	},
};

static struct svs_bank svs_mt8192_banks[] = {
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.type			= SVSB_TYPE_LOW,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.tzone_name		= "gpu",
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 688000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x1,
			.vco			= 0x18,
			.chk_shift		= 0x87,
			.int_st			= BIT(0),
			.ctl0			= 0x00540003,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 0,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 7,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 10, 16 }, { 10, 24 }, { 10, 0 }, { 17, 0 }, { 17, 8 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT,
		.mode_support	= SVSB_MODE_INIT02,
		.freq_base	= 688000000,
		.core_sel	= 0x0fff0100,
		.dvt_fixed	= 0x1,
		.vmax		= 0x60,
		.vmin		= 0x1a,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.type			= SVSB_TYPE_HIGH,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.tzone_name		= "gpu",
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 688000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x1,
			.vco			= 0x18,
			.chk_shift		= 0x87,
			.int_st			= BIT(1),
			.ctl0			= 0x00540003,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 0,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 7,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 9, 16 }, { 9, 24 }, { 17, 0 }, { 17, 16 }, { 17, 24 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 902000000,
		.core_sel	= 0x0fff0101,
		.dvt_fixed	= 0x6,
		.vmax		= 0x60,
		.vmin		= 0x1a,
	},
};

static struct svs_bank svs_mt8188_banks[] = {
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.type			= SVSB_TYPE_LOW,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 640000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x555555,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(0),
			.ctl0			= 0x00100003,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 5, 16 }, { 5, 24 }, { 5, 0 }, { 15, 16 }, { 15, 24 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT,
		.mode_support	= SVSB_MODE_INIT02,
		.freq_base	= 640000000,
		.core_sel	= 0x0fff0000,
		.dvt_fixed	= 0x1,
		.vmax		= 0x38,
		.vmin		= 0x1c,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.type			= SVSB_TYPE_HIGH,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.tzone_name		= "gpu",
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 640000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x555555,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(1),
			.ctl0			= 0x00100003,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 0,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 7,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 4, 16 }, { 4, 24 }, { 4, 0 }, { 14, 0 }, { 14, 8 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 880000000,
		.core_sel	= 0x0fff0001,
		.dvt_fixed	= 0x4,
		.vmax		= 0x38,
		.vmin		= 0x1c,
	},
};

static struct svs_bank svs_mt8186_banks[] = {
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CPU_BIG,
			.type			= SVSB_TYPE_LOW,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.cpu_id			= 6,
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 1670000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x1,
			.dc_config		= 0x1,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(0),
			.ctl0			= 0x00540003,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 3, 16 }, { 3, 24 }, { 3, 0 }, { 14, 16 }, { 14, 24 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT,
		.volt_od	= 4,
		.mode_support	= SVSB_MODE_INIT02,
		.freq_base	= 1670000000,
		.core_sel	= 0x0fff0100,
		.dvt_fixed	= 0x3,
		.vmax		= 0x59,
		.vmin		= 0x20,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CPU_BIG,
			.type			= SVSB_TYPE_HIGH,
			.set_freq_pct		= svs_set_bank_freq_pct_v3,
			.get_volts		= svs_get_bank_volts_v3,
			.cpu_id			= 6,
			.tzone_name		= "cpu-big",
			.opp_count		= MAX_OPP_ENTRIES,
			.turn_freq_base		= 1670000000,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x1,
			.dc_config		= 0x1,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(1),
			.ctl0			= 0x00540003,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 8,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 8,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 2, 16 }, { 2, 24 }, { 2, 0 }, { 13, 0 }, { 13, 8 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.volt_od	= 4,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 2050000000,
		.core_sel	= 0x0fff0101,
		.dvt_fixed	= 0x6,
		.vmax		= 0x73,
		.vmin		= 0x20,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CPU_LITTLE,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.cpu_id			= 0,
			.tzone_name		= "cpu-little",
			.opp_count		= MAX_OPP_ENTRIES,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x1,
			.dc_config		= 0x1,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(2),
			.ctl0			= 0x3210000f,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 8,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 8,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 4, 16 }, { 4, 24 }, { 4, 0 }, { 14, 0 }, { 14, 8 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.volt_od	= 3,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 2000000000,
		.core_sel	= 0x0fff0102,
		.dvt_fixed	= 0x6,
		.vmax		= 0x65,
		.vmin		= 0x20,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CCI,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.tzone_name		= "cci",
			.opp_count		= MAX_OPP_ENTRIES,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x1,
			.dc_config		= 0x1,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(3),
			.ctl0			= 0x3210000f,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 8,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 8,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 5, 16 }, { 5, 24 }, { 5, 0 }, { 15, 16 }, { 15, 24 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.volt_od	= 3,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 1400000000,
		.core_sel	= 0x0fff0103,
		.dvt_fixed	= 0x6,
		.vmax		= 0x65,
		.vmin		= 0x20,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.tzone_name		= "gpu",
			.opp_count		= MAX_OPP_ENTRIES,
			.volt_step		= 6250,
			.volt_base		= 400000,
			.age_config		= 0x555555,
			.dc_config		= 0x1,
			.vco			= 0x10,
			.chk_shift		= 0x87,
			.int_st			= BIT(4),
			.ctl0			= 0x00100003,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 8,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 7,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 6, 16 }, { 6, 24 }, { 6, 0 }, { 15, 8 }, { 15, 0 }
			}
		},
		.volt_flags	= SVSB_REMOVE_DVTFIXED_VOLT | SVSB_MON_VOLT_IGNORE,
		.mode_support	= SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 850000000,
		.core_sel	= 0x0fff0104,
		.dvt_fixed	= 0x4,
		.vmax		= 0x58,
		.vmin		= 0x20,
	},
};

static struct svs_bank svs_mt8183_banks[] = {
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CPU_LITTLE,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.cpu_id			= 0,
			.buck_name		= "proc",
			.opp_count		= MAX_OPP_ENTRIES,
			.vboot			= 0x30,
			.volt_step		= 6250,
			.volt_base		= 500000,
			.age_config		= 0x555555,
			.dc_config		= 0x555555,
			.vco			= 0x10,
			.chk_shift		= 0x77,
			.int_st			= BIT(0),
			.ctl0			= 0x00010001,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 16, 0 }, { 16, 8 }, { 17, 16 }, { 16, 16 }, { 16, 24 }
			}
		},
		.volt_flags	= SVSB_INIT01_VOLT_INC_ONLY,
		.mode_support	= SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
		.freq_base	= 1989000000,
		.core_sel	= 0x8fff0000,
		.dvt_fixed	= 0x7,
		.vmax		= 0x64,
		.vmin		= 0x18,

	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CPU_BIG,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.cpu_id			= 4,
			.buck_name		= "proc",
			.opp_count		= MAX_OPP_ENTRIES,
			.vboot			= 0x30,
			.volt_step		= 6250,
			.volt_base		= 500000,
			.age_config		= 0x555555,
			.dc_config		= 0x555555,
			.vco			= 0x10,
			.chk_shift		= 0x77,
			.int_st			= BIT(1),
			.ctl0			= 0x00000001,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 18, 0 }, { 18, 8 }, { 17, 0 }, { 18, 16 }, { 18, 24 }
			}
		},
		.volt_flags	= SVSB_INIT01_VOLT_INC_ONLY,
		.mode_support	= SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
		.freq_base	= 1989000000,
		.core_sel	= 0x8fff0001,
		.dvt_fixed	= 0x7,
		.vmax		= 0x58,
		.vmin		= 0x10,

	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_CCI,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.buck_name		= "proc",
			.opp_count		= MAX_OPP_ENTRIES,
			.vboot			= 0x30,
			.volt_step		= 6250,
			.volt_base		= 500000,
			.age_config		= 0x555555,
			.dc_config		= 0x555555,
			.vco			= 0x10,
			.chk_shift		= 0x77,
			.int_st			= BIT(2),
			.ctl0			= 0x00100003,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 4, 0 }, { 4, 8 }, { 5, 16 }, { 4, 16 }, { 4, 24 }
			}
		},
		.volt_flags	= SVSB_INIT01_VOLT_INC_ONLY,
		.mode_support	= SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
		.freq_base	= 1196000000,
		.core_sel	= 0x8fff0002,
		.dvt_fixed	= 0x7,
		.vmax		= 0x64,
		.vmin		= 0x18,
	},
	{
		.pdata = (const struct svs_bank_pdata) {
			.sw_id			= SVSB_SWID_GPU,
			.set_freq_pct		= svs_set_bank_freq_pct_v2,
			.get_volts		= svs_get_bank_volts_v2,
			.buck_name		= "mali",
			.tzone_name		= "gpu",
			.opp_count		= MAX_OPP_ENTRIES,
			.vboot			= 0x30,
			.volt_step		= 6250,
			.volt_base		= 500000,
			.age_config		= 0x555555,
			.dc_config		= 0x555555,
			.vco			= 0x10,
			.chk_shift		= 0x77,
			.int_st			= BIT(3),
			.ctl0			= 0x00050001,
			.tzone_htemp		= 85000,
			.tzone_htemp_voffset	= 0,
			.tzone_ltemp		= 25000,
			.tzone_ltemp_voffset	= 3,
			.dev_fuse_map		= (const struct svs_fusemap[BDEV_MAX]) {
				{ 6, 0 }, { 6, 8 }, { 5, 0 }, { 6, 16 }, { 6, 24 }
			}
		},
		.volt_flags	= SVSB_INIT01_PD_REQ | SVSB_INIT01_VOLT_INC_ONLY,
		.mode_support	= SVSB_MODE_INIT01 | SVSB_MODE_INIT02 | SVSB_MODE_MON,
		.freq_base	= 900000000,
		.core_sel	= 0x8fff0003,
		.dvt_fixed	= 0x3,
		.vmax		= 0x40,
		.vmin		= 0x14,
	},
};

static const struct svs_platform_data svs_mt8195_platform_data = {
	.name = "mt8195-svs",
	.banks = svs_mt8195_banks,
	.efuse_parsing = svs_common_parse_efuse,
	.probe = svs_mt8192_platform_probe,
	.regs = svs_regs_v2,
	.bank_max = ARRAY_SIZE(svs_mt8195_banks),
	.ts_coeff = SVSB_TS_COEFF_MT8195,
	.glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) {
		{ 0, 0 }, { 19, 4 }
	}
};

static const struct svs_platform_data svs_mt8192_platform_data = {
	.name = "mt8192-svs",
	.banks = svs_mt8192_banks,
	.efuse_parsing = svs_common_parse_efuse,
	.probe = svs_mt8192_platform_probe,
	.regs = svs_regs_v2,
	.bank_max = ARRAY_SIZE(svs_mt8192_banks),
	.ts_coeff = SVSB_TS_COEFF_MT8195,
	.glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) {
		/* FT_PGM not present */
		{ -1, 0 }, { 19, 4 }
	}
};

static const struct svs_platform_data svs_mt8188_platform_data = {
	.name = "mt8188-svs",
	.banks = svs_mt8188_banks,
	.efuse_parsing = svs_common_parse_efuse,
	.probe = svs_mt8192_platform_probe,
	.regs = svs_regs_v2,
	.bank_max = ARRAY_SIZE(svs_mt8188_banks),
	.ts_coeff = SVSB_TS_COEFF_MT8195,
	.glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) {
		/* FT_PGM and VMIN not present */
		{ -1, 0 }, { -1, 0 }
	}
};

static const struct svs_platform_data svs_mt8186_platform_data = {
	.name = "mt8186-svs",
	.banks = svs_mt8186_banks,
	.efuse_parsing = svs_common_parse_efuse,
	.probe = svs_mt8192_platform_probe,
	.regs = svs_regs_v2,
	.bank_max = ARRAY_SIZE(svs_mt8186_banks),
	.ts_coeff = SVSB_TS_COEFF_MT8186,
	.glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) {
		/* FT_PGM and VMIN not present */
		{ -1, 0 }, { -1, 0 }
	}
};

static const struct svs_platform_data svs_mt8183_platform_data = {
	.name = "mt8183-svs",
	.banks = svs_mt8183_banks,
	.efuse_parsing = svs_mt8183_efuse_parsing,
	.probe = svs_mt8183_platform_probe,
	.regs = svs_regs_v2,
	.bank_max = ARRAY_SIZE(svs_mt8183_banks),
	.glb_fuse_map = (const struct svs_fusemap[GLB_MAX]) {
		/* VMIN not present */
		{ 0, 4 }, { -1, 0 }
	}
};

static const struct of_device_id svs_of_match[] = {
	{ .compatible = "mediatek,mt8195-svs", .data = &svs_mt8195_platform_data },
	{ .compatible = "mediatek,mt8192-svs", .data = &svs_mt8192_platform_data },
	{ .compatible = "mediatek,mt8188-svs", .data = &svs_mt8188_platform_data },
	{ .compatible = "mediatek,mt8186-svs", .data = &svs_mt8186_platform_data },
	{ .compatible = "mediatek,mt8183-svs", .data = &svs_mt8183_platform_data },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, svs_of_match);

static int svs_probe(struct platform_device *pdev)
{
	struct svs_platform *svsp;
	const struct svs_platform_data *svsp_data;
	int ret, svsp_irq;

	svsp_data = of_device_get_match_data(&pdev->dev);

	svsp = devm_kzalloc(&pdev->dev, sizeof(*svsp), GFP_KERNEL);
	if (!svsp)
		return -ENOMEM;

	svsp->dev = &pdev->dev;
	svsp->banks = svsp_data->banks;
	svsp->regs = svsp_data->regs;
	svsp->bank_max = svsp_data->bank_max;
	svsp->ts_coeff = svsp_data->ts_coeff;

	ret = svsp_data->probe(svsp);
	if (ret)
		return ret;

	ret = svs_get_efuse_data(svsp, "svs-calibration-data",
				 &svsp->efuse, &svsp->efuse_max);
	if (ret)
		return dev_err_probe(&pdev->dev, ret, "Cannot read SVS calibration\n");

	ret = svs_get_efuse_data(svsp, "t-calibration-data",
				 &svsp->tefuse, &svsp->tefuse_max);
	if (ret) {
		dev_err_probe(&pdev->dev, ret, "Cannot read SVS-Thermal calibration\n");
		goto svs_probe_free_efuse;
	}

	if (!svsp_data->efuse_parsing(svsp, svsp_data)) {
		ret = dev_err_probe(svsp->dev, -EINVAL, "efuse data parsing failed\n");
		goto svs_probe_free_tefuse;
	}

	ret = svs_bank_resource_setup(svsp);
	if (ret) {
		dev_err_probe(svsp->dev, ret, "svs bank resource setup fail\n");
		goto svs_probe_free_tefuse;
	}

	svsp_irq = platform_get_irq(pdev, 0);
	if (svsp_irq < 0) {
		ret = svsp_irq;
		goto svs_probe_free_tefuse;
	}

	svsp->main_clk = devm_clk_get(svsp->dev, "main");
	if (IS_ERR(svsp->main_clk)) {
		ret = dev_err_probe(svsp->dev, PTR_ERR(svsp->main_clk),
				    "failed to get clock\n");
		goto svs_probe_free_tefuse;
	}

	ret = clk_prepare_enable(svsp->main_clk);
	if (ret) {
		dev_err_probe(svsp->dev, ret, "cannot enable main clk\n");
		goto svs_probe_free_tefuse;
	}

	svsp->base = of_iomap(svsp->dev->of_node, 0);
	if (IS_ERR_OR_NULL(svsp->base)) {
		ret = dev_err_probe(svsp->dev, -EINVAL, "cannot find svs register base\n");
		goto svs_probe_clk_disable;
	}

	ret = devm_request_threaded_irq(svsp->dev, svsp_irq, NULL, svs_isr,
					IRQF_ONESHOT, svsp_data->name, svsp);
	if (ret) {
		dev_err_probe(svsp->dev, ret, "register irq(%d) failed\n", svsp_irq);
		goto svs_probe_iounmap;
	}

	ret = svs_start(svsp);
	if (ret) {
		dev_err_probe(svsp->dev, ret, "svs start fail\n");
		goto svs_probe_iounmap;
	}

#ifdef CONFIG_DEBUG_FS
	ret = svs_create_debug_cmds(svsp);
	if (ret) {
		dev_err_probe(svsp->dev, ret, "svs create debug cmds fail\n");
		goto svs_probe_iounmap;
	}
#endif

	return 0;

svs_probe_iounmap:
	iounmap(svsp->base);
svs_probe_clk_disable:
	clk_disable_unprepare(svsp->main_clk);
svs_probe_free_tefuse:
	kfree(svsp->tefuse);
svs_probe_free_efuse:
	kfree(svsp->efuse);
	return ret;
}

static DEFINE_SIMPLE_DEV_PM_OPS(svs_pm_ops, svs_suspend, svs_resume);

static struct platform_driver svs_driver = {
	.probe	= svs_probe,
	.driver	= {
		.name		= "mtk-svs",
		.pm		= &svs_pm_ops,
		.of_match_table	= svs_of_match,
	},
};

module_platform_driver(svs_driver);

MODULE_AUTHOR("Roger Lu <roger.lu@mediatek.com>");
MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>");
MODULE_DESCRIPTION("MediaTek SVS driver");
MODULE_LICENSE("GPL");