2019-09-06 11:15:30

by Roger Lu

[permalink] [raw]
Subject: [PATCH v5 0/3] PM / AVS: SVS: Introduce SVS engine

1. SVS driver use OPP adjust event in [1] to update OPP table voltage part.
2. SVS dts node refers to CPU opp table [2] and GPU opp table [3].
3. SVS dts node refers to thermal efuse [4] and PMIC regulator [5].

[1] https://patchwork.kernel.org/patch/11092245/
[2] https://patchwork.kernel.org/patch/10934123/
[3] https://patchwork.kernel.org/patch/11132381/
[4] https://patchwork.kernel.org/patch/11093655/
[5] https://patchwork.kernel.org/patch/11110493/

Roger Lu (3):
dt-bindings: soc: add mtk svs dt-bindings
arm64: dts: mt8183: add svs device information
PM / AVS: SVS: Introduce SVS engine

.../devicetree/bindings/power/mtk-svs.txt | 88 +
arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +
arch/arm64/boot/dts/mediatek/mt8183.dtsi | 38 +
drivers/power/avs/Kconfig | 10 +
drivers/power/avs/Makefile | 1 +
drivers/power/avs/mtk_svs.c | 2075 +++++++++++++++++
include/linux/power/mtk_svs.h | 23 +
7 files changed, 2251 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
create mode 100644 drivers/power/avs/mtk_svs.c
create mode 100644 include/linux/power/mtk_svs.h


2019-09-06 11:15:39

by Roger Lu

[permalink] [raw]
Subject: [PATCH v5 2/3] arm64: dts: mt8183: add svs device information

Add pmic/clock/irq/efuse setting in svs noce

Signed-off-by: Roger Lu <[email protected]>
---
arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +++++++++
arch/arm64/boot/dts/mediatek/mt8183.dtsi | 38 +++++++++++++++++++++
2 files changed, 54 insertions(+)

diff --git a/arch/arm64/boot/dts/mediatek/mt8183-evb.dts b/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
index d8e555cbb5d3..7c1d6e6a2a85 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
+++ b/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
@@ -135,6 +135,22 @@

};

+&svs_cpu_little {
+ vcpu-little-supply = <&mt6358_vproc12_reg>;
+};
+
+&svs_cpu_big {
+ vcpu-big-supply = <&mt6358_vproc11_reg>;
+};
+
+&svs_cci {
+ vcci-supply = <&mt6358_vproc12_reg>;
+};
+
+&svs_gpu {
+ vgpu-spply = <&mt6358_vgpu_reg>;
+};
+
&uart0 {
status = "okay";
};
diff --git a/arch/arm64/boot/dts/mediatek/mt8183.dtsi b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
index 66aaa07f6cec..48343328bec2 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
@@ -351,6 +351,39 @@
status = "disabled";
};

+ svs: svs@1100b000 {
+ compatible = "mediatek,mt8183-svs";
+ reg = <0 0x1100b000 0 0x1000>;
+ interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW>;
+ clocks = <&infracfg CLK_INFRA_THERM>;
+ clock-names = "main_clk";
+ nvmem-cells = <&svs_calibration>,
+ <&thermal_calibration>;
+ nvmem-cell-names = "svs-calibration-data",
+ "calibration-data";
+
+ svs_cpu_little: svs_cpu_little {
+ compatible = "mediatek,mt8183-svs-cpu-little";
+ operating-points-v2 = <&cluster0_opp>;
+ };
+
+ svs_cpu_big: svs_cpu_big {
+ compatible = "mediatek,mt8183-svs-cpu-big";
+ operating-points-v2 = <&cluster1_opp>;
+ };
+
+ svs_cci: svs_cci {
+ compatible = "mediatek,mt8183-svs-cci";
+ operating-points-v2 = <&cci_opp>;
+ };
+
+ svs_gpu: svs_gpu {
+ compatible = "mediatek,mt8183-svs-gpu";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
+ operating-points-v2 = <&gpu_opp_table>;
+ };
+ };
+
spi0: spi@1100a000 {
compatible = "mediatek,mt8183-spi";
#address-cells = <1>;
@@ -439,6 +472,11 @@
compatible = "mediatek,mt8183-efuse",
"mediatek,efuse";
reg = <0 0x11f10000 0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ svs_calibration: calib@580 {
+ reg = <0x580 0x64>;
+ };
};

