2020-01-07 07:03:24

by Roger Lu

[permalink] [raw]
Subject: 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 and thermal dts use the same thermal efuse [4].
4. SVS dts needs PMIC regulator [5].

[1] https://patchwork.kernel.org/patch/11193513/
[2] https://patchwork.kernel.org/patch/11304935/
[3] https://patchwork.kernel.org/patch/11132381/
[4] https://patchwork.kernel.org/patch/11316495/
[5] https://patchwork.kernel.org/patch/11284617/

changes since v5:
- Add CONFIG_NVMEM dependency in SVS Kconfig.
- Apply the latest dev_pm_opp_adjust_voltage() from [1] in SVS driver.
- Add dev_pm_opp_put(opp) after calling dev_pm_opp_get_voltage(opp).
- Fix multi-line comment style.
- Add #define w/bit for magic constants.
- Replace "int svs_is_supported()" with "bool svs_is_supported()".
- Use default/max size for allocating memories to
opp_volts/init02_volts/volts/opp_freqs/freqs_pct in struct svs_bank.
- Name "main" instead of "main_clk" in SVS nodes for getting SVS clk.
- In SVS binding document, remove fourth parameter of GIC interrupts.
- Use '-' in node names.

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 | 76 +
arch/arm64/boot/dts/mediatek/mt8183-evb.dts | 16 +
arch/arm64/boot/dts/mediatek/mt8183.dtsi | 41 +
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, 2242 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


2020-01-07 07:03:33

by Roger Lu

[permalink] [raw]
Subject: [PATCH v6 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 | 76 +++++++++++++++++++
1 file changed, 76 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..9a3e81b9e1d2
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
@@ -0,0 +1,76 @@
+* 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 hardware. required
+ clocks are:
+ "main": Main clock for svs controller to work.
+- 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-supply: 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>;
+ 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>;
+ vcpu-little-supply = <&mt6358_vproc12_reg>;
+ };
+
+ svs_cpu_big: svs-cpu-big {
+ compatible = "mediatek,mt8183-svs-cpu-big";
+ operating-points-v2 = <&cluster1_opp>;
+ vcpu-big-supply = <&mt6358_vproc11_reg>;
+ };
+
+ svs_cci: svs-cci {
+ compatible = "mediatek,mt8183-svs-cci";
+ operating-points-v2 = <&cci_opp>;
+ vcci-supply = <&mt6358_vproc12_reg>;
+ };
+
+ svs_gpu: svs-gpu {
+ compatible = "mediatek,mt8183-svs-gpu";
+ power-domains = <&scpsys MT8183_POWER_DOMAIN_MFG_2D>;
+ operating-points-v2 = <&gpu_opp_table>;
+ vgpu-spply = <&mt6358_vgpu_reg>;
+ };
+ };
--
2.18.0

2020-01-07 07:04:17

by Roger Lu

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

add pmic/clock/irq/efuse setting in svs node

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

diff --git a/arch/arm64/boot/dts/mediatek/mt8183-evb.dts b/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
index 1fb195c683c3..6ae1d9a1bcbf 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
+++ b/arch/arm64/boot/dts/mediatek/mt8183-evb.dts
@@ -231,6 +231,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 10b32471bc7b..996e65942f48 100644
--- a/arch/arm64/boot/dts/mediatek/mt8183.dtsi
+++ b/arch/arm64/boot/dts/mediatek/mt8183.dtsi
@@ -389,6 +389,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";
+ 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>;
+ };
+ };
+
i2c3: i2c@1100f000 {
compatible = "mediatek,mt8183-i2c";
reg = <0 0x1100f000 0 0x1000>,
@@ -580,6 +613,14 @@
compatible = "mediatek,mt8183-efuse",
"mediatek,efuse";
reg = <0 0x11f10000 0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ thermal_calibration: calib@180 {
+ reg = <0x180 0xc>;
+ };
+ svs_calibration: calib@580 {
+ reg = <0x580 0x64>;
+ };
};

