diff mbox series

[v8.1,4/7] thermal: mediatek: Add LVTS driver for mt8192 thermal zones

Message ID 20220804130912.676043-5-bchihi@baylibre.com (mailing list archive)
State New, archived
Headers show
Series Add LVTS architecture thermal | expand

Commit Message

Balsam CHIHI Aug. 4, 2022, 1:09 p.m. UTC
From: Michael Kao <michael.kao@mediatek.com>

Add a LVTS V4 (Low Voltage Thermal Sensor) driver to report junction
temperatures in MediaTek SoC mt8192 and register the maximum temperature
of sensors and each sensor as a thermal zone.

Signed-off-by: Yu-Chia Chang <ethan.chang@mediatek.com>
Signed-off-by: Michael Kao <michael.kao@mediatek.com>
Signed-off-by: Ben Tseng <ben.tseng@mediatek.com>
Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
Signed-off-by: Balsam CHIHI <bchihi@baylibre.com>
---
 drivers/thermal/mediatek/Kconfig        |  24 +
 drivers/thermal/mediatek/Makefile       |   2 +
 drivers/thermal/mediatek/lvts_thermal.c | 855 ++++++++++++++++++++++++
 drivers/thermal/mediatek/lvts_thermal.h | 376 +++++++++++
 drivers/thermal/mediatek/lvts_v4.c      | 241 +++++++
 5 files changed, 1498 insertions(+)
 create mode 100644 drivers/thermal/mediatek/lvts_thermal.c
 create mode 100644 drivers/thermal/mediatek/lvts_thermal.h
 create mode 100644 drivers/thermal/mediatek/lvts_v4.c

Comments

Randy Dunlap Aug. 4, 2022, 6:15 p.m. UTC | #1
Hi--

On 8/4/22 06:09, bchihi@baylibre.com wrote:
> diff --git a/drivers/thermal/mediatek/Kconfig b/drivers/thermal/mediatek/Kconfig
> index 592c849b9fed..afef43a0e7ca 100644
> --- a/drivers/thermal/mediatek/Kconfig
> +++ b/drivers/thermal/mediatek/Kconfig
> @@ -20,4 +20,28 @@ config MTK_SOC_THERMAL
>  	  configures thermal controllers to collect temperature
>  	  via AUXADC interface.
>  
> +config MTK_LVTS_THERMAL
> +	tristate "LVTS (Low Voltage Thermal Sensor) driver for MediaTek SoCs"
> +	depends on HAS_IOMEM
> +	depends on NVMEM
> +	depends on RESET_CONTROLLER
> +	help
> +	  Enable this option if you want to get SoC temperature
> +	  information for MediaTek platforms. This driver configures
> +	  LVTS (Low Voltage Thermal Sensor) thermal controllers to
> +	  collect temperatures via ASIF (Analog Serial Interface).
> +
> +if MTK_LVTS_THERMAL
> +
> +config MTK_LVTS_V4
> +	tristate "LVTS V4 Thermal Driver for MediaTek SoCs"
> +	depends on HAS_IOMEM
> +	depends on NVMEM
> +	depends on RESET_CONTROLLER

You shouldn't need to repeat all of these "depends on" lines since this
Kconfig symbol depends on MTK_LVTS_THERMAL, which already depends on these
3 items.

Have you seen any issue that this is supposed to fix?

> +	help
> +	  Enable this option if you want to get SoC temperature
> +	  information for LVTS V4.
> +
> +endif

thanks.
Nícolas F. R. A. Prado Aug. 4, 2022, 11:39 p.m. UTC | #2
On Thu, Aug 04, 2022 at 03:09:09PM +0200, bchihi@baylibre.com wrote:
> From: Michael Kao <michael.kao@mediatek.com>
> 
> Add a LVTS V4 (Low Voltage Thermal Sensor) driver to report junction
> temperatures in MediaTek SoC mt8192 and register the maximum temperature
> of sensors and each sensor as a thermal zone.
> 
> Signed-off-by: Yu-Chia Chang <ethan.chang@mediatek.com>
> Signed-off-by: Michael Kao <michael.kao@mediatek.com>
> Signed-off-by: Ben Tseng <ben.tseng@mediatek.com>
> Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
> Signed-off-by: Balsam CHIHI <bchihi@baylibre.com>

You should have a Co-developed-by tag for each person that wrote the code [1].

[1] https://www.kernel.org/doc/html/latest/process/submitting-patches.html#when-to-use-acked-by-cc-and-co-developed-by

> ---
[..]
> --- /dev/null
> +++ b/drivers/thermal/mediatek/lvts_thermal.c
[..]
> +static int prepare_calibration_data(struct lvts_data *lvts_data)
> +{
[..]
> +	lvts_data->coeff.golden_temp = cal_data->golden_temp;
> +	dev_dbg(dev, "golden_temp = %d\n", cal_data->golden_temp);
> +	offset = snprintf(buffer, sizeof(buffer), "[lvts_cal] num:g_count:g_count_rc ");
> +	for (i = 0; i < lvts_data->num_sensor; i++)
> +		offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%d:%d:%d ",
> +			i, cal_data->count_r[i], cal_data->count_rc[i]);

Like Angelo already mentioned [2], you're not using this string for anything, so
just remove the code.

[2] https://lore.kernel.org/linux-mediatek/82f6cd96-65e2-57f7-b7c5-05c111874087@collabora.com/

> +
> +	return 0;
> +}
[..]
> +int lvts_device_read_count_rc_n_v4(struct lvts_data *lvts_data)
> +{
[..]
> +	offset = snprintf(buffer, sizeof(buffer), "[COUNT_RC_NOW] ");
> +	for (i = 0; i < lvts_data->num_sensor; i++)
> +		offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%d:%d ", i,
> +			cal_data->count_rc_now[i]);

Again, just formatting a string and throwing it away, please just drop the code.

> +
> +	return 0;
> +}
[..]
> +int lvts_resume(struct platform_device *pdev)
> +{
> +	struct lvts_data *lvts_data;
> +	int ret;
> +
> +	lvts_data = (struct lvts_data *)platform_get_drvdata(pdev);
> +	ret = lvts_init(lvts_data);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}

You have a bug in your resume path somewhere. I get this error during resume on
mt8192-asurada-spherion:

<1>[  237.487428] Unable to handle kernel write to read-only memory at virtual address ffffc15096e491c0
<1>[  237.496663] Mem abort info:
<1>[  237.499794]   ESR = 0x000000009600004e
<1>[  237.504334]   EC = 0x25: DABT (current EL), IL = 32 bits
<1>[  237.509982]   SET = 0, FnV = 0
<1>[  237.513444]   EA = 0, S1PTW = 0
<1>[  237.516911]   FSC = 0x0e: level 2 permission fault
<1>[  237.522041] Data abort info:
<1>[  237.525263]   ISV = 0, ISS = 0x0000004e
<1>[  237.529458]   CM = 0, WnR = 1
<1>[  237.532739] swapper pgtable: 4k pages, 48-bit VAs, pgdp=0000000041782000
<1>[  237.539754] [ffffc15096e491c0] pgd=100000023ffff003, p4d=100000023ffff003, pud=100000023fffe003, pmd=0060000041200f81
<0>[  237.550821] Internal error: Oops: 9600004e [#1] PREEMPT SMP
<4>[  237.556669] Modules linked in: af_alg mtk_vcodec_dec_hw snd_soc_hdmi_codec panel_edp mtk_vcodec_dec v4l2_vp9 v4l2_h264 mtk_vcodec_enc btusb mtk_vcodec_common btrtl videobuf2_dma_contig uvcvideo btintel videobuf2_vmalloc mtk_vpu btmtk videobuf2_memops v4l2_mem2mem mt7921e btbcm cdc_ether usbnet videobuf2_v4l2 mt7921_common bluetooth crct10dif_ce videobuf2_common r8152 mt76_connac_lib videodev ecdh_generic ecc mc mt76 mac80211 sbs_battery cros_usbpd_charger cros_usbpd_logger cros_ec_chardev libarc4 pwm_cros_ec anx7625 snd_soc_rt5682_i2c elan_i2c snd_soc_rt5682 cros_ec_typec snd_soc_rl6231 drm_dp_aux_bus typec drm_display_helper panfrost mediatek_drm drm_cma_helper rtc_mt6397 drm_shmem_helper phy_mtk_mipi_dsi_drv gpu_sched mt8192_mt6359_rt1015_rt5682 drm_kms_helper pwm_mtk_disp pwm_bl snd_soc_rt1015p snd_soc_mt8192_afe snd_soc_dmic snd_soc_mtk_common cfg80211 rfkill drm fuse backlight ip_tables x_tables ipv6
<4>[  237.637538] CPU: 1 PID: 533 Comm: bash Tainted: G        W          5.19.0-rc8-next-20220725+ #254
<4>[  237.646781] Hardware name: Google Spherion (rev0 - 3) (DT)
<4>[  237.652539] pstate: 60400009 (nZCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
<4>[  237.659778] pc : lvts_device_enable_and_init+0xdc/0xf0
<4>[  237.665198] lr : lvts_device_enable_and_init+0xc8/0xf0
<4>[  237.670609] sp : ffff8000098eb9b0
<4>[  237.674188] x29: ffff8000098eb9b0 x28: 0000000000000000 x27: 0000000000000003
<4>[  237.681609] x26: ffff41b141151010 x25: ffffc15097260c50 x24: 0000000000000000
<4>[  237.689028] x23: 0000000000000038 x22: 0000000000000001 x21: 000000008502fc00
<4>[  237.696448] x20: ffffc15096e49188 x19: 0000000000000003 x18: 0000000000000000
<4>[  237.703867] x17: 0000000000000001 x16: ffffc15095cc9838 x15: 00000000000001ca
<4>[  237.711287] x14: 0000000000000002 x13: 0000000000000000 x12: 000000000000045d
<4>[  237.718706] x11: 0000000000000000 x10: 00000000000027d0 x9 : ffffc15095d81d18
<4>[  237.726126] x8 : ffffc150974eb008 x7 : ffff41b15fdc2f80 x6 : ffffc15095ce58c0
<4>[  237.733545] x5 : 0000000000000000 x4 : ffff8000098eb6f0 x3 : 0000000000000000
<4>[  237.740963] x2 : 7ec5fa8849bae900 x1 : 7ec5fa8849bae900 x0 : 0000000000000014
<4>[  237.748383] Call trace:
<4>[  237.751091]  lvts_device_enable_and_init+0xdc/0xf0
<4>[  237.756155]  lvts_init+0x180/0x520
<4>[  237.759824]  lvts_resume+0x1c/0x78
<4>[  237.763494]  platform_pm_resume+0x5c/0x80
<4>[  237.767777]  dpm_run_callback+0x80/0x2e8
<4>[  237.771972]  device_resume+0x90/0x1c0
<4>[  237.775904]  dpm_resume+0x110/0x470
<4>[  237.779663]  dpm_resume_end+0x20/0x38
<4>[  237.783596]  suspend_devices_and_enter+0x1e4/0xb90
<4>[  237.788662]  pm_suspend+0x270/0x318
<4>[  237.792419]  state_store+0x94/0x120
<4>[  237.796176]  kobj_attr_store+0x18/0x30
<4>[  237.800198]  sysfs_kf_write+0x54/0x80
<4>[  237.804131]  kernfs_fop_write_iter+0x128/0x1c0
<4>[  237.808845]  vfs_write+0x39c/0x510
<4>[  237.812517]  ksys_write+0x74/0x100
<4>[  237.816187]  __arm64_sys_write+0x24/0x30
<4>[  237.820380]  invoke_syscall+0x4c/0x110
<4>[  237.824401]  el0_svc_common.constprop.0+0x68/0x128
<4>[  237.829464]  do_el0_svc+0x34/0xc0
<4>[  237.833048]  el0_svc+0x4c/0xc0
<4>[  237.836371]  el0t_64_sync_handler+0xb8/0xc0
<4>[  237.840824]  el0t_64_sync+0x18c/0x190
<0>[  237.844759] Code: 11000673 6b13001f 54fffaa8 52800280 (b9003a80)
<4>[  237.851129] ---[ end trace 0000000000000000 ]---

Thanks,
Nícolas
Nícolas F. R. A. Prado Aug. 5, 2022, midnight UTC | #3
Hi Balsam,

another thing.

On Thu, Aug 04, 2022 at 03:09:09PM +0200, bchihi@baylibre.com wrote:
[..]
> --- /dev/null
> +++ b/drivers/thermal/mediatek/lvts_thermal.h
[..]
> +#define ONE_SAMPLE							(lvts_data->counting_window_us + 2 * BUS_ACCESS_US)

This macro is still using a hardcoded variable.

> +#define NUM_OF_SAMPLE(tc_id)				((lvts_data->tc[tc_id].hw_filter < LVTS_FILTER_2) ? \
> +	1 : ((lvts_data->tc[tc_id].hw_filter > LVTS_FILTER_16_OF_18) ? \
> +	1 : ((lvts_data->tc[tc_id].hw_filter == LVTS_FILTER_16_OF_18) ? \
> +	18 : ((lvts_data->tc[tc_id].hw_filter == LVTS_FILTER_8_OF_10) ? \
> +	10 : (lvts_data->tc[tc_id].hw_filter * 2)))))

Ditto.

> +
> +#define PERIOD_UNIT_US(tc_id)		((lvts_data->tc[tc_id].tc_speed->period_unit * 256 * \
> +	CLOCK_26MHZ_CYCLE_NS) / 1000)
> +#define FILTER_INT_US(tc_id)		(lvts_data->tc[tc_id].tc_speed->filter_interval_delay * \
> +	PERIOD_UNIT_US(tc_id))
> +#define SENSOR_INT_US(tc_id)		(lvts_data->tc[tc_id].tc_speed->sensor_interval_delay * \
> +	PERIOD_UNIT_US(tc_id))
> +#define GROUP_INT_US(tc_id)			(lvts_data->tc[tc_id].tc_speed->group_interval_delay * \
> +	PERIOD_UNIT_US(tc_id))

All of these too.

> +#define SENSOR_LATENCY_US(tc_id)	((NUM_OF_SAMPLE(tc_id) - 1) * FILTER_INT_US(tc_id) + \
[..]
> +#define DEVICE_READ				(CK26M_ACTIVE(lvts_data) | DEVICE_ACCESS)

And this.

> +#define DEVICE_WRITE			(CK26M_ACTIVE(lvts_data) | DEVICE_ACCESS | WRITE_ACCESS)

And this.

> +#define RESET_ALL_DEVICES		(DEVICE_WRITE | RG_TSFM_RST << 8 | 0xFF)
> +#define READ_DEVICE_REG(reg_id)	(DEVICE_READ | (reg_id) << 8 | 0x00)
> +#define READ_BACK_DEVICE_ID		(CK26M_ACTIVE(lvts_data) | DEVICE_ACCESS | BROADCAST_ID_UPDATE | \

And finally this one.

I might've have missed some, so please double-check and remove any lingering
hardcoded variable in macros.

Thanks,
Nícolas

> +	RG_DID_LVTS << 8)
[..]
diff mbox series

Patch

diff --git a/drivers/thermal/mediatek/Kconfig b/drivers/thermal/mediatek/Kconfig
index 592c849b9fed..afef43a0e7ca 100644
--- a/drivers/thermal/mediatek/Kconfig
+++ b/drivers/thermal/mediatek/Kconfig
@@ -20,4 +20,28 @@  config MTK_SOC_THERMAL
 	  configures thermal controllers to collect temperature
 	  via AUXADC interface.
 
+config MTK_LVTS_THERMAL
+	tristate "LVTS (Low Voltage Thermal Sensor) driver for MediaTek SoCs"
+	depends on HAS_IOMEM
+	depends on NVMEM
+	depends on RESET_CONTROLLER
+	help
+	  Enable this option if you want to get SoC temperature
+	  information for MediaTek platforms. This driver configures
+	  LVTS (Low Voltage Thermal Sensor) thermal controllers to
+	  collect temperatures via ASIF (Analog Serial Interface).
+
+if MTK_LVTS_THERMAL
+
+config MTK_LVTS_V4
+	tristate "LVTS V4 Thermal Driver for MediaTek SoCs"
+	depends on HAS_IOMEM
+	depends on NVMEM
+	depends on RESET_CONTROLLER
+	help
+	  Enable this option if you want to get SoC temperature
+	  information for LVTS V4.
+
+endif
+
 endif
diff --git a/drivers/thermal/mediatek/Makefile b/drivers/thermal/mediatek/Makefile
index c2b9a166fbef..80a14be07053 100644
--- a/drivers/thermal/mediatek/Makefile
+++ b/drivers/thermal/mediatek/Makefile
@@ -1 +1,3 @@ 
 obj-$(CONFIG_MTK_SOC_THERMAL)		+= mtxxxx_thermal.o