mfgcfg: syscon@13000000 {
--
2.18.0

2019-09-06 11:15:59

by Roger Lu

[permalink] [raw]
Subject: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

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 <[email protected]>
---
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

2019-09-06 14:19:39

by Roger Lu

[permalink] [raw]
Subject: [PATCH v5 1/3] dt-bindings: soc: add mtk svs dt-bindings

Document the binding for enabling mtk svs on MediaTek SoC.

Signed-off-by: Roger Lu <[email protected]>
---
.../devicetree/bindings/power/mtk-svs.txt | 88 +++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt

diff --git a/Documentation/devicetree/bindings/power/mtk-svs.txt b/Documentation/devicetree/bindings/power/mtk-svs.txt
new file mode 100644
index 000000000000..6a71992ef162
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
@@ -0,0 +1,88 @@
+* Mediatek Smart Voltage Scaling (MTK SVS)
+
+This describes the device tree binding for the MTK SVS controller (bank)
+which helps provide the optimized CPU/GPU/CCI voltages. This device also
+needs thermal data to calculate thermal slope for accurately compensate
+the voltages when temperature change.
+
+Required properties:
+- compatible:
+ - "mediatek,mt8183-svs" : For MT8183 family of SoCs
+- reg: Address range of the MTK SVS controller.
+- interrupts: IRQ for the MTK SVS controller.
+- clocks, clock-names: Clocks needed for the svs controller. required
+ clocks are:
+ "main_clk": Main clock needed for register access
+- nvmem-cells: Phandle to the calibration data provided by a nvmem device.
+- nvmem-cell-names: Should be "svs-calibration-data" and "calibration-data"
+
+Subnodes:
+- svs_cpu_little: SVS bank device node of little CPU
+ compatible: "mediatek,mt8183-svs-cpu-little"
+ operating-points-v2: OPP table hooked by SVS little CPU bank.
+ SVS will optimze this OPP table voltage part.
+ vcpu-little-supply: PMIC buck of little CPU
+- svs_cpu_big: SVS bank device node of big CPU
+ compatible: "mediatek,mt8183-svs-cpu-big"
+ operating-points-v2: OPP table hooked by SVS big CPU bank.
+ SVS will optimze this OPP table voltage part.
+ vcpu-big-supply: PMIC buck of big CPU
+- svs_cci: SVS bank device node of CCI
+ compatible: "mediatek,mt8183-svs-cci"
+ operating-points-v2: OPP table hooked by SVS CCI bank.
+ SVS will optimze this OPP table voltage part.
+ vcci-supply: PMIC buck of CCI
+- svs_gpu: SVS bank device node of GPU
+ compatible: "mediatek,mt8183-svs-gpu"
+ operating-points-v2: OPP table hooked by SVS GPU bank.
+ SVS will optimze this OPP table voltage part.
+ vgpu-spply: PMIC buck of GPU
+
+Example:
+
+ svs: svs@1100b000 {
+ compatible = "mediatek,mt8183-svs";
+ reg = <0 0x1100b000 0 0x1000>;
+ interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW 0>;
+ clocks = <&infracfg CLK_INFRA_THERM>;
+ clock-names = "main_clk";
+ nvmem-cells = <&svs_calibration>, <&thermal_calibration>;
+ nvmem-cell-names = "svs-calibration-data", "calibration-data";
+
+ svs_cpu_little: svs_cpu_little {
+ compatible = "mediatek,mt8183-svs-cpu-little";
+ operating-points-v2 = <&cluster0_opp>;
+ };
+
+ svs_cpu_big: svs_cpu_big {
+ compatible = "mediatek,mt8183-svs-cpu-big";
+ operating-points-v2 = <&cluster1_opp>;
+ };
+
+ svs_cci: svs_cci {
+ compatible = "mediatek,mt8183-svs-cci";
+ operating-points-v2 = <&cci_opp>;
+ };
+
+ svs_gpu: svs_gpu {
+ compatible = "mediatek,mt8183-svs-gpu";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
+ operating-points-v2 = <&gpu_opp_table>;
+ };
+ };
+
+ &svs_cpu_little {
+ vcpu-little-supply = <&mt6358_vproc12_reg>;
+ };
+
+ &svs_cpu_big {
+ vcpu-big-supply = <&mt6358_vproc11_reg>;
+ };
+
+ &svs_cci {
+ vcci-supply = <&mt6358_vproc12_reg>;
+ };
+
+ &svs_gpu {
+ vgpu-spply = <&mt6358_vgpu_reg>;
+ };
--
2.18.0

2019-09-06 15:48:14

by Roger Lu

[permalink] [raw]
Subject: Re: [PATCH v5 0/3] PM / AVS: SVS: Introduce SVS engine

Excuse me, I forgot to add "changes since" below. Add it back now.

On Fri, 2019-09-06 at 18:05 +0800, Roger Lu wrote:
> 1. SVS driver use OPP adjust event in [1] to update OPP table voltage part.
> 2. SVS dts node refers to CPU opp table [2] and GPU opp table [3].
> 3. SVS dts node refers to thermal efuse [4] and PMIC regulator [5].
>
> [1] https://patchwork.kernel.org/patch/11092245/
> [2] https://patchwork.kernel.org/patch/10934123/
> [3] https://patchwork.kernel.org/patch/11132381/
> [4] https://patchwork.kernel.org/patch/11093655/
> [5] https://patchwork.kernel.org/patch/11110493/

changes since v4:
- Add SVS dts node patch in SVS patch series.

> Roger Lu (3):
> dt-bindings: soc: add mtk svs dt-bindings
> arm64: dts: mt8183: add svs device information
> PM / AVS: SVS: Introduce SVS engine
>
> .../devicetree/bindings/power/mtk-svs.txt | 88 +
> arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +
> arch/arm64/boot/dts/mediatek/mt8183.dtsi | 38 +
> drivers/power/avs/Kconfig | 10 +
> drivers/power/avs/Makefile | 1 +
> drivers/power/avs/mtk_svs.c | 2075 +++++++++++++++++
> include/linux/power/mtk_svs.h | 23 +
> 7 files changed, 2251 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
> create mode 100644 drivers/power/avs/mtk_svs.c
> create mode 100644 include/linux/power/mtk_svs.h


2019-09-26 22:42:30

by Kevin Hilman

[permalink] [raw]
Subject: Re: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

Hi Roger,

Roger Lu <[email protected]> writes:

> 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 <[email protected]>

I don't have any documentation on this SVS hardware, so I won't be able
to comment on the specific functionality, so I attempted a first-pass
high-level review...

In the absence of documentation, it would be helpful to give a high
level description of how this hardware works. In particular, a
high-level overview of the control loop, which appears to be primarily
interrupt driven, would be helpful. e.g. how often are the interrupts
firing, and why.

It's hard to understand the "init01" vs the "init02" patch since
those names aren't very descriptive. :)

Also, there are lots of hard-coded constants (masks, shifts, etc.) used
throughout the calculations. #defines could help readability for many
of these, especially the masks.

> ---
> 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);