mfgcfg: syscon@13000000 {
--
2.18.0

2020-01-07 07:04:50

by Roger Lu

[permalink] [raw]
Subject: [PATCH v6 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 089b6244b716..ba9d296bbdc1 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 && NVMEM
+ 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..c46211a15fcd
--- /dev/null
+++ b/drivers/power/avs/mtk_svs.c
@@ -0,0 +1,2075 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#define pr_fmt(fmt) "[mtk_svs] " fmt
+
+#include <linux/bits.h>
+#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>
+
+/* svs sw id */
+#define SVS_CPU_LITTLE BIT(0)
+#define SVS_CPU_BIG BIT(1)
+#define SVS_CCI BIT(2)
+#define SVS_GPU BIT(3)
+
+/* svs bank mode support */
+#define SVSB_MODE_ALL_DISABLE (0)
+#define SVSB_MODE_INIT01 BIT(1)
+#define SVSB_MODE_INIT02 BIT(2)
+#define SVSB_MODE_MON BIT(3)
+
+/* svs bank init01 condition */
+#define SVSB_INIT01_VOLT_IGNORE BIT(1)
+#define SVSB_INIT01_VOLT_INC_ONLY BIT(2)
+
+/* svs bank common setting */
+#define RUNCONFIG_DEFAULT (0x80000000)
+#define DC_SIGNED_BIT (0x8000)
+#define INTEN_INIT0x (0x00005f01)
+#define INTEN_MONVOPEN (0x00ff0000)
+#define SVSEN_OFF (0x0)
+#define SVSEN_MASK (0x7)
+#define SVSEN_INIT01 (0x1)
+#define SVSEN_INIT02 (0x5)
+#define SVSEN_MON (0x2)
+#define INTSTS_MONVOP (0x00ff0000)
+#define INTSTS_COMPLETE (0x1)
+#define INTSTS_CLEAN (0x00ffffff)
+
+#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 svsb_phase {
+ SVSB_PHASE_INIT01 = 0,
+ SVSB_PHASE_INIT02,
+ SVSB_PHASE_MON,
+ SVSB_PHASE_ERROR,
+};
+
+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;
+ s32 volt_offset;
+ u32 mode_support;
+ u32 opp_freqs[16];
+ u32 freqs_pct[16];
+ u32 opp_volts[16];
+ u32 init02_volts[16];
+ u32 volts[16];
+ 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 bank_id;
+ u32 ctl0;
+ u8 *of_compatible;
+ u8 *name;
+ u8 *zone_name;
+ u8 *buck_name;
+};
+
+struct svs_platform {
+ struct svs_bank *banks;
+ bool (*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)
+ __acquires(&mtk_svs_lock)
+{
+ 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)
+ __releases(&mtk_svs_lock)
+{
+ 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;
+ percent &= GENMASK(7, 0);
+
+ return percent;
+}
+
+static u32 svs_readl(struct mtk_svs *svs, enum reg_index rg_i)
+{
+ return readl(svs->base + svs->platform->regs[rg_i]);
+}
+
+static void svs_writel(struct mtk_svs *svs, u32 val, enum reg_index rg_i)
+{
+ writel(val, svs->base + svs->platform->regs[rg_i]);
+}
+
+static void svs_switch_bank(struct mtk_svs *svs)
+{
+ struct svs_bank *svsb = svs->bank;
+
+ svs_writel(svs, svsb->coresel, CORESEL);
+}
+
+static u32 svsb_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 svsb_get_zone_temperature(struct svs_bank *svsb, int *zone_temp)
+{
+ struct thermal_zone_device *tzd;
+
+ tzd = thermal_zone_get_zone_by_name(svsb->zone_name);
+ if (IS_ERR(tzd))
+ return PTR_ERR(tzd);
+
+ return thermal_zone_get_temp(tzd, zone_temp);
+}
+
+static int svsb_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 == SVSB_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 = svsb_get_zone_temperature(svsb, &zone_temp);
+ if (ret) {
+ pr_err("%s: cannot get zone \"%s\" temperature(%d)\n",
+ svsb->name, svsb->zone_name, ret);
+ 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 == SVSB_PHASE_MON) {
+ svsb_volt = max((svsb->volts[i] + svsb->volt_offset +
+ low_temp_offset), svsb->vmin);
+ opp_volt = svsb_volt_to_opp_volt(svsb_volt,
+ svsb->volt_step,
+ svsb->volt_base);
+ } else if (svsb->phase == SVSB_PHASE_INIT02) {
+ svsb_volt = max((svsb->init02_volts[i] +
+ svsb->volt_offset), svsb->vmin);
+ opp_volt = svsb_volt_to_opp_volt(svsb_volt,
+ svsb->volt_step,
+ svsb->volt_base);
+ } else if (svsb->phase == SVSB_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, opp_volt,
+ svsb->opp_volts[i]);
+ 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) & GENMASK(7, 0);
+ svsb->volts[4] = (temp >> 16) & GENMASK(7, 0);
+ svsb->volts[2] = (temp >> 8) & GENMASK(7, 0);
+ svsb->volts[0] = (temp & GENMASK(7, 0));
+
+ temp = svs_readl(svs, VOP74);
+ svsb->volts[14] = (temp >> 24) & GENMASK(7, 0);
+ svsb->volts[12] = (temp >> 16) & GENMASK(7, 0);
+ svsb->volts[10] = (temp >> 8) & GENMASK(7, 0);
+ svsb->volts[8] = (temp & GENMASK(7, 0));
+
+ 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) & GENMASK(31, 24)) |
+ ((svsb->freqs_pct[4] << 16) & GENMASK(23, 16)) |
+ ((svsb->freqs_pct[2] << 8) & GENMASK(15, 8)) |
+ (svsb->freqs_pct[0] & GENMASK(7, 0)),
+ FREQPCT30);
+ svs_writel(svs,
+ ((svsb->freqs_pct[14] << 24) & GENMASK(31, 24)) |
+ ((svsb->freqs_pct[12] << 16) & GENMASK(23, 16)) |
+ ((svsb->freqs_pct[10] << 8) & GENMASK(15, 8)) |
+ ((svsb->freqs_pct[8]) & GENMASK(7, 0)),
+ 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) & GENMASK(15, 8)) |
+ (svsb->mdes & GENMASK(7, 0));
+ svs_writel(svs, des_char, DESCHAR);
+
+ temp_char = ((svsb->vco << 16) & GENMASK(23, 16)) |
+ ((svsb->mtdes << 8) & GENMASK(15, 8)) |
+ (svsb->dvt_fixed & GENMASK(7, 0));
+ svs_writel(svs, temp_char, TEMPCHAR);
+
+ det_char = ((svsb->dcbdet << 8) & GENMASK(15, 8)) |
+ (svsb->dcmdet & GENMASK(7, 0));
+ 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, RUNCONFIG_DEFAULT, 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) & GENMASK(31, 24)) |
+ ((svsb->vmin << 16) & GENMASK(23, 16)) |
+ ((svsb->dthi << 8) & GENMASK(15, 8)) |
+ (svsb->dtlo & GENMASK(7, 0));
+ svs_writel(svs, limit_vals, LIMITVALS);
+ svs_writel(svs, (svsb->vboot & GENMASK(7, 0)), VBOOT);
+ svs_writel(svs, (svsb->det_window & GENMASK(15, 0)), DETWINDOW);
+ svs_writel(svs, (svsb->det_max & GENMASK(15, 0)), CONFIG);
+
+ if (svsb->chkshift != 0)
+ svs_writel(svs, (svsb->chkshift & GENMASK(7, 0)), CHKSHIFT);
+
+ if (svsb->ctl0 != 0)
+ svs_writel(svs, svsb->ctl0, CTL0);
+
+ svs_writel(svs, INTSTS_CLEAN, INTSTS);
+
+ switch (target_phase) {
+ case SVSB_PHASE_INIT01:
+ svs_writel(svs, INTEN_INIT0x, INTEN);
+ svs_writel(svs, SVSEN_INIT01, SVSEN);
+ break;
+ case SVSB_PHASE_INIT02:
+ svs_writel(svs, INTEN_INIT0x, INTEN);
+ init2vals = ((svsb->age_voffset_in << 16) & GENMASK(31, 16)) |
+ (svsb->dc_voffset_in & GENMASK(15, 0));
+ svs_writel(svs, init2vals, INIT2VALS);
+ svs_writel(svs, SVSEN_INIT02, SVSEN);
+ break;
+ case SVSB_PHASE_MON:
+ ts_calcs = ((svsb->bts << 12) & GENMASK(23, 12)) |
+ (svsb->mts & GENMASK(11, 0));
+ svs_writel(svs, ts_calcs, TSCALCS);
+ svs_writel(svs, INTEN_MONVOPEN, INTEN);
+ svs_writel(svs, SVSEN_MON, 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[SVSB_PHASE_INIT01][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->dc_voffset_in = ~(svs_readl(svs, DCVALUES) & GENMASK(15, 0)) + 1;
+ if (svsb->init01_volt_flag == SVSB_INIT01_VOLT_IGNORE)
+ svsb->dc_voffset_in = 0;
+ else if ((svsb->dc_voffset_in & DC_SIGNED_BIT) &&
+ (svsb->init01_volt_flag == SVSB_INIT01_VOLT_INC_ONLY))
+ svsb->dc_voffset_in = 0;
+
+ svsb->age_voffset_in = svs_readl(svs, AGEVALUES) & GENMASK(15, 0);
+
+ svs_writel(svs, SVSEN_OFF, SVSEN);
+ svs_writel(svs, INTSTS_COMPLETE, INTSTS);
+
+ /* svs init01 clock gating */
+ svsb->coresel &= ~svsb->systemclk_en;
+
+ svsb->phase = SVSB_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[SVSB_PHASE_INIT02][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->ops->get_vops(svs);
+ memcpy(svsb->init02_volts, svsb->volts, sizeof(u32) * svsb->opp_count);
+ svsb->phase = SVSB_PHASE_INIT02;
+
+ svs_writel(svs, SVSEN_OFF, SVSEN);
+ svs_writel(svs, INTSTS_COMPLETE, 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[SVSB_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->svs_temp = svs_readl(svs, TEMP) & GENMASK(7, 0);
+
+ svsb->ops->get_vops(svs);
+ svsb->phase = SVSB_PHASE_MON;
+
+ svs_writel(svs, INTSTS_MONVOP, 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[SVSB_PHASE_MON][rg_i] = svs_readl(svs, rg_i);
+
+ svsb->mode_support = SVSB_MODE_ALL_DISABLE;
+
+ if (svsb->phase == SVSB_PHASE_MON)
+ svsb->phase = SVSB_PHASE_INIT02;
+
+ svs_writel(svs, SVSEN_OFF, SVSEN);
+ svs_writel(svs, INTSTS_CLEAN, 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 == INTSTS_COMPLETE &&
+ ((svsen & SVSEN_MASK) == SVSEN_INIT01))
+ svs_init01_isr_handler(svs);
+ else if ((intsts == INTSTS_COMPLETE) &&
+ ((svsen & SVSEN_MASK) == SVSEN_INIT02))
+ svs_init02_isr_handler(svs);
+ else if (!!(intsts & INTSTS_MONVOP))
+ 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 != SVSB_PHASE_INIT01)
+ svsb_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->mode_support & SVSB_MODE_MON))
+ continue;
+
+ svs_set_phase(svs, SVSB_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->mode_support & SVSB_MODE_INIT02))
+ continue;
+
+ reinit_completion(&svsb->init_completion);
+ flags = claim_mtk_svs_lock();
+ svs_set_phase(svs, SVSB_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;
+ unsigned long flags, time_left;
+ bool search_done;
+ int ret = -EINVAL;
+ u32 opp_freqs, opp_vboot, buck_volt, idx, i;
+
+ qos_request = kzalloc(sizeof(*qos_request), GFP_KERNEL);
+ if (!qos_request)
+ return -ENOMEM;
+
+ /* 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 banks 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->mode_support & SVSB_MODE_INIT01))
+ 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 = svsb_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,
+ opp_vboot,
+ 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]);
+ }
+ }
+ }
+
+ /* svs bank init01 begins */
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svs->bank = svsb;
+
+ if (!(svsb->mode_support & SVSB_MODE_INIT01))
+ continue;
+
+ opp_vboot = svsb_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, SVSB_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->mode_support & SVSB_MODE_INIT01))
+ 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);
+ kfree(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 bool 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 = 0;
+ 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;
+ }
+
+ 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]);
+ }
+
+ /* 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 = SVSB_INIT01_VOLT_IGNORE;
+
+ switch (svsb->sw_id) {
+ case SVS_CPU_LITTLE:
+ svsb->bdes = svs->efuse[16] & GENMASK(7, 0);
+ svsb->mdes = (svs->efuse[16] >> 8) & GENMASK(7, 0);
+ svsb->dcbdet = (svs->efuse[16] >> 16) & GENMASK(7, 0);
+ svsb->dcmdet = (svs->efuse[16] >> 24) & GENMASK(7, 0);
+ svsb->mtdes = (svs->efuse[17] >> 16) & GENMASK(7, 0);
+
+ if (ft_pgm <= 3)
+ svsb->volt_offset += 10;
+ else
+ svsb->volt_offset += 2;
+ break;
+ case SVS_CPU_BIG:
+ svsb->bdes = svs->efuse[18] & GENMASK(7, 0);
+ svsb->mdes = (svs->efuse[18] >> 8) & GENMASK(7, 0);
+ svsb->dcbdet = (svs->efuse[18] >> 16) & GENMASK(7, 0);
+ svsb->dcmdet = (svs->efuse[18] >> 24) & GENMASK(7, 0);
+ svsb->mtdes = svs->efuse[17] & GENMASK(7, 0);
+
+ if (ft_pgm <= 3)
+ svsb->volt_offset += 15;
+ else
+ svsb->volt_offset += 12;
+ break;
+ case SVS_CCI:
+ svsb->bdes = svs->efuse[4] & GENMASK(7, 0);
+ svsb->mdes = (svs->efuse[4] >> 8) & GENMASK(7, 0);
+ svsb->dcbdet = (svs->efuse[4] >> 16) & GENMASK(7, 0);
+ svsb->dcmdet = (svs->efuse[4] >> 24) & GENMASK(7, 0);
+ svsb->mtdes = (svs->efuse[5] >> 16) & GENMASK(7, 0);
+
+ if (ft_pgm <= 3)
+ svsb->volt_offset += 10;
+ else
+ svsb->volt_offset += 2;
+ break;
+ case SVS_GPU:
+ svsb->bdes = svs->efuse[6] & GENMASK(7, 0);
+ svsb->mdes = (svs->efuse[6] >> 8) & GENMASK(7, 0);
+ svsb->dcbdet = (svs->efuse[6] >> 16) & GENMASK(7, 0);
+ svsb->dcmdet = (svs->efuse[6] >> 24) & GENMASK(7, 0);
+ svsb->mtdes = svs->efuse[5] & GENMASK(7, 0);
+
+ if (ft_pgm >= 2) {
+ svsb->freq_base = 800000000; /* 800MHz */
+ svsb->dvt_fixed = 2;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Thermal efuse parsing */
+ if (!svs->thermal_efuse)
+ return true;
+
+ tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & GENMASK(9, 0);
+ tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & GENMASK(9, 0);
+
+ tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & GENMASK(8, 0);
+ tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & GENMASK(8, 0);
+ tp.o_vtsmcu3 = svs->thermal_efuse[1] & GENMASK(8, 0);
+ tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & GENMASK(8, 0);
+ tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & GENMASK(8, 0);
+ tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & GENMASK(8, 0);
+
+ tp.degc_cali = (svs->thermal_efuse[0] >> 1) & GENMASK(5, 0);
+ 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) & GENMASK(5, 0);
+
+ 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 (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ svsb->mode_support &= ~SVSB_MODE_MON;
+ }
+
+ return true;
+ }
+
+ 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:
+ break;
+ }
+
+ 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 true;
+}
+
+static bool svs_is_supported(struct mtk_svs *svs)
+{
+ const struct svs_platform *svsp = svs->platform;
+ struct svs_bank *svsb;
+ struct nvmem_cell *cell;
+ size_t len;
+ bool ret;
+ u32 idx;
+
+ if (svsp->fake_efuse) {
+ len = svsp->efuse_num * 4;
+ svs->efuse = devm_kzalloc(svs->dev, len, GFP_KERNEL);
+ if (!svs->efuse) {
+ pr_err("no memory for allocating svs_efuse\n");
+ return false;
+ }
+
+ len = svsp->thermal_efuse_num * 4;
+ svs->thermal_efuse = devm_kzalloc(svs->dev, len, GFP_KERNEL);
+ if (!svs->thermal_efuse) {
+ pr_err("no memory for allocating svs_thermal_efuse\n");
+ return false;
+ }
+
+ 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 false;
+ }
+
+ svs->efuse = (u32 *)nvmem_cell_read(cell, &len);
+ nvmem_cell_put(cell);
+
+ if (svs->efuse[svsp->efuse_check] == 0) {
+ pr_err("svs_efuse[%u] is empty\n", svsp->efuse_check);
+ return false;
+ }
+
+ /* 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->mode_support &= ~SVSB_MODE_MON;
+ }
+ 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;
+ int count, ret;
+ u32 idx, i;
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+
+ 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;
+ }
+
+ 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);
+ dev_pm_opp_put(opp);
+ }
+ }
+
+ 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, SVSEN_OFF, SVSEN);
+ svs_writel(svs, INTSTS_CLEAN, INTSTS);
+ svsb->suspended = true;
+ }
+ release_mtk_svs_lock(flags);
+
+ for (idx = 0; idx < svsp->bank_num; idx++) {
+ svsb = &svsp->banks[idx];
+ if (svsb->phase == SVSB_PHASE_MON) {
+ svsb->phase = SVSB_PHASE_INIT02;
+ svsb_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 == SVSB_PHASE_INIT01)
+ seq_puts(m, "init1\n");
+ else if (svsb->phase == SVSB_PHASE_INIT02)
+ seq_puts(m, "init2\n");
+ else if (svsb->phase == SVSB_PHASE_MON)
+ seq_puts(m, "mon mode\n");
+ else if (svsb->phase == SVSB_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 == SVSB_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->mode_support = SVSB_MODE_ALL_DISABLE;
+ svs_switch_bank(svs);
+ svs_writel(svs, SVSEN_OFF, SVSEN);
+ svs_writel(svs, INTSTS_CLEAN, INTSTS);
+ release_mtk_svs_lock(flags);
+ }
+
+ svsb->phase = SVSB_PHASE_ERROR;
+ svsb_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];
+
+ for (i = SVSB_PHASE_INIT01; i <= SVSB_PHASE_MON; i++) {
+ seq_printf(m, "Bank_number = %u\n", svsb->bank_id);
+
+ if (i < SVSB_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 = svsb_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]);
+ dev_pm_opp_put(opp);
+ }
+
+ 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 = svsb_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->mode_support == SVSB_MODE_ALL_DISABLE)
+ 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;
+}
+
+static struct svs_bank_ops svs_mt8183_banks_ops = {
+ .set_freqs_pct = svs_set_freqs_pct_v2,
+ .get_vops = svs_get_vops_v2,
+};
+
+static struct svs_bank svs_mt8183_banks[4] = {
+ {
+ .of_compatible = "mediatek,mt8183-svs-cpu-little",
+ .sw_id = SVS_CPU_LITTLE,
+ .bank_id = 0,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts4",
+ .buck_name = "vcpu-little",
+ .mtcmos_request = false,
+ .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY,
+ .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
+ .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,
+ .bank_id = 1,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts5",
+ .buck_name = "vcpu-big",
+ .mtcmos_request = false,
+ .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY,
+ .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
+ .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,
+ .bank_id = 2,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts4",
+ .buck_name = "vcci",
+ .mtcmos_request = false,
+ .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY,
+ .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02,
+ .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,
+ .bank_id = 3,
+ .ops = &svs_mt8183_banks_ops,
+ .zone_name = "tzts2",
+ .buck_name = "vgpu",
+ .mtcmos_request = true,
+ .init01_volt_flag = SVSB_INIT01_VOLT_INC_ONLY,
+ .mode_support = SVSB_MODE_INIT01 | SVSB_MODE_INIT02 |
+ SVSB_MODE_MON,
+ .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");
+ 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;
+ }
+
+ if (!svs_is_supported(svs)) {
+ pr_notice("svs is not supported\n");
+ 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..5c03982e3576
--- /dev/null
+++ b/include/linux/power/mtk_svs.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0
+ *
+ * Copyright (C) 2020 MediaTek Inc.
+ */
+
+#ifndef __MTK_SVS_H__
+#define __MTK_SVS_H__
+
+#if IS_ENABLED(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

2020-01-08 11:21:48

by Pi-Hsun Shih

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

Hi Roger,

On Tue, Jan 7, 2020 at 3:02 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
> [...]
> new file mode 100644
> index 000000000000..c46211a15fcd
> --- /dev/null
> +++ b/drivers/power/avs/mtk_svs.c
> [...]
> +
> +static bool 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 = 0;
> + 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;
> + }
> +
> + 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]);
> + }
> +
> + /* 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 = SVSB_INIT01_VOLT_IGNORE;
> +
> + switch (svsb->sw_id) {
> + case SVS_CPU_LITTLE:
> + svsb->bdes = svs->efuse[16] & GENMASK(7, 0);
> + svsb->mdes = (svs->efuse[16] >> 8) & GENMASK(7, 0);
> + svsb->dcbdet = (svs->efuse[16] >> 16) & GENMASK(7, 0);
> + svsb->dcmdet = (svs->efuse[16] >> 24) & GENMASK(7, 0);
> + svsb->mtdes = (svs->efuse[17] >> 16) & GENMASK(7, 0);
> +
> + if (ft_pgm <= 3)
> + svsb->volt_offset += 10;
> + else
> + svsb->volt_offset += 2;
> + break;
> + case SVS_CPU_BIG:
> + svsb->bdes = svs->efuse[18] & GENMASK(7, 0);
> + svsb->mdes = (svs->efuse[18] >> 8) & GENMASK(7, 0);
> + svsb->dcbdet = (svs->efuse[18] >> 16) & GENMASK(7, 0);
> + svsb->dcmdet = (svs->efuse[18] >> 24) & GENMASK(7, 0);
> + svsb->mtdes = svs->efuse[17] & GENMASK(7, 0);
> +
> + if (ft_pgm <= 3)
> + svsb->volt_offset += 15;
> + else
> + svsb->volt_offset += 12;
> + break;
> + case SVS_CCI:
> + svsb->bdes = svs->efuse[4] & GENMASK(7, 0);
> + svsb->mdes = (svs->efuse[4] >> 8) & GENMASK(7, 0);
> + svsb->dcbdet = (svs->efuse[4] >> 16) & GENMASK(7, 0);
> + svsb->dcmdet = (svs->efuse[4] >> 24) & GENMASK(7, 0);
> + svsb->mtdes = (svs->efuse[5] >> 16) & GENMASK(7, 0);
> +
> + if (ft_pgm <= 3)
> + svsb->volt_offset += 10;
> + else
> + svsb->volt_offset += 2;
> + break;
> + case SVS_GPU:
> + svsb->bdes = svs->efuse[6] & GENMASK(7, 0);
> + svsb->mdes = (svs->efuse[6] >> 8) & GENMASK(7, 0);
> + svsb->dcbdet = (svs->efuse[6] >> 16) & GENMASK(7, 0);
> + svsb->dcmdet = (svs->efuse[6] >> 24) & GENMASK(7, 0);
> + svsb->mtdes = svs->efuse[5] & GENMASK(7, 0);
> +
> + if (ft_pgm >= 2) {
> + svsb->freq_base = 800000000; /* 800MHz */
> + svsb->dvt_fixed = 2;
> + }
> + break;
> + default:
> + break;
> + }
> + }
> +
> + /* Thermal efuse parsing */
> + if (!svs->thermal_efuse)
> + return true;
> +
> + tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & GENMASK(9, 0);
> + tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & GENMASK(9, 0);
> +
> + tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & GENMASK(8, 0);
> + tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & GENMASK(8, 0);
> + tp.o_vtsmcu3 = svs->thermal_efuse[1] & GENMASK(8, 0);
> + tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & GENMASK(8, 0);
> + tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & GENMASK(8, 0);
> + tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & GENMASK(8, 0);
> +
> + tp.degc_cali = (svs->thermal_efuse[0] >> 1) & GENMASK(5, 0);
> + 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) & GENMASK(5, 0);
> +
> + 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 (idx = 0; idx < svsp->bank_num; idx++) {
> + svsb = &svsp->banks[idx];
> + svsb->mode_support &= ~SVSB_MODE_MON;
> + }
> +
> + return true;
> + }
> +
> + 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:
> + break;