+obj-$(CONFIG_MTK_LVTS_THERMAL)		+= lvts_thermal.o
+obj-$(CONFIG_MTK_LVTS_V4)			+= lvts_v4.o
diff --git a/drivers/thermal/mediatek/lvts_thermal.c b/drivers/thermal/mediatek/lvts_thermal.c
new file mode 100644
index 000000000000..77297a91b9fa
--- /dev/null
+++ b/drivers/thermal/mediatek/lvts_thermal.c
@@ -0,0 +1,855 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/thermal.h>
+#include "lvts_thermal.h"
+
+static int lvts_raw_to_temp(struct lvts_formula_coeff *co, unsigned int msr_raw)
+{
+	/* This function returns degree mC */
+
+	int temp;
+
+	temp = (co->a * ((unsigned long long)msr_raw)) >> 14;
+	temp = temp + co->golden_temp * 500 + co->b;
+
+	return temp;
+}
+
+static unsigned int lvts_temp_to_raw(struct lvts_formula_coeff *co, int temp)
+{
+	unsigned int msr_raw;
+
+	msr_raw = div_s64((s64)((co->golden_temp * 500 + co->b - temp)) << 14, (-1 * co->a));
+
+	return msr_raw;
+}
+
+static int soc_temp_lvts_read_temp(void *data, int *temperature)
+{
+	struct soc_temp_tz *lvts_tz = (struct soc_temp_tz *)data;
+	struct lvts_data *lvts_data = lvts_tz->lvts_data;
+	struct device *dev = lvts_data->dev;
+	unsigned int msr_raw;
+
+	msr_raw = readl(lvts_data->reg[lvts_tz->id]) & MRS_RAW_MASK;
+	if (msr_raw == 0) {
+		/* Prevents a false critical temperature trap */
+		*temperature = 0;
+		dev_dbg(dev, "LVTS not yet ready\n");
+
+	} else
+		*temperature = lvts_raw_to_temp(&lvts_data->coeff, msr_raw);
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops soc_temp_lvts_ops = {
+	.get_temp = soc_temp_lvts_read_temp,
+};
+
+static void lvts_write_device(struct lvts_data *lvts_data, unsigned int data, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+
+	writel(DEVICE_WRITE | data, LVTS_CONFIG_0 + base);
+	usleep_range(20, 30);
+}
+
+static unsigned int lvts_read_device(struct lvts_data *lvts_data, unsigned int reg_idx, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	struct device *dev = lvts_data->dev;
+	unsigned int data;
+	int ret;
+
+	writel(READ_DEVICE_REG(reg_idx), LVTS_CONFIG_0 + base);
+	usleep_range(20, 30);
+	ret = readl_poll_timeout(LVTS_CONFIG_0 + base, data, !(data & DEVICE_ACCESS_STARTUS), 2, 200);
+	if (ret)
+		dev_err(dev, "LVTS_TC_%d DEVICE_ACCESS_START is not ready\n", tc_id);
+
+	data = readl(LVTSRDATA0_0 + base);
+
+	return data;
+}
+
+static const char * const lvts_error_table[] = {
+	"Idle",
+	"Write transaction",
+	"Waiting for read after write",
+	"Disable continue fetching on device",
+	"Read transaction",
+	"Set device special register for voltage threshold",
+	"Set TSMCU number for fetch"
+};
+
+static void wait_all_tc_sensing_point_idle(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	struct device *dev = lvts_data->dev;
+	unsigned int error_code, is_error;
+	int i, cnt, ret;
+
+	for (cnt = 0; cnt < 2; cnt++) {
+		is_error = 0;
+
+		for (i = 0; i < lvts_data->num_tc; i++) {
+			base = GET_BASE_ADDR(lvts_data, i);
+			ret = readl_poll_timeout(LVTSMSRCTL1_0 + base, error_code,
+				!(error_code & ALL_TC_SENSING_POINT_STATUS), 2, 200);
+			error_code = ((error_code & TC_SENSING_POINT_10) >> 8) +
+				((error_code & TC_SENSING_POINT_7) >> 6) + (error_code & TC_SENSING_POINT_0);
+			if (ret)
+				dev_err(dev, "LVTS_TC_%d Error Code : %s\n", i, lvts_error_table[error_code]);
+
+			if (error_code != 0)
+				is_error = 1;
+		}
+
+		if (is_error == 0)
+			break;
+	}
+}
+
+static void lvts_reset(struct lvts_data *lvts_data)
+{
+	if (lvts_data->reset)
+		reset_control_assert(lvts_data->reset);
+
+	if (lvts_data->reset)
+		reset_control_deassert(lvts_data->reset);
+}
+
+static void device_identification(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	struct device *dev = lvts_data->dev;
+	unsigned int i, data;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		writel(ENABLE_LVTS_CTRL_CLK, LVTSCLKEN_0 + base);
+		lvts_write_device(lvts_data, RESET_ALL_DEVICES, i);
+		writel(READ_BACK_DEVICE_ID, LVTS_CONFIG_0 + base);
+		usleep_range(20, 30);
+		/* Check LVTS device ID */
+		data = (readl(LVTS_ID_0 + base) & DEVICE_REG_DATA);
+		if (data != (lvts_data->tc->dev_id + i))
+			dev_err(dev, "LVTS_TC_%d, Device ID should be 0x%x, but 0x%x\n",
+				i, (lvts_data->tc->dev_id + i), data);
+	}
+}
+
+static void disable_all_sensing_points(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	unsigned int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		writel(DISABLE_SENSING_POINT, LVTSMONCTL0_0 + base);
+	}
+}
+
+static void enable_all_sensing_points(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	struct device *dev = lvts_data->dev;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	unsigned int i, num;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		num = tc[i].num_sensor;
+		if (num > ALL_SENSING_POINTS) {
+			dev_err(dev, "LVTS_TC_%d, illegal number of sensors: %d\n", i, tc[i].num_sensor);
+
+			continue;
+		}
+
+		writel(ENABLE_SENSING_POINT(num), LVTSMONCTL0_0 + base);
+	}
+}
+
+static void set_polling_speed(struct lvts_data *lvts_data, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	struct device *dev = lvts_data->dev;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	unsigned int lvts_mon_ctl_1, lvts_mon_ctl_2;
+
+	lvts_mon_ctl_1 = ((tc[tc_id].tc_speed->group_interval_delay << 20) & GROUP_INTERVAL_DELAY_MASK) |
+		(tc[tc_id].tc_speed->period_unit & PERIOD_UNIT_MASK);
+	lvts_mon_ctl_2 = ((tc[tc_id].tc_speed->filter_interval_delay << 16) & FILTER_INTERVAL_DELAY_MASK) |
+		(tc[tc_id].tc_speed->sensor_interval_delay & SENSOR_INTERVAL_DELAY_MASK);
+	/*
+	 * Clock source of LVTS thermal controller is 26MHz.
+	 * Period unit is a base for all interval delays
+	 * All interval delays must multiply it to convert a setting to time.
+	 * Filter interval delay is a delay between two samples of the same sensor
+	 * Sensor interval delay is a delay between two samples of differnet sensors
+	 * Group interval delay is a delay between different rounds.
+	 * For example:
+	 *     If Period unit = C, filter delay = 1, sensor delay = 2, group delay = 1,
+	 *     and two sensors, TS1 and TS2, are in a LVTS thermal controller
+	 *     and then
+	 *     Period unit = C * 1/26M * 256 = 12 * 38.46ns * 256 = 118.149us
+	 *     Filter interval delay = 1 * Period unit = 118.149us
+	 *     Sensor interval delay = 2 * Period unit = 236.298us
+	 *     Group interval delay = 1 * Period unit = 118.149us
+	 *
+	 *     TS1    TS1 ... TS1    TS2    TS2 ... TS2    TS1...
+	 *        <--> Filter interval delay
+	 *                       <--> Sensor interval delay
+	 *                                             <--> Group interval delay
+	 */
+	writel(lvts_mon_ctl_1, LVTSMONCTL1_0 + base);
+	writel(lvts_mon_ctl_2, LVTSMONCTL2_0 + base);
+	dev_dbg(dev, "lvts_tc_%d, LVTSMONCTL1_0= 0x%x, LVTSMONCTL2_0= 0x%x\n",
+		 tc_id, readl(LVTSMONCTL1_0 + base), readl(LVTSMONCTL2_0 + base));
+}
+
+static void set_hw_filter(struct lvts_data *lvts_data, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	struct device *dev = lvts_data->dev;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	unsigned int option = tc[tc_id].hw_filter & 0x7;
+
+	/*
+	 * hw filter
+	 * 000: Get one sample
+	 * 001: Get 2 samples and average them
+	 * 010: Get 4 samples, drop max and min, then average the rest of 2 samples
+	 * 011: Get 6 samples, drop max and min, then average the rest of 4 samples
+	 * 100: Get 10 samples, drop max and min, then average the rest of 8 samples
+	 * 101: Get 18 samples, drop max and min, then average the rest of 16 samples
+	 */
+	option = (option << 9) | (option << 6) | (option << 3) | option;
+	writel(option, LVTSMSRCTL0_0 + base);
+	dev_dbg(dev, "lvts_tc_%d, LVTSMSRCTL0_0= 0x%x\n", tc_id, readl(LVTSMSRCTL0_0 + base));
+}
+
+static int get_dominator_index(struct lvts_data *lvts_data, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	int d_index;
+
+	if (tc[tc_id].dominator_sensing_point == ALL_SENSING_POINTS) {
+		d_index = ALL_SENSING_POINTS;
+	} else if (tc[tc_id].dominator_sensing_point < tc[tc_id].num_sensor) {
+		d_index = tc[tc_id].dominator_sensing_point;
+	} else {
+		dev_err(dev, "LVTS_TC_%d: dominator sensing point = %d. \
+			It should be smaller than num_sensor %d\n",
+			tc_id, tc[tc_id].dominator_sensing_point, tc[tc_id].num_sensor);
+		dev_err(dev, "Using the sensing point 0 as the dominated sensor\n");
+		d_index = SENSING_POINT0;
+	}
+
+	return d_index;
+}
+
+static void disable_hw_reboot_interrupt(struct lvts_data *lvts_data, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	unsigned int temp;
+
+	/*
+	* LVTS thermal controller has two interrupts for thermal HW reboot.
+	* One is for AP SW and the other is for RGU.
+	* The interrupt of AP SW can be turned off by a bit of a register,
+	* but the other for RGU cannot.
+	* To prevent rebooting device accidentally, we are going to add
+	* a huge offset 0x3FFF to LVTS and make it always report extremely low temperature.
+	* LVTS always adds the offset 0x3FFF to MSR_RAW.
+	* When MSR_RAW is larger, SW will convert lower temperature.
+	*/
+	temp = readl(LVTSPROTCTL_0 + base);
+	writel(temp | 0x3FFF, LVTSPROTCTL_0 + base);
+
+	/* Disable the interrupt of AP SW */
+	temp = readl(LVTSMONINT_0 + base);
+	writel(temp & ~(STAGE3_INT_EN), LVTSMONINT_0 + base);
+}
+
+static void enable_hw_reboot_interrupt(struct lvts_data *lvts_data, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	unsigned int temp;
+
+	/* Enable the interrupt of AP SW */
+	temp = readl(LVTSMONINT_0 + base);
+	writel(temp | STAGE3_INT_EN, LVTSMONINT_0 + base);
+
+	/* Clear the offset */
+	temp = readl(LVTSPROTCTL_0 + base);
+	writel(temp & ~PROTOFFSET, LVTSPROTCTL_0 + base);
+}
+
+static void set_tc_hw_reboot_threshold(struct lvts_data *lvts_data, int trip_point, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	struct device *dev = lvts_data->dev;
+	unsigned int msr_raw, temp, config, d_index;
+
+	d_index = get_dominator_index(lvts_data, tc_id);
+
+	dev_dbg(dev, "lvts_tc_%d: dominator sensing point = %d\n", tc_id, d_index);
+
+	disable_hw_reboot_interrupt(lvts_data, tc_id);
+	temp = readl(LVTSPROTCTL_0 + base);
+	if (d_index == ALL_SENSING_POINTS) {
+		/* Maximum of 4 sensing points */
+		config = (0x1 << 16);
+		writel(config | temp, LVTSPROTCTL_0 + base);
+
+	} else {
+		/* Select protection sensor */
+		config = ((d_index << 2) + 0x2) << 16;
+		writel(config | temp, LVTSPROTCTL_0 + base);
+	}
+
+	msr_raw = lvts_temp_to_raw(&lvts_data->coeff, trip_point);
+	writel(msr_raw, LVTSPROTTC_0 + base);
+	enable_hw_reboot_interrupt(lvts_data, tc_id);
+}
+
+static void set_all_tc_hw_reboot(struct lvts_data *lvts_data)
+{
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	int i, trip_point;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		trip_point = tc[i].hw_reboot_trip_point;
+
+		if (tc[i].num_sensor == 0)
+			continue;
+
+		if (trip_point == THERMAL_TEMP_INVALID)
+			continue;
+
+		set_tc_hw_reboot_threshold(lvts_data, trip_point, i);
+	}
+}
+
+static int lvts_init(struct lvts_data *lvts_data)
+{
+	struct platform_ops *ops = &lvts_data->ops;
+	struct device *dev = lvts_data->dev;
+	int ret;
+
+	ret = clk_prepare_enable(lvts_data->clk);
+	if (ret) {
+		dev_err(dev, "Failed to enable lvts controller clock: %d\n", ret);
+
+		return ret;
+	}
+
+	lvts_reset(lvts_data);
+	device_identification(lvts_data);
+	if (ops->device_enable_and_init)
+		ops->device_enable_and_init(lvts_data);
+
+	if (HAS_FEATURE(lvts_data, FEATURE_DEVICE_AUTO_RCK) && (ops->device_enable_auto_rck))
+		ops->device_enable_auto_rck(lvts_data);
+
+	else if (ops->device_read_count_rc_n)
+		ops->device_read_count_rc_n(lvts_data);
+
+	if (ops->set_cal_data)
+		ops->set_cal_data(lvts_data);
+
+	disable_all_sensing_points(lvts_data);
+	wait_all_tc_sensing_point_idle(lvts_data);
+	if (ops->init_controller)
+		ops->init_controller(lvts_data);
+
+	enable_all_sensing_points(lvts_data);
+	set_all_tc_hw_reboot(lvts_data);
+
+	return 0;
+}
+
+static int prepare_calibration_data(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	struct lvts_sensor_cal_data *cal_data = &lvts_data->cal_data;
+	struct platform_ops *ops = &lvts_data->ops;
+	int i, offset;
+	char buffer[512];
+
+	cal_data->count_r = devm_kcalloc(dev, lvts_data->num_sensor,
+		sizeof(*cal_data->count_r), GFP_KERNEL);
+	if (!cal_data->count_r)
+		return -ENOMEM;
+
+	cal_data->count_rc = devm_kcalloc(dev, lvts_data->num_sensor,
+		sizeof(*cal_data->count_rc), GFP_KERNEL);
+	if (!cal_data->count_rc)
+		return -ENOMEM;
+
+	if (ops->efuse_to_cal_data && !cal_data->use_fake_efuse)
+		ops->efuse_to_cal_data(lvts_data);
+	if (cal_data->golden_temp == 0 || cal_data->golden_temp > GOLDEN_TEMP_MAX)
+		cal_data->use_fake_efuse = 1;
+
+	if (cal_data->use_fake_efuse) {
+		/* It means all efuse data are equal to 0 */
+		dev_err(dev, "This sample is not calibrated, fake !!\n");
+		cal_data->golden_temp = cal_data->default_golden_temp;
+		for (i = 0; i < lvts_data->num_sensor; i++) {
+			cal_data->count_r[i] = cal_data->default_count_r;
+			cal_data->count_rc[i] = cal_data->default_count_rc;
+		}
+	}
+
+	lvts_data->coeff.golden_temp = cal_data->golden_temp;
+	dev_dbg(dev, "golden_temp = %d\n", cal_data->golden_temp);
+	offset = snprintf(buffer, sizeof(buffer), "[lvts_cal] num:g_count:g_count_rc ");
+	for (i = 0; i < lvts_data->num_sensor; i++)
+		offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%d:%d:%d ",
+			i, cal_data->count_r[i], cal_data->count_rc[i]);
+
+	return 0;
+}
+
+static int get_calibration_data(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	char cell_name[32];
+	struct nvmem_cell *cell;
+	u32 *buf;
+	size_t len;
+	int i, j, index = 0, ret;
+
+	lvts_data->efuse = devm_kcalloc(dev, lvts_data->num_efuse_addr,
+		sizeof(*lvts_data->efuse), GFP_KERNEL);
+	if (!lvts_data->efuse)
+		return -ENOMEM;
+
+	for (i = 0; i < lvts_data->num_efuse_block; i++) {
+		snprintf(cell_name, sizeof(cell_name), "lvts_calib_data%d", i + 1);
+		cell = nvmem_cell_get(dev, cell_name);
+		if (IS_ERR(cell)) {
+			dev_err(dev, "Failed to get nvmem cell %s\n", cell_name);
+
+			return PTR_ERR(cell);
+		}
+
+		buf = (u32 *)nvmem_cell_read(cell, &len);
+		nvmem_cell_put(cell);
+		if (IS_ERR(buf))
+			return PTR_ERR(buf);
+
+		for (j = 0; j < (len / sizeof(u32)); j++) {
+			if (index >= lvts_data->num_efuse_addr) {
+				dev_err(dev, "Array efuse is going to overflow");
+				kfree(buf);
+
+				return -EINVAL;
+			}
+
+			lvts_data->efuse[index] = buf[j];
+			index++;
+		}
+
+		kfree(buf);
+	}
+
+	ret = prepare_calibration_data(lvts_data);
+
+	return ret;
+}
+
+static int lvts_init_tc_regs(struct device *dev, struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	unsigned int i, j, s_index;
+
+	lvts_data->reg = devm_kcalloc(dev, lvts_data->num_sensor,
+		sizeof(*lvts_data->reg), GFP_KERNEL);
+	if (!lvts_data->reg)
+		return -ENOMEM;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		for (j = 0; j < tc[i].num_sensor; j++) {
+			s_index = tc[i].sensor_map[j];
+			lvts_data->reg[s_index] = LVTSMSR0_0 + base + 0x4 * j;
+		}
+	}
+
+	return 0;
+}
+
+static int of_update_lvts_data(struct lvts_data *lvts_data, struct platform_device *pdev)
+{
+	struct device *dev = lvts_data->dev;
+	struct resource *res;
+	int ret;
+
+	lvts_data->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(lvts_data->clk))
+		return PTR_ERR(lvts_data->clk);
+
+	/* Get base address */
+	res = platform_get_mem_or_io(pdev, 0);
+	if (!res) {
+		dev_err(dev, "No IO resource\n");
+
+		return -ENXIO;
+	}
+
+	lvts_data->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(lvts_data->base)) {
+		dev_err(dev, "Failed to remap io\n");
+
+		return PTR_ERR(lvts_data->base);
+	}
+
+	/* Get interrupt number */
+	ret = platform_get_irq(pdev, 0);
+	if (ret < 0) {
+		dev_err(dev, "No irq resource\n");
+
+		return ret;
+	}
+
+	lvts_data->irq_num = ret;
+	/* Get reset control */
+	lvts_data->reset = devm_reset_control_get_by_index(dev, 0);
+	if (IS_ERR(lvts_data->reset)) {
+		dev_err(dev, "Failed to get reset control\n");
+
+		return PTR_ERR(lvts_data->reset);
+	}
+
+	ret = lvts_init_tc_regs(dev, lvts_data);
+	if (ret)
+		return ret;
+
+	ret = get_calibration_data(lvts_data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void lvts_device_close(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	unsigned int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		lvts_write_device(lvts_data, RESET_ALL_DEVICES, i);
+		writel(DISABLE_LVTS_CTRL_CLK, LVTSCLKEN_0 + base);
+	}
+}
+
+static void lvts_close(struct lvts_data *lvts_data)
+{
+	disable_all_sensing_points(lvts_data);
+	wait_all_tc_sensing_point_idle(lvts_data);
+	lvts_device_close(lvts_data);
+	clk_disable_unprepare(lvts_data->clk);
+}
+
+static void tc_irq_handler(struct lvts_data *lvts_data, int tc_id)
+{
+	void __iomem *base = GET_BASE_ADDR(lvts_data, tc_id);
+	const struct device *dev = lvts_data->dev;
+	unsigned int ret = readl(LVTSMONINTSTS_0 + base);
+
+	/* Write back to clear interrupt status */
+	writel(ret, LVTSMONINTSTS_0 + base);
+	dev_dbg(dev, "LVTS thermal controller %d, LVTSMONINTSTS=0x%08x\n", tc_id, ret);
+	if (ret & THERMAL_PROTECTION_STAGE_3)
+		dev_dbg(dev, "Thermal protection stage 3 interrupt triggered\n");
+}
+
+static irqreturn_t irq_handler(int irq, void *dev_id)
+{
+	void __iomem *base;
+	struct lvts_data *lvts_data = (struct lvts_data *)dev_id;
+	struct device *dev = lvts_data->dev;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	unsigned int i, irq_bitmap;
+
+	base = lvts_data->base;
+	irq_bitmap = readl(THERMINTST + base);
+	dev_dbg(dev, "THERMINTST = 0x%x\n", irq_bitmap);
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		if (tc[i].irq_bit == 0)
+			tc_irq_handler(lvts_data, i);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int lvts_register_irq_handler(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	int ret;
+
+	ret = devm_request_irq(dev, lvts_data->irq_num, irq_handler, IRQF_TRIGGER_NONE,
+		"mtk_lvts", lvts_data);
+	if (ret) {
+		dev_err(dev, "Failed to register LVTS IRQ, ret %d, irq_num %d\n",
+			ret, lvts_data->irq_num);
+		lvts_close(lvts_data);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+static int lvts_register_thermal_zones(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	struct thermal_zone_device *tzdev;
+	struct soc_temp_tz *lvts_tz;
+	int i, ret;
+
+	for (i = 0; i < lvts_data->num_sensor; i++) {
+		lvts_tz = devm_kzalloc(dev, sizeof(*lvts_tz), GFP_KERNEL);
+		if (!lvts_tz) {
+			lvts_close(lvts_data);
+
+			return -ENOMEM;
+		}
+
+		lvts_tz->id = i;
+		lvts_tz->lvts_data = lvts_data;
+		tzdev = devm_thermal_zone_of_sensor_register(dev, lvts_tz->id, lvts_tz, &soc_temp_lvts_ops);
+		if (IS_ERR(tzdev)) {
+			if (lvts_tz->id != 0)
+				return 0;
+
+			ret = PTR_ERR(tzdev);
+			dev_err(dev, "Failed to register lvts tz %d, ret = %d\n", lvts_tz->id, ret);
+			lvts_close(lvts_data);
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+void lvts_device_enable_and_init(struct lvts_data *lvts_data)
+{
+	unsigned int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		lvts_write_device(lvts_data, STOP_COUNTING_V4, i);
+		lvts_write_device(lvts_data, SET_RG_TSFM_LPDLY_V4, i);
+		lvts_write_device(lvts_data, SET_COUNTING_WINDOW_20US1_V4, i);
+		lvts_write_device(lvts_data, SET_COUNTING_WINDOW_20US2_V4, i);
+		lvts_write_device(lvts_data, TSV2F_CHOP_CKSEL_AND_TSV2F_EN_V4, i);
+		lvts_write_device(lvts_data, TSBG_DEM_CKSEL_X_TSBG_CHOP_EN_V4, i);
+		lvts_write_device(lvts_data, SET_TS_RSV_V4, i);
+		lvts_write_device(lvts_data, SET_TS_EN_V4, i);
+		lvts_write_device(lvts_data, TOGGLE_RG_TSV2F_VCO_RST1_V4, i);
+		lvts_write_device(lvts_data, TOGGLE_RG_TSV2F_VCO_RST2_V4, i);
+	}
+
+	lvts_data->counting_window_us = 20;
+}
+EXPORT_SYMBOL_GPL(lvts_device_enable_and_init);
+
+void lvts_device_enable_auto_rck_v4(struct lvts_data *lvts_data)
+{
+	unsigned int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++)
+		lvts_write_device(lvts_data, SET_LVTS_AUTO_RCK_V4, i);
+}
+EXPORT_SYMBOL_GPL(lvts_device_enable_auto_rck_v4);
+
+int lvts_device_read_count_rc_n_v4(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	struct device *dev = lvts_data->dev;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	struct lvts_sensor_cal_data *cal_data = &lvts_data->cal_data;
+	unsigned int offset, s_index, data;
+	int ret, i, j;
+	char buffer[128];
+
+	cal_data->count_rc_now = devm_kcalloc(dev, lvts_data->num_sensor,
+		sizeof(*cal_data->count_rc_now), GFP_KERNEL);
+	if (!cal_data->count_rc_now)
+		return -ENOMEM;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		for (j = 0; j < tc[i].num_sensor; j++) {
+			s_index = tc[i].sensor_map[j];
+			lvts_write_device(lvts_data, SELECT_SENSOR_RCK_V4(j), i);
+			lvts_write_device(lvts_data, SET_DEVICE_SINGLE_MODE_V4, i);
+			lvts_write_device(lvts_data, KICK_OFF_RCK_COUNTING_V4, i);
+			ret = readl_poll_timeout(LVTS_CONFIG_0 + base, data,
+				!(data & DEVICE_SENSING_STATUS), 2, 200);
+			if (ret)
+				dev_err(dev, "LVTS_TC_%d DEVICE_SENSING_STATUS didn't ready\n", i);
+
+			data = lvts_read_device(lvts_data, 0x00, i);
+			cal_data->count_rc_now[s_index] = (data & COUNT_RC_NOW_MASK);
+		}
+
+		/* Recover Setting for Normal Access on
+		 * temperature fetch
+		 */
+		lvts_write_device(lvts_data, SET_SENSOR_NO_RCK_V4, i);
+		lvts_write_device(lvts_data, SET_DEVICE_LOW_POWER_SINGLE_MODE_V4, i);
+	}
+
+	offset = snprintf(buffer, sizeof(buffer), "[COUNT_RC_NOW] ");
+	for (i = 0; i < lvts_data->num_sensor; i++)
+		offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%d:%d ", i,
+			cal_data->count_rc_now[i]);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(lvts_device_read_count_rc_n_v4);
+
+void lvts_set_calibration_data_v4(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	const struct lvts_tc_settings *tc = lvts_data->tc;
+	struct lvts_sensor_cal_data *cal_data = &lvts_data->cal_data;
+	unsigned int i, j, s_index;
+	u32 lvts_calib_data;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		for (j = 0; j < tc[i].num_sensor; j++) {
+			s_index = tc[i].sensor_map[j];
+			if (HAS_FEATURE(lvts_data, FEATURE_DEVICE_AUTO_RCK))
+				lvts_calib_data = cal_data->count_r[s_index];
+
+			else
+				lvts_calib_data = (((u32)cal_data->count_rc_now[s_index]) *
+					cal_data->count_r[s_index]) >> 14;
+
+			writel(lvts_calib_data, LVTSEDATA00_0 + base + 0x4 * j);
+		}
+	}
+}
+EXPORT_SYMBOL_GPL(lvts_set_calibration_data_v4);
+
+void lvts_init_controller_v4(struct lvts_data *lvts_data)
+{
+	void __iomem *base;
+	struct device *dev = lvts_data->dev;
+	unsigned int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(lvts_data, i);
+		lvts_write_device(lvts_data, SET_DEVICE_LOW_POWER_SINGLE_MODE_V4, i);
+		writel(SET_SENSOR_INDEX, LVTSTSSEL_0 + base);
+		writel(SET_CALC_SCALE_RULES, LVTSCALSCALE_0 + base);
+		set_polling_speed(lvts_data, i);
+		set_hw_filter(lvts_data, i);
+		dev_dbg(dev, "lvts_tc_%d: read all %d sensors in %d us, one in %d us\n",
+			i, GET_TC_SENSOR_NUM(lvts_data, i), GROUP_LATENCY_US(i), SENSOR_LATENCY_US(i));
+	}
+}
+EXPORT_SYMBOL_GPL(lvts_init_controller_v4);
+
+int lvts_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct lvts_data *lvts_data;
+	int ret;
+
+	lvts_data = (struct lvts_data *)of_device_get_match_data(dev);
+	if (!lvts_data)	{
+		dev_err(dev, "Failed to get lvts platform data\n");
+
+		return -ENODATA;
+	}
+
+	lvts_data->dev = &pdev->dev;
+	ret = of_update_lvts_data(lvts_data, pdev);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, lvts_data);
+	ret = lvts_init(lvts_data);
+	if (ret)
+		return ret;
+
+	ret = lvts_register_irq_handler(lvts_data);
+	if (ret)
+		return ret;
+
+	ret = lvts_register_thermal_zones(lvts_data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+int lvts_remove(struct platform_device *pdev)
+{
+	struct lvts_data *lvts_data;
+
+	lvts_data = (struct lvts_data *)platform_get_drvdata(pdev);
+	lvts_close(lvts_data);
+
+	return 0;
+}
+
+int lvts_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct lvts_data *lvts_data;
+
+	lvts_data = (struct lvts_data *)platform_get_drvdata(pdev);
+	lvts_close(lvts_data);
+
+	return 0;
+}
+
+int lvts_resume(struct platform_device *pdev)
+{
+	struct lvts_data *lvts_data;
+	int ret;
+
+	lvts_data = (struct lvts_data *)platform_get_drvdata(pdev);
+	ret = lvts_init(lvts_data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+MODULE_AUTHOR("Yu-Chia Chang <ethan.chang@mediatek.com>");
+MODULE_AUTHOR("Michael Kao <michael.kao@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek LVTS Thermal Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/thermal/mediatek/lvts_thermal.h b/drivers/thermal/mediatek/lvts_thermal.h
new file mode 100644
index 000000000000..9095f9cd232b
--- /dev/null
+++ b/drivers/thermal/mediatek/lvts_thermal.h
@@ -0,0 +1,376 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#ifndef __MTK_SOC_TEMP_LVTS_H__
+#define __MTK_SOC_TEMP_LVTS_H__
+
+#define PERIOD_UNIT				12
+#define GROUP_INTERVAL_DELAY	1
+#define FILTER_INTERVAL_DELAY	1
+#define SENSOR_INTERVAL_DELAY	1
+
+#define HW_REBOOT_TRIP_POINT	117000
+
+#define FEATURE_DEVICE_AUTO_RCK	BIT(0)
+#define NUM_EFUSE_ADDR			22
+#define NUM_EFUSE_BLOCK_MT8192	1
+#define DEFAULT_GOLDEN_TEMP		50
+#define DEFAULT_CUONT_R			35000
+#define DEFAULT_CUONT_RC		2750
+#define COEFF_A					-250460
+#define COEFF_B					250460
+
+#define CLOCK_26MHZ_CYCLE_NS	38
+#define BUS_ACCESS_US			2
+#define GOLDEN_TEMP_MAX			62
+
+#define TC_SENSING_POINT_0				BIT(0)
+#define TC_SENSING_POINT_7				BIT(7)
+#define TC_SENSING_POINT_10				BIT(10)
+#define ALL_TC_SENSING_POINT_STATUS		(BIT(10) | BIT(7) | BIT(0))
+#define COUNT_RC_NOW_MASK				GENMASK(23, 0)
+#define PERIOD_UNIT_MASK				GENMASK(9, 0)
+#define GROUP_INTERVAL_DELAY_MASK		GENMASK(29, 20)
+#define FILTER_INTERVAL_DELAY_MASK		GENMASK(25, 16)
+#define SENSOR_INTERVAL_DELAY_MASK		GENMASK(9, 0)
+
+/* LVTS device register */
+#define RG_TSFM_DATA_0			0x00
+#define RG_TSFM_DATA_1			0x01
+#define RG_TSFM_DATA_2			0x02
+#define RG_TSFM_CTRL_0			0x03
+#define RG_TSFM_CTRL_1			0x04
+#define RG_TSFM_CTRL_2			0x05
+#define RG_TSFM_CTRL_3			0x06
+#define RG_TSFM_CTRL_4			0x07
+#define RG_TSV2F_CTRL_0			0x08
+#define RG_TSV2F_CTRL_1			0x09
+#define RG_TSV2F_CTRL_2			0x0A
+#define RG_TSV2F_CTRL_3			0x0B
+#define RG_TSV2F_CTRL_4			0x0C
+#define RG_TSV2F_CTRL_5			0x0D
+#define RG_TSV2F_CTRL_6			0x0E
+#define RG_TEMP_DATA_0			0x10
+#define RG_TEMP_DATA_1			0x11
+#define RG_TEMP_DATA_2			0x12
+#define RG_TEMP_DATA_3			0x13
+#define RG_RC_DATA_0			0x14
+#define RG_RC_DATA_1			0x15
+#define RG_RC_DATA_2			0x16
+#define RG_RC_DATA_3			0x17
+#define RG_DIV_DATA_0			0x18
+#define RG_DIV_DATA_1			0x19
+#define RG_DIV_DATA_2			0x1A
+#define RG_DIV_DATA_3			0x1B
+#define RG_TST_DATA_0			0x70
+#define RG_TST_DATA_1			0x71
+#define RG_TST_DATA_2			0x72
+#define RG_TST_CTRL				0x73
+#define RG_DBG_FQMTR			0xF0
+#define RG_DBG_LPSEQ			0xF1
+#define RG_DBG_STATE			0xF2
+#define RG_DBG_CHKSUM			0xF3
+#define RG_DID_LVTS				0xFC
+#define RG_DID_REV				0xFD
+#define RG_TSFM_RST				0xFF
+
+/* LVTS controller register */
+#define LVTSMONCTL0_0				0x000
+#define ENABLE_SENSING_POINT(num)	(LVTS_SINGLE_SENSE | GENMASK(((num) - 1), 0))
+#define DISABLE_SENSING_POINT		(LVTS_SINGLE_SENSE | 0x0)
+#define LVTSMONCTL1_0				0x004
+#define LVTSMONCTL2_0				0x008
+#define LVTSMONINT_0				0x00C
+#define STAGE3_INT_EN				BIT(31)
+#define LVTSMONINTSTS_0				0x010
+#define LVTSMONIDET0_0				0x014
+#define LVTSMONIDET1_0				0x018
+#define LVTSMONIDET2_0				0x01C
+#define LVTSMONIDET3_0				0x020
+#define LVTSH2NTHRE_0				0x024
+#define LVTSHTHRE_0					0x028
+#define LVTSCTHRE_0					0x02C
+#define LVTSOFFSETH_0				0x030
+#define LVTSOFFSETL_0				0x034
+#define LVTSMSRCTL0_0				0x038
+#define LVTSMSRCTL1_0				0x03C
+#define LVTSTSSEL_0					0x040
+#define SET_SENSOR_INDEX			0x13121110
+#define LVTSDEVICETO_0				0x044
+#define LVTSCALSCALE_0				0x048
+#define SET_CALC_SCALE_RULES		0x00000300
+#define LVTS_ID_0					0x04C
+#define LVTS_CONFIG_0				0x050
+
+#define SCK_ONLY					BIT(31)
+#define BROADCAST_ID_UPDATE			BIT(26)
+#define DEVICE_SENSING_STATUS		BIT(25)
+#define DEVICE_ACCESS_STARTUS		BIT(24)
+#define READ_32BIT_ACCESS			BIT(17)
+#define WRITE_ACCESS				BIT(16)
+#define LVTS_SINGLE_SENSE			BIT(9)
+#define FEATURE_CK26M_ACTIVE		BIT(1)
+#define DEVICE_REG_DATA				GENMASK(7, 0)
+
+#define LVTSEDATA00_0				0x054
+#define LVTSEDATA01_0				0x058
+#define LVTSEDATA02_0				0x05C
+#define LVTSEDATA03_0				0x060
+#define LVTSMSR0_0					0x090
+#define MRS_RAW_MASK				GENMASK(15, 0)
+#define MRS_RAW_VALID_BIT			BIT(16)
+#define LVTSMSR1_0					0x094
+#define LVTSMSR2_0					0x098
+#define LVTSMSR3_0					0x09C
+#define LVTSIMMD0_0					0x0A0
+#define LVTSIMMD1_0					0x0A4
+#define LVTSIMMD2_0					0x0A8
+#define LVTSIMMD3_0					0x0AC
+#define LVTSRDATA0_0				0x0B0
+#define LVTSRDATA1_0				0x0B4
+#define LVTSRDATA2_0				0x0B8
+#define LVTSRDATA3_0				0x0BC
+#define LVTSPROTCTL_0				0x0C0
+#define PROTOFFSET					GENMASK(15, 0)
+#define LVTSPROTTA_0				0x0C4
+#define LVTSPROTTB_0				0x0C8
+#define LVTSPROTTC_0				0x0CC
+#define LVTSCLKEN_0					0x0E4
+#define ENABLE_LVTS_CTRL_CLK		(1)
+#define DISABLE_LVTS_CTRL_CLK		(0)
+#define LVTSDBGSEL_0				0x0E8
+#define LVTSDBGSIG_0				0x0EC
+#define LVTSSPARE0_0				0x0F0
+#define LVTSSPARE1_0				0x0F4
+#define LVTSSPARE2_0				0x0F8
+#define LVTSSPARE3_0				0x0FC
+#define THERMINTST					0xF04
+
+/* LVTS register mask */
+#define THERMAL_COLD_INTERRUPT_0			BIT(0)
+#define THERMAL_HOT_INTERRUPT_0				BIT(1)
+#define THERMAL_LOW_OFFSET_INTERRUPT_0		BIT(2)
+#define THERMAL_HIGH_OFFSET_INTERRUPT_0		BIT(3)
+#define THERMAL_HOT2NORMAL_INTERRUPT_0		BIT(4)
+#define THERMAL_COLD_INTERRUPT_1			BIT(5)
+#define THERMAL_HOT_INTERRUPT_1				BIT(6)
+#define THERMAL_LOW_OFFSET_INTERRUPT_1		BIT(7)
+#define THERMAL_HIGH_OFFSET_INTERRUPT_1		BIT(8)
+#define THERMAL_HOT2NORMAL_INTERRUPT_1		BIT(9)
+#define THERMAL_COLD_INTERRUPT_2			BIT(10)
+#define THERMAL_HOT_INTERRUPT_2				BIT(11)
+#define THERMAL_LOW_OFFSET_INTERRUPT_2		BIT(12)
+#define THERMAL_HIGH_OFFSET_INTERRUPT_2		BIT(13)
+#define THERMAL_HOT2NORMAL_INTERRUPT_2		BIT(14)
+#define THERMAL_AHB_TIMEOUT_INTERRUPT		BIT(15)
+#define THERMAL_DEVICE_TIMEOUT_INTERRUPT	BIT(15)
+#define THERMAL_IMMEDIATE_INTERRUPT_0		BIT(16)
+#define THERMAL_IMMEDIATE_INTERRUPT_1		BIT(17)
+#define THERMAL_IMMEDIATE_INTERRUPT_2		BIT(18)
+#define THERMAL_FILTER_INTERRUPT_0			BIT(19)
+#define THERMAL_FILTER_INTERRUPT_1			BIT(20)
+#define THERMAL_FILTER_INTERRUPT_2			BIT(21)
+#define THERMAL_COLD_INTERRUPT_3			BIT(22)
+#define THERMAL_HOT_INTERRUPT_3				BIT(23)
+#define THERMAL_LOW_OFFSET_INTERRUPT_3		BIT(24)
+#define THERMAL_HIGH_OFFSET_INTERRUPT_3		BIT(25)
+#define THERMAL_HOT2NORMAL_INTERRUPT_3		BIT(26)
+#define THERMAL_IMMEDIATE_INTERRUPT_3		BIT(27)
+#define THERMAL_FILTER_INTERRUPT_3			BIT(28)
+#define THERMAL_PROTECTION_STAGE_1			BIT(29)
+#define THERMAL_PROTECTION_STAGE_2			BIT(30)
+#define THERMAL_PROTECTION_STAGE_3			BIT(31)
+
+#define CFG_REGISTER(reg, value)			(reg << 8 | value)
+
+#define STOP_COUNTING_V4					CFG_REGISTER(RG_TSFM_CTRL_0, 0x00)
+#define SET_RG_TSFM_LPDLY_V4				CFG_REGISTER(RG_TSFM_CTRL_4, 0xA6)
+#define SET_COUNTING_WINDOW_20US1_V4		CFG_REGISTER(RG_TSFM_CTRL_2, 0x00)
+#define SET_COUNTING_WINDOW_20US2_V4		CFG_REGISTER(RG_TSFM_CTRL_1, 0x20)
+#define TSV2F_CHOP_CKSEL_AND_TSV2F_EN_V4	CFG_REGISTER(RG_TSV2F_CTRL_2, 0x84)
+#define TSBG_DEM_CKSEL_X_TSBG_CHOP_EN_V4	CFG_REGISTER(RG_TSV2F_CTRL_4, 0x7C)
+#define SET_TS_RSV_V4						CFG_REGISTER(RG_TSV2F_CTRL_1, 0x8D)
+#define SET_TS_EN_V4						CFG_REGISTER(RG_TSV2F_CTRL_0, 0xF4)
+#define TOGGLE_RG_TSV2F_VCO_RST1_V4			CFG_REGISTER(RG_TSV2F_CTRL_0, 0xFC)
+#define TOGGLE_RG_TSV2F_VCO_RST2_V4			CFG_REGISTER(RG_TSV2F_CTRL_0, 0xF4)
+
+#define SET_LVTS_AUTO_RCK_V4				CFG_REGISTER(RG_TSV2F_CTRL_6, 0x01)
+#define SELECT_SENSOR_RCK_V4(id)			CFG_REGISTER(RG_TSV2F_CTRL_5, (id))
+#define SET_DEVICE_SINGLE_MODE_V4			CFG_REGISTER(RG_TSFM_CTRL_3, 0x78)
+#define KICK_OFF_RCK_COUNTING_V4			CFG_REGISTER(RG_TSFM_CTRL_0, 0x02)
+#define SET_SENSOR_NO_RCK_V4				CFG_REGISTER(RG_TSV2F_CTRL_5, 0x10)
+#define SET_DEVICE_LOW_POWER_SINGLE_MODE_V4	CFG_REGISTER(RG_TSFM_CTRL_3, 0xB8)
+
+#define HAS_FEATURE(lvts_data, feature)		(lvts_data->feature_bitmap & (feature))
+#define GET_BASE_ADDR(lvts_data, tc_id)		(lvts_data->base + lvts_data->tc[tc_id].addr_offset)
+#define GET_CAL_DATA_BITMASK(index, lvts_data, h, l)	(((index) < lvts_data->num_efuse_addr) ? \
+	((lvts_data->efuse[(index)] & GENMASK(h, l)) >> l) : 0)
+
+#define GET_TC_SENSOR_NUM(lvts_data, tc_id)	(lvts_data->tc[tc_id].num_sensor)
+#define ONE_SAMPLE							(lvts_data->counting_window_us + 2 * BUS_ACCESS_US)
+#define NUM_OF_SAMPLE(tc_id)				((lvts_data->tc[tc_id].hw_filter < LVTS_FILTER_2) ? \
+	1 : ((lvts_data->tc[tc_id].hw_filter > LVTS_FILTER_16_OF_18) ? \
+	1 : ((lvts_data->tc[tc_id].hw_filter == LVTS_FILTER_16_OF_18) ? \
+	18 : ((lvts_data->tc[tc_id].hw_filter == LVTS_FILTER_8_OF_10) ? \
+	10 : (lvts_data->tc[tc_id].hw_filter * 2)))))
+
+#define PERIOD_UNIT_US(tc_id)		((lvts_data->tc[tc_id].tc_speed->period_unit * 256 * \
+	CLOCK_26MHZ_CYCLE_NS) / 1000)
+#define FILTER_INT_US(tc_id)		(lvts_data->tc[tc_id].tc_speed->filter_interval_delay * \
+	PERIOD_UNIT_US(tc_id))
+#define SENSOR_INT_US(tc_id)		(lvts_data->tc[tc_id].tc_speed->sensor_interval_delay * \
+	PERIOD_UNIT_US(tc_id))
+#define GROUP_INT_US(tc_id)			(lvts_data->tc[tc_id].tc_speed->group_interval_delay * \
+	PERIOD_UNIT_US(tc_id))
+#define SENSOR_LATENCY_US(tc_id)	((NUM_OF_SAMPLE(tc_id) - 1) * FILTER_INT_US(tc_id) + \
+	NUM_OF_SAMPLE(tc_id) * ONE_SAMPLE)
+#define GROUP_LATENCY_US(tc_id)		(GET_TC_SENSOR_NUM(lvts_data, tc_id) * \
+	SENSOR_LATENCY_US(tc_id) + (GET_TC_SENSOR_NUM(lvts_data, tc_id) - 1) * \
+	SENSOR_INT_US(tc_id) + GROUP_INT_US(tc_id))
+
+#define CK26M_ACTIVE(lvts_data)	(((lvts_data->feature_bitmap & FEATURE_CK26M_ACTIVE) ? 1 : 0) << 30)
+#define DEVICE_ACCESS			(SCK_ONLY | DEVICE_ACCESS_STARTUS | READ_32BIT_ACCESS)
+#define DEVICE_READ				(CK26M_ACTIVE(lvts_data) | DEVICE_ACCESS)
+#define DEVICE_WRITE			(CK26M_ACTIVE(lvts_data) | DEVICE_ACCESS | WRITE_ACCESS)
+#define RESET_ALL_DEVICES		(DEVICE_WRITE | RG_TSFM_RST << 8 | 0xFF)
+#define READ_DEVICE_REG(reg_id)	(DEVICE_READ | (reg_id) << 8 | 0x00)
+#define READ_BACK_DEVICE_ID		(CK26M_ACTIVE(lvts_data) | DEVICE_ACCESS | BROADCAST_ID_UPDATE | \
+	RG_DID_LVTS << 8)
+
+/*
+ * LVTS HW filter settings
+ * 000: Get one sample
+ * 001: Get 2 samples and average them
+ * 010: Get 4 samples, drop max and min, then average the rest of 2 samples
+ * 011: Get 6 samples, drop max and min, then average the rest of 4 samples
+ * 100: Get 10 samples, drop max and min, then average the rest of 8 samples
+ * 101: Get 18 samples, drop max and min, then average the rest of 16 samples
+ */
+enum lvts_hw_filter {
+	LVTS_FILTER_1,
+	LVTS_FILTER_2,
+	LVTS_FILTER_2_OF_4,
+	LVTS_FILTER_4_OF_6,
+	LVTS_FILTER_8_OF_10,
+	LVTS_FILTER_16_OF_18
+};
+
+enum lvts_sensing_point {
+	SENSING_POINT0,
+	SENSING_POINT1,
+	SENSING_POINT2,
+	SENSING_POINT3,
+	ALL_SENSING_POINTS
+};
+
+struct lvts_data;
+
+/**
+ * struct lvts_speed_settings - A structure to hold the data related to polling rate
+ * @period_unit: Period unit is a base for all interval delays
+ * @group_interval_delay:  Delay between different rounds
+ * @filter_interval_delay: Delay between two samples of the same sensor
+ * @sensor_interval_delay: Delay between two samples of differnet sensors
+ *
+ * Calculation is achieved with the following equations:
+ * For the period unit: (period_us * 1000) / (256 * clock_26mhz_cycle_ns)
+ * For the interval delays: delay / period_us
+ */
+struct lvts_speed_settings {
+	unsigned int period_unit;
+	unsigned int group_interval_delay;
+	unsigned int filter_interval_delay;
+	unsigned int sensor_interval_delay;
+};
+
+struct lvts_tc_settings {
+	unsigned int dev_id;
+	unsigned int addr_offset;
+	unsigned int num_sensor;
+	unsigned int sensor_map[ALL_SENSING_POINTS];	/* In sensor ID */
+	struct lvts_speed_settings *tc_speed;
+	/*
+	 * HW filter setting
+	 * 000: Get one sample
+	 * 001: Get 2 samples and average them
+	 * 010: Get 4 samples, drop max and min, then average the rest of 2 samples
+	 * 011: Get 6 samples, drop max and min, then average the rest of 4 samples
+	 * 100: Get 10 samples, drop max and min, then average the rest of 8 samples
+	 * 101: Get 18 samples, drop max and min, then average the rest of 16 samples
+	 */
+	unsigned int hw_filter;
+	/*
+	 * Dominator_sensing point is used to select a sensing point
+	 * and reference its temperature to trigger Thermal HW Reboot
+	 * When it is ALL_SENSING_POINTS, it will select all sensing points
+	 */
+	int dominator_sensing_point;
+	int hw_reboot_trip_point;		/* -274000: Disable HW reboot */
+	unsigned int irq_bit;
+};
+
+struct lvts_formula_coeff {
+	int a;
+	int b;
+	unsigned int golden_temp;
+};
+
+struct lvts_sensor_cal_data {
+	int use_fake_efuse;				/* 1: Use fake efuse, 0: Use real efuse */
+	unsigned int golden_temp;
+	unsigned int *count_r;
+	unsigned int *count_rc;
+	unsigned int *count_rc_now;
+	unsigned int default_golden_temp;
+	unsigned int default_count_r;
+	unsigned int default_count_rc;
+};
+
+struct platform_ops {
+	void (*efuse_to_cal_data)(struct lvts_data *lvts_data);
+	void (*device_enable_and_init)(struct lvts_data *lvts_data);
+	void (*device_enable_auto_rck)(struct lvts_data *lvts_data);
+	int (*device_read_count_rc_n)(struct lvts_data *lvts_data);
+	void (*set_cal_data)(struct lvts_data *lvts_data);
+	void (*init_controller)(struct lvts_data *lvts_data);
+};
+
+struct lvts_data {
+	struct device *dev;
+	struct clk *clk;
+	void __iomem *base;				/* LVTS base addresses */
+	unsigned int irq_num;			/* LVTS interrupt numbers */
+	struct reset_control *reset;
+	int num_tc;						/* Number of LVTS thermal controllers */
+	const struct lvts_tc_settings *tc;
+	int counting_window_us;			/* LVTS device counting window */
+	int num_sensor;					/* Number of sensors in this platform */
+	void __iomem **reg;
+	struct platform_ops ops;
+	int feature_bitmap;				/* Show what features are enabled */
+	unsigned int num_efuse_addr;
+	unsigned int *efuse;
+	unsigned int num_efuse_block;	/* Number of contiguous efuse indexes */
+	struct lvts_sensor_cal_data cal_data;
+	struct lvts_formula_coeff coeff;
+};
+
+struct soc_temp_tz {
+	unsigned int id;
+	struct lvts_data *lvts_data;
+};
+
+extern void lvts_device_enable_and_init(struct lvts_data *lvts_data);
+extern void lvts_device_enable_auto_rck_v4(struct lvts_data *lvts_data);
+extern int lvts_device_read_count_rc_n_v4(struct lvts_data *lvts_data);
+extern void lvts_set_calibration_data_v4(struct lvts_data *lvts_data);
+extern void lvts_init_controller_v4(struct lvts_data *lvts_data);
+
+extern int lvts_probe(struct platform_device *pdev);
+extern int lvts_remove(struct platform_device *pdev);
+extern int lvts_suspend(struct platform_device *pdev, pm_message_t state);
+extern int lvts_resume(struct platform_device *pdev);
+
+#endif /* __MTK_SOC_TEMP_LVTS_H__ */
diff --git a/drivers/thermal/mediatek/lvts_v4.c b/drivers/thermal/mediatek/lvts_v4.c
new file mode 100644
index 000000000000..6477d386c9e1
--- /dev/null
+++ b/drivers/thermal/mediatek/lvts_v4.c
@@ -0,0 +1,241 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022 MediaTek Inc.
+ */
+
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include "lvts_thermal.h"
+
+enum mt8192_lvts_mcu_sensor_enum {
+	MT8192_TS1_0,
+	MT8192_TS1_1,
+	MT8192_TS2_0,
+	MT8192_TS2_1,
+	MT8192_TS3_0,
+	MT8192_TS3_1,
+	MT8192_TS3_2,
+	MT8192_TS3_3,
+	MT8192_NUM_TS_MCU
+};
+
+enum mt8192_lvts_ap_sensor_enum {
+	MT8192_TS4_0,
+	MT8192_TS4_1,
+	MT8192_TS5_0,
+	MT8192_TS5_1,
+	MT8192_TS6_0,
+	MT8192_TS6_1,
+	MT8192_TS7_0,
+	MT8192_TS7_1,
+	MT8192_TS7_2,
+	MT8192_NUM_TS_AP
+};
+
+static void mt8192_mcu_efuse_to_cal_data(struct lvts_data *lvts_data)
+{
+	const unsigned int mt8192_ts[] = { MT8192_TS2_0, MT8192_TS3_0 };
+	struct lvts_sensor_cal_data *cal_data = &lvts_data->cal_data;
+	unsigned int i, j;
+
+	cal_data->golden_temp = GET_CAL_DATA_BITMASK(0, lvts_data, 31, 24);
+
+	for (i = 0; i < MT8192_NUM_TS_MCU; i++)
+		cal_data->count_r[i] = GET_CAL_DATA_BITMASK(i + 1, lvts_data, 23, 0);
+
+	cal_data->count_rc[MT8192_TS1_0] = GET_CAL_DATA_BITMASK(21, lvts_data, 23, 0);
+
+	for (i = 0; i < (ARRAY_SIZE(mt8192_ts)); i++) {
+		for (j = 1; j <= 18; j++) {
+			cal_data->count_rc[mt8192_ts[i]] = (GET_CAL_DATA_BITMASK(j, lvts_data, 31, 24) << 16) +
+				(GET_CAL_DATA_BITMASK(j, lvts_data, 31, 24) << 8) +
+					GET_CAL_DATA_BITMASK(j, lvts_data, 31, 24);
+		}
+	}
+}
+
+static void mt8192_ap_efuse_to_cal_data(struct lvts_data *lvts_data)
+{
+	const unsigned int mt8192_ts[] = { MT8192_TS4_0, MT8192_TS5_0, MT8192_TS6_0, MT8192_TS7_0 };
+	struct lvts_sensor_cal_data *cal_data = &lvts_data->cal_data;
+	unsigned int i, j;
+
+	cal_data->golden_temp = GET_CAL_DATA_BITMASK(0, lvts_data, 31, 24);
+
+	for (i = 0; i < MT8192_NUM_TS_AP; i++)
+		cal_data->count_r[i] = GET_CAL_DATA_BITMASK(i + 1, lvts_data, 23, 0);
+
+	for (i = 0; i < (ARRAY_SIZE(mt8192_ts)); i++) {
+		for (j = 1; j <= 18; j++) {
+			cal_data->count_rc[mt8192_ts[i]] = (GET_CAL_DATA_BITMASK(j, lvts_data, 31, 24) << 16) +
+				(GET_CAL_DATA_BITMASK(j, lvts_data, 31, 24) << 8) +
+					GET_CAL_DATA_BITMASK(j, lvts_data, 31, 24);
+		}
+	}
+}
+
+static struct lvts_speed_settings tc_speed_mt8192 = {
+	.period_unit = PERIOD_UNIT,
+	.group_interval_delay = GROUP_INTERVAL_DELAY,
+	.filter_interval_delay = FILTER_INTERVAL_DELAY,
+	.sensor_interval_delay = SENSOR_INTERVAL_DELAY,
+};
+
+static const struct lvts_tc_settings mt8192_tc_mcu_settings[] = {
+	[0] = {
+		.dev_id = 0x81,
+		.addr_offset = 0x0,
+		.num_sensor = 2,
+		.sensor_map = { MT8192_TS1_0, MT8192_TS1_1 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT1,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(3),
+	},
+	[1] = {
+		.dev_id = 0x82,
+		.addr_offset = 0x100,
+		.num_sensor = 2,
+		.sensor_map = { MT8192_TS2_0, MT8192_TS2_1 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT0,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(4),
+	},
+	[2] = {
+		.dev_id = 0x83,
+		.addr_offset = 0x200,
+		.num_sensor = 4,
+		.sensor_map = { MT8192_TS3_0, MT8192_TS3_1, MT8192_TS3_2, MT8192_TS3_3 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT0,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(5),
+	}
+};
+
+static const struct lvts_tc_settings mt8192_tc_ap_settings[] = {
+	[0] = {
+		.dev_id = 0x84,
+		.addr_offset = 0x0,
+		.num_sensor = 2,
+		.sensor_map = { MT8192_TS4_0, MT8192_TS4_1 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT0,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(3),
+	},
+	[1] = {
+		.dev_id = 0x85,
+		.addr_offset = 0x100,
+		.num_sensor = 2,
+		.sensor_map = { MT8192_TS5_0, MT8192_TS5_1 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT1,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(4),
+	},
+	[2] = {
+		.dev_id = 0x86,
+		.addr_offset = 0x200,
+		.num_sensor = 2,
+		.sensor_map = { MT8192_TS6_0, MT8192_TS6_1 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT1,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(5),
+	},
+	[3] = {
+		.dev_id = 0x87,
+		.addr_offset = 0x300,
+		.num_sensor = 3,
+		.sensor_map = { MT8192_TS7_0, MT8192_TS7_1, MT8192_TS7_2 },
+		.tc_speed = &tc_speed_mt8192,
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT2,
+		.hw_reboot_trip_point = HW_REBOOT_TRIP_POINT,
+		.irq_bit = BIT(6),
+	}
+};
+
+static const struct lvts_data mt8192_lvts_mcu_data = {
+	.num_tc = (ARRAY_SIZE(mt8192_tc_mcu_settings)),
+	.tc = mt8192_tc_mcu_settings,
+	.num_sensor = MT8192_NUM_TS_MCU,
+	.ops = {
+		.efuse_to_cal_data = mt8192_mcu_efuse_to_cal_data,
+		.device_enable_and_init = lvts_device_enable_and_init,
+		.device_enable_auto_rck = lvts_device_enable_auto_rck_v4,
+		.device_read_count_rc_n = lvts_device_read_count_rc_n_v4,
+		.set_cal_data = lvts_set_calibration_data_v4,
+		.init_controller = lvts_init_controller_v4,
+	},
+	.feature_bitmap = FEATURE_DEVICE_AUTO_RCK,
+	.num_efuse_addr = NUM_EFUSE_ADDR,
+	.num_efuse_block = NUM_EFUSE_BLOCK_MT8192,
+	.cal_data = {
+		.default_golden_temp = DEFAULT_GOLDEN_TEMP,
+		.default_count_r = DEFAULT_CUONT_R,
+		.default_count_rc = DEFAULT_CUONT_RC,
+	},
+	.coeff = {
+		.a = COEFF_A,
+		.b = COEFF_B,
+	},
+};
+
+static const struct lvts_data mt8192_lvts_ap_data = {
+	.num_tc = (ARRAY_SIZE(mt8192_tc_ap_settings)),
+	.tc = mt8192_tc_ap_settings,
+	.num_sensor = MT8192_NUM_TS_AP,
+	.ops = {
+		.efuse_to_cal_data = mt8192_ap_efuse_to_cal_data,
+		.device_enable_and_init = lvts_device_enable_and_init,
+		.device_enable_auto_rck = lvts_device_enable_auto_rck_v4,
+		.device_read_count_rc_n = lvts_device_read_count_rc_n_v4,
+		.set_cal_data = lvts_set_calibration_data_v4,
+		.init_controller = lvts_init_controller_v4,
+	},
+	.feature_bitmap = FEATURE_DEVICE_AUTO_RCK,
+	.num_efuse_addr = NUM_EFUSE_ADDR,
+	.num_efuse_block = NUM_EFUSE_BLOCK_MT8192,
+	.cal_data = {
+		.default_golden_temp = DEFAULT_GOLDEN_TEMP,
+		.default_count_r = DEFAULT_CUONT_R,
+		.default_count_rc = DEFAULT_CUONT_RC,
+	},
+	.coeff = {
+		.a = COEFF_A,
+		.b = COEFF_B,
+	},
+};
+
+static const struct of_device_id lvts_of_match[] = {
+	{ .compatible = "mediatek,mt8192-lvts-mcu", .data = &mt8192_lvts_mcu_data, },
+	{ .compatible = "mediatek,mt8192-lvts-ap", .data = &mt8192_lvts_ap_data, },
+	{},
+};
+MODULE_DEVICE_TABLE(of, lvts_of_match);
+
+static struct platform_driver soc_temp_lvts = {
+	.probe = lvts_probe,
+	.remove = lvts_remove,
+	.suspend = lvts_suspend,
+	.resume = lvts_resume,
+	.driver = {
+		.name = "mtk-lvts-v4",
+		.of_match_table = lvts_of_match,
+	},
+};
+module_platform_driver(soc_temp_lvts);
+
+MODULE_AUTHOR("Yu-Chia Chang <ethan.chang@mediatek.com>");
+MODULE_AUTHOR("Michael Kao <michael.kao@mediatek.com>");
+MODULE_DESCRIPTION("MediaTek LVTS V4 Thermal Driver");
+MODULE_LICENSE("GPL");