I don't see the need for these wrapper functions. You seem to be
declaring this lock in the header which implies there are external
users, but there is none in this series, so this lock should stay local
IMO.

> +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;
> +}

Maybe some math helpers from math64.h would help you not have to
open-code this?

> +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.
> + */

minor nit: fix multi-line comment style

> + 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;
> + }
> +}

lots of magic constants for these register writes. #defines w/bit
descriptions would be very helpful for the reader.

> +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)

nit: this appears to be a check if the platform supports SVS. If so,
then it should probably be 'bool' and be called svs_is_supported().

> +{
> + 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;
> +}

There are lots of resources (platform_device, all the kmalloc regions)
that are never free'd here. You might consider using devm for this.

Related, all the small malloc's are calculated based on the opp_count
for each bank, but they are all hard-coded to 16. Seems it would be
better to have a default/max size for this in the bank structure itself
instead of making these small allocations.

> +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);

I'm assuming this part will disable future interrupts?

I'm not seeing where they get re-enabled on the resume path.


> + 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;
> +}

Kevin

2019-09-30 13:37:39

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v5 1/3] dt-bindings: soc: add mtk svs dt-bindings

On Fri, Sep 06, 2019 at 06:05:13PM +0800, Roger Lu wrote:
> Document the binding for enabling mtk svs on MediaTek SoC.
>
> Signed-off-by: Roger Lu <[email protected]>
> ---
> .../devicetree/bindings/power/mtk-svs.txt | 88 +++++++++++++++++++
> 1 file changed, 88 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
>
> diff --git a/Documentation/devicetree/bindings/power/mtk-svs.txt b/Documentation/devicetree/bindings/power/mtk-svs.txt
> new file mode 100644
> index 000000000000..6a71992ef162
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> @@ -0,0 +1,88 @@
> +* Mediatek Smart Voltage Scaling (MTK SVS)
> +
> +This describes the device tree binding for the MTK SVS controller (bank)
> +which helps provide the optimized CPU/GPU/CCI voltages. This device also
> +needs thermal data to calculate thermal slope for accurately compensate
> +the voltages when temperature change.
> +
> +Required properties:
> +- compatible:
> + - "mediatek,mt8183-svs" : For MT8183 family of SoCs
> +- reg: Address range of the MTK SVS controller.
> +- interrupts: IRQ for the MTK SVS controller.
> +- clocks, clock-names: Clocks needed for the svs controller. required
> + clocks are:
> + "main_clk": Main clock needed for register access

'_clk' is redundant.