There was a "return -EINVAL;" here in v5, should this be a "return
false"? This function currently always return true.

> + }
> +
> + 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 true;
> +}
> [...]
> +
> +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;
> + int count, ret;
> + u32 idx, i;
> +
> + for (idx = 0; idx < svsp->bank_num; idx++) {
> + svsb = &svsp->banks[idx];
> +
> + 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;
> + }
> +
> + for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {

Can use U32_MAX instead of (u32)-1.

> + 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);
> + dev_pm_opp_put(opp);
> + }
> + }
> +
> + 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 == SVSB_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';

Can use memdup_user_nul to allocate the buf and copy from user buffer
for the above operations (and for other _write() functions).

> +
> + ret = kstrtoint(buf, 10, &enabled);
> + if (ret)
> + return ret;
> +
> + if (!enabled) {
> + flags = claim_mtk_svs_lock();
> + svs->bank = svsb;
> + svsb->mode_support = SVSB_MODE_ALL_DISABLE;
> + svs_switch_bank(svs);
> + svs_writel(svs, SVSEN_OFF, SVSEN);
> + svs_writel(svs, INTSTS_CLEAN, INTSTS);
> + release_mtk_svs_lock(flags);
> + }
> +
> + svsb->phase = SVSB_PHASE_ERROR;
> + svsb_set_volts(svsb, true);

Missing free_page() (or kfree() if changing to memdup_user_nul) here
(and in other _write() functions).

> +
> + return count;
> +}
> +
> +proc_fops_rw(svs_debug);
> +
> [...]

2020-01-08 20:39:39

by Rob Herring (Arm)

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

On Tue, Jan 07, 2020 at 03:01:52PM +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 | 76 +++++++++++++++++++
> 1 file changed, 76 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..9a3e81b9e1d2
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> @@ -0,0 +1,76 @@
> +* 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 hardware. required
> + clocks are:
> + "main": Main clock for svs controller to work.
> +- 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-supply: 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>;
> + 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>;
> + vcpu-little-supply = <&mt6358_vproc12_reg>;
> + };

I don't think this is a good binding. This information already exists
elsewhere in the DT, so your driver should just look in those nodes.
For example the regulator can be in the cpu nodes or the OPP table
itself.

Rob

2020-01-09 05:04:08

by Roger Lu

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

Dear Pi-Hsun,

Thanks for the advices.

On Wed, 2020-01-08 at 19:12 +0800, Pi-Hsun Shih wrote:
> Hi Roger,
>
> On Tue, Jan 7, 2020 at 3:02 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
> > [...]
> > new file mode 100644
> > index 000000000000..c46211a15fcd
> > --- /dev/null
> > +++ b/drivers/power/avs/mtk_svs.c
> > [...]
> > +
> > +static bool 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 = 0;
> > + 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;
> > + }
> > +
> > + 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]);
> > + }
> > +
> > + /* 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 = SVSB_INIT01_VOLT_IGNORE;
> > +
> > + switch (svsb->sw_id) {
> > + case SVS_CPU_LITTLE:
> > + svsb->bdes = svs->efuse[16] & GENMASK(7, 0);
> > + svsb->mdes = (svs->efuse[16] >> 8) & GENMASK(7, 0);
> > + svsb->dcbdet = (svs->efuse[16] >> 16) & GENMASK(7, 0);
> > + svsb->dcmdet = (svs->efuse[16] >> 24) & GENMASK(7, 0);
> > + svsb->mtdes = (svs->efuse[17] >> 16) & GENMASK(7, 0);
> > +
> > + if (ft_pgm <= 3)
> > + svsb->volt_offset += 10;
> > + else
> > + svsb->volt_offset += 2;
> > + break;
> > + case SVS_CPU_BIG:
> > + svsb->bdes = svs->efuse[18] & GENMASK(7, 0);
> > + svsb->mdes = (svs->efuse[18] >> 8) & GENMASK(7, 0);
> > + svsb->dcbdet = (svs->efuse[18] >> 16) & GENMASK(7, 0);
> > + svsb->dcmdet = (svs->efuse[18] >> 24) & GENMASK(7, 0);
> > + svsb->mtdes = svs->efuse[17] & GENMASK(7, 0);
> > +
> > + if (ft_pgm <= 3)
> > + svsb->volt_offset += 15;
> > + else
> > + svsb->volt_offset += 12;
> > + break;
> > + case SVS_CCI:
> > + svsb->bdes = svs->efuse[4] & GENMASK(7, 0);
> > + svsb->mdes = (svs->efuse[4] >> 8) & GENMASK(7, 0);
> > + svsb->dcbdet = (svs->efuse[4] >> 16) & GENMASK(7, 0);
> > + svsb->dcmdet = (svs->efuse[4] >> 24) & GENMASK(7, 0);
> > + svsb->mtdes = (svs->efuse[5] >> 16) & GENMASK(7, 0);
> > +
> > + if (ft_pgm <= 3)
> > + svsb->volt_offset += 10;
> > + else
> > + svsb->volt_offset += 2;
> > + break;
> > + case SVS_GPU:
> > + svsb->bdes = svs->efuse[6] & GENMASK(7, 0);
> > + svsb->mdes = (svs->efuse[6] >> 8) & GENMASK(7, 0);
> > + svsb->dcbdet = (svs->efuse[6] >> 16) & GENMASK(7, 0);
> > + svsb->dcmdet = (svs->efuse[6] >> 24) & GENMASK(7, 0);
> > + svsb->mtdes = svs->efuse[5] & GENMASK(7, 0);
> > +
> > + if (ft_pgm >= 2) {
> > + svsb->freq_base = 800000000; /* 800MHz */
> > + svsb->dvt_fixed = 2;
> > + }
> > + break;
> > + default:
> > + break;
> > + }
> > + }
> > +
> > + /* Thermal efuse parsing */
> > + if (!svs->thermal_efuse)
> > + return true;
> > +
> > + tp.adc_ge_t = (svs->thermal_efuse[1] >> 22) & GENMASK(9, 0);
> > + tp.adc_oe_t = (svs->thermal_efuse[1] >> 12) & GENMASK(9, 0);
> > +
> > + tp.o_vtsmcu1 = (svs->thermal_efuse[0] >> 17) & GENMASK(8, 0);
> > + tp.o_vtsmcu2 = (svs->thermal_efuse[0] >> 8) & GENMASK(8, 0);
> > + tp.o_vtsmcu3 = svs->thermal_efuse[1] & GENMASK(8, 0);
> > + tp.o_vtsmcu4 = (svs->thermal_efuse[2] >> 23) & GENMASK(8, 0);
> > + tp.o_vtsmcu5 = (svs->thermal_efuse[2] >> 5) & GENMASK(8, 0);
> > + tp.o_vtsabb = (svs->thermal_efuse[2] >> 14) & GENMASK(8, 0);
> > +
> > + tp.degc_cali = (svs->thermal_efuse[0] >> 1) & GENMASK(5, 0);
> > + 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) & GENMASK(5, 0);
> > +
> > + 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 (idx = 0; idx < svsp->bank_num; idx++) {
> > + svsb = &svsp->banks[idx];
> > + svsb->mode_support &= ~SVSB_MODE_MON;
> > + }
> > +
> > + return true;
> > + }
> > +
> > + 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:
> > + break;
>
> There was a "return -EINVAL;" here in v5, should this be a "return
> false"? This function currently always return true.