> +- nvmem-cells: Phandle to the calibration data provided by a nvmem device.
> +- nvmem-cell-names: Should be "svs-calibration-data" and "calibration-data"
> +
> +Subnodes:
> +- svs_cpu_little: SVS bank device node of little CPU
> + compatible: "mediatek,mt8183-svs-cpu-little"
> + operating-points-v2: OPP table hooked by SVS little CPU bank.
> + SVS will optimze this OPP table voltage part.
> + vcpu-little-supply: PMIC buck of little CPU
> +- svs_cpu_big: SVS bank device node of big CPU
> + compatible: "mediatek,mt8183-svs-cpu-big"
> + operating-points-v2: OPP table hooked by SVS big CPU bank.
> + SVS will optimze this OPP table voltage part.
> + vcpu-big-supply: PMIC buck of big CPU
> +- svs_cci: SVS bank device node of CCI
> + compatible: "mediatek,mt8183-svs-cci"
> + operating-points-v2: OPP table hooked by SVS CCI bank.
> + SVS will optimze this OPP table voltage part.
> + vcci-supply: PMIC buck of CCI
> +- svs_gpu: SVS bank device node of GPU
> + compatible: "mediatek,mt8183-svs-gpu"
> + operating-points-v2: OPP table hooked by SVS GPU bank.
> + SVS will optimze this OPP table voltage part.
> + vgpu-spply: PMIC buck of GPU
> +
> +Example:
> +
> + svs: svs@1100b000 {
> + compatible = "mediatek,mt8183-svs";
> + reg = <0 0x1100b000 0 0x1000>;
> + interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW 0>;

GIC interrupts are 3 cells, you have 4.

> + clocks = <&infracfg CLK_INFRA_THERM>;
> + clock-names = "main_clk";
> + nvmem-cells = <&svs_calibration>, <&thermal_calibration>;
> + nvmem-cell-names = "svs-calibration-data", "calibration-data";
> +
> + svs_cpu_little: svs_cpu_little {

Don't use '_' in node names.

> + compatible = "mediatek,mt8183-svs-cpu-little";
> + operating-points-v2 = <&cluster0_opp>;
> + };
> +
> + svs_cpu_big: svs_cpu_big {
> + compatible = "mediatek,mt8183-svs-cpu-big";
> + operating-points-v2 = <&cluster1_opp>;
> + };
> +
> + svs_cci: svs_cci {
> + compatible = "mediatek,mt8183-svs-cci";
> + operating-points-v2 = <&cci_opp>;
> + };
> +
> + svs_gpu: svs_gpu {
> + compatible = "mediatek,mt8183-svs-gpu";
> + power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
> + operating-points-v2 = <&gpu_opp_table>;
> + };
> + };
> +
> + &svs_cpu_little {
> + vcpu-little-supply = <&mt6358_vproc12_reg>;

It's already defined to have OPP and supply in the cpu nodes. Parse them
to get this information rather than duplicating it here.

The same should apply to the CCI and GPU.

Rob

2019-10-21 07:55:03

by Pi-Hsun Shih

[permalink] [raw]
Subject: Re: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

Hi Roger,

On Fri, Sep 6, 2019 at 6:06 PM Roger Lu <[email protected]> wrote:
> ...
> +static int svs_resource_setup(struct mtk_svs *svs)
> ...
> + 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;

Should have dev_pm_opp_put(opp); here.

> + }
> + }
> +
> + return 0;
> +}
> ...
> +static int svs_status_proc_show(struct seq_file *m, void *v)
> ...
> + 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]);

Same here.

> + }
> +
> + return 0;
> +}
> ...

2019-11-01 06:14:08

by Roger Lu

[permalink] [raw]
Subject: Re: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

Dear Pi-Hsun,

Thanks for reminding me. I'll add dev_pm_opp_put(opp) in the next
patchset.

On Mon, 2019-10-21 at 15:51 +0800, Pi-Hsun Shih wrote:
> Hi Roger,
>
> On Fri, Sep 6, 2019 at 6:06 PM Roger Lu <[email protected]> wrote:
> > ...
> > +static int svs_resource_setup(struct mtk_svs *svs)
> > ...
> > + 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;
>
> Should have dev_pm_opp_put(opp); here.
Sure. Thanks.

>
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > ...
> > +static int svs_status_proc_show(struct seq_file *m, void *v)
> > ...
> > + 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]);
>
> Same here.
Sure. Thanks.

>
> > + }
> > +
> > + return 0;
> > +}
> > ...


2019-11-14 07:43:57

by Pi-Hsun Shih

[permalink] [raw]
Subject: Re: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

Hi Roger,

On Fri, Sep 6, 2019 at 6:06 PM Roger Lu <[email protected]> wrote:
>
> 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 <[email protected]>
> ---
> 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/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
> [...]
> +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);

The version of this function in opp tree
(https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm.git/commit/?h=opp/linux-next&id=25cb20a212a1f989385dfe23230817e69c62bee5)
has a different function signature, so this should be changed too.