This "return -EINVAL" will be detected in svs common flow
"svs_resource_setup()". Platform efuse parsing flow does efuse
parsing/checking job only.

>
> > + }
> > +
> > + 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 true;
> > +}
> > [...]
> > +
> > +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;
> > + int count, ret;
> > + u32 idx, i;
> > +
> > + for (idx = 0; idx < svsp->bank_num; idx++) {
> > + svsb = &svsp->banks[idx];
> > +
> > + 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;
> > + }
> > +
> > + for (i = 0, freq = (u32)-1; i < svsb->opp_count; i++, freq--) {
>
> Can use U32_MAX instead of (u32)-1.

Oh Sure. Thanks for the reference. I'll update it in the next patch.

>
> > + 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);
> > + dev_pm_opp_put(opp);
> > + }
> > + }
> > +
> > + 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 == SVSB_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';
>
> Can use memdup_user_nul to allocate the buf and copy from user buffer
> for the above operations (and for other _write() functions).

Cool. I'll use memdup_user_nul() instead in the next patch. Thanks.

>
> > +
> > + ret = kstrtoint(buf, 10, &enabled);
> > + if (ret)
> > + return ret;
> > +
> > + if (!enabled) {
> > + flags = claim_mtk_svs_lock();
> > + svs->bank = svsb;
> > + svsb->mode_support = SVSB_MODE_ALL_DISABLE;
> > + svs_switch_bank(svs);
> > + svs_writel(svs, SVSEN_OFF, SVSEN);
> > + svs_writel(svs, INTSTS_CLEAN, INTSTS);
> > + release_mtk_svs_lock(flags);
> > + }
> > +
> > + svsb->phase = SVSB_PHASE_ERROR;
> > + svsb_set_volts(svsb, true);
>
> Missing free_page() (or kfree() if changing to memdup_user_nul) here
> (and in other _write() functions).