> + 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 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);

Same here.

> + 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;
> +}
> +
> [...]

2019-12-27 06:52:26

by Roger Lu

[permalink] [raw]
Subject: Re: [PATCH v5 1/3] dt-bindings: soc: add mtk svs dt-bindings

Dear Rob,

Sorry for the late reply.

On Mon, 2019-09-30 at 08:35 -0500, Rob Herring wrote:
> On Fri, Sep 06, 2019 at 06:05:13PM +0800, Roger Lu wrote:
> > Document the binding for enabling mtk svs on MediaTek SoC.
> >
> > Signed-off-by: Roger Lu <[email protected]>
> > ---
> > .../devicetree/bindings/power/mtk-svs.txt | 88 +++++++++++++++++++
> > 1 file changed, 88 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
> >
> > diff --git a/Documentation/devicetree/bindings/power/mtk-svs.txt b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > new file mode 100644
> > index 000000000000..6a71992ef162
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > @@ -0,0 +1,88 @@
> > +* Mediatek Smart Voltage Scaling (MTK SVS)
> > +
> > +This describes the device tree binding for the MTK SVS controller (bank)
> > +which helps provide the optimized CPU/GPU/CCI voltages. This device also
> > +needs thermal data to calculate thermal slope for accurately compensate
> > +the voltages when temperature change.
> > +
> > +Required properties:
> > +- compatible:
> > + - "mediatek,mt8183-svs" : For MT8183 family of SoCs
> > +- reg: Address range of the MTK SVS controller.
> > +- interrupts: IRQ for the MTK SVS controller.
> > +- clocks, clock-names: Clocks needed for the svs controller. required
> > + clocks are:
> > + "main_clk": Main clock needed for register access
>
> '_clk' is redundant.

Oh Okay. I'll remove _clk. Thanks.

>
> > +- nvmem-cells: Phandle to the calibration data provided by a nvmem device.
> > +- nvmem-cell-names: Should be "svs-calibration-data" and "calibration-data"
> > +
> > +Subnodes:
> > +- svs_cpu_little: SVS bank device node of little CPU
> > + compatible: "mediatek,mt8183-svs-cpu-little"
> > + operating-points-v2: OPP table hooked by SVS little CPU bank.
> > + SVS will optimze this OPP table voltage part.
> > + vcpu-little-supply: PMIC buck of little CPU
> > +- svs_cpu_big: SVS bank device node of big CPU
> > + compatible: "mediatek,mt8183-svs-cpu-big"
> > + operating-points-v2: OPP table hooked by SVS big CPU bank.
> > + SVS will optimze this OPP table voltage part.
> > + vcpu-big-supply: PMIC buck of big CPU
> > +- svs_cci: SVS bank device node of CCI
> > + compatible: "mediatek,mt8183-svs-cci"
> > + operating-points-v2: OPP table hooked by SVS CCI bank.
> > + SVS will optimze this OPP table voltage part.
> > + vcci-supply: PMIC buck of CCI
> > +- svs_gpu: SVS bank device node of GPU
> > + compatible: "mediatek,mt8183-svs-gpu"
> > + operating-points-v2: OPP table hooked by SVS GPU bank.
> > + SVS will optimze this OPP table voltage part.
> > + vgpu-spply: PMIC buck of GPU
> > +
> > +Example:
> > +
> > + svs: svs@1100b000 {
> > + compatible = "mediatek,mt8183-svs";
> > + reg = <0 0x1100b000 0 0x1000>;
> > + interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW 0>;
>
> GIC interrupts are 3 cells, you have 4.

Oops, I'll remove the fourth parameter. Thanks a lot.

>
> > + clocks = <&infracfg CLK_INFRA_THERM>;
> > + clock-names = "main_clk";
> > + nvmem-cells = <&svs_calibration>, <&thermal_calibration>;
> > + nvmem-cell-names = "svs-calibration-data", "calibration-data";
> > +
> > + svs_cpu_little: svs_cpu_little {
>
> Don't use '_' in node names.

Okay. I'll replace it with '-'. Thanks.

>
> > + compatible = "mediatek,mt8183-svs-cpu-little";
> > + operating-points-v2 = <&cluster0_opp>;
> > + };
> > +
> > + svs_cpu_big: svs_cpu_big {
> > + compatible = "mediatek,mt8183-svs-cpu-big";
> > + operating-points-v2 = <&cluster1_opp>;
> > + };
> > +
> > + svs_cci: svs_cci {
> > + compatible = "mediatek,mt8183-svs-cci";
> > + operating-points-v2 = <&cci_opp>;
> > + };
> > +
> > + svs_gpu: svs_gpu {
> > + compatible = "mediatek,mt8183-svs-gpu";
> > + power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
> > + operating-points-v2 = <&gpu_opp_table>;
> > + };
> > + };
> > +
> > + &svs_cpu_little {
> > + vcpu-little-supply = <&mt6358_vproc12_reg>;
>
> It's already defined to have OPP and supply in the cpu nodes. Parse them
> to get this information rather than duplicating it here.
>
> The same should apply to the CCI and GPU.

Please let me explain the reason why I add SVS sub-nodes. I ever try to
parse other nodes to get desired power-domains/OPP table. However, it
makes SVS driver harder to develop and maintain.

1. When a SVS-controller-init wants GPU_CORE0's OPP table in one node
but it needs power-domains(GPU_MFG_2D) in another node, it becomes
complicated and confusing when SVS sub-node tries to parse many nodes.
Therefore, we want SVS sub-node to focus on what SVS bank requires by
how we do in this patch.

2. In hardware point of view, SVS controller depends on other hardware's
power only. All the SVS controller registers are in SVS hardware. So, we
think It's good that SVS sub-node describes what SVS controller requires
instead of linking other subsys nodes and parse the property that SVS
controller needs.

3. We want SVS driver to have a generic way to attain subsys device for
using "pm_runtime and OPP framework" API. If SVS driver tries to parse
CPU(little/big core) and other subsys device node(e.g cci/gpu), it means
SVS driver has to maintain different methodologies(cpu-specific?
devfreq? others?) in order to get CPU(little/big core) and other subsys
device(e.g cci/gpu) for using "pm_runtime and OPP framework" API.

>
> Rob

Sincerely,
Roger Lu.

2019-12-27 07:17:05

by Roger Lu

[permalink] [raw]
Subject: Re: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

Dear Pi-Hsun,

Sorry for the late reply.

On Thu, 2019-11-14 at 15:41 +0800, Pi-Hsun Shih wrote:
> Hi Roger,
>
> On Fri, Sep 6, 2019 at 6:06 PM Roger Lu <[email protected]> wrote:
> >
> > 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 <[email protected]>
> > ---
> > 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/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
> > [...]
> > +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);
>
> The version of this function in opp tree
> (https://git.kernel.org/pub/scm/linux/kernel/git/vireshk/pm.git/commit/?h=opp/linux-next&id=25cb20a212a1f989385dfe23230817e69c62bee5)
> has a different function signature, so this should be changed too.

Sure. I'll update it in the next patch.

>
> > + 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 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);
>
> Same here.

Sure. I'll update it in the next patch.

>
> > + 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;
> > +}
> > +
> > [...]
>
> _______________________________________________
> Linux-mediatek mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-mediatek

2020-01-03 05:46:05

by Roger Lu

[permalink] [raw]
Subject: Re: [PATCH v5 3/3] PM / AVS: SVS: Introduce SVS engine

Dear Kevin Sir,

Sorry for the late reply and thanks for the advice to this patch.

On Thu, 2019-09-26 at 15:39 -0700, Kevin Hilman wrote:
> Hi Roger,
>
> Roger Lu <[email protected]> writes:
>
> > 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 <[email protected]>
>
> I don't have any documentation on this SVS hardware, so I won't be able
> to comment on the specific functionality, so I attempted a first-pass
> high-level review...

Excuse me, we don't have the documentation for SVS hardware. I'll try my
best to introduce SVS online.

>
> In the absence of documentation, it would be helpful to give a high
> level description of how this hardware works. In particular, a
> high-level overview of the control loop, which appears to be primarily
> interrupt driven, would be helpful. e.g. how often are the interrupts
> firing, and why.

1. MT8183 SVS has 4 controllers (banks). One bank for optimizing one
power-domain OPP voltages.
- 4 power-domain (CPU-little / CPU-BIG / CCI / GPU) OPP voltages will be
optimized by SVS banks..
2. Each SVS bank needs to run its init01/init02 separately so that SVS
bank's optimized OPP voltages can be calculated to system.
- init01: SVS bank basic init (must)
- init02: SVS bank provide high temperature OPP voltages (must)
- mon mode: SVS bank dynamically optimizes OPP voltages basing on
temperature change. (optional)
3. SVS interrupt behavior
- In init01/init02, SVS only fire one interrupt to inform SW that init
is finished and optimized OPP voltages are ready.
- In mon mode, SVS fire interrupt when temperature changes. Mon mode
interrupt is also for informing new optimized OPP voltages are ready.

>
> It's hard to understand the "init01" vs the "init02" patch since
> those names aren't very descriptive. :)
>
> Also, there are lots of hard-coded constants (masks, shifts, etc.) used
> throughout the calculations. #defines could help readability for many
> of these, especially the masks.
>
> > ---
> > 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);
>
> I don't see the need for these wrapper functions. You seem to be
> declaring this lock in the header which implies there are external
> users, but there is none in this series, so this lock should stay local
> IMO.