No problem. I'll add free method here in the next patch. Thanks.

>
> > +
> > + return count;
> > +}
> > +
> > +proc_fops_rw(svs_debug);
> > +
> > [...]

2020-01-13 06:46:08

by Nicolas Boichat

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

On Thu, Jan 9, 2020 at 4:38 AM Rob Herring <[email protected]> wrote:
>
> On Tue, Jan 07, 2020 at 03:01:52PM +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 | 76 +++++++++++++++++++
> > 1 file changed, 76 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..9a3e81b9e1d2
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > @@ -0,0 +1,76 @@
> > +* 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 hardware. required
> > + clocks are:
> > + "main": Main clock for svs controller to work.
> > +- 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-supply: 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>;
> > + 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>;
> > + vcpu-little-supply = <&mt6358_vproc12_reg>;
> > + };
>
> I don't think this is a good binding. This information already exists
> elsewhere in the DT, so your driver should just look in those nodes.
> For example the regulator can be in the cpu nodes or the OPP table
> itself.

Roger, if that helps, without changing any other binding, on 8183,
basically you could have:
- svs-cpu-little: Add a handle to &cpu0 and get the regulator/opp
table from it.
- svs-cpu-big: Handle to &cpu4
- svs-cci: Handle to &cci
- svs-gpu: Handle to &gpu (BTW, it is expected that SVS would only
apply to vgpu/mali regulator, and not vsram regulator?)

I'm not too sure how we'd fetch the right regulator name, however (for
the first 3 the name is "proc", for the last one it's "mali"), maybe
add a regulator-name list in the DT?

>
> Rob

2020-01-13 15:53:16

by Rob Herring (Arm)

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

On Mon, Jan 13, 2020 at 12:44 AM Nicolas Boichat <[email protected]> wrote:
>
> On Thu, Jan 9, 2020 at 4:38 AM Rob Herring <[email protected]> wrote:
> >
> > On Tue, Jan 07, 2020 at 03:01:52PM +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 | 76 +++++++++++++++++++
> > > 1 file changed, 76 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..9a3e81b9e1d2
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > > @@ -0,0 +1,76 @@
> > > +* 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 hardware. required
> > > + clocks are:
> > > + "main": Main clock for svs controller to work.
> > > +- 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-supply: 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>;
> > > + 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>;
> > > + vcpu-little-supply = <&mt6358_vproc12_reg>;
> > > + };
> >
> > I don't think this is a good binding. This information already exists
> > elsewhere in the DT, so your driver should just look in those nodes.
> > For example the regulator can be in the cpu nodes or the OPP table
> > itself.
>
> Roger, if that helps, without changing any other binding, on 8183,
> basically you could have:
> - svs-cpu-little: Add a handle to &cpu0 and get the regulator/opp
> table from it.
> - svs-cpu-big: Handle to &cpu4