This export API is for mtk thermal driver.
https://patchwork.kernel.org/patch/10938841/

>
> > +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;
> > +}
>
> Maybe some math helpers from math64.h would help you not have to
> open-code this?

Oh, thanks for the reminding. I haven't found the suitable "percent()
API" from math64.h and will keep looking suitable API to replace these
codes.

>
> > +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.
> > + */
>
> minor nit: fix multi-line comment style

Sure. I'll fix it in the next patch.Thanks.

>
> > + 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;
> > + }
> > +}
>
> lots of magic constants for these register writes. #defines w/bit
> descriptions would be very helpful for the reader.

Sure, I'll add #define w/bit for these magic constants in the next
patch. Thanks.

>
> > +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)
>
> nit: this appears to be a check if the platform supports SVS. If so,
> then it should probably be 'bool' and be called svs_is_supported().

bool/svs_is_supported are good ideas. I'll apply it in the next patch.
Thanks.

>
> > +{
> > + 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;
> > +}
>
> There are lots of resources (platform_device, all the kmalloc regions)
> that are never free'd here. You might consider using devm for this.
>
> Related, all the small malloc's are calculated based on the opp_count
> for each bank, but they are all hard-coded to 16. Seems it would be
> better to have a default/max size for this in the bank structure itself
> instead of making these small allocations.

Thanks for the advices. The default/max size are good ideas. I'll apply
it in the next patch.

>
> > +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);
>
> I'm assuming this part will disable future interrupts?
Yes, this for-loop will disable all the SVS banks' interrupts.

> I'm not seeing where they get re-enabled on the resume path.
In the svs_resume(), we re-run svs_init02() to enable each SVS bank.

>
>
> > + 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;
> > +}
>
> Kevin

2020-01-13 07:05:10

by Nicolas Boichat

[permalink] [raw]
Subject: Re: [PATCH v5 1/3] dt-bindings: soc: add mtk svs dt-bindings