Why do you need those? Use the compatible of the cpus to determine big
and little cores. Or there's the cpu capacity property that could be
used instead.

> - svs-cci: Handle to &cci

Is there more than 1 CCI? Just retrieve the node by the compatible.
There's no need to have nodes that simply serve as a collection of
data for some driver.

> - svs-gpu: Handle to &gpu (BTW, it is expected that SVS would only
> apply to vgpu/mali regulator, and not vsram regulator?)
>
> I'm not too sure how we'd fetch the right regulator name, however (for
> the first 3 the name is "proc", for the last one it's "mali"), maybe
> add a regulator-name list in the DT?

To put this another way, write an SoC specific driver that understands
to some extent what exists in the SoC (and DT). I doubt something like
this is going to be generic across more than a few SoCs at most.

Rob

2020-02-11 07:36:56

by Roger Lu

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

Hi Rob & Nicolas,

Sorry for the late reply.

On Mon, 2020-01-13 at 23:50 +0800, Rob Herring wrote:
> On Mon, Jan 13, 2020 at 12:44 AM Nicolas Boichat <[email protected]> wrote:
> >
> > On Thu, Jan 9, 2020 at 4:38 AM Rob Herring <[email protected]> wrote:
> > >
> > > On Tue, Jan 07, 2020 at 03:01:52PM +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 | 76 +++++++++++++++++++
> > > > 1 file changed, 76 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..9a3e81b9e1d2
> > > > --- /dev/null
> > > > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > > > @@ -0,0 +1,76 @@
> > > > +* 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 hardware. required
> > > > + clocks are:
> > > > + "main": Main clock for svs controller to work.
> > > > +- 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-supply: 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>;
> > > > + 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>;
> > > > + vcpu-little-supply = <&mt6358_vproc12_reg>;
> > > > + };
> > >
> > > I don't think this is a good binding. This information already exists
> > > elsewhere in the DT, so your driver should just look in those nodes.
> > > For example the regulator can be in the cpu nodes or the OPP table
> > > itself.
> >
> > Roger, if that helps, without changing any other binding, on 8183,
> > basically you could have:
> > - svs-cpu-little: Add a handle to &cpu0 and get the regulator/opp
> > table from it.
> > - svs-cpu-big: Handle to &cpu4
>
> Why do you need those? Use the compatible of the cpus to determine big
> and little cores. Or there's the cpu capacity property that could be
> used instead.
>
> > - svs-cci: Handle to &cci
>
> Is there more than 1 CCI? Just retrieve the node by the compatible.
> There's no need to have nodes that simply serve as a collection of
> data for some driver.
>
> > - svs-gpu: Handle to &gpu (BTW, it is expected that SVS would only
> > apply to vgpu/mali regulator, and not vsram regulator?)

svs-gpu depends on vgpu power on for init (don't care vgpu_sram). After
svs-gpu init is done, it doesn't need vgpu power on anymore. (vgpu can
be turned off)

Please allows me to introduce more about what svs-gpu device needs.
1. It needs gpu opp table from "gpu node" and gpu_core2 power-domains
from "gpu_core2 node". When svs-gpu has those resources, it turns on
gpu_core2 power-domain for svs-gpu-hw to have power (for calculating)
and svs-gpu-sw will update gpu opp table voltages' part.
2. Therefore, if I retrieve gpu-related node from phandle or compatible,
it means svs-gpu device in driver needs to attach two different gpu
nodes for attaining gpu opp table and gpu_core2 power-domains. I think
this architecture of svs-gpu confuses maintainer why it attaches two
different nodes instead of having a device to describe what it needs.
3. Is it acceptable to have a Linux device attaching two different
nodes? If yes, could you guide us some APIs for one device to attach two
nodes? I don't know how to implement it. Thanks.

> >
> > I'm not too sure how we'd fetch the right regulator name, however (for
> > the first 3 the name is "proc", for the last one it's "mali"), maybe
> > add a regulator-name list in the DT?
>
> To put this another way, write an SoC specific driver that understands
> to some extent what exists in the SoC (and DT). I doubt something like
> this is going to be generic across more than a few SoCs at most.

>
> Rob

2020-02-27 03:56:38

by Nicolas Boichat

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

Hi Rob,

On Tue, Feb 11, 2020 at 3:36 PM Roger Lu <[email protected]> wrote:
>
> Hi Rob & Nicolas,
>
> Sorry for the late reply.
>
> On Mon, 2020-01-13 at 23:50 +0800, Rob Herring wrote:
> > On Mon, Jan 13, 2020 at 12:44 AM Nicolas Boichat <[email protected]> wrote:
> > >
> > > On Thu, Jan 9, 2020 at 4:38 AM Rob Herring <[email protected]> wrote:
> > > >
> > > > On Tue, Jan 07, 2020 at 03:01:52PM +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 | 76 +++++++++++++++++++
> > > > > 1 file changed, 76 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..9a3e81b9e1d2
> > > > > --- /dev/null
> > > > > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > > > > @@ -0,0 +1,76 @@
> > > > > +* 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 hardware. required
> > > > > + clocks are:
> > > > > + "main": Main clock for svs controller to work.
> > > > > +- 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-supply: 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>;
> > > > > + 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>;
> > > > > + vcpu-little-supply = <&mt6358_vproc12_reg>;
> > > > > + };
> > > >
> > > > I don't think this is a good binding. This information already exists
> > > > elsewhere in the DT, so your driver should just look in those nodes.
> > > > For example the regulator can be in the cpu nodes or the OPP table
> > > > itself.
> > >
> > > Roger, if that helps, without changing any other binding, on 8183,
> > > basically you could have:
> > > - svs-cpu-little: Add a handle to &cpu0 and get the regulator/opp
> > > table from it.
> > > - svs-cpu-big: Handle to &cpu4
> >
> > Why do you need those? Use the compatible of the cpus to determine big
> > and little cores. Or there's the cpu capacity property that could be
> > used instead.
> >
> > > - svs-cci: Handle to &cci
> >
> > Is there more than 1 CCI? Just retrieve the node by the compatible.
> > There's no need to have nodes that simply serve as a collection of
> > data for some driver.
> >
> > > - svs-gpu: Handle to &gpu (BTW, it is expected that SVS would only
> > > apply to vgpu/mali regulator, and not vsram regulator?)
>
> svs-gpu depends on vgpu power on for init (don't care vgpu_sram). After
> svs-gpu init is done, it doesn't need vgpu power on anymore. (vgpu can
> be turned off)
>
> Please allows me to introduce more about what svs-gpu device needs.
> 1. It needs gpu opp table from "gpu node" and gpu_core2 power-domains
> from "gpu_core2 node". When svs-gpu has those resources, it turns on
> gpu_core2 power-domain for svs-gpu-hw to have power (for calculating)
> and svs-gpu-sw will update gpu opp table voltages' part.
> 2. Therefore, if I retrieve gpu-related node from phandle or compatible,
> it means svs-gpu device in driver needs to attach two different gpu
> nodes for attaining gpu opp table and gpu_core2 power-domains. I think
> this architecture of svs-gpu confuses maintainer why it attaches two
> different nodes instead of having a device to describe what it needs.