On Fri, Dec 27, 2019 at 2:51 PM Roger Lu <[email protected]> wrote:
>
> Dear Rob,
>
> Sorry for the late reply.
>
> On Mon, 2019-09-30 at 08:35 -0500, Rob Herring wrote:
> > On Fri, Sep 06, 2019 at 06:05:13PM +0800, Roger Lu wrote:
> > > Document the binding for enabling mtk svs on MediaTek SoC.
> > >
> > > Signed-off-by: Roger Lu <[email protected]>
> > > ---
> > > .../devicetree/bindings/power/mtk-svs.txt | 88 +++++++++++++++++++
> > > 1 file changed, 88 insertions(+)
> > > create mode 100644 Documentation/devicetree/bindings/power/mtk-svs.txt
> > >
> > > diff --git a/Documentation/devicetree/bindings/power/mtk-svs.txt b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > > new file mode 100644
> > > index 000000000000..6a71992ef162
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > > @@ -0,0 +1,88 @@
> > > +* Mediatek Smart Voltage Scaling (MTK SVS)
> > > +
> > > +This describes the device tree binding for the MTK SVS controller (bank)
> > > +which helps provide the optimized CPU/GPU/CCI voltages. This device also
> > > +needs thermal data to calculate thermal slope for accurately compensate
> > > +the voltages when temperature change.
> > > +
> > > +Required properties:
> > > +- compatible:
> > > + - "mediatek,mt8183-svs" : For MT8183 family of SoCs
> > > +- reg: Address range of the MTK SVS controller.
> > > +- interrupts: IRQ for the MTK SVS controller.
> > > +- clocks, clock-names: Clocks needed for the svs controller. required
> > > + clocks are:
> > > + "main_clk": Main clock needed for register access
> >
> > '_clk' is redundant.
>
> Oh Okay. I'll remove _clk. Thanks.
>
> >
> > > +- nvmem-cells: Phandle to the calibration data provided by a nvmem device.
> > > +- nvmem-cell-names: Should be "svs-calibration-data" and "calibration-data"
> > > +
> > > +Subnodes:
> > > +- svs_cpu_little: SVS bank device node of little CPU
> > > + compatible: "mediatek,mt8183-svs-cpu-little"
> > > + operating-points-v2: OPP table hooked by SVS little CPU bank.
> > > + SVS will optimze this OPP table voltage part.
> > > + vcpu-little-supply: PMIC buck of little CPU
> > > +- svs_cpu_big: SVS bank device node of big CPU
> > > + compatible: "mediatek,mt8183-svs-cpu-big"
> > > + operating-points-v2: OPP table hooked by SVS big CPU bank.
> > > + SVS will optimze this OPP table voltage part.
> > > + vcpu-big-supply: PMIC buck of big CPU
> > > +- svs_cci: SVS bank device node of CCI
> > > + compatible: "mediatek,mt8183-svs-cci"
> > > + operating-points-v2: OPP table hooked by SVS CCI bank.
> > > + SVS will optimze this OPP table voltage part.
> > > + vcci-supply: PMIC buck of CCI
> > > +- svs_gpu: SVS bank device node of GPU
> > > + compatible: "mediatek,mt8183-svs-gpu"
> > > + operating-points-v2: OPP table hooked by SVS GPU bank.
> > > + SVS will optimze this OPP table voltage part.
> > > + vgpu-spply: PMIC buck of GPU
> > > +
> > > +Example:
> > > +
> > > + svs: svs@1100b000 {
> > > + compatible = "mediatek,mt8183-svs";
> > > + reg = <0 0x1100b000 0 0x1000>;
> > > + interrupts = <GIC_SPI 127 IRQ_TYPE_LEVEL_LOW 0>;
> >
> > GIC interrupts are 3 cells, you have 4.
>
> Oops, I'll remove the fourth parameter. Thanks a lot.
>
> >
> > > + clocks = <&infracfg CLK_INFRA_THERM>;
> > > + clock-names = "main_clk";
> > > + nvmem-cells = <&svs_calibration>, <&thermal_calibration>;
> > > + nvmem-cell-names = "svs-calibration-data", "calibration-data";
> > > +
> > > + svs_cpu_little: svs_cpu_little {
> >
> > Don't use '_' in node names.
>
> Okay. I'll replace it with '-'. Thanks.
>
> >
> > > + compatible = "mediatek,mt8183-svs-cpu-little";
> > > + operating-points-v2 = <&cluster0_opp>;
> > > + };
> > > +
> > > + svs_cpu_big: svs_cpu_big {
> > > + compatible = "mediatek,mt8183-svs-cpu-big";
> > > + operating-points-v2 = <&cluster1_opp>;
> > > + };
> > > +
> > > + svs_cci: svs_cci {
> > > + compatible = "mediatek,mt8183-svs-cci";
> > > + operating-points-v2 = <&cci_opp>;
> > > + };
> > > +
> > > + svs_gpu: svs_gpu {
> > > + compatible = "mediatek,mt8183-svs-gpu";
> > > + power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
> > > + operating-points-v2 = <&gpu_opp_table>;
> > > + };
> > > + };
> > > +
> > > + &svs_cpu_little {
> > > + vcpu-little-supply = <&mt6358_vproc12_reg>;
> >
> > It's already defined to have OPP and supply in the cpu nodes. Parse them
> > to get this information rather than duplicating it here.
> >
> > The same should apply to the CCI and GPU.
>
> Please let me explain the reason why I add SVS sub-nodes. I ever try to
> parse other nodes to get desired power-domains/OPP table. However, it
> makes SVS driver harder to develop and maintain.
>
> 1. When a SVS-controller-init wants GPU_CORE0's OPP table in one node
> but it needs power-domains(GPU_MFG_2D) in another node, it becomes
> complicated and confusing when SVS sub-node tries to parse many nodes.
> Therefore, we want SVS sub-node to focus on what SVS bank requires by
> how we do in this patch.
>
> 2. In hardware point of view, SVS controller depends on other hardware's
> power only. All the SVS controller registers are in SVS hardware. So, we
> think It's good that SVS sub-node describes what SVS controller requires
> instead of linking other subsys nodes and parse the property that SVS
> controller needs.
>
> 3. We want SVS driver to have a generic way to attain subsys device for
> using "pm_runtime and OPP framework" API. If SVS driver tries to parse
> CPU(little/big core) and other subsys device node(e.g cci/gpu), it means
> SVS driver has to maintain different methodologies(cpu-specific?
> devfreq? others?) in order to get CPU(little/big core) and other subsys
> device(e.g cci/gpu) for using "pm_runtime and OPP framework" API.

(Didn't see this more complete reply before replying to v6, I can see
your argument, but if we wanted to push further to have the sub-device
node in the DT)

From what I see, the SVS driver for node x (cpu/cci/gpu) requires only 3 things:
- An OPP table => that should always be "operating-points-v2"
property of the node x.
- A power domain => that should always be power-domains property of node x.
- A regulator. That one is a bit tricky as the cpu/cci uses "proc",
but gpu uses "mali" (at least on 8183). But maybe you can add a
child-regulator-name property or something to the DT so that the SVS
driver can find the correct regulator?

Seems like the solution could be quite generic?

> >
> > Rob
>
> Sincerely,
> Roger Lu.