> 3. Is it acceptable to have a Linux device attaching two different
> nodes? If yes, could you guide us some APIs for one device to attach two
> nodes? I don't know how to implement it. Thanks.

I'm also trying to understand how that would work. The way the code
works now (https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19/drivers/power/avs/mtk_svs.c#1388):

The SVS driver creates a platform device for each sub-node, find the
sub-node that matches the compatible (pdev->dev.of_node):
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;
}
}

Then, thanks to that, the 2 functions dev_pm_opp_of_add_table and
devm_regulator_get_optional "just work", as the get the opp table and
regulator from the device tree node.

So what you suggest is basically something like this:
pdev->dev.of_node = of_find_compatible_node(NULL, NULL, "mediatek,mt8183-cci");

I came up with a (very dirty) prototype here:
https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/2076718
... and it doesn't really work
(https://gist.github.com/drinkcat/61e50eedbdc301d418c9cee3ee5b6b06, I
think the kernel is probing more than it should, like the DMA mask
errors should not happen...)

Before I dig further... I have the same concern as Roger, is it ok to
have 2 devices bound to the same device tree node/compatible? My
understanding was also that it's not.

> > >
> > > I'm not too sure how we'd fetch the right regulator name, however (for
> > > the first 3 the name is "proc", for the last one it's "mali"), maybe
> > > add a regulator-name list in the DT?
> >
> > To put this another way, write an SoC specific driver that understands
> > to some extent what exists in the SoC (and DT). I doubt something like
> > this is going to be generic across more than a few SoCs at most.
>
> >
> > Rob
>

2020-04-08 06:00:21

by Nicolas Boichat

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

On Thu, Feb 27, 2020 at 11:55 AM Nicolas Boichat <[email protected]> wrote:
>
> Hi Rob,
>
> On Tue, Feb 11, 2020 at 3:36 PM Roger Lu <[email protected]> wrote:
> >
> > Hi Rob & Nicolas,
> >
> > Sorry for the late reply.
> >
> > On Mon, 2020-01-13 at 23:50 +0800, Rob Herring wrote:
> > > On Mon, Jan 13, 2020 at 12:44 AM Nicolas Boichat <[email protected]> wrote:
> > > >
> > > > On Thu, Jan 9, 2020 at 4:38 AM Rob Herring <[email protected]> wrote:
> > > > >
> > > > > On Tue, Jan 07, 2020 at 03:01:52PM +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 | 76 +++++++++++++++++++
> > > > > > 1 file changed, 76 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..9a3e81b9e1d2
> > > > > > --- /dev/null
> > > > > > +++ b/Documentation/devicetree/bindings/power/mtk-svs.txt
> > > > > > @@ -0,0 +1,76 @@
> > > > > > +* 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 hardware. required
> > > > > > + clocks are:
> > > > > > + "main": Main clock for svs controller to work.
> > > > > > +- 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-supply: 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>;
> > > > > > + 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>;
> > > > > > + vcpu-little-supply = <&mt6358_vproc12_reg>;
> > > > > > + };
> > > > >
> > > > > I don't think this is a good binding. This information already exists
> > > > > elsewhere in the DT, so your driver should just look in those nodes.
> > > > > For example the regulator can be in the cpu nodes or the OPP table
> > > > > itself.
> > > >
> > > > Roger, if that helps, without changing any other binding, on 8183,
> > > > basically you could have:
> > > > - svs-cpu-little: Add a handle to &cpu0 and get the regulator/opp
> > > > table from it.
> > > > - svs-cpu-big: Handle to &cpu4
> > >
> > > Why do you need those? Use the compatible of the cpus to determine big
> > > and little cores. Or there's the cpu capacity property that could be
> > > used instead.
> > >
> > > > - svs-cci: Handle to &cci
> > >
> > > Is there more than 1 CCI? Just retrieve the node by the compatible.
> > > There's no need to have nodes that simply serve as a collection of
> > > data for some driver.
> > >
> > > > - svs-gpu: Handle to &gpu (BTW, it is expected that SVS would only
> > > > apply to vgpu/mali regulator, and not vsram regulator?)
> >
> > svs-gpu depends on vgpu power on for init (don't care vgpu_sram). After
> > svs-gpu init is done, it doesn't need vgpu power on anymore. (vgpu can
> > be turned off)
> >
> > Please allows me to introduce more about what svs-gpu device needs.
> > 1. It needs gpu opp table from "gpu node" and gpu_core2 power-domains
> > from "gpu_core2 node". When svs-gpu has those resources, it turns on
> > gpu_core2 power-domain for svs-gpu-hw to have power (for calculating)
> > and svs-gpu-sw will update gpu opp table voltages' part.
> > 2. Therefore, if I retrieve gpu-related node from phandle or compatible,
> > it means svs-gpu device in driver needs to attach two different gpu
> > nodes for attaining gpu opp table and gpu_core2 power-domains. I think
> > this architecture of svs-gpu confuses maintainer why it attaches two
> > different nodes instead of having a device to describe what it needs.
>
> > 3. Is it acceptable to have a Linux device attaching two different
> > nodes? If yes, could you guide us some APIs for one device to attach two
> > nodes? I don't know how to implement it. Thanks.
>
> I'm also trying to understand how that would work. The way the code
> works now (https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-4.19/drivers/power/avs/mtk_svs.c#1388):
>
> The SVS driver creates a platform device for each sub-node, find the
> sub-node that matches the compatible (pdev->dev.of_node):
> 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;
> }
> }
>
> Then, thanks to that, the 2 functions dev_pm_opp_of_add_table and
> devm_regulator_get_optional "just work", as the get the opp table and
> regulator from the device tree node.
>
> So what you suggest is basically something like this:
> pdev->dev.of_node = of_find_compatible_node(NULL, NULL, "mediatek,mt8183-cci");
>
> I came up with a (very dirty) prototype here:
> https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/2076718
> ... and it doesn't really work
> (https://gist.github.com/drinkcat/61e50eedbdc301d418c9cee3ee5b6b06, I
> think the kernel is probing more than it should, like the DMA mask
> errors should not happen...)
>
> Before I dig further... I have the same concern as Roger, is it ok to
> have 2 devices bound to the same device tree node/compatible? My
> understanding was also that it's not.

Rob: It seems like this conversation died here. Do you have any
suggestions for the above?

Thanks,