[Feature] add GA346 baseline version

Change-Id: Ic62933698569507dcf98240cdf5d9931ae34348f
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/Kconfig b/src/kernel/linux/v4.19/drivers/thermal/mediatek/Kconfig
new file mode 100644
index 0000000..9bb0d71
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/Kconfig
@@ -0,0 +1,128 @@
+config MTK_THERMAL
+	tristate "Mediatek thermal drivers"
+	depends on THERMAL_OF
+	help
+	  This is the option for Mediatek thermal software
+	  solutions. Please enable corresponding options to
+	  get temperature information from thermal sensors or
+	  turn on throttle mechaisms for thermal mitigation.
+
+if MTK_THERMAL
+
+config MTK_SOC_THERMAL
+	tristate "Temperature sensor driver for Mediatek SoCs"
+	depends on HAS_IOMEM
+	depends on NVMEM || NVMEM=n
+	depends on RESET_CONTROLLER
+	help
+	  Enable this option if you want to get SoC temperature
+	  information for Mediatek platforms. This driver
+	  configures thermal controllers to collect temperature
+	  via AUXADC interface.
+
+config MTK_SOC_THERMAL_LVTS
+	tristate "LVTS (Low voltage thermal sensor) driver for Mediatek SoCs"
+	depends on HAS_IOMEM
+	depends on NVMEM
+	depends on RESET_TI_SYSCON
+	help
+	  Enable this option if you want to get SoC temperature
+	  information for Mediatek platforms. This driver
+	  configures LVTS thermal controllers to collect temperatures
+	  via ASIF interface.
+
+config MTK_BOARD_THERMAL
+	tristate "On-board NTC thermistor driver for Mediatek SoCs"
+	help
+	  Enable this option if you want to get PCB temperature
+	  via on-board NTC for Mediatek platforms.
+	  This driver reads the voltage value from TIA hardware
+	  which is responsible for reading all PMIC AUXADC channels
+	  sequentially. The address of TIA register for each channel
+	  and temperature convert table should be specified in
+	  device tree.
+
+config MTK_PMIC_THERMAL
+	tristate "PMIC thermal sensor driver for Mediatek SoCs"
+	help
+	  Enable this option if you want to get PMIC temperature
+	  for Mediatek platforms.
+	  This driver reads the voltage value from PMIC AUXADC channels,
+	  and converts raw value to PMIC temperature via PMIC efuse data.
+
+config MTK_MD_THERMAL
+	tristate "Mediatek Modem cooling framework"
+	depends on MTK_ECCCI_DRIVER
+	help
+	  Enable this option to turn on modem throttle mechanisms
+	  for Mediatek platforms.
+	  This option depends on MTK_ECCCI_DRIVER which is the
+	  IPC interface between AP SoC and modem.
+
+if MTK_MD_THERMAL
+
+config MTK_MD_THERMAL_MUTT
+	tristate "Mediatek modem uplink throughput throttle driver"
+	help
+	  Enable this option if you want to use modem
+	  uplink throughput throttle cooler for Mediatek
+	  platforms.
+	  The throttling levels are defined in modem.
+
+config MTK_MD_THERMAL_TX_POWER
+	tristate "Mediatek modem TX power throttle driver"
+	help
+	  Enable this option if you want use modem TX power
+	  throttle cooler for Mediatek platforms.
+	  This feature is only supported for mt6297 or later
+	  modem platforms.
+
+config MTK_MD_THERMAL_SCG_OFF
+	tristate "Mediatek modem SCG cell off driver"
+	help
+	  Enable this option if you want use modem SCG cell
+	  throttle cooler for Mediatek platforms.
+	  This feature is only supported for mt6297 or later
+	  modem platforms.
+
+config MTK_MD_THERMAL_SYSFS
+	tristate "Mediatek modem throttle sysfs driver"
+	default n
+	help
+	  Enable this option if you want to use sysfs debug
+	  interface for Mediatek modem coolers.
+	  This option should be enabled only for HW power
+	  evaluation purpose.
+endif
+
+config MTK_MD_RF_THERMAL
+	tristate "Mediatek modem RF thermal zone driver"
+	depends on MTK_ECCCI_DRIVER
+	help
+	  Enable this option if you want to get modem RF
+	  temperature information for Mediatek platforms.
+	  This driver queries temperature via CCCI interface
+	  and handles the feedback from modem in the callback
+	  function which is registered to CCCI driver at
+	  driver initialization stage.
+
+config MTK_THERMAL_IPI
+	tristate "Mediatek thermal IPI interface driver"
+	depends on MTK_TINYSYS_SSPM_SUPPORT
+	help
+	  Enable this option if you want to use and communicate
+	  with tinysys microprocessors for Mediatek platforms.
+	  This driver defines thermal ipi commands and provides an
+	  API to send a ipi command to tinysys microprocessors.
+
+if MTK_THERMAL_IPI
+config MTK_THERMAL_TRM
+	tristate "Mediatek thermal risk monitor (TRM) driver"
+	help
+	  Enable this option if you want to offload Linux thermal
+	  framework (LTF) onto a tinysys microprocesssor when AP MCU
+	  suspended. TRM will take it over and keep monitoring
+	  temperatures. If a device is going to overheat, TRM will
+	  wake AP MCU up and give the control back to LTF.
+endif
+endif
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/Makefile b/src/kernel/linux/v4.19/drivers/thermal/mediatek/Makefile
new file mode 100644
index 0000000..9c724ce
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/Makefile
@@ -0,0 +1,24 @@
+ccflags-y += \
+	-I$(srctree)/drivers/thermal/mediatek \
+	-I$(srctree)/drivers/misc/mediatek/include/mt-plat \
+	-I$(srctree)/include/dt-bindings \
+	-I$(srctree)/drivers/misc/mediatek/sspm/v2/ \
+
+obj-y += mtk_thermal_trace.o
+
+obj-$(CONFIG_MTK_SOC_THERMAL)	+= mtk_thermal.o
+obj-$(CONFIG_MTK_SOC_THERMAL_LVTS)	+= soc_temp_lvts.o
+obj-$(CONFIG_MTK_SOC_THERMAL_LVTS)	+= lvts_v5.o
+
+obj-$(CONFIG_MTK_BOARD_THERMAL) += board_temp.o
+obj-$(CONFIG_MTK_PMIC_THERMAL) += pmic_temp.o
+
+obj-$(CONFIG_MTK_MD_THERMAL)		+= md_cooling.o
+obj-$(CONFIG_MTK_MD_THERMAL_MUTT)	+= md_cooling_mutt.o
+obj-$(CONFIG_MTK_MD_THERMAL_TX_POWER)	+= md_cooling_tx_pwr.o
+obj-$(CONFIG_MTK_MD_THERMAL_SCG_OFF)	+= md_cooling_scg_off.o
+obj-$(CONFIG_MTK_MD_THERMAL_SYSFS)	+= md_cooling_sysfs.o
+obj-$(CONFIG_MTK_MD_RF_THERMAL)		+= md_rf_temp.o
+
+obj-$(CONFIG_MTK_THERMAL_IPI)		+= mtk_thermal_ipi.o
+obj-$(CONFIG_MTK_THERMAL_TRM)		+= thermal_risk_monitor.o
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/board_temp.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/board_temp.c
new file mode 100644
index 0000000..1c160da
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/board_temp.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/mt6330/core.h>
+#include <linux/mfd/mt6330/registers.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#if IS_ENABLED(CONFIG_MTK_THERMAL_IPI)
+#include "mtk_thermal_ipi.h"
+#endif
+
+#define get_tia_rc_sel(val, offset, mask) (((val) & (mask)) >> (offset))
+#define is_adc_data_valid(val, bit)       (((val) & BIT(bit)) != 0)
+#define get_adc_data(val, bit)            ((val) & GENMASK(bit, 0))
+#define adc_data_to_v_in(val)             (1900 - (((val) * 1000) >> 14))
+
+/**
+ * struct tia_data - parameters to parse the data from TIA
+ * @valid_bit: valid bit in TIA DATA register
+ * @rc_offset: RC bit offset in TIA DATA register
+ * @rc_mask: bitmask for RC bit
+ * @rc_sel_to_value: function to get default pullup resistance value
+ */
+struct tia_data {
+	unsigned int valid_bit;
+	unsigned int rc_offset;
+	unsigned int rc_mask;
+	unsigned int (*rc_sel_to_value)(unsigned int sel);
+};
+
+/**
+ * struct pmic_auxadc_data - parameters and callback functions for NTC
+ *                           resistance value calculation
+ * @is_initialized: indicate the auxadc data was been initialized or not
+ * @default_pullup_v: voltage of internal pullup resistance
+ * @pullup_v: pullup voltage of each pullup resistance type. It should
+ *            equal to default_pullup_v if no extra input buffer for SDM.
+ * @pullup_r: pullup resistance value
+ * @num_of_pullup_r_type: number of pullup resistance type
+ * @pullup_r_calibration: calculate the parameters for actual NTC resitance
+ *                        value calculation. Set to NULL if no extra input
+ *                        buffer for SDM.
+ * @tia_param: parameters to parse auxadc value from TIA DATA register
+ */
+struct pmic_auxadc_data {
+	bool is_initialized;
+	unsigned int default_pullup_v;
+	unsigned int *pullup_v;
+	unsigned int *pullup_r;
+	unsigned int num_of_pullup_r_type;
+	int (*pullup_r_calibration)(struct device *dev,
+				struct pmic_auxadc_data *adc_data);
+	struct tia_data *tia_param;
+};
+
+struct board_ntc_info {
+	struct device *dev;
+	int *lookup_table;
+	int lookup_table_num;
+	void __iomem *data_reg;
+	struct pmic_auxadc_data *adc_data;
+};
+
+unsigned int tia2_rc_sel_to_value(unsigned int sel)
+{
+	unsigned int resistance;
+
+	switch (sel) {
+	case 1:
+		resistance = 30000; /* 30K */
+		break;
+	case 2:
+		resistance = 400000; /* 400K */
+		break;
+	case 0:
+	default:
+		resistance = 100000; /* 100K */
+		break;
+	}
+
+	return resistance;
+}
+
+int mt6330_pullup_r_calibration(struct device *dev,
+					struct pmic_auxadc_data* adc_data)
+{
+	struct platform_device *auxadc_pdev;
+	struct device_node *auxadc_node;
+	struct mt6330_chip *chip;
+	struct regmap *regmap;
+	unsigned int efuse_v = 0, efuse_r_l = 0, efuse_r_h = 0;
+	unsigned int i, v_aux18, r_in_buf, r_in;
+	int ret = 0;
+#if IS_ENABLED(CONFIG_MTK_THERMAL_IPI)
+	struct thermal_ipi_data thermal_data;
+#endif
+
+	auxadc_node = of_parse_phandle(dev->of_node, "pmic_auxadc", 0);
+	if (!auxadc_node) {
+		dev_err(dev, "fail to find pmic_auxadc of node\n");
+		return -ENODEV;
+	}
+	auxadc_pdev = of_find_device_by_node(auxadc_node);
+	if (!auxadc_pdev) {
+		dev_err(dev, "fail to find pmic_auxadc device\n");
+		return -ENODEV;
+	}
+	chip = dev_get_drvdata(auxadc_pdev->dev.parent);
+	if (!chip) {
+		dev_err(dev, "fail to find pmic drv data\n");
+		return -ENODEV;
+	}
+	regmap = chip->regmap;
+	if (IS_ERR_VALUE(regmap)) {
+		dev_err(dev, "get pmic regmap fail\n");
+		return -ENODEV;
+	}
+
+	ret = regmap_read(regmap, PMIC_AUXADC_EFUSE_VAUX18_ADDR, &efuse_v);
+	if (ret) {
+		dev_err(dev, "fail to read efuse_vaux18\n");
+		return ret;
+	}
+	v_aux18 = ((efuse_v & PMIC_AUXADC_EFUSE_VAUX18_MASK) / 2 + 1840);
+
+	ret = regmap_read(regmap, PMIC_EFUSE_GAIN_CH12_TRIM_L_ADDR,
+		&efuse_r_l);
+	if (ret) {
+		dev_err(dev, "fail to read efuse_rin_buf_l\n");
+		return ret;
+	}
+	ret = regmap_read(regmap, PMIC_EFUSE_OFFSET_CH12_TRIM_L_ADDR,
+		&efuse_r_h);
+	if (ret) {
+		dev_err(dev, "fail to read efuse_rin_buf_h\n");
+		return ret;
+	}
+	r_in_buf = ((efuse_r_h & PMIC_EFUSE_OFFSET_CH12_TRIM_L_MASK) << 8
+		| (efuse_r_l & PMIC_EFUSE_GAIN_CH12_TRIM_L_MASK)) * 10
+		+ 1000000;
+
+	dev_info(dev, "v_aux18 = %d, r_in_buf = %d\n", v_aux18, r_in_buf);
+
+#if IS_ENABLED(CONFIG_MTK_THERMAL_IPI)
+	thermal_data.u.data.arg[0] = efuse_v;
+	thermal_data.u.data.arg[1] = (efuse_r_h & PMIC_EFUSE_OFFSET_CH12_TRIM_L_MASK) << 8
+		| (efuse_r_l & PMIC_EFUSE_GAIN_CH12_TRIM_L_MASK);
+
+	while (thermal_to_sspm(TIA_IPI_INIT_GRP1, &thermal_data) != IPI_SUCCESS)
+		udelay(100);
+#endif
+
+	for (i = 0; i < adc_data->num_of_pullup_r_type; i++) {
+		r_in = adc_data->tia_param->rc_sel_to_value(i);
+
+		/* Calculate R_parallel and Vin of each type */
+		adc_data->pullup_r[i] = (unsigned int)
+			(((unsigned long)r_in * (unsigned long)r_in_buf) /
+			(unsigned long)(r_in + r_in_buf));
+		adc_data->pullup_v[i] =
+			v_aux18 * (r_in / 2 + r_in_buf) / (r_in + r_in_buf);
+
+		dev_info(dev, "%d: r_in = %d, pullup_r = %d, pullup_v = %d\n",
+			i, r_in, adc_data->pullup_r[i], adc_data->pullup_v[i]);
+	}
+
+	return ret;
+}
+
+static struct tia_data tia2_data = {
+	.valid_bit = 15,
+	.rc_offset = 16,
+	.rc_mask = GENMASK(17, 16),
+	.rc_sel_to_value = tia2_rc_sel_to_value,
+};
+
+static const struct pmic_auxadc_data mt6330_pmic_auxadc_data = {
+	.default_pullup_v = 1800,
+	.num_of_pullup_r_type = 3,
+	.pullup_r_calibration = mt6330_pullup_r_calibration,
+	.tia_param = &tia2_data,
+};
+
+static const struct of_device_id board_ntc_of_match[] = {
+	{
+		.compatible = "mediatek,mt6880-board-ntc",
+		.data = (void *)&mt6330_pmic_auxadc_data,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, board_ntc_of_match);
+
+static int board_ntc_r_to_temp(struct board_ntc_info *ntc_info,
+						int val)
+{
+	int temp, temp_hi, temp_lo, r_hi, r_lo;
+	int i;
+
+	for (i = 0; i < ntc_info->lookup_table_num; i++) {
+		if (val >= ntc_info->lookup_table[2 * i + 1])
+			break;
+	}
+
+	if (i == 0) {
+		temp = ntc_info->lookup_table[0];
+	} else if (i >= ntc_info->lookup_table_num) {
+		temp = ntc_info->lookup_table[2 *
+			(ntc_info->lookup_table_num - 1)];
+	} else {
+		r_hi = ntc_info->lookup_table[2 * i - 1];
+		r_lo = ntc_info->lookup_table[2 * i + 1];
+
+		temp_hi = ntc_info->lookup_table[2 * i - 2];
+		temp_lo = ntc_info->lookup_table[2 * i];
+
+		temp = temp_hi + mult_frac(temp_lo - temp_hi, val - r_hi,
+					   r_lo - r_hi);
+	}
+
+	return temp;
+}
+
+static unsigned int calculate_r_ntc(unsigned int v_in,
+				unsigned int pullup_r, unsigned int pullup_v)
+{
+	unsigned int r_ntc;
+
+	if (v_in >= pullup_v)
+		return 0;
+
+	r_ntc = (unsigned int)((unsigned long)v_in * (unsigned long)pullup_r)
+		/ (pullup_v - v_in);
+
+	return r_ntc;
+}
+
+static int board_ntc_get_temp(void *data, int *temp)
+{
+	struct board_ntc_info *ntc_info = (struct board_ntc_info *)data;
+	struct pmic_auxadc_data *adc_data = ntc_info->adc_data;
+	struct tia_data *tia_param = ntc_info->adc_data->tia_param;
+	unsigned int val, v_in, r_type, r_ntc;
+
+	val = readl(ntc_info->data_reg);
+	r_type = get_tia_rc_sel(val, tia_param->rc_offset, tia_param->rc_mask);
+	if (r_type >= adc_data->num_of_pullup_r_type) {
+		dev_err(ntc_info->dev, "Invalud r_type = %d\n", r_type);
+		return -EINVAL;
+	}
+
+	if (!is_adc_data_valid(val, tia_param->valid_bit)) {
+		dev_err(ntc_info->dev, "TIA data is invalid now\n");
+		return -EAGAIN;
+	}
+
+	v_in = adc_data_to_v_in(get_adc_data(val, tia_param->valid_bit - 1));
+	r_ntc = calculate_r_ntc(v_in, adc_data->pullup_r[r_type],
+				adc_data->pullup_v[r_type]);
+	*temp = board_ntc_r_to_temp(ntc_info, r_ntc);
+	if (!r_ntc) {
+		dev_err(ntc_info->dev,
+			"r_ntc is 0! v_in/pullup_r/pullup_v=%d/%d/%d\n",
+			v_in, adc_data->pullup_r[r_type],
+			adc_data->pullup_v[r_type]);
+		*temp = THERMAL_TEMP_INVALID;
+	} else {
+		*temp = board_ntc_r_to_temp(ntc_info, r_ntc);
+	}
+
+	dev_dbg(ntc_info->dev, "val=0x%x, v_in/r_type/r_ntc/t=%d/%d/%d/%d\n",
+		val, v_in, r_type, r_ntc, *temp);
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops board_ntc_ops = {
+	.get_temp = board_ntc_get_temp,
+};
+
+static int board_ntc_init_auxadc_data(struct device *dev,
+				struct pmic_auxadc_data *adc_data)
+{
+	int ret = 0, size, i;
+	int num = adc_data->num_of_pullup_r_type;
+
+	if (num <= 0)
+		return -EINVAL;
+
+	size = sizeof(*adc_data->pullup_v) * num * 2;
+	adc_data->pullup_v = devm_kzalloc(dev, size, GFP_KERNEL);
+	if (!adc_data->pullup_v)
+		return -ENOMEM;
+
+	adc_data->pullup_r = (unsigned int *)(adc_data->pullup_v + num);
+	if (adc_data->pullup_r_calibration) {
+		ret = adc_data->pullup_r_calibration(dev, adc_data);
+	} else {
+		for (i = 0; i < num; i++) {
+			adc_data->pullup_r[i] =
+				adc_data->tia_param->rc_sel_to_value(i);
+			adc_data->pullup_v[i] = adc_data->default_pullup_v;
+
+			dev_info(dev, "%d: default pullup_r=%d, pullup_v=%d\n",
+				i, adc_data->pullup_r[i],
+				adc_data->pullup_v[i]);
+		}
+	}
+
+	return ret;
+}
+
+static int board_ntc_parse_lookup_table(struct device *dev,
+					struct board_ntc_info *ntc_info)
+{
+	struct device_node *np = dev->of_node;
+	int num, ret;
+
+	num = of_property_count_elems_of_size(np, "temperature-lookup-table",
+						sizeof(unsigned int));
+	if (num < 0) {
+		dev_err(dev, "lookup table is not found\n");
+		return num;
+	}
+
+	if (num % 2) {
+		dev_err(dev, "temp vs ADC value in table are unpaired\n");
+		return -EINVAL;
+	}
+
+	ntc_info->lookup_table = devm_kcalloc(dev, num,
+					 sizeof(*ntc_info->lookup_table),
+					 GFP_KERNEL);
+	if (!ntc_info->lookup_table)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_array(np, "temperature-lookup-table",
+					(unsigned int *)ntc_info->lookup_table,
+					num);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read temperature lookup table: %d\n",
+			ret);
+		return ret;
+	}
+
+	ntc_info->lookup_table_num = num / 2;
+
+	return 0;
+}
+
+static int board_ntc_probe(struct platform_device *pdev)
+{
+	struct board_ntc_info *ntc_info;
+	struct resource *res;
+	void __iomem *tia_reg;
+	struct thermal_zone_device *tz_dev;
+	int ret;
+
+	if (!pdev->dev.of_node) {
+		dev_err(&pdev->dev, "Only DT based supported\n");
+		return -ENODEV;
+	}
+
+	ntc_info = devm_kzalloc(&pdev->dev, sizeof(*ntc_info), GFP_KERNEL);
+	if (!ntc_info)
+		return -ENOMEM;
+
+	ret = board_ntc_parse_lookup_table(&pdev->dev, ntc_info);
+	if (ret < 0)
+		return ret;
+
+	ntc_info->dev = &pdev->dev;
+	ntc_info->adc_data = (struct pmic_auxadc_data *)
+		of_device_get_match_data(&pdev->dev);
+	if (!ntc_info->adc_data->is_initialized) {
+		ret = board_ntc_init_auxadc_data(&pdev->dev,
+						ntc_info->adc_data);
+		if (ret)
+			return ret;
+
+		ntc_info->adc_data->is_initialized = true;
+	}
+
+	platform_set_drvdata(pdev, ntc_info);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tia_reg = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(tia_reg))
+		return PTR_ERR(tia_reg);
+
+	ntc_info->data_reg = tia_reg;
+
+	tz_dev = devm_thermal_zone_of_sensor_register(
+			&pdev->dev, 0, ntc_info, &board_ntc_ops);
+	if (IS_ERR(tz_dev)) {
+		ret = PTR_ERR(tz_dev);
+		dev_err(&pdev->dev, "Thermal zone sensor register fail:%d\n",
+			ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct platform_driver board_ntc_driver = {
+	.probe = board_ntc_probe,
+	.driver = {
+		.name = "mtk-board-ntc",
+		.of_match_table = board_ntc_of_match,
+	},
+};
+
+module_platform_driver(board_ntc_driver);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek on board NTC driver via TIA HW");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/lvts_v5.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/lvts_v5.c
new file mode 100644
index 0000000..24742cf
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/lvts_v5.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/bits.h>
+#include "soc_temp_lvts.h"
+/*==================================================
+ * LVTS v5 common definition or macro function
+ *==================================================
+ */
+#define STOP_COUNTING (DEVICE_WRITE | RG_TSFM_CTRL_0 << 8 | 0x00)
+#define SET_RG_TSFM_LPDLY (DEVICE_WRITE | RG_TSFM_CTRL_4 << 8 | 0xA6)
+#define SET_COUNTING_WINDOW_20US1 (DEVICE_WRITE | RG_TSFM_CTRL_2 << 8 | 0x00)
+#define SET_COUNTING_WINDOW_20US2 (DEVICE_WRITE | RG_TSFM_CTRL_1 << 8 | 0x20)
+#define TSV2F_CHOP_CKSEL_AND_TSV2F_EN (DEVICE_WRITE | RG_TSV2F_CTRL_2 << 8	\
+						| 0x8C)
+#define TSBG_DEM_CKSEL_X_TSBG_CHOP_EN (DEVICE_WRITE | RG_TSV2F_CTRL_4 << 8	\
+						| 0xFC)
+#define SET_TS_RSV (DEVICE_WRITE | RG_TSV2F_CTRL_1 << 8 | 0x8D)
+#define SET_TS_CHOP_CONTROL (DEVICE_WRITE | RG_TSV2F_CTRL_0 << 8 | 0xF1)
+#define SET_LVTS_AUTO_RCK (DEVICE_WRITE | RG_TSV2F_CTRL_6 << 8 | 0x01)
+#define SELECT_SENSOR_RCK(id) (DEVICE_WRITE | RG_TSV2F_CTRL_5 << 8 | id)
+#define SET_DEVICE_SINGLE_MODE (DEVICE_WRITE | RG_TSFM_CTRL_3 << 8 | 0x78)
+#define SET_TS_EN_AND_DIV_EN (DEVICE_WRITE | RG_TSV2F_CTRL_0 << 8 | 0xF5)
+#define TOGGLE_VOC_RST1 (DEVICE_WRITE | RG_TSV2F_CTRL_0 << 8 | 0xFD)
+#define TOGGLE_VOC_RST2 (DEVICE_WRITE | RG_TSV2F_CTRL_0 << 8 | 0xF5)
+#define KICK_OFF_RCK_COUNTING (DEVICE_WRITE | RG_TSFM_CTRL_0 << 8 | 0x02)
+#define DISABLE_TS_EN (DEVICE_WRITE | RG_TSV2F_CTRL_0 << 8 | 0xF1)
+#define SET_SENSOR_NO_RCK (DEVICE_WRITE | RG_TSV2F_CTRL_5 << 8 | 0x10)
+#define SET_DEVICE_LOW_POWER_SINGLE_MODE (DEVICE_WRITE | RG_TSFM_CTRL_3 << 8	\
+						| 0xB8)
+/*==================================================
+ * LVTS MT6880 and MT6890
+ *==================================================
+ */
+#define MT6880_NUM_LVTS (ARRAY_SIZE(mt6880_tc_settings))
+
+enum mt6880_lvts_domain {
+	MT6880_AP_DOMAIN,
+	MT6880_MCU_DOMAIN,
+	MT6880_NUM_DOMAIN
+};
+
+enum mt6880_lvts_sensor_enum {
+	MT6880_TS1_0,
+	MT6880_TS1_1,
+	MT6880_TS1_2,
+	MT6880_TS1_3,
+	MT6880_TS2_0,
+	MT6880_TS2_1,
+	MT6880_TS2_2,
+	MT6880_TS2_3,
+	MT6880_TS3_0,
+	MT6880_TS3_1,
+	MT6880_TS3_2,
+	MT6880_NUM_TS
+};
+
+static void mt6880_efuse_to_cal_data(struct lvts_data *lvts_data)
+{
+	struct sensor_cal_data *cal_data = &lvts_data->cal_data;
+
+	cal_data->golden_temp = GET_CAL_DATA_BITMASK(2, 31, 24);
+	cal_data->count_r[MT6880_TS1_0] = GET_CAL_DATA_BITMASK(0, 15, 0);
+	cal_data->count_r[MT6880_TS1_1] = GET_CAL_DATA_BITMASK(0, 31, 16);
+	cal_data->count_r[MT6880_TS1_2] = GET_CAL_DATA_BITMASK(1, 15, 0);
+	cal_data->count_r[MT6880_TS1_3] = GET_CAL_DATA_BITMASK(1, 31, 16);
+	cal_data->count_rc[MT6880_TS1_0] = GET_CAL_DATA_BITMASK(2, 23, 0);
+
+	cal_data->count_r[MT6880_TS2_0] = GET_CAL_DATA_BITMASK(3, 15, 0);
+	cal_data->count_r[MT6880_TS2_1] = GET_CAL_DATA_BITMASK(3, 31, 16);
+	cal_data->count_r[MT6880_TS2_2] = GET_CAL_DATA_BITMASK(4, 15, 0);
+	cal_data->count_r[MT6880_TS2_3] = GET_CAL_DATA_BITMASK(4, 31, 16);
+	cal_data->count_rc[MT6880_TS2_0] = GET_CAL_DATA_BITMASK(5, 23, 0);
+
+	cal_data->count_r[MT6880_TS3_0] = GET_CAL_DATA_BITMASK(6, 15, 0);
+	cal_data->count_r[MT6880_TS3_1] = GET_CAL_DATA_BITMASK(6, 31, 16);
+	cal_data->count_r[MT6880_TS3_2] = GET_CAL_DATA_BITMASK(7, 15, 0);
+	cal_data->count_rc[MT6880_TS3_0] = GET_CAL_DATA_BITMASK(8, 23, 0);
+}
+
+static struct tc_settings mt6880_tc_settings[] = {
+	[0] = {
+		.domain_index = MT6880_MCU_DOMAIN,
+		.addr_offset = 0x0,
+		.num_sensor = 4,
+		.sensor_map = {MT6880_TS1_0, MT6880_TS1_1, MT6880_TS1_2,
+				MT6880_TS1_3},
+		.tc_speed = SET_TC_SPEED_IN_US(118, 9333, 118, 118),
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT2,
+		.hw_reboot_trip_point = 117000,
+		.irq_bit = BIT(1),
+	},
+	[1] = {
+		.domain_index = MT6880_AP_DOMAIN,
+		.addr_offset = 0x0,
+		.num_sensor = 4,
+		.sensor_map = {MT6880_TS2_0, MT6880_TS2_1, MT6880_TS2_2,
+				MT6880_TS2_3},
+		.tc_speed = SET_TC_SPEED_IN_US(118, 9333, 118, 118),
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT0,
+		.hw_reboot_trip_point = 117000,
+		.irq_bit = BIT(1),
+	},
+	[2] = {
+		.domain_index = MT6880_AP_DOMAIN,
+		.addr_offset = 0x100,
+		.num_sensor = 3,
+		.sensor_map = {MT6880_TS3_0, MT6880_TS3_1, MT6880_TS3_2},
+		.tc_speed = SET_TC_SPEED_IN_US(118, 9333, 118, 118),
+		.hw_filter = LVTS_FILTER_2_OF_4,
+		.dominator_sensing_point = SENSING_POINT1,
+		.hw_reboot_trip_point = 117000,
+		.irq_bit = BIT(2),
+	}
+};
+
+static struct lvts_data mt6880_lvts_data = {
+	.num_domain = MT6880_NUM_DOMAIN,
+	.num_tc = MT6880_NUM_LVTS,
+	.tc = mt6880_tc_settings,
+	.num_sensor = MT6880_NUM_TS,
+	.ops = {
+		.efuse_to_cal_data = mt6880_efuse_to_cal_data,
+	},
+	.feature_bitmap = FEATURE_DEVICE_AUTO_RCK | FEATURE_CK26M_ACTIVE,
+	.num_efuse_addr = 9,
+	.num_efuse_block = 1,
+	.cal_data = {
+		.default_golden_temp = 50,
+		.default_count_r = 35000,
+		.default_count_rc = 2750,
+	},
+	.coeff = {
+		.a = -250460,
+		.b = 250460,
+	},
+};
+/*==================================================
+ * Platform data
+ *==================================================
+ */
+static struct match_entry lvts_v5_match_table[] = {
+	{
+		.chip = "mt6880",
+		.lvts_data = &mt6880_lvts_data,
+	},
+	{
+	},
+};
+/*==================================================
+ * LVTS v5 common code
+ *==================================================
+ */
+static void device_enable_and_init_v5(struct lvts_data *lvts_data)
+{
+	unsigned int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		lvts_write_device(lvts_data, STOP_COUNTING,  i);
+		lvts_write_device(lvts_data, SET_RG_TSFM_LPDLY,  i);
+		lvts_write_device(lvts_data, SET_COUNTING_WINDOW_20US1,  i);
+		lvts_write_device(lvts_data, SET_COUNTING_WINDOW_20US2,  i);
+		lvts_write_device(lvts_data, TSV2F_CHOP_CKSEL_AND_TSV2F_EN,  i);
+		lvts_write_device(lvts_data, TSBG_DEM_CKSEL_X_TSBG_CHOP_EN,  i);
+		lvts_write_device(lvts_data, SET_TS_RSV,  i);
+		lvts_write_device(lvts_data, SET_TS_CHOP_CONTROL,  i);
+	}
+
+	lvts_data->counting_window_us = 20;
+}
+
+static void device_enable_auto_rck_v5(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,  i);
+}
+
+static int device_read_count_rc_n_v5(struct lvts_data *lvts_data)
+{
+
+	/* Resistor-Capacitor Calibration */
+	/* count_RC_N: count RC now */
+	struct device *dev = lvts_data->dev;
+	struct tc_settings *tc = lvts_data->tc;
+	struct sensor_cal_data *cal_data = &lvts_data->cal_data;
+	unsigned int offset, size, s_index, data;
+	void __iomem *base;
+	int i, j;
+	char buffer[512];
+
+	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(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(j),  i);
+			lvts_write_device(lvts_data, SET_DEVICE_SINGLE_MODE,  i);
+			lvts_write_device(lvts_data, SET_TS_EN_AND_DIV_EN,  i);
+			lvts_write_device(lvts_data, TOGGLE_VOC_RST1,  i);
+			lvts_write_device(lvts_data, TOGGLE_VOC_RST2,  i);
+			udelay(10);
+
+			lvts_write_device(lvts_data, KICK_OFF_RCK_COUNTING,  i);
+			udelay(30);
+
+			device_wait_counting_finished(lvts_data, i);
+
+			lvts_write_device(lvts_data, DISABLE_TS_EN,  i);
+			data = lvts_read_device(lvts_data, 0x00, i);
+
+			cal_data->count_rc_now[s_index] = (data & GENMASK(23,0));
+		}
+
+		/* Recover Setting for Normal Access on
+		 * temperature fetch
+		 */
+		lvts_write_device(lvts_data, SET_SENSOR_NO_RCK,  i);
+		lvts_write_device(lvts_data, SET_DEVICE_LOW_POWER_SINGLE_MODE,  i);
+	}
+
+	size = sizeof(buffer);
+	offset = snprintf(buffer, size, "[COUNT_RC_NOW] ");
+	for (i = 0; i < lvts_data->num_sensor; i++)
+		offset += snprintf(buffer + offset, size - offset, "%d:%d ",
+				i, cal_data->count_rc_now[i]);
+
+	buffer[offset] = '\0';
+	dev_info(dev, "%s\n", buffer);
+
+	return 0;
+}
+
+static void set_calibration_data_v5(struct lvts_data *lvts_data)
+{
+	struct tc_settings *tc = lvts_data->tc;
+	struct sensor_cal_data *cal_data = &lvts_data->cal_data;
+	unsigned int i, j, s_index, e_data;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+
+		for (j = 0; j < tc[i].num_sensor; j++) {
+			s_index = tc[i].sensor_map[j];
+			if (IS_ENABLE(FEATURE_DEVICE_AUTO_RCK))
+				e_data = cal_data->count_r[s_index];
+			else
+				e_data = (((unsigned long long int)
+					cal_data->count_rc_now[s_index]) *
+					cal_data->count_r[s_index]) >> 14;
+
+			lvts_writel_print(e_data,
+					LVTSEDATA00_0 + base + 0x4 * j);
+		}
+	}
+}
+
+static void init_controller_v5(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	unsigned int i;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+
+		lvts_write_device(lvts_data, SET_DEVICE_LOW_POWER_SINGLE_MODE,  i);
+
+		lvts_writel_print(SET_SENSOR_INDEX, LVTSTSSEL_0 + base);
+		lvts_writel_print(SET_CALC_SCALE_RULES, LVTSCALSCALE_0 + base);
+
+		set_polling_speed(lvts_data, i);
+		set_hw_filter(lvts_data, i);
+
+		dev_info(dev, "lvts%d: read all %d sensors in %d us, one in %d us\n",
+			i, GET_TC_SENSOR_NUM(i), GROUP_LATENCY_US(i),
+			SENSOR_LATENCY_US(i));
+	}
+
+}
+
+static void set_up_v5_common_callbacks(struct lvts_data *lvts_data)
+{
+	struct platform_ops *ops = &lvts_data->ops;
+
+	ops->device_enable_and_init = device_enable_and_init_v5;
+	ops->device_enable_auto_rck = device_enable_auto_rck_v5;
+	ops->device_read_count_rc_n = device_read_count_rc_n_v5;
+	ops->set_cal_data = set_calibration_data_v5;
+	ops->init_controller = init_controller_v5;
+}
+/*==================================================
+ * Extern function
+ *==================================================
+ */
+struct lvts_match_data lvts_v5_match_data = {
+	.hw_version = 5,
+	.table = lvts_v5_match_table,
+	.set_up_common_callbacks = set_up_v5_common_callbacks,
+};
+
+struct lvts_match_data *register_v5_lvts_match_data(void)
+{
+	return &lvts_v5_match_data;
+}
+EXPORT_SYMBOL_GPL(register_v5_lvts_match_data);
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling.c
new file mode 100644
index 0000000..74cfaa9
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <mtk_ccci_common.h>
+#include <md_cooling.h>
+
+#define is_no_ims_node(np)	\
+	((strnstr(np->name, "no-ims", strlen(np->name)) == NULL) ? 0 : 1)
+
+static DEFINE_MUTEX(md_cdev_list_lock);
+static DEFINE_MUTEX(md_cooling_lock);
+static LIST_HEAD(md_cdev_list);
+static enum md_status md_current_status;
+static unsigned int pa_num;
+
+enum md_status get_md_status(void)
+{
+	enum md_status cur_status = MD_OFF;
+	int md_state;
+
+	mutex_lock(&md_cooling_lock);
+	md_state = exec_ccci_kern_func_by_md_id(0, ID_GET_MD_STATE, NULL, 0);
+	if (md_state == MD_STATE_INVALID || md_state == MD_STATE_EXCEPTION) {
+		pr_warn("Invalid MD state(%d)!\n", md_state);
+		cur_status = MD_OFF;
+	} else {
+		cur_status = md_current_status;
+	}
+	mutex_unlock(&md_cooling_lock);
+
+	return cur_status;
+}
+EXPORT_SYMBOL_GPL(get_md_status);
+
+void set_md_status(enum md_status status)
+{
+	mutex_lock(&md_cooling_lock);
+	md_current_status = status;
+	mutex_unlock(&md_cooling_lock);
+}
+EXPORT_SYMBOL_GPL(set_md_status);
+
+int send_throttle_msg(unsigned int msg)
+{
+	int ret = 0;
+
+	mutex_lock(&md_cooling_lock);
+	ret = exec_ccci_kern_func_by_md_id(MD_SYS1,
+			ID_THROTTLING_CFG, (char *)&msg, 4);
+	mutex_unlock(&md_cooling_lock);
+
+	if (ret)
+		pr_err("send tmc msg 0x%x failed, ret:%d\n", msg, ret);
+	else
+		pr_debug("send tmc msg 0x%x done\n", msg);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(send_throttle_msg);
+
+void update_throttle_power(unsigned int pa_id, unsigned int *pwr)
+{
+	struct md_cooling_device *md_cdev;
+
+	mutex_lock(&md_cdev_list_lock);
+	list_for_each_entry(md_cdev, &md_cdev_list, node) {
+		if (md_cdev->type == MD_COOLING_TYPE_TX_PWR &&
+			md_cdev->pa_id == pa_id) {
+			memcpy(md_cdev->throttle_tx_power, pwr,
+				sizeof(md_cdev->throttle_tx_power));
+			mutex_unlock(&md_cdev_list_lock);
+			return;
+		}
+	}
+	mutex_unlock(&md_cdev_list_lock);
+}
+EXPORT_SYMBOL_GPL(update_throttle_power);
+
+struct md_cooling_device*
+get_md_cdev(enum md_cooling_type type, unsigned int pa_id)
+{
+	struct md_cooling_device *md_cdev;
+
+	mutex_lock(&md_cdev_list_lock);
+	list_for_each_entry(md_cdev, &md_cdev_list, node) {
+		if (md_cdev->type == type &&
+			md_cdev->pa_id == pa_id) {
+			mutex_unlock(&md_cdev_list_lock);
+			return md_cdev;
+		}
+	}
+	mutex_unlock(&md_cdev_list_lock);
+
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(get_md_cdev);
+
+unsigned int get_pa_num(void)
+{
+	return pa_num;
+}
+EXPORT_SYMBOL_GPL(get_pa_num);
+
+static void update_pa_num(unsigned int id)
+{
+	mutex_lock(&md_cooling_lock);
+	if (!pa_num || id > pa_num - 1)
+		pa_num = id + 1;
+	mutex_unlock(&md_cooling_lock);
+}
+
+static int _md_cooling_register(struct device_node *np,
+		enum md_cooling_type type, unsigned long max_level,
+		unsigned int *throttle_pwr,
+		struct thermal_cooling_device_ops *cooling_ops, void *data)
+{
+	struct md_cooling_device *md_cdev;
+	struct thermal_cooling_device *cdev;
+	unsigned int id;
+
+	if (of_property_read_u32(np, "id", &id)) {
+		pr_err("%s: Missing id property in DT\n", __func__);
+		return -EINVAL;
+	}
+
+	md_cdev = kzalloc(sizeof(*md_cdev), GFP_KERNEL);
+	if (!md_cdev)
+		return -ENOMEM;
+
+	strncpy(md_cdev->name, np->name, strlen(np->name));
+	md_cdev->type = type;
+	md_cdev->pa_id = id;
+	md_cdev->target_level = MD_COOLING_UNLIMITED_LV;
+	md_cdev->max_level = max_level;
+	if (throttle_pwr)
+		memcpy(md_cdev->throttle_tx_power, throttle_pwr,
+			sizeof(md_cdev->throttle_tx_power));
+	if (data)
+		md_cdev->dev_data = data;
+
+	cdev = thermal_of_cooling_device_register(np, md_cdev->name,
+			md_cdev, cooling_ops);
+	if (IS_ERR(cdev)) {
+		kfree(md_cdev);
+		return -EINVAL;
+	}
+	md_cdev->cdev = cdev;
+
+	mutex_lock(&md_cdev_list_lock);
+	list_add(&md_cdev->node, &md_cdev_list);
+	mutex_unlock(&md_cdev_list_lock);
+
+	update_pa_num(md_cdev->pa_id);
+
+	pr_info("register %s done, id=%d\n", md_cdev->name, md_cdev->pa_id);
+
+	return 0;
+}
+
+int md_cooling_register(struct device_node *np, enum md_cooling_type type,
+		unsigned long max_level, unsigned int *throttle_pwr,
+		struct thermal_cooling_device_ops *cooling_ops, void *data)
+{
+	struct device_node *child;
+	int count, ret = -1;
+
+	if (!np)
+		return ret;
+
+	count = of_get_child_count(np);
+	if (!count)
+		return -EINVAL;
+
+	for_each_child_of_node(np, child) {
+		/* ignore type mis-matched child node */
+		if ((type == MD_COOLING_TYPE_MUTT && is_no_ims_node(child)) ||
+			(type == MD_COOLING_TYPE_NO_IMS && !is_no_ims_node(child)))
+			continue;
+		ret = _md_cooling_register(child, type,
+			max_level, throttle_pwr, cooling_ops, data);
+		of_node_put(child);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(md_cooling_register);
+
+void md_cooling_unregister(enum md_cooling_type type)
+{
+	struct list_head *pos, *next;
+	struct md_cooling_device *md_cdev;
+
+	mutex_lock(&md_cdev_list_lock);
+	list_for_each_safe(pos, next, &md_cdev_list) {
+		md_cdev = list_entry(pos, struct md_cooling_device, node);
+		if (md_cdev->type == type) {
+			thermal_cooling_device_unregister(md_cdev->cdev);
+			list_del(&md_cdev->node);
+			kfree(md_cdev);
+		}
+	}
+	mutex_unlock(&md_cdev_list_lock);
+}
+EXPORT_SYMBOL_GPL(md_cooling_unregister);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek modem cooling common driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling.h b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling.h
new file mode 100644
index 0000000..01fb58a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling.h
@@ -0,0 +1,178 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#ifndef _MD_COOLING_H
+#define _MD_COOLING_H
+
+#include <linux/thermal.h>
+#include <linux/types.h>
+
+/*===========================================================
+ *  Macro Definitions
+ *===========================================================
+ */
+#define MAX_MD_COOLER_NAME_LEN	(20)
+#define MAX_NUM_TX_PWR_LV	(3)
+#define MD_COOLING_UNLIMITED_LV	(0)
+
+#define is_mutt_enabled(status)	(status >= MD_LV_THROTTLE_ENABLED)
+#define is_md_inactive(status)	(status == MD_OFF || status == MD_NO_IMS)
+#define is_md_off(status)	(status == MD_OFF)
+
+
+enum md_status {
+	MD_LV_THROTTLE_DISABLED,
+	MD_LV_THROTTLE_ENABLED,
+	MD_IMS_ONLY,
+	MD_NO_IMS,
+	MD_OFF,
+};
+
+enum md_cooling_type {
+	MD_COOLING_TYPE_MUTT,
+	MD_COOLING_TYPE_NO_IMS,
+	MD_COOLING_TYPE_TX_PWR,
+	MD_COOLING_TYPE_SCG_OFF,
+
+	NR_MD_COOLING_TYPE
+};
+
+/*===========================================================
+ * TMC message (must be align with MD site!)
+ *===========================================================
+ */
+enum tmc_ctrl_cmd {
+	TMC_CTRL_CMD_THROTTLING = 0,
+	TMC_CTRL_CMD_CA_CTRL,
+	TMC_CTRL_CMD_PA_CTRL,
+	TMC_CTRL_CMD_COOLER_LV,
+	/* 4~7 are for MD internal use */
+	TMC_CTRL_CMD_SCG_OFF = 8,
+	TMC_CTRL_CMD_SCG_ON,
+	TMC_CTRL_CMD_TX_POWER,
+	TMC_CTRL_CMD_DEFAULT,
+};
+
+enum tmc_throttle_ctrl {
+	TMC_THROTTLE_ENABLE_IMS_ENABLE = 0,
+	TMC_THROTTLE_ENABLE_IMS_DISABLE,
+	TMC_THROTTLE_DISABLE,
+};
+
+enum tmc_ca_ctrl {
+	TMC_CA_ON = 0, /* leave thermal control*/
+	TMC_CA_OFF,
+};
+
+enum tmc_pa_ctrl {
+	TMC_PA_ALL_ON = 0, /* leave thermal control*/
+	TMC_PA_OFF_1PA,
+};
+
+enum tmc_cooler_lv_ctrl {
+	TMC_COOLER_LV_ENABLE = 0,
+	TMC_COOLER_LV_DISABLE
+};
+
+enum tmc_overheated_rat {
+	TMC_OVERHEATED_LTE = 0,
+	TMC_OVERHEATED_NR,
+};
+
+enum tmc_tx_pwr_event {
+	TMC_TX_PWR_VOLTAGE_LOW_EVENT = 0,
+	TMC_TX_PWR_LOW_BATTERY_EVENT,
+	TMC_TX_PWR_OVER_CURRENT_EVENT,
+	/* reserved for reduce 2G/3G/4G/C2K max TX power for certain value */
+	TMC_TX_PWR_REDUCE_OTHER_MAX_TX_EVENT,
+	/* reserved for reduce 5G max TX power for certain value */
+	TMC_TX_PWR_REDUCE_NR_MAX_TX_EVENT,
+};
+
+#define TMC_THROTTLE_DISABLE_MSG \
+	(TMC_CTRL_CMD_THROTTLING | TMC_THROTTLE_DISABLE << 8)
+#define TMC_IMS_ENABLE_MSG \
+	(TMC_CTRL_CMD_THROTTLING | TMC_THROTTLE_ENABLE_IMS_ENABLE << 8)
+#define TMC_IMS_DISABLE_MSG \
+	(TMC_CTRL_CMD_THROTTLING | TMC_THROTTLE_ENABLE_IMS_DISABLE << 8)
+#define TMC_CA_CTRL_CA_ON_MSG \
+	(TMC_CTRL_CMD_CA_CTRL | TMC_CA_ON << 8)
+#define TMC_CA_CTRL_CA_OFF_MSG \
+	(TMC_CTRL_CMD_CA_CTRL | TMC_CA_OFF << 8)
+#define TMC_PA_CTRL_PA_ALL_ON_MSG \
+	(TMC_CTRL_CMD_PA_CTRL | TMC_PA_ALL_ON << 8)
+#define TMC_PA_CTRL_PA_OFF_1PA_MSG \
+	(TMC_CTRL_CMD_PA_CTRL | TMC_PA_OFF_1PA << 8)
+#define TMC_COOLER_LV_ENABLE_MSG \
+	(TMC_CTRL_CMD_COOLER_LV | TMC_COOLER_LV_ENABLE << 8)
+#define TMC_COOLER_LV_DISABLE_MSG \
+	(TMC_CTRL_CMD_COOLER_LV | TMC_COOLER_LV_DISABLE << 8)
+
+#define mutt_lv_to_tmc_msg(id, lv)	\
+	((TMC_COOLER_LV_ENABLE_MSG | (lv) << 16) | ((id) << 24))
+#define duty_ctrl_to_tmc_msg(active, suspend, ims)	\
+	((ims)	\
+	? ((active << 16) | (suspend << 24) | TMC_IMS_ENABLE_MSG)	\
+	: ((1 << 16) | (255 << 24) | TMC_IMS_DISABLE_MSG)	\
+	)
+#define ca_ctrl_to_tmc_msg(ca_ctrl)	\
+	((ca_ctrl) ? TMC_CA_CTRL_CA_OFF_MSG : TMC_CA_CTRL_CA_ON_MSG)
+#define pa_ctrl_to_tmc_msg(pa_ctrl)	\
+	((pa_ctrl) ? TMC_PA_CTRL_PA_OFF_1PA_MSG : TMC_PA_CTRL_PA_ALL_ON_MSG)
+#define scg_off_to_tmc_msg(off)	\
+	((off) ? TMC_CTRL_CMD_SCG_OFF : TMC_CTRL_CMD_SCG_ON)
+#define reduce_tx_pwr_to_tmc_msg(id, pwr)	\
+	(((id) == TMC_OVERHEATED_NR)	\
+	? (TMC_CTRL_CMD_TX_POWER |	\
+		(TMC_TX_PWR_REDUCE_NR_MAX_TX_EVENT << 16) |	\
+		((pwr) << 24)) \
+	: (TMC_CTRL_CMD_TX_POWER |	\
+		(TMC_TX_PWR_REDUCE_OTHER_MAX_TX_EVENT << 16) |	\
+		((pwr) << 24)) \
+	)
+
+/*==================================================
+ * Type Definitions
+ *==================================================
+ */
+/**
+ * struct md_cooling_device - data for MD cooling device
+ * @name: naming string for this cooling device
+ * @type: type of cooling device with different throttle method
+ * @pa_id: hint to MD to know the heat source
+ * @target_level: target cooling level which is set in set_cur_state()
+ *	callback.
+ * @max_level: maximum level supported for this cooling device
+ * @cdev: thermal_cooling_device pointer to keep track of the
+ *	registered cooling device.
+ * @throttle_tx_power: array of throttle TX power from device tree.
+ * @node: list_head to link all md_cooling_device.
+ * @dev_data: device private data
+ */
+struct md_cooling_device {
+	char name[MAX_MD_COOLER_NAME_LEN];
+	enum md_cooling_type type;
+	unsigned int pa_id;
+	unsigned long target_level;
+	unsigned long max_level;
+	struct thermal_cooling_device *cdev;
+	unsigned int throttle_tx_power[MAX_NUM_TX_PWR_LV];
+	struct list_head node;
+	void *dev_data;
+};
+
+enum md_status get_md_status(void);
+void set_md_status(enum md_status status);
+int send_throttle_msg(unsigned int msg);
+void update_throttle_power(unsigned int pa_id, unsigned int *pwr);
+struct md_cooling_device*
+get_md_cdev(enum md_cooling_type type, unsigned int pa_id);
+unsigned int get_pa_num(void);
+int md_cooling_register(struct device_node *np, enum md_cooling_type type,
+		unsigned long max_level, unsigned int *throttle_pwr,
+		struct thermal_cooling_device_ops *cooling_ops, void *data);
+void md_cooling_unregister(enum md_cooling_type type);
+
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_mutt.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_mutt.c
new file mode 100644
index 0000000..60f2f2a
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_mutt.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "../thermal_core.h"
+#include <md_cooling.h>
+#include <mtk_thermal_trace.h>
+
+/**
+ * struct mutt_platform_data - platform data for MD cooling MUTT driver
+ * @state_to_cooler_lv: callback function to transfer cooling state
+ *			to cooler LV defined by MD
+ * @max_lv: max cooler LV supported by MD
+ */
+struct mutt_platform_data {
+	unsigned long (*state_to_cooler_lv)(unsigned long state);
+	unsigned long max_lv;
+};
+
+struct mutt_driver_data {
+	unsigned long current_level;
+	struct mutex lock;
+	struct platform_device *pdev;
+	struct mutt_platform_data *pdata;
+};
+
+static unsigned long find_max_mutt_level(unsigned int id,
+				unsigned long lv, unsigned long max)
+{
+	struct md_cooling_device *md_cdev;
+	unsigned long final_lv = lv;
+	int i, pa_num;
+
+	pa_num = get_pa_num();
+	if (pa_num == 1)
+		return final_lv;
+
+	for (i = 0; i < pa_num; i++) {
+		if (i == id)
+			continue;
+
+		md_cdev = get_md_cdev(MD_COOLING_TYPE_NO_IMS, i);
+		if (md_cdev && md_cdev->target_level)
+			return max;
+
+		md_cdev = get_md_cdev(MD_COOLING_TYPE_MUTT, i);
+		if (md_cdev)
+			final_lv = max(final_lv, md_cdev->target_level);
+	}
+
+	return final_lv;
+}
+
+static enum md_status
+state_to_md_status(unsigned long state, unsigned long max_lv)
+{
+	enum md_status status;
+
+	if (state == max_lv)
+		status = MD_NO_IMS;
+	else if (state == max_lv - 1)
+		status = MD_IMS_ONLY;
+	else if (state == MD_COOLING_UNLIMITED_LV)
+		status = MD_LV_THROTTLE_DISABLED;
+	else
+		status = MD_LV_THROTTLE_ENABLED;
+
+	return status;
+}
+
+static void notify_data_status_changed(
+	struct thermal_cooling_device *cdev, unsigned long target, int status)
+{
+	struct thermal_instance *instance;
+	char *thermal_prop[5];
+	int i;
+
+	list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) {
+		if (instance->target == target) {
+			thermal_prop[0] = kasprintf(GFP_KERNEL,
+				"NAME=%s", instance->tz->type);
+			thermal_prop[1] = kasprintf(GFP_KERNEL,
+				"TEMP=%d", instance->tz->temperature);
+			thermal_prop[2] = kasprintf(GFP_KERNEL,
+				"TRIP=%d", instance->trip);
+			thermal_prop[3] = kasprintf(GFP_KERNEL,
+				"EVENT=%d", status);
+			thermal_prop[4] = NULL;
+			kobject_uevent_env(&instance->tz->device.kobj,
+				KOBJ_CHANGE, thermal_prop);
+			for (i = 0; i < 4; ++i)
+				kfree(thermal_prop[i]);
+
+			break;
+		}
+	}
+}
+
+static int md_cooling_mutt_get_max_state(
+	struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+
+	*state = md_cdev->max_level;
+
+	return 0;
+}
+
+static int md_cooling_mutt_get_cur_state(
+	struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+
+	*state = md_cdev->target_level;
+
+	return 0;
+}
+
+static int md_cooling_mutt_set_cur_state(
+		struct thermal_cooling_device *cdev, unsigned long state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+	struct mutt_driver_data *drv_data;
+	struct device *dev;
+	enum md_status status, new_status;
+	unsigned int msg;
+	unsigned long target_lv, final_lv;
+	int ret = 0;
+
+	/* Request state should be less than max_level */
+	if (WARN_ON(state > md_cdev->max_level))
+		return -EINVAL;
+
+	drv_data = (struct mutt_driver_data *)md_cdev->dev_data;
+	if (!drv_data)
+		return -EINVAL;
+	dev = &drv_data->pdev->dev;
+
+	if (md_cdev->target_level == state)
+		return 0;
+
+	if (md_cdev->type == MD_COOLING_TYPE_NO_IMS)
+		target_lv = (state) ? (drv_data->pdata->max_lv)
+				: (drv_data->pdata->max_lv - 1);
+	else
+		target_lv = state;
+
+	status = get_md_status();
+	if (is_md_off(status)) {
+		dev_info(dev, "skip mutt due to MD is off\n");
+		md_cdev->target_level = MD_COOLING_UNLIMITED_LV;
+		mutex_lock(&drv_data->lock);
+		drv_data->current_level = MD_COOLING_UNLIMITED_LV;
+		mutex_unlock(&drv_data->lock);
+		return 0;
+	}
+
+	mutex_lock(&drv_data->lock);
+	final_lv = find_max_mutt_level(md_cdev->pa_id,
+				target_lv, drv_data->pdata->max_lv);
+	/**
+	 * target_lv < final_lv implies the other cooler has higher lv.
+	 * target_lv == drv_data->current_level implies the same target lv
+	 * was already set by the other cooler before.
+	 * We should ignore current request in both cases.
+	 */
+	if (target_lv < final_lv || target_lv == drv_data->current_level) {
+		if (target_lv < final_lv)
+			dev_info(dev,
+				"%s: target_lv(%ld) < final_lv(%ld), skip!\n",
+				md_cdev->name, target_lv, final_lv);
+		else
+			dev_info(dev,
+				"%s: target_lv(%ld) is equal to current lv\n",
+				md_cdev->name, target_lv);
+		md_cdev->target_level = state;
+		mutex_unlock(&drv_data->lock);
+		return 0;
+	}
+
+	msg = (final_lv == MD_COOLING_UNLIMITED_LV)
+		? TMC_COOLER_LV_DISABLE_MSG
+		: mutt_lv_to_tmc_msg(md_cdev->pa_id,
+			drv_data->pdata->state_to_cooler_lv(final_lv));
+	ret = send_throttle_msg(msg);
+	if (ret) {
+		mutex_unlock(&drv_data->lock);
+		return ret;
+	}
+
+	new_status = state_to_md_status(final_lv, drv_data->pdata->max_lv);
+	set_md_status(new_status);
+	md_cdev->target_level = state;
+	drv_data->current_level = final_lv;
+
+	/* send notification to userspace due to data is on/off */
+	if (new_status == MD_IMS_ONLY && !is_md_inactive(status)) {
+		notify_data_status_changed(cdev, state, 0);
+		dev_info(dev, "%s: data is off\n", md_cdev->name);
+	} else if (new_status == MD_LV_THROTTLE_ENABLED &&
+		status == MD_IMS_ONLY) {
+		notify_data_status_changed(cdev, state, 1);
+		dev_info(dev, "%s: data is on\n", md_cdev->name);
+	}
+	mutex_unlock(&drv_data->lock);
+
+	dev_info(dev, "%s: set lv = %ld done\n", md_cdev->name, state);
+	trace_md_mutt_limit(md_cdev, status);
+
+	return ret;
+}
+
+/**
+ * For MT6295, throttle LV start from LV 1
+ * For MT6297, throttle LV start from LV 0
+ */
+static unsigned long mt6295_thermal_state_to_cooler_lv(unsigned long state)
+{
+	return state;
+}
+static unsigned long mt6297_thermal_state_to_cooler_lv(unsigned long state)
+{
+	return (state > 0) ? (state - 1) : 0;
+}
+
+static const struct mutt_platform_data mt6295_mutt_pdata = {
+	.state_to_cooler_lv = mt6295_thermal_state_to_cooler_lv,
+	.max_lv = 5,
+};
+static const struct mutt_platform_data mt6297_mutt_pdata = {
+	.state_to_cooler_lv = mt6297_thermal_state_to_cooler_lv,
+	.max_lv = 9,
+};
+
+static const struct of_device_id md_cooling_mutt_of_match[] = {
+	{
+		.compatible = "mediatek,mt6295-md-cooler-mutt",
+		.data = (void *)&mt6295_mutt_pdata,
+	},
+	{
+		.compatible = "mediatek,mt6297-md-cooler-mutt",
+		.data = (void *)&mt6297_mutt_pdata,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, md_cooling_mutt_of_match);
+
+static struct thermal_cooling_device_ops md_cooling_mutt_ops = {
+	.get_max_state		= md_cooling_mutt_get_max_state,
+	.get_cur_state		= md_cooling_mutt_get_cur_state,
+	.set_cur_state		= md_cooling_mutt_set_cur_state,
+};
+
+static int md_cooling_mutt_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	int ret = -1;
+	struct mutt_driver_data *drv_data;
+
+	if (!np) {
+		dev_err(dev, "MD cooler DT node not found\n");
+		return -ENODEV;
+	}
+
+	drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL);
+	if (!drv_data)
+		return -ENOMEM;
+
+	mutex_init(&drv_data->lock);
+	drv_data->current_level = MD_COOLING_UNLIMITED_LV;
+	drv_data->pdev = pdev;
+	drv_data->pdata =
+		(struct mutt_platform_data *)of_device_get_match_data(dev);
+
+	platform_set_drvdata(pdev, drv_data);
+
+	ret = md_cooling_register(np,
+				MD_COOLING_TYPE_MUTT,
+				drv_data->pdata->max_lv - 1,
+				NULL,
+				&md_cooling_mutt_ops,
+				drv_data);
+	if (ret) {
+		dev_err(dev, "register mutt cdev failed!\n");
+		return ret;
+	}
+
+	ret = md_cooling_register(np,
+				MD_COOLING_TYPE_NO_IMS,
+				1,
+				NULL,
+				&md_cooling_mutt_ops,
+				drv_data);
+	if (ret) {
+		dev_err(dev, "register mutt_no_ims cdev failed!\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int md_cooling_mutt_remove(struct platform_device *pdev)
+{
+	md_cooling_unregister(MD_COOLING_TYPE_MUTT);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver md_cooling_mutt_driver = {
+	.probe = md_cooling_mutt_probe,
+	.remove = md_cooling_mutt_remove,
+	.driver = {
+		.name = "mtk-md-cooling-mutt",
+		.of_match_table = md_cooling_mutt_of_match,
+	},
+};
+module_platform_driver(md_cooling_mutt_driver);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek modem cooling MUTT driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_scg_off.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_scg_off.c
new file mode 100644
index 0000000..a4e244b
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_scg_off.c
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <md_cooling.h>
+#include <mtk_thermal_trace.h>
+
+#define SCG_OFF_MAX_LEVEL	(1)
+
+static int md_cooling_scg_off_get_max_state(
+	struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+
+	*state = md_cdev->max_level;
+
+	return 0;
+}
+
+static int md_cooling_scg_off_get_cur_state(
+	struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+
+	*state = md_cdev->target_level;
+
+	return 0;
+}
+
+static int md_cooling_scg_off_set_cur_state(
+		struct thermal_cooling_device *cdev, unsigned long state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+	struct device *dev = (struct device *)md_cdev->dev_data;
+	enum md_status status;
+	unsigned int msg;
+	int ret = 0;
+
+	/* Request state should be less than max_level */
+	if (WARN_ON(state > md_cdev->max_level))
+		return -EINVAL;
+
+	if (md_cdev->target_level == state)
+		return 0;
+
+	status = get_md_status();
+	if (is_mutt_enabled(status)) {
+		dev_info(dev, "skip SCG control due to MUTT is enabled\n");
+		if (is_md_off(status))
+			md_cdev->target_level = MD_COOLING_UNLIMITED_LV;
+		trace_md_scg_off(md_cdev, status);
+		return -EACCES;
+	}
+
+	msg = (state == MD_COOLING_UNLIMITED_LV)
+		? scg_off_to_tmc_msg(0) : scg_off_to_tmc_msg(1);
+	ret = send_throttle_msg(msg);
+	if (!ret)
+		md_cdev->target_level = state;
+
+	dev_info(dev, "%s: set lv = %ld done\n", md_cdev->name, state);
+	trace_md_scg_off(md_cdev, status);
+
+	return ret;
+}
+
+static const struct of_device_id md_cooling_scg_off_of_match[] = {
+	{ .compatible = "mediatek,md-cooler-scg-off", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, md_cooling_scg_off_of_match);
+
+static struct thermal_cooling_device_ops md_cooling_scg_off_ops = {
+	.get_max_state		= md_cooling_scg_off_get_max_state,
+	.get_cur_state		= md_cooling_scg_off_get_cur_state,
+	.set_cur_state		= md_cooling_scg_off_set_cur_state,
+};
+
+static int md_cooling_scg_off_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	int ret = -1;
+
+	if (!np) {
+		dev_err(dev, "MD cooler DT node not found\n");
+		return -ENODEV;
+	}
+
+	ret = md_cooling_register(np,
+				MD_COOLING_TYPE_SCG_OFF,
+				SCG_OFF_MAX_LEVEL,
+				NULL,
+				&md_cooling_scg_off_ops,
+				dev);
+	if (ret) {
+		dev_err(dev, "register scg-off cdev failed!\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int md_cooling_scg_off_remove(struct platform_device *pdev)
+{
+	md_cooling_unregister(MD_COOLING_TYPE_SCG_OFF);
+
+	return 0;
+}
+
+static struct platform_driver md_cooling_scg_off_driver = {
+	.probe = md_cooling_scg_off_probe,
+	.remove = md_cooling_scg_off_remove,
+	.driver = {
+		.name = "mtk-md-cooling-scg-off",
+		.of_match_table = md_cooling_scg_off_of_match,
+	},
+};
+module_platform_driver(md_cooling_scg_off_driver);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek modem cooling SCG off driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_sysfs.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_sysfs.c
new file mode 100644
index 0000000..a89e85b
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_sysfs.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/module.h>
+#include <md_cooling.h>
+
+#define MD_COOLING_ATTR_RO(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_RO(_name)
+#define MD_COOLING_ATTR_WO(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_WO(_name)
+
+static ssize_t status_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	struct md_cooling_device *md_cdev;
+	int len = 0, i, j;
+	unsigned int pa_num = get_pa_num();
+
+	len += snprintf(buf + len, PAGE_SIZE - len, "PA num = %d\n", pa_num);
+	len += snprintf(buf + len, PAGE_SIZE - len,
+		"MD status = %d\n", get_md_status());
+
+	for (i = 0; i < NR_MD_COOLING_TYPE; i++) {
+		for (j = 0; j < pa_num; j++) {
+			md_cdev = get_md_cdev((enum md_cooling_type)i, j);
+			if (!md_cdev)
+				continue;
+
+			len += snprintf(buf + len, PAGE_SIZE - len,
+				"\n[%s, %d]\n", md_cdev->name, md_cdev->pa_id);
+			len += snprintf(buf + len, PAGE_SIZE - len,
+				"target/max = %ld/%ld\n",
+				md_cdev->target_level, md_cdev->max_level);
+			if (md_cdev->type == MD_COOLING_TYPE_TX_PWR)
+				len += snprintf(buf + len, PAGE_SIZE - len,
+					"throttle tx_pwr = %d/%d/%d\n",
+					md_cdev->throttle_tx_power[0],
+					md_cdev->throttle_tx_power[1],
+					md_cdev->throttle_tx_power[2]);
+		}
+	}
+
+	return len;
+}
+
+static ssize_t duty_ctrl_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int ret = 0, msg, no_ims, active, suspend;
+
+	if ((sscanf(buf, "%d %d %d", &no_ims, &active, &suspend) != 3))
+		return -EINVAL;
+
+	if (get_md_status() != MD_LV_THROTTLE_DISABLED)
+		return -EBUSY;
+
+	if (no_ims)
+		/* set active/suspend=1/255 for no IMS case */
+		msg = duty_ctrl_to_tmc_msg(1, 255, 0);
+	else if (active >= 1 && active <= 255
+		&& suspend >= 1 && suspend <= 255)
+		msg = duty_ctrl_to_tmc_msg(active, suspend, 1);
+	else
+		msg = TMC_THROTTLE_DISABLE_MSG;
+	ret = send_throttle_msg(msg);
+	if (ret)
+		return -EBUSY;
+
+	return count;
+}
+
+static ssize_t ca_ctrl_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int ret = 0, ca_ctrl;
+
+	if ((kstrtouint(buf, 10, &ca_ctrl)))
+		return -EINVAL;
+
+	if (get_md_status() != MD_LV_THROTTLE_DISABLED)
+		return -EBUSY;
+
+	ret = send_throttle_msg(ca_ctrl_to_tmc_msg(ca_ctrl));
+	if (ret)
+		return -EBUSY;
+
+	return count;
+}
+
+static ssize_t pa_ctrl_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int ret = 0, pa_ctrl;
+
+	if ((kstrtouint(buf, 10, &pa_ctrl)))
+		return -EINVAL;
+
+	if (get_md_status() != MD_LV_THROTTLE_DISABLED)
+		return -EBUSY;
+
+	ret = send_throttle_msg(pa_ctrl_to_tmc_msg(pa_ctrl));
+	if (ret)
+		return -EBUSY;
+
+	return count;
+}
+
+static ssize_t update_tx_pwr_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	unsigned int id, tx_pwr_lv1, tx_pwr_lv2, tx_pwr_lv3;
+	unsigned int tx_pwr[MAX_NUM_TX_PWR_LV];
+
+	if ((sscanf(buf, "%d %d %d %d", &id, &tx_pwr_lv1,
+		&tx_pwr_lv2, &tx_pwr_lv3) != (MAX_NUM_TX_PWR_LV + 1)))
+		return -EINVAL;
+
+	if (id >= get_pa_num()) {
+		pr_err("Invalid PA id(%d)\n", id);
+		return -EINVAL;
+	}
+
+	tx_pwr[0] = tx_pwr_lv1;
+	tx_pwr[1] = tx_pwr_lv2;
+	tx_pwr[2] = tx_pwr_lv3;
+
+	update_throttle_power(id, tx_pwr);
+
+	return count;
+}
+
+MD_COOLING_ATTR_RO(status);
+MD_COOLING_ATTR_WO(duty_ctrl);
+MD_COOLING_ATTR_WO(ca_ctrl);
+MD_COOLING_ATTR_WO(pa_ctrl);
+MD_COOLING_ATTR_WO(update_tx_pwr);
+
+static struct attribute *md_cooling_attrs[] = {
+	&status_attr.attr,
+	&duty_ctrl_attr.attr,
+	&ca_ctrl_attr.attr,
+	&pa_ctrl_attr.attr,
+	&update_tx_pwr_attr.attr,
+	NULL
+};
+
+static struct attribute_group md_cooling_attr_group = {
+	.name	= "md_cooling",
+	.attrs	= md_cooling_attrs,
+};
+
+static int __init md_cooling_sysfs_init(void)
+{
+	int ret;
+
+	ret = sysfs_create_group(kernel_kobj, &md_cooling_attr_group);
+	if (ret)
+		pr_err("failed to create md cooling sysfs, ret=%d!\n", ret);
+
+	return ret;
+}
+
+static void __exit md_cooling_sysfs_exit(void)
+{
+	sysfs_remove_group(kernel_kobj, &md_cooling_attr_group);
+}
+module_init(md_cooling_sysfs_init);
+module_exit(md_cooling_sysfs_exit);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek modem cooling sysfs driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_tx_pwr.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_tx_pwr.c
new file mode 100644
index 0000000..3f1e29f
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_cooling_tx_pwr.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <md_cooling.h>
+#include <mtk_thermal_trace.h>
+
+#define DEFAULT_THROTTLE_TX_PWR_LV1	(4)
+#define DEFAULT_THROTTLE_TX_PWR_LV2	(6)
+#define DEFAULT_THROTTLE_TX_PWR_LV3	(8)
+
+static int md_cooling_tx_pwr_get_max_state(
+	struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+
+	*state = md_cdev->max_level;
+
+	return 0;
+}
+
+static int md_cooling_tx_pwr_get_cur_state(
+	struct thermal_cooling_device *cdev, unsigned long *state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+
+	*state = md_cdev->target_level;
+
+	return 0;
+}
+
+static int md_cooling_tx_pwr_set_cur_state(
+		struct thermal_cooling_device *cdev, unsigned long state)
+{
+	struct md_cooling_device *md_cdev = cdev->devdata;
+	struct device *dev = (struct device *)md_cdev->dev_data;
+	enum md_status status;
+	unsigned int msg, pwr;
+	int ret = 0;
+
+	/* Request state should be less than max_level */
+	if (WARN_ON(state > md_cdev->max_level))
+		return -EINVAL;
+
+	if (md_cdev->target_level == state)
+		return 0;
+
+	status = get_md_status();
+	if (is_md_inactive(status)) {
+		dev_info(dev, "skip tx pwr control due to MD is inactive\n");
+		if (is_md_off(status))
+			md_cdev->target_level = MD_COOLING_UNLIMITED_LV;
+		trace_md_tx_pwr_limit(md_cdev, status);
+		return -EACCES;
+	}
+
+	pwr = (state == MD_COOLING_UNLIMITED_LV)
+		? 0 : md_cdev->throttle_tx_power[state - 1];
+	msg = reduce_tx_pwr_to_tmc_msg(md_cdev->pa_id, pwr);
+	ret = send_throttle_msg(msg);
+	if (!ret)
+		md_cdev->target_level = state;
+
+	dev_info(dev, "%s: set lv = %ld done\n", md_cdev->name, state);
+	trace_md_tx_pwr_limit(md_cdev, status);
+
+	return ret;
+}
+
+static const struct of_device_id md_cooling_tx_pwr_of_match[] = {
+	{ .compatible = "mediatek,md-cooler-tx-pwr", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, md_cooling_tx_pwr_of_match);
+
+static struct thermal_cooling_device_ops md_cooling_tx_pwr_ops = {
+	.get_max_state		= md_cooling_tx_pwr_get_max_state,
+	.get_cur_state		= md_cooling_tx_pwr_get_cur_state,
+	.set_cur_state		= md_cooling_tx_pwr_set_cur_state,
+};
+
+static int md_cooling_tx_pwr_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	unsigned int throttle_tx_pwr[MAX_NUM_TX_PWR_LV] = {
+		DEFAULT_THROTTLE_TX_PWR_LV1,
+		DEFAULT_THROTTLE_TX_PWR_LV2,
+		DEFAULT_THROTTLE_TX_PWR_LV3,
+	};
+	int ret = -1;
+
+	if (!np) {
+		dev_err(dev, "MD cooler DT node not found\n");
+		return -ENODEV;
+	}
+
+	ret = md_cooling_register(np,
+				MD_COOLING_TYPE_TX_PWR,
+				MAX_NUM_TX_PWR_LV,
+				throttle_tx_pwr,
+				&md_cooling_tx_pwr_ops,
+				dev);
+	if (ret) {
+		dev_err(dev, "register tx-pwr cdev failed!\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int md_cooling_tx_pwr_remove(struct platform_device *pdev)
+{
+	md_cooling_unregister(MD_COOLING_TYPE_TX_PWR);
+
+	return 0;
+}
+
+static struct platform_driver md_cooling_tx_pwr_driver = {
+	.probe = md_cooling_tx_pwr_probe,
+	.remove = md_cooling_tx_pwr_remove,
+	.driver = {
+		.name = "mtk-md-cooling-tx-pwr",
+		.of_match_table = md_cooling_tx_pwr_of_match,
+	},
+};
+module_platform_driver(md_cooling_tx_pwr_driver);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek modem cooling TX power throttle driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_rf_temp.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_rf_temp.c
new file mode 100644
index 0000000..e7d7d49
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/md_rf_temp.c
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/wait.h>
+#include <mtk_ccci_common.h>
+
+#define WAIT_MD_FEEDBACK_TIMEOUT_MS	(20)
+#define RF_TEMP_SIGN_BIT		(7)
+
+enum md_rf_thread_state {
+	MD_RF_NOT_STARTED = 0,
+	MD_RF_WAITING_RESPONSE,
+	MD_RF_RECEIVED_RESPONSE,
+};
+
+struct md_rf_info {
+	struct device *dev;
+	enum md_rf_thread_state state;
+	int cur_temp;
+	wait_queue_head_t md_rf_wq;
+};
+
+/* Use global variable because we need to use it in md_rf_handler */
+static struct md_rf_info *rf_info;
+
+static int md_rf_get_temp(void *no_used, int *temp)
+{
+	int ret;
+
+	rf_info->state = MD_RF_WAITING_RESPONSE;
+	ret = exec_ccci_kern_func_by_md_id(MD_SYS1,
+			MD_RF_MAX_TEMPERATURE_SUB6, NULL, 0);
+	if (ret) {
+		if (ret == -CCCI_ERR_MD_NOT_READY) {
+			dev_dbg(rf_info->dev,
+				"MD is not ready yet, retry next time!\n");
+			return -EAGAIN;
+		} else {
+			dev_err(rf_info->dev,
+				"send msg to MD fail, ret=%d\n", ret);
+			return -EINVAL;
+		}
+	}
+
+	dev_dbg(rf_info->dev, "send request done, waiting ack event\n");
+	ret = wait_event_timeout(rf_info->md_rf_wq,
+		rf_info->state == MD_RF_RECEIVED_RESPONSE,
+		msecs_to_jiffies(WAIT_MD_FEEDBACK_TIMEOUT_MS));
+	if (!ret) {
+		dev_dbg(rf_info->dev,
+			"wait event timeout, state=%d, ret=%d\n",
+			rf_info->state, ret);
+		*temp = THERMAL_TEMP_INVALID;
+	} else {
+		*temp = rf_info->cur_temp;
+	}
+
+	dev_dbg(rf_info->dev, "RF temp = %d\n", *temp);
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops md_rf_ops = {
+	.get_temp = md_rf_get_temp,
+};
+
+static int md_msg_handler(int md_id, int data)
+{
+	unsigned int rat, temp;
+
+	rat = (data >> 8) & 0xFF;
+	temp = data & 0xFF;
+
+	rf_info->cur_temp =
+		sign_extend32(temp, RF_TEMP_SIGN_BIT) * 1000;
+	rf_info->state = MD_RF_RECEIVED_RESPONSE;
+
+	dev_dbg(rf_info->dev, "rat=%d, temp=%d\n", rat, temp);
+
+	wake_up(&rf_info->md_rf_wq);
+
+	return 0;
+}
+
+static int md_rf_probe(struct platform_device *pdev)
+{
+	struct thermal_zone_device *tz_dev;
+	int ret;
+
+	if (!pdev->dev.of_node) {
+		dev_err(&pdev->dev, "Only DT based supported\n");
+		return -ENODEV;
+	}
+
+	rf_info = devm_kzalloc(&pdev->dev, sizeof(*rf_info), GFP_KERNEL);
+	if (!rf_info)
+		return -ENOMEM;
+
+	rf_info->dev = &pdev->dev;
+	rf_info->state = MD_RF_NOT_STARTED;
+	init_waitqueue_head(&rf_info->md_rf_wq);
+	ret = register_ccci_sys_call_back(MD_SYS1, MD_RF_MAX_TEMPERATURE_SUB6,
+				md_msg_handler);
+	if (ret) {
+		dev_err(&pdev->dev, "register CB to CCCI failed, ret = %d\n",
+			ret);
+		return -EINVAL;
+	}
+
+	tz_dev = devm_thermal_zone_of_sensor_register(
+			&pdev->dev, 0, NULL, &md_rf_ops);
+	if (IS_ERR(tz_dev)) {
+		dev_err(&pdev->dev, "MD RF TZ sensor register fail!\n");
+		return PTR_ERR(tz_dev);
+	}
+
+	return 0;
+}
+
+static const struct of_device_id md_rf_of_match[] = {
+	{ .compatible = "mediatek,md-rf", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, md_rf_of_match);
+
+static struct platform_driver md_rf_driver = {
+	.probe = md_rf_probe,
+	.driver = {
+		.name = "mtk-md-rf",
+		.of_match_table = md_rf_of_match,
+	},
+};
+
+module_platform_driver(md_rf_driver);
+
+MODULE_AUTHOR("Shun-Yao Yang <brian-sy.yang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek modem RF thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal.c
new file mode 100644
index 0000000..d48dff6
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal.c
@@ -0,0 +1,793 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 MediaTek Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.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/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/thermal.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+
+/* AUXADC Registers */
+#define AUXADC_CON1_SET_V	0x008
+#define AUXADC_CON1_CLR_V	0x00c
+#define AUXADC_CON2_V		0x010
+#define AUXADC_DATA(channel)	(0x14 + (channel) * 4)
+
+#define APMIXED_SYS_TS_CON1	0x604
+
+/* Thermal Controller Registers */
+#define TEMP_MONCTL0		0x000
+#define TEMP_MONCTL1		0x004
+#define TEMP_MONCTL2		0x008
+#define TEMP_MONIDET0		0x014
+#define TEMP_MONIDET1		0x018
+#define TEMP_MSRCTL0		0x038
+#define TEMP_AHBPOLL		0x040
+#define TEMP_AHBTO		0x044
+#define TEMP_ADCPNP0		0x048
+#define TEMP_ADCPNP1		0x04c
+#define TEMP_ADCPNP2		0x050
+#define TEMP_ADCPNP3		0x0b4
+
+#define TEMP_ADCMUX		0x054
+#define TEMP_ADCEN		0x060
+#define TEMP_PNPMUXADDR		0x064
+#define TEMP_ADCMUXADDR		0x068
+#define TEMP_ADCENADDR		0x074
+#define TEMP_ADCVALIDADDR	0x078
+#define TEMP_ADCVOLTADDR	0x07c
+#define TEMP_RDCTRL		0x080
+#define TEMP_ADCVALIDMASK	0x084
+#define TEMP_ADCVOLTAGESHIFT	0x088
+#define TEMP_ADCWRITECTRL	0x08c
+#define TEMP_MSR0		0x090
+#define TEMP_MSR1		0x094
+#define TEMP_MSR2		0x098
+#define TEMP_MSR3		0x0B8
+
+#define TEMP_SPARE0		0x0f0
+
+#define PTPCORESEL		0x400
+
+#define TEMP_MONCTL1_PERIOD_UNIT(x)	((x) & 0x3ff)
+
+#define TEMP_MONCTL2_FILTER_INTERVAL(x)	(((x) & 0x3ff) << 16)
+#define TEMP_MONCTL2_SENSOR_INTERVAL(x)	((x) & 0x3ff)
+
+#define TEMP_AHBPOLL_ADC_POLL_INTERVAL(x)	(x)
+
+#define TEMP_ADCWRITECTRL_ADC_PNP_WRITE		BIT(0)
+#define TEMP_ADCWRITECTRL_ADC_MUX_WRITE		BIT(1)
+
+#define TEMP_ADCVALIDMASK_VALID_HIGH		BIT(5)
+#define TEMP_ADCVALIDMASK_VALID_POS(bit)	(bit)
+
+/* MT8173 thermal sensors */
+#define MT8173_TS1	0
+#define MT8173_TS2	1
+#define MT8173_TS3	2
+#define MT8173_TS4	3
+#define MT8173_TSABB	4
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT8173_TEMP_AUXADC_CHANNEL	11
+
+/* The total number of temperature sensors in the MT8173 */
+#define MT8173_NUM_SENSORS		5
+
+/* The number of banks in the MT8173 */
+#define MT8173_NUM_ZONES		4
+
+/* The number of sensing points per bank */
+#define MT8173_NUM_SENSORS_PER_ZONE	4
+
+/*
+ * Layout of the fuses providing the calibration data
+ * These macros could be used for MT8173, MT2701, and MT2712.
+ * MT8173 has 5 sensors and needs 5 VTS calibration data.
+ * MT2701 has 3 sensors and needs 3 VTS calibration data.
+ * MT2712 has 4 sensors and needs 4 VTS calibration data.
+ */
+#define MT8173_CALIB_BUF0_VALID		BIT(0)
+#define MT8173_CALIB_BUF1_ADC_GE(x)	(((x) >> 22) & 0x3ff)
+#define MT8173_CALIB_BUF0_VTS_TS1(x)	(((x) >> 17) & 0x1ff)
+#define MT8173_CALIB_BUF0_VTS_TS2(x)	(((x) >> 8) & 0x1ff)
+#define MT8173_CALIB_BUF1_VTS_TS3(x)	(((x) >> 0) & 0x1ff)
+#define MT8173_CALIB_BUF2_VTS_TS4(x)	(((x) >> 23) & 0x1ff)
+#define MT8173_CALIB_BUF2_VTS_TSABB(x)	(((x) >> 14) & 0x1ff)
+#define MT8173_CALIB_BUF0_DEGC_CALI(x)	(((x) >> 1) & 0x3f)
+#define MT8173_CALIB_BUF0_O_SLOPE(x)	(((x) >> 26) & 0x3f)
+#define MT8173_CALIB_BUF0_O_SLOPE_SIGN(x)	(((x) >> 7) & 0x1)
+#define MT8173_CALIB_BUF1_ID(x)	(((x) >> 9) & 0x1)
+
+/* MT2701 thermal sensors */
+#define MT2701_TS1	0
+#define MT2701_TS2	1
+#define MT2701_TSABB	2
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT2701_TEMP_AUXADC_CHANNEL	11
+
+/* The total number of temperature sensors in the MT2701 */
+#define MT2701_NUM_SENSORS	3
+
+/* The number of sensing points per bank */
+#define MT2701_NUM_SENSORS_PER_ZONE	3
+
+/* MT2712 thermal sensors */
+#define MT2712_TS1	0
+#define MT2712_TS2	1
+#define MT2712_TS3	2
+#define MT2712_TS4	3
+
+/* AUXADC channel 11 is used for the temperature sensors */
+#define MT2712_TEMP_AUXADC_CHANNEL	11
+
+/* The total number of temperature sensors in the MT2712 */
+#define MT2712_NUM_SENSORS	4
+
+/* The number of sensing points per bank */
+#define MT2712_NUM_SENSORS_PER_ZONE	4
+
+#define MT7622_TEMP_AUXADC_CHANNEL	11
+#define MT7622_NUM_SENSORS		1
+#define MT7622_NUM_ZONES		1
+#define MT7622_NUM_SENSORS_PER_ZONE	1
+#define MT7622_TS1	0
+
+struct mtk_thermal;
+
+struct thermal_bank_cfg {
+	unsigned int num_sensors;
+	const int *sensors;
+};
+
+struct mtk_thermal_bank {
+	struct mtk_thermal *mt;
+	int id;
+};
+
+struct mtk_thermal_data {
+	s32 num_banks;
+	s32 num_sensors;
+	s32 auxadc_channel;
+	const int *sensor_mux_values;
+	const int *msr;
+	const int *adcpnp;
+	struct thermal_bank_cfg bank_data[];
+};
+
+struct mtk_thermal {
+	struct device *dev;
+	void __iomem *thermal_base;
+
+	struct clk *clk_peri_therm;
+	struct clk *clk_auxadc;
+	/* lock: for getting and putting banks */
+	struct mutex lock;
+
+	/* Calibration values */
+	s32 adc_ge;
+	s32 degc_cali;
+	s32 o_slope;
+	s32 vts[MT8173_NUM_SENSORS];
+
+	const struct mtk_thermal_data *conf;
+	struct mtk_thermal_bank banks[];
+};
+
+/* MT8173 thermal sensor data */
+static const int mt8173_bank_data[MT8173_NUM_ZONES][3] = {
+	{ MT8173_TS2, MT8173_TS3 },
+	{ MT8173_TS2, MT8173_TS4 },
+	{ MT8173_TS1, MT8173_TS2, MT8173_TSABB },
+	{ MT8173_TS2 },
+};
+
+static const int mt8173_msr[MT8173_NUM_SENSORS_PER_ZONE] = {
+	TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR3
+};
+
+static const int mt8173_adcpnp[MT8173_NUM_SENSORS_PER_ZONE] = {
+	TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2, TEMP_ADCPNP3
+};
+
+static const int mt8173_mux_values[MT8173_NUM_SENSORS] = { 0, 1, 2, 3, 16 };
+
+/* MT2701 thermal sensor data */
+static const int mt2701_bank_data[MT2701_NUM_SENSORS] = {
+	MT2701_TS1, MT2701_TS2, MT2701_TSABB
+};
+
+static const int mt2701_msr[MT2701_NUM_SENSORS_PER_ZONE] = {
+	TEMP_MSR0, TEMP_MSR1, TEMP_MSR2
+};
+
+static const int mt2701_adcpnp[MT2701_NUM_SENSORS_PER_ZONE] = {
+	TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2
+};
+
+static const int mt2701_mux_values[MT2701_NUM_SENSORS] = { 0, 1, 16 };
+
+/* MT2712 thermal sensor data */
+static const int mt2712_bank_data[MT2712_NUM_SENSORS] = {
+	MT2712_TS1, MT2712_TS2, MT2712_TS3, MT2712_TS4
+};
+
+static const int mt2712_msr[MT2712_NUM_SENSORS_PER_ZONE] = {
+	TEMP_MSR0, TEMP_MSR1, TEMP_MSR2, TEMP_MSR3
+};
+
+static const int mt2712_adcpnp[MT2712_NUM_SENSORS_PER_ZONE] = {
+	TEMP_ADCPNP0, TEMP_ADCPNP1, TEMP_ADCPNP2, TEMP_ADCPNP3
+};
+
+static const int mt2712_mux_values[MT2712_NUM_SENSORS] = { 0, 1, 2, 3 };
+
+/* MT7622 thermal sensor data */
+static const int mt7622_bank_data[MT7622_NUM_SENSORS] = { MT7622_TS1, };
+static const int mt7622_msr[MT7622_NUM_SENSORS_PER_ZONE] = { TEMP_MSR0, };
+static const int mt7622_adcpnp[MT7622_NUM_SENSORS_PER_ZONE] = { TEMP_ADCPNP0, };
+static const int mt7622_mux_values[MT7622_NUM_SENSORS] = { 0, };
+
+/**
+ * The MT8173 thermal controller has four banks. Each bank can read up to
+ * four temperature sensors simultaneously. The MT8173 has a total of 5
+ * temperature sensors. We use each bank to measure a certain area of the
+ * SoC. Since TS2 is located centrally in the SoC it is influenced by multiple
+ * areas, hence is used in different banks.
+ *
+ * The thermal core only gets the maximum temperature of all banks, so
+ * the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data, and this indeed needs the temperatures of the individual banks
+ * for making better decisions.
+ */
+static const struct mtk_thermal_data mt8173_thermal_data = {
+	.auxadc_channel = MT8173_TEMP_AUXADC_CHANNEL,
+	.num_banks = MT8173_NUM_ZONES,
+	.num_sensors = MT8173_NUM_SENSORS,
+	.bank_data = {
+		{
+			.num_sensors = 2,
+			.sensors = mt8173_bank_data[0],
+		}, {
+			.num_sensors = 2,
+			.sensors = mt8173_bank_data[1],
+		}, {
+			.num_sensors = 3,
+			.sensors = mt8173_bank_data[2],
+		}, {
+			.num_sensors = 1,
+			.sensors = mt8173_bank_data[3],
+		},
+	},
+	.msr = mt8173_msr,
+	.adcpnp = mt8173_adcpnp,
+	.sensor_mux_values = mt8173_mux_values,
+};
+
+/**
+ * The MT2701 thermal controller has one bank, which can read up to
+ * three temperature sensors simultaneously. The MT2701 has a total of 3
+ * temperature sensors.
+ *
+ * The thermal core only gets the maximum temperature of this one bank,
+ * so the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data.
+ */
+static const struct mtk_thermal_data mt2701_thermal_data = {
+	.auxadc_channel = MT2701_TEMP_AUXADC_CHANNEL,
+	.num_banks = 1,
+	.num_sensors = MT2701_NUM_SENSORS,
+	.bank_data = {
+		{
+			.num_sensors = 3,
+			.sensors = mt2701_bank_data,
+		},
+	},
+	.msr = mt2701_msr,
+	.adcpnp = mt2701_adcpnp,
+	.sensor_mux_values = mt2701_mux_values,
+};
+
+/**
+ * The MT2712 thermal controller has one bank, which can read up to
+ * four temperature sensors simultaneously. The MT2712 has a total of 4
+ * temperature sensors.
+ *
+ * The thermal core only gets the maximum temperature of this one bank,
+ * so the bank concept wouldn't be necessary here. However, the SVS (Smart
+ * Voltage Scaling) unit makes its decisions based on the same bank
+ * data.
+ */
+static const struct mtk_thermal_data mt2712_thermal_data = {
+	.auxadc_channel = MT2712_TEMP_AUXADC_CHANNEL,
+	.num_banks = 1,
+	.num_sensors = MT2712_NUM_SENSORS,
+	.bank_data = {
+		{
+			.num_sensors = 4,
+			.sensors = mt2712_bank_data,
+		},
+	},
+	.msr = mt2712_msr,
+	.adcpnp = mt2712_adcpnp,
+	.sensor_mux_values = mt2712_mux_values,
+};
+
+/*
+ * MT7622 have only one sensing point which uses AUXADC Channel 11 for raw data
+ * access.
+ */
+static const struct mtk_thermal_data mt7622_thermal_data = {
+	.auxadc_channel = MT7622_TEMP_AUXADC_CHANNEL,
+	.num_banks = MT7622_NUM_ZONES,
+	.num_sensors = MT7622_NUM_SENSORS,
+	.bank_data = {
+		{
+			.num_sensors = 1,
+			.sensors = mt7622_bank_data,
+		},
+	},
+	.msr = mt7622_msr,
+	.adcpnp = mt7622_adcpnp,
+	.sensor_mux_values = mt7622_mux_values,
+};
+
+/**
+ * raw_to_mcelsius - convert a raw ADC value to mcelsius
+ * @mt:		The thermal controller
+ * @raw:	raw ADC value
+ *
+ * This converts the raw ADC value to mcelsius using the SoC specific
+ * calibration constants
+ */
+static int raw_to_mcelsius(struct mtk_thermal *mt, int sensno, s32 raw)
+{
+	s32 tmp;
+
+	raw &= 0xfff;
+
+	tmp = 203450520 << 3;
+	tmp /= 165 + mt->o_slope;
+	tmp /= 10000 + mt->adc_ge;
+	tmp *= raw - mt->vts[sensno] - 3350;
+	tmp >>= 3;
+
+	return mt->degc_cali * 500 - tmp;
+}
+
+/**
+ * mtk_thermal_get_bank - get bank
+ * @bank:	The bank
+ *
+ * The bank registers are banked, we have to select a bank in the
+ * PTPCORESEL register to access it.
+ */
+static void mtk_thermal_get_bank(struct mtk_thermal_bank *bank)
+{
+	struct mtk_thermal *mt = bank->mt;
+	u32 val;
+
+	mutex_lock(&mt->lock);
+
+	val = readl(mt->thermal_base + PTPCORESEL);
+	val &= ~0xf;
+	val |= bank->id;
+	writel(val, mt->thermal_base + PTPCORESEL);
+}
+
+/**
+ * mtk_thermal_put_bank - release bank
+ * @bank:	The bank
+ *
+ * release a bank previously taken with mtk_thermal_get_bank,
+ */
+static void mtk_thermal_put_bank(struct mtk_thermal_bank *bank)
+{
+	struct mtk_thermal *mt = bank->mt;
+
+	mutex_unlock(&mt->lock);
+}
+
+/**
+ * mtk_thermal_bank_temperature - get the temperature of a bank
+ * @bank:	The bank
+ *
+ * The temperature of a bank is considered the maximum temperature of
+ * the sensors associated to the bank.
+ */
+static int mtk_thermal_bank_temperature(struct mtk_thermal_bank *bank)
+{
+	struct mtk_thermal *mt = bank->mt;
+	const struct mtk_thermal_data *conf = mt->conf;
+	int i, temp = INT_MIN, max = INT_MIN;
+	u32 raw;
+
+	for (i = 0; i < conf->bank_data[bank->id].num_sensors; i++) {
+		raw = readl(mt->thermal_base + conf->msr[i]);
+
+		temp = raw_to_mcelsius(mt,
+				       conf->bank_data[bank->id].sensors[i],
+				       raw);
+
+		/*
+		 * The first read of a sensor often contains very high bogus
+		 * temperature value. Filter these out so that the system does
+		 * not immediately shut down.
+		 */
+		if (temp > 200000)
+			temp = 0;
+
+		if (temp > max)
+			max = temp;
+	}
+
+	return max;
+}
+
+static int mtk_read_temp(void *data, int *temperature)
+{
+	struct mtk_thermal *mt = data;
+	int i;
+	int tempmax = INT_MIN;
+
+	for (i = 0; i < mt->conf->num_banks; i++) {
+		struct mtk_thermal_bank *bank = &mt->banks[i];
+
+		mtk_thermal_get_bank(bank);
+
+		tempmax = max(tempmax, mtk_thermal_bank_temperature(bank));
+
+		mtk_thermal_put_bank(bank);
+	}
+
+	*temperature = tempmax;
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops mtk_thermal_ops = {
+	.get_temp = mtk_read_temp,
+};
+
+static void mtk_thermal_init_bank(struct mtk_thermal *mt, int num,
+				  u32 apmixed_phys_base, u32 auxadc_phys_base)
+{
+	struct mtk_thermal_bank *bank = &mt->banks[num];
+	const struct mtk_thermal_data *conf = mt->conf;
+	int i;
+
+	bank->id = num;
+	bank->mt = mt;
+
+	mtk_thermal_get_bank(bank);
+
+	/* bus clock 66M counting unit is 12 * 15.15ns * 256 = 46.540us */
+	writel(TEMP_MONCTL1_PERIOD_UNIT(12), mt->thermal_base + TEMP_MONCTL1);
+
+	/*
+	 * filt interval is 1 * 46.540us = 46.54us,
+	 * sen interval is 429 * 46.540us = 19.96ms
+	 */
+	writel(TEMP_MONCTL2_FILTER_INTERVAL(1) |
+			TEMP_MONCTL2_SENSOR_INTERVAL(429),
+			mt->thermal_base + TEMP_MONCTL2);
+
+	/* poll is set to 10u */
+	writel(TEMP_AHBPOLL_ADC_POLL_INTERVAL(768),
+	       mt->thermal_base + TEMP_AHBPOLL);
+
+	/* temperature sampling control, 1 sample */
+	writel(0x0, mt->thermal_base + TEMP_MSRCTL0);
+
+	/* exceed this polling time, IRQ would be inserted */
+	writel(0xffffffff, mt->thermal_base + TEMP_AHBTO);
+
+	/* number of interrupts per event, 1 is enough */
+	writel(0x0, mt->thermal_base + TEMP_MONIDET0);
+	writel(0x0, mt->thermal_base + TEMP_MONIDET1);
+
+	/*
+	 * The MT8173 thermal controller does not have its own ADC. Instead it
+	 * uses AHB bus accesses to control the AUXADC. To do this the thermal
+	 * controller has to be programmed with the physical addresses of the
+	 * AUXADC registers and with the various bit positions in the AUXADC.
+	 * Also the thermal controller controls a mux in the APMIXEDSYS register
+	 * space.
+	 */
+
+	/*
+	 * this value will be stored to TEMP_PNPMUXADDR (TEMP_SPARE0)
+	 * automatically by hw
+	 */
+	writel(BIT(conf->auxadc_channel), mt->thermal_base + TEMP_ADCMUX);
+
+	/* AHB address for auxadc mux selection */
+	writel(auxadc_phys_base + AUXADC_CON1_CLR_V,
+	       mt->thermal_base + TEMP_ADCMUXADDR);
+
+	/* AHB address for pnp sensor mux selection */
+	writel(apmixed_phys_base + APMIXED_SYS_TS_CON1,
+	       mt->thermal_base + TEMP_PNPMUXADDR);
+
+	/* AHB value for auxadc enable */
+	writel(BIT(conf->auxadc_channel), mt->thermal_base + TEMP_ADCEN);
+
+	/* AHB address for auxadc enable (channel 0 immediate mode selected) */
+	writel(auxadc_phys_base + AUXADC_CON1_SET_V,
+	       mt->thermal_base + TEMP_ADCENADDR);
+
+	/* AHB address for auxadc valid bit */
+	writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel),
+	       mt->thermal_base + TEMP_ADCVALIDADDR);
+
+	/* AHB address for auxadc voltage output */
+	writel(auxadc_phys_base + AUXADC_DATA(conf->auxadc_channel),
+	       mt->thermal_base + TEMP_ADCVOLTADDR);
+
+	/* read valid & voltage are at the same register */
+	writel(0x0, mt->thermal_base + TEMP_RDCTRL);
+
+	/* indicate where the valid bit is */
+	writel(TEMP_ADCVALIDMASK_VALID_HIGH | TEMP_ADCVALIDMASK_VALID_POS(12),
+	       mt->thermal_base + TEMP_ADCVALIDMASK);
+
+	/* no shift */
+	writel(0x0, mt->thermal_base + TEMP_ADCVOLTAGESHIFT);
+
+	/* enable auxadc mux write transaction */
+	writel(TEMP_ADCWRITECTRL_ADC_MUX_WRITE,
+	       mt->thermal_base + TEMP_ADCWRITECTRL);
+
+	for (i = 0; i < conf->bank_data[num].num_sensors; i++)
+		writel(conf->sensor_mux_values[conf->bank_data[num].sensors[i]],
+		       mt->thermal_base + conf->adcpnp[i]);
+
+	writel((1 << conf->bank_data[num].num_sensors) - 1,
+	       mt->thermal_base + TEMP_MONCTL0);
+
+	writel(TEMP_ADCWRITECTRL_ADC_PNP_WRITE |
+	       TEMP_ADCWRITECTRL_ADC_MUX_WRITE,
+	       mt->thermal_base + TEMP_ADCWRITECTRL);
+
+	mtk_thermal_put_bank(bank);
+}
+
+static u64 of_get_phys_base(struct device_node *np)
+{
+	u64 size64;
+	const __be32 *regaddr_p;
+
+	regaddr_p = of_get_address(np, 0, &size64, NULL);
+	if (!regaddr_p)
+		return OF_BAD_ADDR;
+
+	return of_translate_address(np, regaddr_p);
+}
+
+static int mtk_thermal_get_calibration_data(struct device *dev,
+					    struct mtk_thermal *mt)
+{
+	struct nvmem_cell *cell;
+	u32 *buf;
+	size_t len;
+	int i, ret = 0;
+
+	/* Start with default values */
+	mt->adc_ge = 512;
+	for (i = 0; i < mt->conf->num_sensors; i++)
+		mt->vts[i] = 260;
+	mt->degc_cali = 40;
+	mt->o_slope = 0;
+
+	cell = nvmem_cell_get(dev, "calibration-data");
+	if (IS_ERR(cell)) {
+		if (PTR_ERR(cell) == -EPROBE_DEFER)
+			return PTR_ERR(cell);
+		return 0;
+	}
+
+	buf = (u32 *)nvmem_cell_read(cell, &len);
+
+	nvmem_cell_put(cell);
+
+	if (IS_ERR(buf))
+		return PTR_ERR(buf);
+
+	if (len < 3 * sizeof(u32)) {
+		dev_warn(dev, "invalid calibration data\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (buf[0] & MT8173_CALIB_BUF0_VALID) {
+		mt->adc_ge = MT8173_CALIB_BUF1_ADC_GE(buf[1]);
+		mt->vts[MT8173_TS1] = MT8173_CALIB_BUF0_VTS_TS1(buf[0]);
+		mt->vts[MT8173_TS2] = MT8173_CALIB_BUF0_VTS_TS2(buf[0]);
+		mt->vts[MT8173_TS3] = MT8173_CALIB_BUF1_VTS_TS3(buf[1]);
+		mt->vts[MT8173_TS4] = MT8173_CALIB_BUF2_VTS_TS4(buf[2]);
+		mt->vts[MT8173_TSABB] = MT8173_CALIB_BUF2_VTS_TSABB(buf[2]);
+		mt->degc_cali = MT8173_CALIB_BUF0_DEGC_CALI(buf[0]);
+		if (MT8173_CALIB_BUF1_ID(buf[1]) &
+		    MT8173_CALIB_BUF0_O_SLOPE_SIGN(buf[0]))
+			mt->o_slope = -MT8173_CALIB_BUF0_O_SLOPE(buf[0]);
+		else
+			mt->o_slope = MT8173_CALIB_BUF0_O_SLOPE(buf[0]);
+	} else {
+		dev_info(dev, "Device not calibrated, using default calibration values\n");
+	}
+
+out:
+	kfree(buf);
+
+	return ret;
+}
+
+static const struct of_device_id mtk_thermal_of_match[] = {
+	{
+		.compatible = "mediatek,mt8173-thermal",
+		.data = (void *)&mt8173_thermal_data,
+	},
+	{
+		.compatible = "mediatek,mt2701-thermal",
+		.data = (void *)&mt2701_thermal_data,
+	},
+	{
+		.compatible = "mediatek,mt2712-thermal",
+		.data = (void *)&mt2712_thermal_data,
+	},
+	{
+		.compatible = "mediatek,mt7622-thermal",
+		.data = (void *)&mt7622_thermal_data,
+	}, {
+	},
+};
+MODULE_DEVICE_TABLE(of, mtk_thermal_of_match);
+
+static int mtk_thermal_probe(struct platform_device *pdev)
+{
+	int ret, i;
+	struct device_node *auxadc, *apmixedsys, *np = pdev->dev.of_node;
+	struct mtk_thermal *mt;
+	struct resource *res;
+	u64 auxadc_phys_base, apmixed_phys_base;
+	struct thermal_zone_device *tzdev;
+
+	mt = devm_kzalloc(&pdev->dev, sizeof(*mt), GFP_KERNEL);
+	if (!mt)
+		return -ENOMEM;
+
+	mt->conf = of_device_get_match_data(&pdev->dev);
+
+	mt->clk_peri_therm = devm_clk_get(&pdev->dev, "therm");
+	if (IS_ERR(mt->clk_peri_therm))
+		return PTR_ERR(mt->clk_peri_therm);
+
+	mt->clk_auxadc = devm_clk_get(&pdev->dev, "auxadc");
+	if (IS_ERR(mt->clk_auxadc))
+		return PTR_ERR(mt->clk_auxadc);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	mt->thermal_base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(mt->thermal_base))
+		return PTR_ERR(mt->thermal_base);
+
+	ret = mtk_thermal_get_calibration_data(&pdev->dev, mt);
+	if (ret)
+		return ret;
+
+	mutex_init(&mt->lock);
+
+	mt->dev = &pdev->dev;
+
+	auxadc = of_parse_phandle(np, "mediatek,auxadc", 0);
+	if (!auxadc) {
+		dev_err(&pdev->dev, "missing auxadc node\n");
+		return -ENODEV;
+	}
+
+	auxadc_phys_base = of_get_phys_base(auxadc);
+
+	of_node_put(auxadc);
+
+	if (auxadc_phys_base == OF_BAD_ADDR) {
+		dev_err(&pdev->dev, "Can't get auxadc phys address\n");
+		return -EINVAL;
+	}
+
+	apmixedsys = of_parse_phandle(np, "mediatek,apmixedsys", 0);
+	if (!apmixedsys) {
+		dev_err(&pdev->dev, "missing apmixedsys node\n");
+		return -ENODEV;
+	}
+
+	apmixed_phys_base = of_get_phys_base(apmixedsys);
+
+	of_node_put(apmixedsys);
+
+	if (apmixed_phys_base == OF_BAD_ADDR) {
+		dev_err(&pdev->dev, "Can't get auxadc phys address\n");
+		return -EINVAL;
+	}
+
+	ret = device_reset(&pdev->dev);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(mt->clk_auxadc);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't enable auxadc clk: %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(mt->clk_peri_therm);
+	if (ret) {
+		dev_err(&pdev->dev, "Can't enable peri clk: %d\n", ret);
+		goto err_disable_clk_auxadc;
+	}
+
+	for (i = 0; i < mt->conf->num_banks; i++)
+		mtk_thermal_init_bank(mt, i, apmixed_phys_base,
+				      auxadc_phys_base);
+
+	platform_set_drvdata(pdev, mt);
+
+	tzdev = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mt,
+						     &mtk_thermal_ops);
+	if (IS_ERR(tzdev)) {
+		ret = PTR_ERR(tzdev);
+		goto err_disable_clk_peri_therm;
+	}
+
+	return 0;
+
+err_disable_clk_peri_therm:
+	clk_disable_unprepare(mt->clk_peri_therm);
+err_disable_clk_auxadc:
+	clk_disable_unprepare(mt->clk_auxadc);
+
+	return ret;
+}
+
+static int mtk_thermal_remove(struct platform_device *pdev)
+{
+	struct mtk_thermal *mt = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(mt->clk_peri_therm);
+	clk_disable_unprepare(mt->clk_auxadc);
+
+	return 0;
+}
+
+static struct platform_driver mtk_thermal_driver = {
+	.probe = mtk_thermal_probe,
+	.remove = mtk_thermal_remove,
+	.driver = {
+		.name = "mtk-thermal",
+		.of_match_table = mtk_thermal_of_match,
+	},
+};
+
+module_platform_driver(mtk_thermal_driver);
+
+MODULE_AUTHOR("Louis Yu <louis.yu@mediatek.com>");
+MODULE_AUTHOR("Dawei Chien <dawei.chien@mediatek.com>");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_AUTHOR("Hanyi Wu <hanyi.wu@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek thermal driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_ipi.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_ipi.c
new file mode 100644
index 0000000..ea46a10
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_ipi.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include "mtk_thermal_ipi.h"
+#include "thermal_risk_monitor.h"
+
+static DEFINE_MUTEX(ipi_s_lock);
+static int is_s_ipi_registered;
+static int g_ack_data;
+static unsigned int g_ipi_reply;
+static struct thermal_ipi_data g_ipi_recv_data;
+
+static int thermal_ipi_recv_handler(unsigned int id, void *prdata, void *in_msg, unsigned int len)
+{
+	struct thermal_ipi_data *data = (struct thermal_ipi_data *)in_msg;
+	unsigned int cmd;
+
+	cmd = data->cmd;
+	pr_info("[thermal ipi] Recv thermal cmd=%d\n", cmd);
+
+	g_ipi_reply = IPI_SUCCESS;
+	switch (cmd) {
+		case AP_WAKEUP_EVENT:
+			keep_ap_wake();
+			break;
+		default:
+			pr_err("ERROR: Unknown ipi request from sspm side!\n");
+			g_ipi_reply = IPI_WRONG_MSG_TYPE;
+	}
+
+	return 0;
+}
+
+static int thermal_ipi_recv_thread(void *data)
+{
+	int ret;
+
+	ret = mtk_ipi_register(&sspm_ipidev, IPIR_C_THERMAL, thermal_ipi_recv_handler, NULL,
+		(void *)&g_ipi_recv_data);
+
+	if (ret != 0) {
+		pr_err("[thermal ipi] Fail to register IPIR_C_THERMAL ret:%d\n",
+			ret);
+		return -1;
+	}
+
+	while (!kthread_should_stop()) {
+		mtk_ipi_recv_reply(&sspm_ipidev, IPIR_C_THERMAL, (void *)&g_ipi_reply, 1);
+	}
+
+	return 0;
+}
+
+unsigned int thermal_to_sspm(
+	unsigned int cmd, struct thermal_ipi_data *thermal_data)
+{
+	unsigned int ack_data;
+	int ret;
+
+	mutex_lock(&ipi_s_lock);
+	if (!is_s_ipi_registered) {
+		ret = mtk_ipi_register(&sspm_ipidev, IPIS_C_THERMAL, NULL, NULL,
+				(void *)&g_ack_data);
+
+		if (ret != 0) {
+			pr_err("[thermal ipi] Fail to register IPIS_C_THERMAL ret:%d\n",
+					ret);
+			ack_data = IPI_NOT_SUPPORT;
+			goto end;
+		}
+
+		is_s_ipi_registered = 1;
+	}
+
+	thermal_data->cmd = cmd;
+	ret = mtk_ipi_send_compl(&sspm_ipidev, IPIS_C_THERMAL, IPI_SEND_POLLING,
+		(void *)thermal_data, THERMAL_SLOT_NUM, THERMAL_IPI_TIMEOUT_MS);
+
+	if (ret != 0) {
+		pr_err("[thermal ipi] send cmd(%d) error ret:%d\n", cmd, ret);
+		ack_data = IPI_FAIL;
+		goto end;
+	}
+
+	ack_data = g_ack_data;
+
+	if (ack_data != IPI_SUCCESS)
+		pr_info("[thermal ipi] cmd(%d) reply data:%u\n", cmd, ack_data);
+end:
+	mutex_unlock(&ipi_s_lock);
+
+	return ack_data;
+}
+
+static int __init mtk_thermal_ipi_init(void)
+{
+	struct task_struct *ts1;
+	int err;
+
+	ts1 = kthread_run(thermal_ipi_recv_thread, NULL, "sspm_to_thermal");
+	if (IS_ERR(ts1)) {
+		err = PTR_ERR(ts1);
+		pr_err("[thermal ipi] Fail to create a thread for ipi recv, %d\n",
+			err);
+
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit mtk_thermal_ipi_exit(void)
+{
+	mtk_ipi_unregister(&sspm_ipidev, IPIS_C_THERMAL);
+	mtk_ipi_unregister(&sspm_ipidev, IPIR_C_THERMAL);
+}
+
+module_init(mtk_thermal_ipi_init);
+module_exit(mtk_thermal_ipi_exit);
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_ipi.h b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_ipi.h
new file mode 100644
index 0000000..2d22e62
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_ipi.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#ifndef __MTK_THERMAL_IPI_H__
+#define __MTK_THERMAL_IPI_H__
+#include <sspm_ipi_id.h>
+/*==================================================
+ * Definition or macro function
+ *==================================================
+ */
+#define THERMAL_SLOT_NUM (4)
+#define THERMAL_IPI_TIMEOUT_MS (2000)
+
+enum thermal_ipi_reply_data {
+	IPI_SUCCESS,
+	IPI_FAIL,
+	IPI_NOT_SUPPORT,
+	IPI_WRONG_MSG_TYPE,
+	IPI_SEND_LOCK_TIMEOUT,
+	NUM_THERMAL_IPI_REPLY
+};
+
+enum thermal_ipi_msg_type {
+	TRM_IPI_INIT_GRP1,
+	TRM_IPI_DISABLE_CMD,
+	TIA_IPI_INIT_GRP1,
+	AP_WAKEUP_EVENT,
+	NUM_THERMAL_IPI_TYPE
+};
+
+/*==================================================
+ * Data structure
+ *==================================================
+ */
+struct thermal_ipi_data {
+	unsigned int cmd;
+	union {
+		struct {
+			int arg[THERMAL_SLOT_NUM - 1];
+		} data;
+	} u;
+};
+/*==================================================
+ * Extern function
+ *==================================================
+ */
+extern unsigned int thermal_to_sspm(unsigned int cmd,
+	struct thermal_ipi_data *thermal_data);
+#endif
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_trace.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_trace.c
new file mode 100644
index 0000000..555fe70
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_trace.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+#define CREATE_TRACE_POINTS
+#include <mtk_thermal_trace.h>
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_trace.h b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_trace.h
new file mode 100644
index 0000000..03b56eb
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/mtk_thermal_trace.h
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM mtk_thermal
+
+#if !defined(_TRACE_MTK_THERMAL_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_MTK_THERMAL_H
+
+#include <linux/tracepoint.h>
+#if IS_ENABLED(CONFIG_MTK_MD_THERMAL)
+#include <md_cooling.h>
+#endif
+
+#if IS_ENABLED(CONFIG_MTK_MD_THERMAL)
+TRACE_DEFINE_ENUM(MD_LV_THROTTLE_DISABLED);
+TRACE_DEFINE_ENUM(MD_LV_THROTTLE_ENABLED);
+TRACE_DEFINE_ENUM(MD_IMS_ONLY);
+TRACE_DEFINE_ENUM(MD_NO_IMS);
+TRACE_DEFINE_ENUM(MD_OFF);
+
+#define show_md_status(status)						\
+	__print_symbolic(status,					\
+		{ MD_LV_THROTTLE_DISABLED, "LV_THROTTLE_DISABLED"},	\
+		{ MD_LV_THROTTLE_ENABLED,  "LV_THROTTLE_ENABLED"},	\
+		{ MD_IMS_ONLY,  "IMS_ONLY"},				\
+		{ MD_NO_IMS,    "NO_IMS"},				\
+		{ MD_OFF,       "MD_OFF"})
+
+#if IS_ENABLED(CONFIG_MTK_MD_THERMAL_MUTT)
+TRACE_EVENT(md_mutt_limit,
+
+	TP_PROTO(struct md_cooling_device *md_cdev, enum md_status status),
+
+	TP_ARGS(md_cdev, status),
+
+	TP_STRUCT__entry(
+		__field(unsigned long, lv)
+		__field(unsigned int, id)
+		__field(enum md_status, status)
+	),
+
+	TP_fast_assign(
+		__entry->lv = md_cdev->target_level;
+		__entry->id = md_cdev->pa_id;
+		__entry->status = status;
+	),
+
+	TP_printk("mutt_lv=%ld, pa_id=%d, status=%s",
+		__entry->lv, __entry->id, show_md_status(__entry->status))
+);
+#endif /* CONFIG_MTK_MD_THERMAL_MUTT */
+
+#if IS_ENABLED(CONFIG_MTK_MD_THERMAL_TX_POWER)
+TRACE_EVENT(md_tx_pwr_limit,
+
+	TP_PROTO(struct md_cooling_device *md_cdev, enum md_status status),
+
+	TP_ARGS(md_cdev, status),
+
+	TP_STRUCT__entry(
+		__field(unsigned long, lv)
+		__field(unsigned int, pwr)
+		__field(unsigned int, id)
+		__field(enum md_status, status)
+	),
+
+	TP_fast_assign(
+		__entry->lv = md_cdev->target_level;
+		__entry->pwr =
+			md_cdev->throttle_tx_power[md_cdev->target_level];
+		__entry->id = md_cdev->pa_id;
+		__entry->status = status;
+	),
+
+	TP_printk("tx_pwr_lv=%ld, tx_pwr=%d, pa_id=%d, status=%s",
+		__entry->lv, __entry->pwr, __entry->id,
+		show_md_status(__entry->status))
+);
+#endif /* CONFIG_MTK_MD_THERMAL_TX_POWER */
+
+#if IS_ENABLED(CONFIG_MTK_MD_THERMAL_SCG_OFF)
+TRACE_EVENT(md_scg_off,
+
+	TP_PROTO(struct md_cooling_device *md_cdev, enum md_status status),
+
+	TP_ARGS(md_cdev, status),
+
+	TP_STRUCT__entry(
+		__field(unsigned long, off)
+		__field(unsigned int, id)
+		__field(enum md_status, status)
+	),
+
+	TP_fast_assign(
+		__entry->off = md_cdev->target_level;
+		__entry->id = md_cdev->pa_id;
+		__entry->status = status;
+	),
+
+	TP_printk("scg_off=%ld, pa_id=%d, status=%s",
+		__entry->off, __entry->id, show_md_status(__entry->status))
+);
+#endif /* CONFIG_MTK_MD_THERMAL_SCG_OFF */
+#endif /* CONFIG_MTK_MD_THERMAL */
+
+#endif /* _TRACE_MTK_THERMAL_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_PATH .
+#define TRACE_INCLUDE_FILE mtk_thermal_trace
+#include <trace/define_trace.h>
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/pmic_temp.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/pmic_temp.c
new file mode 100644
index 0000000..f00c829
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/pmic_temp.c
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+
+
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/acpi.h>
+#include <linux/thermal.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#include <iio/mt635x-auxadc.h>
+#include <linux/iio/consumer.h>
+#include <linux/nvmem-consumer.h>
+/*=============================================================
+ *Local variable definition
+ *=============================================================
+ */
+
+/*diff part in pmic, vcore, vproc, vgpu*/
+struct pmic_cali_data {
+	int cali_factor;
+	int slope1;
+	int slope2;
+	int intercept;
+	int iio_chan_id;
+	int o_vts;
+};
+
+/*common part in pmic, vcore, vproc, vgpu*/
+struct pmic_efuse_data {
+	int degc_cali;
+	int adc_cali_en;
+	int o_slope;
+	int o_slope_sign;
+	int id;
+	u16 *efuse_buff;
+	int num_efuse;
+	int (*pmic_get_efuse)(struct device *dev,
+				struct pmic_efuse_data *temp_info,
+				struct pmic_cali_data *cali_data);
+};
+
+struct pmic_temp_info {
+	struct device *dev;
+	struct pmic_cali_data *cali_data;
+	struct pmic_efuse_data *efuse_data;
+	struct iio_channel *chan_temp;
+	struct iio_channel *vcore_temp;
+	struct iio_channel *vproc_temp;
+	struct iio_channel *vgpu_temp;
+};
+
+
+/*=============================================================*/
+
+static int pmic_raw_to_temp(struct pmic_cali_data *cali_data,
+						int val)
+{
+	int t_current;
+
+	int y_curr = val;
+
+	t_current = cali_data->intercept +
+		((cali_data->slope1 * y_curr) / (cali_data->slope2));
+
+	return t_current;
+}
+
+static int mt6330_read_efuse(struct device *dev, struct pmic_efuse_data *efuse,
+	struct pmic_cali_data *cali_data)
+{
+	int val_l = 0;
+	int val_h = 0;
+	size_t len;
+	struct nvmem_cell *cell_1;
+	u16 *buf;
+	int i;
+
+/*read efuse once*/
+	if (cali_data->iio_chan_id == 0) {
+		efuse->efuse_buff = devm_kcalloc(dev, efuse->num_efuse,
+				  sizeof(*efuse->efuse_buff), GFP_KERNEL);
+		if (!efuse->efuse_buff)
+			return -ENOMEM;
+		cell_1 = devm_nvmem_cell_get(dev, "t_e_data1@6c");
+		if (IS_ERR(cell_1)) {
+			dev_err(dev, "Error: Failed to get nvmem cell %s\n",
+				"t_e_data1@6c");
+			return PTR_ERR(cell_1);
+		}
+		buf = (u16 *)nvmem_cell_read(cell_1, &len);
+		nvmem_cell_put(cell_1);
+
+		if (IS_ERR(buf))
+			return PTR_ERR(buf);
+		for (i = 0; i < efuse->num_efuse; i++) {
+			efuse->efuse_buff[i] = buf[i];
+		}
+		kfree(buf);		
+	}
+
+	if (cali_data->iio_chan_id == 0) {
+		val_l = ((efuse->efuse_buff[1] & GENMASK(15, 8)) >> 8);
+		val_h = ((efuse->efuse_buff[2] & GENMASK(4, 0)) << 8);
+		cali_data->o_vts = (val_l | val_h);
+	} else if (cali_data->iio_chan_id == 1) {
+		val_l = ((efuse->efuse_buff[2] & GENMASK(15, 8)) >> 8);
+		val_h = ((efuse->efuse_buff[3] & GENMASK(4, 0)) << 8);
+		cali_data->o_vts = (val_l | val_h);
+	}else if (cali_data->iio_chan_id == 2) {
+		val_l = ((efuse->efuse_buff[3] & GENMASK(15, 8)) >> 8);
+		val_h = ((efuse->efuse_buff[4] & GENMASK(4, 0)) << 8);
+		cali_data->o_vts = (val_l | val_h);
+	} else if (cali_data->iio_chan_id == 3) {
+		val_l = ((efuse->efuse_buff[4] & GENMASK(15, 8)) >> 8);
+		val_h = ((efuse->efuse_buff[5] & GENMASK(4, 0)) << 8);
+		cali_data->o_vts = (val_l | val_h);
+	} else {
+		dev_info(dev, "unsupported iio_chan_id%d\n", cali_data->iio_chan_id);
+	}
+	efuse->degc_cali = ((efuse->efuse_buff[0] & GENMASK(13, 8)) >> 8);
+	efuse->adc_cali_en = ((efuse->efuse_buff[0] & BIT(14)) >> 14);
+	efuse->o_slope_sign = ((efuse->efuse_buff[1] & BIT(7)) >> 7);
+	efuse->o_slope = ((efuse->efuse_buff[1] & GENMASK(6, 1)) >> 1);
+	efuse->id = (efuse->efuse_buff[1] & BIT(0));
+
+	return 0;
+}
+
+int mtktspmic_get_cali_data(struct device *dev,
+				struct pmic_temp_info *pmic_info)
+{
+	struct pmic_cali_data *cali = pmic_info->cali_data;
+	struct pmic_efuse_data *efuse= pmic_info->efuse_data;
+
+	if (efuse->pmic_get_efuse(dev, efuse, cali) < 0)
+		return -1;
+
+	if (efuse->id == 0)
+		efuse->o_slope = 0;
+
+	/* adc_cali_en=0;//FIX ME */
+
+	if (efuse->adc_cali_en == 0) {	/* no calibration */
+		//mtktspmic_info("[pmic_debug]  It isn't calibration values\n");
+		cali->o_vts = 1600;
+		efuse->degc_cali = 50;
+		efuse->o_slope_sign = 0;
+		efuse->o_slope = 0;
+	}
+
+	if (efuse->degc_cali < 38 || efuse->degc_cali > 60)
+		efuse->degc_cali = 53;
+
+	dev_info(dev, "[pmic_debug] o_vts    = 0x%x\n", cali->o_vts);
+	dev_info(dev, "[pmic_debug] degc_cali= 0x%x\n", efuse->degc_cali);
+	dev_info(dev, "[pmic_debug] adc_cali_en        = 0x%x\n", efuse->adc_cali_en);
+	dev_info(dev, "[pmic_debug] o_slope        = 0x%x\n", efuse->o_slope);
+	dev_info(dev, "[pmic_debug] o_slope_sign        = 0x%x\n", efuse->o_slope_sign);
+	dev_info(dev, "[pmic_debug] id        = 0x%x\n", efuse->id);
+	return 0;
+}
+
+void mtktspmic_get_temp_convert_params(struct pmic_temp_info *data)
+{
+	int vbe_t;
+	int factor;
+	struct pmic_temp_info *temp_info = data;
+	struct pmic_cali_data *cali = temp_info->cali_data;
+	struct pmic_efuse_data *efuse= temp_info->efuse_data;
+
+	factor = cali->cali_factor;
+
+	cali->slope1 = (100 * 1000 * 10);	/* 1000 is for 0.001 degree */
+
+	if (efuse->o_slope_sign == 0)
+		cali->slope2 = -(factor + efuse->o_slope);
+	else
+		cali->slope2 = -(factor - efuse->o_slope);
+
+	vbe_t = (-1) * ((((cali->o_vts) * 1800)) / 4096) * 1000;
+
+
+	if (efuse->o_slope_sign == 0)
+		cali->intercept = (vbe_t * 1000) / (-(factor + efuse->o_slope * 10));
+	/*0.001 degree */
+	else
+		cali->intercept = (vbe_t * 1000) / (-(factor - efuse->o_slope * 10));
+	/*0.001 degree */
+
+	cali->intercept = cali->intercept + (efuse->degc_cali * (1000 / 2));
+	/* 1000 is for 0.1 degree */
+}
+/*==================================================
+ * Support chips
+ *==================================================
+ */
+
+static struct pmic_efuse_data mt6330_pmic_efuse_data = {
+	.degc_cali = 50,
+	.adc_cali_en = 0,
+	.o_slope = 0,
+	.o_slope_sign = 0,
+	.id = 0,
+	.num_efuse = 6,
+	.pmic_get_efuse = mt6330_read_efuse,
+};
+
+static const struct of_device_id pmic_temp_of_match[] = {
+	{
+		.compatible = "mediatek,mt6330-pmic-temp",
+		.data = (void *)&mt6330_pmic_efuse_data,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, pmic_temp_of_match);
+
+static int pmic_get_temp(void *data, int *temp)
+{
+	int val = 0;
+	int ret;
+	struct pmic_temp_info *temp_info = (struct pmic_temp_info *)data;
+	struct pmic_cali_data *cali_data = temp_info->cali_data;
+	int iio_chan_id = cali_data->iio_chan_id;
+
+	*temp = -127000;
+	if ((iio_chan_id == 0) && (!IS_ERR(temp_info->chan_temp)))
+		ret = iio_read_channel_processed(temp_info->chan_temp, &val);
+	else if ((iio_chan_id == 1) && (!IS_ERR(temp_info->vcore_temp)))
+		ret = iio_read_channel_processed(temp_info->vcore_temp, &val);
+	else if ((iio_chan_id == 2) && (!IS_ERR(temp_info->vproc_temp)))
+		ret = iio_read_channel_processed(temp_info->vproc_temp, &val);
+	else if ((iio_chan_id == 3) && (!IS_ERR(temp_info->vgpu_temp)))
+		ret = iio_read_channel_processed(temp_info->vgpu_temp, &val);
+	else {
+		pr_notice("unsupport iio channel\n");
+		*temp = -127000;
+		return 0;
+	}
+	if (ret < 0) {
+		pr_notice("pmic_chip_temp read fail, ret=%d\n", ret);
+		*temp = -127000;
+		dev_info(temp_info->dev, "temp = %d\n", *temp);
+		return 0;
+	}
+
+	*temp = pmic_raw_to_temp(cali_data, val);
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops pmic_temp_ops = {
+	.get_temp = pmic_get_temp,
+};
+
+static int pmic_temp_parse_cali_data(struct device *dev,
+					struct pmic_temp_info *pmic_info)
+{
+	struct device_node *np = dev->of_node;
+	int ret;
+	struct pmic_cali_data *cali_data;
+
+	pmic_info->cali_data = devm_kzalloc(dev,
+					 sizeof(*pmic_info->cali_data),
+					 GFP_KERNEL);
+	if (!pmic_info->cali_data)
+		return -ENOMEM;
+	cali_data= pmic_info->cali_data;
+
+	ret = of_property_read_u32(np, "pmic_temp,cali_factor",
+					(unsigned int *)&(cali_data->cali_factor));
+	if (ret < 0) {
+		dev_info(dev, "Failed to read cali_factor: %d\n",
+			ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int pmic_temp_parse_iio_channel(struct device *dev,
+					struct pmic_temp_info *pmic_info)
+{
+	struct device_node *np = dev->of_node;
+	int ret;
+	struct pmic_temp_info *temp_info = pmic_info;
+	struct pmic_cali_data *cali_data= temp_info->cali_data;
+
+	ret = of_property_read_u32(np, "pmic_temp,iio_chan",
+					&(cali_data->iio_chan_id));
+	if (ret < 0) {
+		dev_info(dev, "Failed to read iio_channel_index: %d\n",
+			ret);
+		return ret;
+	}
+
+	if (cali_data->iio_chan_id == 0) {
+		pmic_info->chan_temp = devm_iio_channel_get(dev, "pmic_chip_temp");
+		ret = PTR_ERR_OR_ZERO(pmic_info->chan_temp);
+		if (ret) {
+			if (ret != -EPROBE_DEFER)
+				pr_err("pmic_chip_temp auxadc get fail, ret=%d\n", ret);
+				return ret;
+		}
+	} else if (cali_data->iio_chan_id == 1) {
+		pmic_info->vcore_temp = devm_iio_channel_get(dev, "pmic_buck1_temp");
+		ret = PTR_ERR_OR_ZERO(pmic_info->vcore_temp);
+		if (ret) {
+			if (ret != -EPROBE_DEFER)
+				pr_err("pmic_chip_temp auxadc get fail, ret=%d\n", ret);
+				return ret;
+		}
+	} else if (cali_data->iio_chan_id == 2) {
+		pmic_info->vproc_temp = devm_iio_channel_get(dev, "pmic_buck2_temp");
+		ret = PTR_ERR_OR_ZERO(pmic_info->vproc_temp);
+		if (ret) {
+			if (ret != -EPROBE_DEFER)
+				pr_err("pmic_chip_temp auxadc get fail, ret=%d\n", ret);
+					return ret;
+		}
+	} else if (cali_data->iio_chan_id == 3) {
+		pmic_info->vgpu_temp = devm_iio_channel_get(dev, "pmic_buck3_temp");
+		ret = PTR_ERR_OR_ZERO(pmic_info->vgpu_temp);
+		if (ret) {
+			if (ret != -EPROBE_DEFER)
+				pr_err("pmic_chip_temp auxadc get fail, ret=%d\n", ret);
+					return ret;
+		}
+	} else {
+		dev_info(dev, "unsupported iio_channel_index\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int pmic_temp_probe(struct platform_device *pdev)
+{
+	struct pmic_temp_info *pmic_info;
+	struct thermal_zone_device *tz_dev;
+	int ret;
+
+	if (!pdev->dev.of_node) {
+		dev_info(&pdev->dev, "Only DT based supported\n");
+		return -ENODEV;
+	}
+
+	pmic_info = devm_kzalloc(&pdev->dev, sizeof(*pmic_info), GFP_KERNEL);
+	if (!pmic_info)
+		return -ENOMEM;
+
+	pmic_info->dev = &pdev->dev;
+	pmic_info->efuse_data= (struct pmic_efuse_data *)
+		of_device_get_match_data(&pdev->dev);
+
+	ret = pmic_temp_parse_cali_data(&pdev->dev, pmic_info);
+	if (ret < 0)
+		return ret;
+
+	ret = pmic_temp_parse_iio_channel(&pdev->dev, pmic_info);
+	if (ret < 0)
+		return ret;
+
+	ret = mtktspmic_get_cali_data(&pdev->dev, pmic_info);
+	if (ret < 0)
+		return ret;
+
+	mtktspmic_get_temp_convert_params(pmic_info);
+
+	platform_set_drvdata(pdev, pmic_info);
+
+	tz_dev = devm_thermal_zone_of_sensor_register(
+			&pdev->dev, 0, pmic_info, &pmic_temp_ops);
+	if (IS_ERR(tz_dev)) {
+		ret = PTR_ERR(tz_dev);
+		dev_info(&pdev->dev, "Thermal zone sensor register fail:%d\n",
+			ret);
+		return ret;
+	}
+	return 0;
+}
+
+static struct platform_driver pmic_temp_driver = {
+	.probe = pmic_temp_probe,
+	.driver = {
+		.name = "mtk-pmic-temp",
+		.of_match_table = pmic_temp_of_match,
+	},
+};
+
+module_platform_driver(pmic_temp_driver);
+
+MODULE_AUTHOR("Henry Huang <henry.huang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek pmic temp sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/soc_temp_lvts.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/soc_temp_lvts.c
new file mode 100644
index 0000000..c6f5ffc
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/soc_temp_lvts.c
@@ -0,0 +1,1265 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kobject.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/thermal.h>
+#include <linux/reset.h>
+#include <linux/clk.h>
+#include <linux/bits.h>
+#include <linux/string.h>
+#include "soc_temp_lvts.h"
+/*==================================================
+ * Macro or data structure
+ *==================================================
+ */
+#define LVTS_ATTR_RW(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_RW(_name)
+#define LVTS_ATTR_RO(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_RO(_name)
+#define LVTS_ATTR_WO(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_WO(_name)
+/*==================================================
+ * Global variable
+ *==================================================
+ */
+struct lvts_data *g_lvts_data;
+static DEFINE_MUTEX(platform_data_lock);
+static LIST_HEAD(platform_data_list);
+static void __iomem *infra_base;
+/*==================================================
+ * LVTS local common code
+ *==================================================
+ */
+static int lvts_thermal_clock_on(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	int ret;
+
+	ret = clk_prepare_enable(lvts_data->clk);
+	if (ret)
+		dev_err(dev,
+			"Error: Failed to enable lvts controller clock: %d\n",
+			ret);
+
+	return ret;
+}
+
+static void lvts_thermal_clock_off(struct lvts_data *lvts_data)
+{
+	clk_disable_unprepare(lvts_data->clk);
+}
+
+static void lvts_reset(struct lvts_data *lvts_data)
+{
+#if 0
+	int i;
+#endif
+	unsigned int temp;
+
+#if 0
+	for (i = 0; i < lvts_data->num_domain; i++) {
+		if (lvts_data->domain[i].reset)
+			reset_control_assert(lvts_data->domain[i].reset);
+
+		if (lvts_data->domain[i].reset)
+			reset_control_deassert(lvts_data->domain[i].reset);
+	}
+#endif
+	/* Reset AP domain */
+	temp = readl(infra_base + 0x120);
+	temp |= BIT(0);
+	writel(temp, infra_base + 0x120);
+
+	temp = readl(infra_base + 0x124);
+	temp |= BIT(0);
+	writel(temp, infra_base + 0x124);
+
+	/* Reset MCU domain */
+	temp = readl(infra_base + 0x730);
+	temp |= BIT(12);
+	writel(temp, infra_base + 0x730);
+
+	temp = readl(infra_base + 0x734);
+	temp |= BIT(12);
+	writel(temp, infra_base + 0x734);
+}
+
+static void device_wait_read_write_finished(struct lvts_data *lvts_data, int tc_id)
+{
+	/* Check this when LVTS device is doing a register
+	 * read or write operation
+	 */
+	struct device *dev = lvts_data->dev;
+	unsigned int cnt;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	cnt = 0;
+	while ((readl(LVTS_CONFIG_0 + base) & DEVICE_ACCESS_STARTUS)) {
+		cnt++;
+
+		if (cnt == 100) {
+			dev_err(dev,
+				"Error: LVTS %d DEVICE_ACCESS_START didn't ready\n",
+				tc_id);
+			break;
+		}
+		udelay(2);
+	}
+}
+
+static void device_identification(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	unsigned int i, data;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+
+		lvts_writel_print(ENABLE_LVTS_CTRL_CLK, LVTSCLKEN_0 + base);
+
+		lvts_write_device(lvts_data, RESET_ALL_DEVICES,  i);
+
+		lvts_write_device(lvts_data, READ_BACK_DEVICE_ID,  i);
+
+		/*  Check LVTS device ID */
+		data = (readl(LVTS_ID_0 + base) & GENMASK(7,0));
+		if (data != (0x81 + i))
+			dev_err(dev, "LVTS_TC_%d, Device ID should be 0x%x, but 0x%x\n",
+				i, (0x81 + i), data);
+	}
+}
+
+static int wait_sensing_point_idle(struct lvts_data *lvts_data, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	unsigned int cnt = 0, temp, mask, error_code;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+	mask = BIT(10) | BIT(7) | BIT(0);
+
+	while (1) {
+		temp = readl(LVTSMSRCTL1_0 + base);
+		if ((temp & mask) == 0)
+			break;
+
+		cnt++;
+
+		if (cnt == 100) {
+			/*
+			 * Error code
+			 * 000: IDLE
+			 * 001: Write transaction
+			 * 010: Waiting for read after Write
+			 * 011: Disable Continue fetching on Device
+			 * 100: Read transaction
+			 * 101: Set Device special Register for Voltage threshold
+			 * 111: Set TSMCU number for Fetch
+			 */
+			error_code = ((temp & BIT(10)) >> 10) * 4 +
+				((temp & BIT(7)) >> 7) * 2 +
+				(temp & BIT(0)) * 1;
+			dev_err(dev,
+				"Error LVTS %d sensing points aren't idle, error_code %d\n",
+				tc_id, error_code);
+			return error_code;
+		}
+		udelay(2);
+	}
+
+	return 0;
+}
+
+static void wait_all_tc_sensing_point_idle(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	unsigned int error_code, offset, size, is_error;
+	int i, cnt;
+	char buffer[100];
+
+	size = sizeof(buffer);
+	for (cnt = 0; cnt < 2; cnt++) {
+		is_error = 0;
+		offset = snprintf(buffer, size, "Error: Sensing point of LVTS ");
+
+		for (i = 0; i < lvts_data->num_tc; i++) {
+			error_code = wait_sensing_point_idle(lvts_data, i);
+			if (error_code != 0) {
+				is_error = 1;
+				offset += snprintf(buffer + offset, size - offset,
+					"%d, ", i);
+			}
+		}
+
+		if (is_error == 0)
+			break;
+
+		offset += snprintf(buffer + offset, size - offset,
+			"are not idle\n");
+		dev_info(dev, "%s", buffer);
+	}
+}
+
+static void disable_all_sensing_points(struct lvts_data *lvts_data)
+{
+	unsigned int i;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+		lvts_writel_print(DISABLE_SENSING_POINT, LVTSMONCTL0_0 + base);
+	}
+}
+
+static void enable_all_sensing_points(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	struct tc_settings *tc = lvts_data->tc;
+	unsigned int i, num;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+		num = tc[i].num_sensor;
+
+		if (num > 4) {
+			dev_err(dev,
+				"%s, LVTS%d, illegal number of sensors: %d\n",
+				__func__, i, tc[i].num_sensor);
+			continue;
+		}
+
+		lvts_writel_print(ENABLE_SENSING_POINT(num), LVTSMONCTL0_0 + base);
+	}
+}
+
+static int lvts_raw_to_temp(struct formula_coeff *co, unsigned int msr_raw)
+{
+	/* This function returns degree mC */
+
+	int temp;
+
+	temp = (co->a * ((unsigned long long int)msr_raw)) >> 14;
+	temp = temp + co->golden_temp * 500 + co->b;
+
+	return temp;
+}
+
+static unsigned int lvts_temp_to_raw(struct formula_coeff *co, int temp)
+{
+	unsigned int msr_raw = 0;
+
+	msr_raw = (((long long int)(co->golden_temp) * 500 + co->b - temp) << 14)
+		/ (-1 * co->a);
+
+	return msr_raw;
+}
+
+static int get_dominator_index(struct lvts_data *lvts_data, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	struct 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,
+			"Error: LVTS%d, dominator_sensing_point= %d should smaller than num_sensor= %d\n",
+			tc_id, tc[tc_id].dominator_sensing_point,
+			tc[tc_id].num_sensor);
+
+		dev_err(dev, "Use 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)
+{
+	unsigned int temp;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	/* 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 turn 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 to LVTS and make LVTS always report extremely low
+	 * temperature.
+	 */
+
+	/* After adding the huge offset 0x3FFF, LVTS alawys adds the
+	 * offset to MSR_RAW.
+         * When MSR_RAW is larger, SW will convert lower temperature/
+	 */
+	temp = readl(LVTSPROTCTL_0 + base);
+	lvts_writel_print(temp | 0x3FFF, LVTSPROTCTL_0 + base);
+
+	/* Disable the interrupt of AP SW */
+	temp = readl(LVTSMONINT_0 + base);
+	lvts_writel_print(temp & ~(STAGE3_INT_EN), LVTSMONINT_0 + base);
+}
+
+static void enable_hw_reboot_interrupt(struct lvts_data *lvts_data, int tc_id)
+{
+	unsigned int temp;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	/* Enable the interrupt of AP SW */
+	temp = readl(LVTSMONINT_0 + base);
+	lvts_writel_print(temp | STAGE3_INT_EN, LVTSMONINT_0 + base);
+	/* Clear the offset */
+	temp = readl(LVTSPROTCTL_0 + base);
+	lvts_writel_print(temp & ~PROTOFFSET, LVTSPROTCTL_0 + base);
+}
+
+static void set_tc_hw_reboot_threshold(struct lvts_data *lvts_data,
+	int trip_point, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	struct tc_settings *tc = lvts_data->tc;
+	unsigned int msr_raw, temp, config, ts_name, d_index;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+	d_index = get_dominator_index(lvts_data, tc_id);
+
+	dev_info(dev, "%s: LVTS%d, the dominator sensing point= %d\n",
+		__func__, tc_id, d_index);
+
+	disable_hw_reboot_interrupt(lvts_data, tc_id);
+
+	temp = readl(LVTSPROTCTL_0 + base);
+	if (d_index == ALL_SENSING_POINTS) {
+		ts_name = 0;
+		/* Maximum of 4 sensing points */
+		config = (0x1 << 16);
+		lvts_writel_print(config | temp, LVTSPROTCTL_0 + base);
+	} else {
+		ts_name = tc[tc_id].sensor_map[d_index];
+		/* Select protection sensor */
+		config = ((d_index << 2) + 0x2) << 16;
+		lvts_writel_print(config | temp, LVTSPROTCTL_0 + base);
+	}
+
+	msr_raw = lvts_temp_to_raw(&lvts_data->coeff, trip_point);
+	lvts_writel_print(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)
+{
+	struct 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 == DISABLE_THERMAL_HW_REBOOT) {
+			disable_hw_reboot_interrupt(lvts_data, i);
+			continue;
+		}
+
+		set_tc_hw_reboot_threshold(lvts_data, trip_point, i);
+	}
+}
+
+static int lvts_read_tc_msr_raw(unsigned int *msr_reg)
+{
+	if (msr_reg == 0)
+		return 0;
+
+	return readl(msr_reg) & MRS_RAW_MASK;
+}
+
+static int lvts_read_all_tc_temperature(struct lvts_data *lvts_data)
+{
+	struct tc_settings *tc = lvts_data->tc;
+	unsigned int i, j, s_index, msr_raw;
+	int max_temp, current_temp;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+		for (j = 0; j < tc[i].num_sensor; j++) {
+			s_index = tc[i].sensor_map[j];
+
+			msr_raw = lvts_read_tc_msr_raw(LVTSMSR0_0 + base + 0x4 * j);
+			current_temp = lvts_raw_to_temp(&lvts_data->coeff, msr_raw);
+
+			if (msr_raw == 0)
+				current_temp = THERMAL_TEMP_INVALID;
+
+			if (i == 0 && j == 0)
+				max_temp = current_temp;
+			else if (current_temp > max_temp)
+				max_temp = current_temp;
+
+			lvts_data->sen_data[s_index].msr_raw = msr_raw;
+			lvts_data->sen_data[s_index].temp = current_temp;
+		}
+	}
+
+	return max_temp;
+}
+
+static int lvts_get_max_temp(struct lvts_data *lvts_data)
+{
+	int max_temp;
+
+	max_temp = lvts_read_all_tc_temperature(lvts_data);
+
+	return max_temp;
+}
+
+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;
+
+	if (lvts_tz->id == 0)
+		*temperature = lvts_get_max_temp(lvts_data);
+	else if (lvts_tz->id - 1 < lvts_data->num_sensor)
+		*temperature = lvts_data->sen_data[lvts_tz->id - 1].temp;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct thermal_zone_of_device_ops soc_temp_lvts_ops = {
+	.get_temp = soc_temp_lvts_read_temp,
+};
+
+static void lvts_device_close(struct lvts_data *lvts_data)
+{
+	unsigned int i;
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		base = GET_BASE_ADDR(i);
+
+		lvts_write_device(lvts_data, RESET_ALL_DEVICES,  i);
+		lvts_writel_print(DISABLE_LVTS_CTRL_CLK, LVTSCLKEN_0 + base);
+	}
+}
+
+static void tc_irq_handler(struct lvts_data *lvts_data, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	unsigned int ret = 0;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	ret = readl(LVTSMONINTSTS_0 + base);
+	/* Write back to clear interrupt status */
+	lvts_writel_print(ret, LVTSMONINTSTS_0 + base);
+
+	dev_info(dev, "[Thermal IRQ] LVTS thermal controller %d, LVTSMONINTSTS=0x%08x\n",
+		tc_id, ret);
+
+	if (ret & THERMAL_COLD_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: Cold interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_HOT_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: Hot interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_LOW_OFFSET_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: Low offset interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_HIGH_OFFSET_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: High offset interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_HOT2NORMAL_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: Hot to normal interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_COLD_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: Cold interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_HOT_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: Hot interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_LOW_OFFSET_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: Low offset interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_HIGH_OFFSET_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: High offset interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_HOT2NORMAL_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: Hot to normal interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_COLD_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: Cold interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_HOT_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: Hot interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_LOW_OFFSET_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: Low offset interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_HIGH_OFFSET_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: High offset interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_HOT2NORMAL_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: Hot to normal interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_DEVICE_TIMEOUT_INTERRUPT)
+		dev_info(dev, "[Thermal IRQ]: Device access timeout triggered\n");
+
+	if (ret & THERMAL_IMMEDIATE_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: Immediate sense interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_IMMEDIATE_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: Immediate sense interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_IMMEDIATE_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: Immediate sense interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_FILTER_INTERRUPT_0)
+		dev_info(dev, "[Thermal IRQ]: Filter sense interrupt triggered, sensor point 0\n");
+
+	if (ret & THERMAL_FILTER_INTERRUPT_1)
+		dev_info(dev, "[Thermal IRQ]: Filter sense interrupt triggered, sensor point 1\n");
+
+	if (ret & THERMAL_FILTER_INTERRUPT_2)
+		dev_info(dev, "[Thermal IRQ]: Filter sense interrupt triggered, sensor point 2\n");
+
+	if (ret & THERMAL_COLD_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: Cold interrupt triggered, sensor point 3\n");
+
+	if (ret & THERMAL_HOT_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: Hot interrupt triggered, sensor point 3\n");
+
+	if (ret & THERMAL_LOW_OFFSET_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: Low offset interrupt triggered, sensor point 3\n");
+
+	if (ret & THERMAL_HIGH_OFFSET_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: High offset triggered, sensor point 3\n");
+
+	if (ret & THERMAL_HOT2NORMAL_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: Hot to normal interrupt triggered, sensor point 3\n");
+
+	if (ret & THERMAL_IMMEDIATE_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: Immediate sense interrupt triggered, sensor point 3\n");
+
+	if (ret & THERMAL_FILTER_INTERRUPT_3)
+		dev_info(dev, "[Thermal IRQ]: Filter sense interrupt triggered, sensor point 3\n");
+
+	if (ret & THERMAL_PROTECTION_STAGE_1)
+		dev_info(dev, "[Thermal IRQ]: Thermal protection stage 1 interrupt triggered\n");
+
+	if (ret & THERMAL_PROTECTION_STAGE_2)
+		dev_info(dev, "[Thermal IRQ]: Thermal protection stage 2 interrupt triggered\n");
+
+	if (ret & THERMAL_PROTECTION_STAGE_3)
+		dev_err(dev, "[Thermal IRQ]: Thermal protection stage 3 interrupt triggered, Thermal HW reboot\n");
+}
+
+static irqreturn_t irq_handler(int irq, void *dev_id)
+{
+	struct lvts_data *lvts_data = (struct lvts_data *) dev_id;
+	struct device *dev = lvts_data->dev;
+	struct tc_settings *tc = lvts_data->tc;
+	unsigned int i, irq_bitmap[lvts_data->num_domain];
+	void __iomem *base;
+
+	for (i = 0; i < lvts_data->num_domain; i++) {
+		base = lvts_data->domain[i].base;
+		irq_bitmap[i] = readl(THERMINTST + base);
+		dev_info(dev, "%s : THERMINTST = 0x%x\n", __func__, irq_bitmap[i]);
+	}
+
+	for (i = 0; i < lvts_data->num_tc; i++) {
+		if ((irq_bitmap[tc[i].domain_index] & tc[i].irq_bit) == 0)
+			tc_irq_handler(lvts_data, i);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int prepare_calibration_data(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	struct sensor_cal_data *cal_data = &lvts_data->cal_data;
+	struct platform_ops *ops = &lvts_data->ops;
+	int i, offset, size;
+	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)
+		ops->efuse_to_cal_data(lvts_data);
+
+	cal_data->use_fake_efuse = 1;
+	if (cal_data->golden_temp != 0) {
+		cal_data->use_fake_efuse = 0;
+	} else {
+		for (i = 0; i < lvts_data->num_sensor; i++) {
+			if (cal_data->count_r[i] != 0 ||
+				cal_data->count_rc[i] != 0) {
+				cal_data->use_fake_efuse = 0;
+				break;
+			}
+		}
+	}
+
+	if (cal_data->use_fake_efuse) {
+		/* It means all efuse data are equal to 0 */
+		dev_err(dev,
+			"[lvts_cal] 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_info(dev, "[lvts_cal] golden_temp = %d\n", cal_data->golden_temp);
+
+	size = sizeof(buffer);
+	offset = snprintf(buffer, size, "[lvts_cal] num:g_count:g_count_rc ");
+	for (i = 0; i < lvts_data->num_sensor; i++)
+		offset += snprintf(buffer + offset, size - offset, "%d:%d:%d ",
+				i, cal_data->count_r[i], cal_data->count_rc[i]);
+
+	buffer[offset] = '\0';
+	dev_info(dev, "%s\n", buffer);
+
+	return 0;
+}
+
+static int get_calibration_data(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	char cell_name[8] = "e_data0";
+	struct nvmem_cell *cell;
+	u32 *buf;
+	size_t len = 0;
+	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++) {
+		cell_name[6] = '1' + i;
+		cell = nvmem_cell_get(dev, cell_name);
+		if (IS_ERR(cell)) {
+			dev_err(dev, "Error: 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(struct lvts_data *lvts_data)
+{
+	struct platform_ops *ops = &lvts_data->ops;
+	int ret;
+
+	ret = lvts_thermal_clock_on(lvts_data);
+	if (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 (IS_ENABLE(FEATURE_DEVICE_AUTO_RCK)) {
+		if (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 of_update_lvts_data(struct lvts_data *lvts_data,
+	struct platform_device *pdev)
+{
+	struct device *dev = lvts_data->dev;
+	struct power_domain *domain;
+	struct resource *res;
+	unsigned int i;
+	int ret;
+
+	lvts_data->clk = devm_clk_get(dev, "lvts_clk");
+	if (IS_ERR(lvts_data->clk))
+		return PTR_ERR(lvts_data->clk);
+
+	domain = devm_kcalloc(dev, lvts_data->num_domain, sizeof(*domain),
+			GFP_KERNEL);
+	if (!domain)
+		return -ENOMEM;
+
+	for (i = 0; i < lvts_data->num_domain; i++) {
+		/* Get base address */
+		res = platform_get_resource(pdev, IORESOURCE_MEM, i);
+		if (!res) {
+			dev_err(dev, "No IO resource, index %d\n", i);
+			return -ENXIO;
+		}
+
+		domain[i].base = devm_ioremap_resource(dev, res);
+		if (IS_ERR(domain[i].base)) {
+			dev_err(dev, "Failed to remap io, index %d\n", i);
+			return PTR_ERR(domain[i].base);
+		}
+
+		/* Get interrupt number */
+		res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
+		if (!res) {
+			dev_err(dev, "No irq resource, index %d\n", i);
+			return -EINVAL;
+		}
+		domain[i].irq_num = res->start;
+
+		/* Get reset control */
+		domain[i].reset = devm_reset_control_get_by_index(dev, i);
+		if (IS_ERR(domain[i].reset)) {
+			dev_err(dev, "Failed to get, index %d\n", i);
+			return PTR_ERR(domain[i].reset);
+		}
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	if (!res) {
+		dev_err(dev, "No IO resource, index %d\n", 2);
+		return -ENXIO;
+	}
+
+	infra_base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(infra_base)) {
+		dev_err(dev, "Failed to remap io, index %d\n", i);
+		return PTR_ERR(infra_base);
+	}
+	lvts_data->domain = domain;
+
+	lvts_data->sen_data = devm_kcalloc(dev, lvts_data->num_sensor,
+				sizeof(*lvts_data->sen_data), GFP_KERNEL);
+	if (!lvts_data->sen_data)
+		return -ENOMEM;
+
+	ret = get_calibration_data(lvts_data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+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);
+	lvts_thermal_clock_off(lvts_data);
+}
+
+static void add_match_data_to_list(struct lvts_match_data *match_data)
+{
+	if (!match_data)
+		return;
+
+	mutex_lock(&platform_data_lock);
+	list_add(&match_data->node, &platform_data_list);
+	mutex_unlock(&platform_data_lock);
+}
+
+static void init_lvts_match_data(void)
+{
+	struct lvts_match_data *match_data;
+
+	match_data = register_v5_lvts_match_data();
+	add_match_data_to_list(match_data);
+}
+
+static struct match_entry *get_match_entry(struct match_entry *table, char *chip)
+{
+	unsigned int index = 0;
+
+	while (!IS_EMPTY_STR(table[index].chip)) {
+		if (!strncmp(table[index].chip, chip, strlen(chip) - 1))
+			return &table[index];
+
+		index++;
+	}
+
+	return NULL;
+}
+
+static struct lvts_data *get_lvts_data(struct lvts_id *lvts_id)
+{
+	struct lvts_match_data *match_data;
+	struct lvts_data *lvts_data;
+	struct match_entry *table, *entry;
+
+        mutex_lock(&platform_data_lock);
+        list_for_each_entry(match_data, &platform_data_list, node) {
+                if (match_data->hw_version == lvts_id->hw_version) {
+			table = match_data->table;
+
+			entry = get_match_entry(table, lvts_id->chip);
+
+			if (entry) {
+				lvts_data = entry->lvts_data;
+				match_data->set_up_common_callbacks(lvts_data);
+
+				return lvts_data;
+			}
+                }
+        }
+        mutex_unlock(&platform_data_lock);
+
+	return NULL;
+}
+
+static int lvts_register_irq_handler(struct lvts_data *lvts_data)
+{
+	struct device *dev = lvts_data->dev;
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < lvts_data->num_domain; i++) {
+		ret = devm_request_irq(dev, lvts_data->domain[i].irq_num,
+			irq_handler, IRQF_TRIGGER_HIGH, "mtk_lvts", lvts_data);
+
+		if (ret) {
+			dev_err(dev, "LVTS IRQ register fail\n");
+			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 + 1; i++) {
+		lvts_tz = devm_kzalloc(dev, sizeof(*lvts_tz), GFP_KERNEL);
+		if (!lvts_tz) {
+			dev_err(dev,
+				"Error: Failed to allocate memory lvts tz %d\n",
+				i);
+			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)) {
+			ret = PTR_ERR(tzdev);
+			dev_err(dev,
+				"Error: Failed to register lvts tz %d, ret = %d\n",
+				lvts_tz->id, ret);
+			lvts_close(lvts_data);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void update_all_tc_hw_reboot_point(struct lvts_data *lvts_data,
+	int trip_point)
+{
+	struct tc_settings *tc = lvts_data->tc;
+	int i;
+
+	for (i = 0; i < lvts_data->num_tc; i++)
+		tc[i].hw_reboot_trip_point = trip_point;
+}
+
+static ssize_t lvts_reboot_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	char cmd[20];
+	int hw_reboot_point;
+	struct device *dev = g_lvts_data->dev;
+
+
+	if ((sscanf(buf, "%20s %d", cmd, &hw_reboot_point) != 2))
+		return -EINVAL;
+
+	if (strncmp(cmd, "update", 6))
+		return -EINVAL;
+
+	if (hw_reboot_point != DISABLE_THERMAL_HW_REBOOT)
+		dev_info(dev,"LVTS: Update HW reboot point to %d\n", hw_reboot_point);
+	else
+		dev_info(dev,"LVTS: Disable thermal HW reboot\n");
+
+	update_all_tc_hw_reboot_point(g_lvts_data, hw_reboot_point);
+	set_all_tc_hw_reboot(g_lvts_data);
+
+	return count;
+}
+
+LVTS_ATTR_WO(lvts_reboot);
+
+static struct attribute *lvts_attrs[] = {
+	&lvts_reboot_attr.attr,
+	NULL
+};
+
+static struct attribute_group lvts_attr_group = {
+	.name	= "lvts",
+	.attrs	= lvts_attrs,
+};
+
+static int lvts_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct lvts_data *lvts_data;
+	struct lvts_id *lvts_id;
+	int ret;
+
+	init_lvts_match_data();
+	lvts_id = (struct lvts_id *) of_device_get_match_data(dev);
+	lvts_data = get_lvts_data(lvts_id);
+
+	if (!lvts_data)	{
+		dev_err(dev, "Error: 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);
+	g_lvts_data = 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;
+
+	ret = sysfs_create_group(kernel_kobj, &lvts_attr_group);
+	if (ret)
+		dev_err(dev, "failed to create lvts sysfs, ret=%d!\n", ret);
+
+	return ret;
+}
+
+static 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);
+	mutex_destroy(&platform_data_lock);
+
+	sysfs_remove_group(kernel_kobj, &lvts_attr_group);
+
+	return 0;
+}
+
+static int lvts_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	/* TODO: Guarantee suspend sequence with PTP
+	 * PTP -> LVTS
+	 */
+	struct lvts_data *lvts_data;
+
+	lvts_data = (struct lvts_data *) platform_get_drvdata(pdev);
+
+	lvts_close(lvts_data);
+
+	return 0;
+}
+
+static int lvts_resume(struct platform_device *pdev)
+{
+	/* TODO: Guarantee resume sequence with PTP
+	 * LVTS -> PTP
+	 */
+	int ret;
+	struct lvts_data *lvts_data;
+
+	lvts_data = (struct lvts_data *) platform_get_drvdata(pdev);
+
+	ret = lvts_init(lvts_data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+/*==================================================
+ * Extern function
+ *==================================================
+ */
+void lvts_writel_print(unsigned int val, void __iomem *addr)
+{
+/* TODO: Add Ftrace here
+	if (p_data->lvts_debug_log)
+		dev_info(dev, "### LVTS_REG: addr 0x%p, val 0x%x\n",
+								addr, val);
+	pr_err("### LVTS_REG: addr 0x%p, val 0x%x\n", addr, val);
+*/
+	writel(val, addr);
+}
+EXPORT_SYMBOL_GPL(lvts_writel_print);
+
+void device_wait_counting_finished(struct lvts_data *lvts_data, int tc_id)
+{
+	/* Check this when LVTS device is counting for
+	 * a temperature or a RC now
+	 */
+	struct device *dev = lvts_data->dev;
+	unsigned int cnt;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	cnt = 0;
+	while ((readl(LVTS_CONFIG_0 + base) & DEVICE_SENSING_STATUS)) {
+		cnt++;
+
+		if (cnt == 100) {
+			dev_err(dev,
+				"Error: LVTS %d DEVICE_SENSING_STATUS didn't ready\n",
+				tc_id);
+			break;
+		}
+		udelay(2);
+	}
+}
+EXPORT_SYMBOL_GPL(device_wait_counting_finished);
+
+void lvts_write_device(struct lvts_data *lvts_data, unsigned int data,
+	int tc_id)
+{
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	lvts_writel_print(data, LVTS_CONFIG_0 + base);
+
+	udelay(5);
+}
+EXPORT_SYMBOL_GPL(lvts_write_device);
+
+unsigned int lvts_read_device(struct lvts_data *lvts_data,
+	unsigned int reg_idx, int tc_id)
+{
+	void __iomem *base;
+	unsigned int data;
+
+	base = GET_BASE_ADDR(tc_id);
+	lvts_writel_print(READ_DEVICE_REG(reg_idx), LVTS_CONFIG_0 + base);
+
+	device_wait_read_write_finished(lvts_data, tc_id);
+
+	data = (readl(LVTSRDATA0_0 + base));
+
+	return data;
+}
+EXPORT_SYMBOL_GPL(lvts_read_device);
+
+void set_polling_speed(struct lvts_data *lvts_data, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	struct tc_settings *tc = lvts_data->tc;
+	unsigned int lvtsMonCtl1, lvtsMonCtl2;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+
+	lvtsMonCtl1 = (((tc[tc_id].tc_speed.group_interval_delay
+			<< 20) & GENMASK(29,20)) |
+			(tc[tc_id].tc_speed.period_unit &
+			GENMASK(9,0)));
+	lvtsMonCtl2 = (((tc[tc_id].tc_speed.filter_interval_delay
+			<< 16) & GENMASK(25,16)) |
+			(tc[tc_id].tc_speed.sensor_interval_delay
+			& GENMASK(9,0)));
+	/*
+	 * 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
+	 */
+	lvts_writel_print(lvtsMonCtl1, LVTSMONCTL1_0 + base);
+	lvts_writel_print(lvtsMonCtl2, LVTSMONCTL2_0 + base);
+
+	udelay(1);
+	dev_info(dev, "%s %d, LVTSMONCTL1_0= 0x%x,LVTSMONCTL2_0= 0x%x\n",
+		__func__, tc_id,
+		readl(LVTSMONCTL1_0 + base),
+		readl(LVTSMONCTL2_0 + base));
+}
+EXPORT_SYMBOL_GPL(set_polling_speed);
+
+void set_hw_filter(struct lvts_data *lvts_data, int tc_id)
+{
+	struct device *dev = lvts_data->dev;
+	struct tc_settings *tc = lvts_data->tc;
+	unsigned int option;
+	void __iomem *base;
+
+	base = GET_BASE_ADDR(tc_id);
+	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;
+
+        lvts_writel_print(option, LVTSMSRCTL0_0 + base);
+	dev_info(dev, "%s %d, LVTSMSRCTL0_0= 0x%x\n",
+		__func__, tc_id, readl(LVTSMSRCTL0_0 + base));
+}
+EXPORT_SYMBOL_GPL(set_hw_filter);
+/*==================================================
+ * Support chips
+ *==================================================
+ */
+static struct lvts_id mt6880_lvts_id = {
+	.hw_version = 5,
+	.chip = "mt6880",
+};
+static const struct of_device_id lvts_of_match[] = {
+	{
+		.compatible = "mediatek,mt6880-lvts",
+		.data = (void *)&mt6880_lvts_id,
+	},
+	{
+	},
+};
+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-soc-temp-lvts",
+		.of_match_table = lvts_of_match,
+	},
+};
+
+module_platform_driver(soc_temp_lvts);
+MODULE_AUTHOR("Yu-Chia Chang <ethan.chang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek soc temperature driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/soc_temp_lvts.h b/src/kernel/linux/v4.19/drivers/thermal/mediatek/soc_temp_lvts.h
new file mode 100644
index 0000000..eeb095c
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/soc_temp_lvts.h
@@ -0,0 +1,396 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#ifndef __MTK_SOC_TEMP_LVTS_H__
+#define __MTK_SOC_TEMP_LVTS_H__
+/*==================================================
+ * Definition or macro function
+ *==================================================
+ */
+#define DISABLE_THERMAL_HW_REBOOT (-274000)
+
+#define CLOCK_26MHZ_CYCLE_NS	(38)
+#define BUS_ACCESS_US		(2)
+
+#define FEATURE_DEVICE_AUTO_RCK	(BIT(0))
+#define FEATURE_CK26M_ACTIVE	(BIT(1))
+#define ENABLE_FEATURE(feature)		(lvts_data->feature_bitmap |= feature)
+#define DISABLE_FEATURE(feature)	(lvts_data->feature_bitmap &= (~feature))
+#define IS_ENABLE(feature)		(lvts_data->feature_bitmap & feature)
+
+#define IS_EMPTY_STR(str)	(str[0] == '\0')
+
+#define GET_BASE_ADDR(tc_id)	\
+	(lvts_data->domain[lvts_data->tc[tc_id].domain_index].base	\
+	+ lvts_data->tc[tc_id].addr_offset)
+
+#define SET_TC_SPEED_IN_US(pu, gd, fd, sd) \
+	{	\
+		.period_unit = ((pu * 1000) / (256 * CLOCK_26MHZ_CYCLE_NS)),	\
+		.group_interval_delay = (gd / pu),	\
+		.filter_interval_delay = (fd / pu),	\
+		.sensor_interval_delay = (sd / pu),	\
+	}
+
+#define GET_CAL_DATA_BITMASK(index, h, l)	\
+	((index < lvts_data->num_efuse_addr)	\
+	? ((lvts_data->efuse[index] & GENMASK(h,l)) >> l)	\
+	: 0)
+#define GET_CAL_DATA_BIT(index, bit)	\
+	((index < lvts_data->num_efuse_addr)	\
+	? ((lvts_data->efuse[index] & BIT(bit)) >> bit)	\
+	: 0)
+
+#define GET_TC_SENSOR_NUM(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(tc_id) * SENSOR_LATENCY_US(tc_id)	\
+	+ (GET_TC_SENSOR_NUM(tc_id) - 1) * SENSOR_INT_US(tc_id)	\
+	+ GROUP_INT_US(tc_id))
+/* 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
+};
+/*==================================================
+ * Data structure
+ *==================================================
+ */
+struct lvts_data;
+
+struct speed_settings {
+	unsigned int period_unit;
+	unsigned int group_interval_delay;
+	unsigned int filter_interval_delay;
+	unsigned int sensor_interval_delay;
+};
+
+struct tc_settings {
+	unsigned int domain_index;
+	unsigned int addr_offset;
+	unsigned int num_sensor;
+	unsigned int sensor_map[ALL_SENSING_POINTS]; /* In sensor ID */
+	struct 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 formula_coeff {
+	int a;
+	int b;
+	unsigned int golden_temp;
+};
+
+struct 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 power_domain {
+	void __iomem *base;	/* LVTS base addresses */
+	unsigned int irq_num;	/* LVTS interrupt numbers */
+	struct reset_control *reset;
+};
+
+struct sensor_data {
+	int temp;		/* Current temperature */
+	unsigned int msr_raw;	/* MSR raw data from LVTS */
+};
+
+struct lvts_data {
+	struct device *dev;
+	struct clk *clk;
+	unsigned int num_domain;
+	struct power_domain *domain;
+
+	int num_tc;			/* Number of LVTS thermal controllers */
+	struct tc_settings *tc;
+	int counting_window_us;		/* LVTS device counting window */
+
+	int num_sensor;			/* Number of sensors in this platform */
+	struct sensor_data *sen_data;
+
+	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 sensor_cal_data cal_data;
+	struct formula_coeff coeff;
+};
+
+struct soc_temp_tz {
+	unsigned int id; /* if id is 0, get max temperature of all sensors */
+	struct lvts_data *lvts_data;
+};
+
+struct match_entry {
+	char	chip[32];
+	struct lvts_data *lvts_data;
+};
+
+struct lvts_match_data {
+	unsigned int hw_version;
+	struct match_entry *table;
+	void (*set_up_common_callbacks)(struct lvts_data *lvts_data);
+	struct list_head node;
+};
+
+struct lvts_id {
+	unsigned int hw_version;
+	char	chip[32];
+};
+/*==================================================
+ * Extern function
+ *==================================================
+ */
+extern void device_wait_counting_finished(struct lvts_data *lvts_data, int tc_id);
+extern void lvts_writel_print(unsigned int val, void *addr);
+extern void lvts_write_device(struct lvts_data *lvts_data, unsigned int data,
+	int tc_id);
+extern unsigned int lvts_read_device(struct lvts_data *lvts_data,
+	unsigned int reg_idx, int tc_id);
+extern void set_polling_speed(struct lvts_data *lvts_data, int tc_id);
+extern void set_hw_filter(struct lvts_data *lvts_data, int tc_id);
+
+extern struct lvts_match_data *register_v1_lvts_match_data(void);
+extern struct lvts_match_data *register_v4_lvts_match_data(void);
+extern struct lvts_match_data *register_v5_lvts_match_data(void);
+/*==================================================
+ * 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 LVTS_SINGLE_SENSE	(1 << 9)
+#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	(1 << 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 CK26M_ACTIVE	(((lvts_data->feature_bitmap & FEATURE_CK26M_ACTIVE)	\
+			? 1: 0) << 30)
+#define BROADCAST_ID_UPDATE	(1 << 26)
+#define DEVICE_SENSING_STATUS	(1 << 25)
+#define DEVICE_ACCESS_STARTUS	(1 << 24)
+#define WRITE_ACCESS		(1 << 16)
+#define DEVICE_WRITE		(1 << 31 | CK26M_ACTIVE | DEVICE_ACCESS_STARTUS \
+				| 1 << 17 | WRITE_ACCESS)
+#define DEVICE_READ		(1 << 31 | CK26M_ACTIVE | DEVICE_ACCESS_STARTUS \
+				| 1 << 17)
+#define RESET_ALL_DEVICES	(DEVICE_WRITE | RG_TSFM_RST << 8 | 0xFF)
+#define READ_BACK_DEVICE_ID	(1 << 31 | CK26M_ACTIVE | BROADCAST_ID_UPDATE	\
+				| DEVICE_ACCESS_STARTUS | 1 << 17	\
+				| RG_DID_LVTS << 8)
+#define READ_DEVICE_REG(reg_idx)	(DEVICE_READ | reg_idx << 8 | 0x00)
+#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		0x00000001
+#define THERMAL_HOT_INTERRUPT_0			0x00000002
+#define THERMAL_LOW_OFFSET_INTERRUPT_0		0x00000004
+#define THERMAL_HIGH_OFFSET_INTERRUPT_0		0x00000008
+#define THERMAL_HOT2NORMAL_INTERRUPT_0		0x00000010
+#define THERMAL_COLD_INTERRUPT_1		0x00000020
+#define THERMAL_HOT_INTERRUPT_1			0x00000040
+#define THERMAL_LOW_OFFSET_INTERRUPT_1		0x00000080
+#define THERMAL_HIGH_OFFSET_INTERRUPT_1		0x00000100
+#define THERMAL_HOT2NORMAL_INTERRUPT_1		0x00000200
+#define THERMAL_COLD_INTERRUPT_2		0x00000400
+#define THERMAL_HOT_INTERRUPT_2			0x00000800
+#define THERMAL_LOW_OFFSET_INTERRUPT_2		0x00001000
+#define THERMAL_HIGH_OFFSET_INTERRUPT_2		0x00002000
+#define THERMAL_HOT2NORMAL_INTERRUPT_2		0x00004000
+#define THERMAL_AHB_TIMEOUT_INTERRUPT		0x00008000
+#define THERMAL_DEVICE_TIMEOUT_INTERRUPT	0x00008000
+#define THERMAL_IMMEDIATE_INTERRUPT_0		0x00010000
+#define THERMAL_IMMEDIATE_INTERRUPT_1		0x00020000
+#define THERMAL_IMMEDIATE_INTERRUPT_2		0x00040000
+#define THERMAL_FILTER_INTERRUPT_0		0x00080000
+#define THERMAL_FILTER_INTERRUPT_1		0x00100000
+#define THERMAL_FILTER_INTERRUPT_2		0x00200000
+#define THERMAL_COLD_INTERRUPT_3		0x00400000
+#define THERMAL_HOT_INTERRUPT_3			0x00800000
+#define THERMAL_LOW_OFFSET_INTERRUPT_3		0x01000000
+#define THERMAL_HIGH_OFFSET_INTERRUPT_3		0x02000000
+#define THERMAL_HOT2NORMAL_INTERRUPT_3		0x04000000
+#define THERMAL_IMMEDIATE_INTERRUPT_3		0x08000000
+#define THERMAL_FILTER_INTERRUPT_3		0x10000000
+#define THERMAL_PROTECTION_STAGE_1		0x20000000
+#define THERMAL_PROTECTION_STAGE_2		0x40000000
+#define THERMAL_PROTECTION_STAGE_3		0x80000000
+#endif /* __MTK_SOC_TEMP_LVTS_H__ */
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/thermal_risk_monitor.c b/src/kernel/linux/v4.19/drivers/thermal/mediatek/thermal_risk_monitor.c
new file mode 100644
index 0000000..b8b9215
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/thermal_risk_monitor.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kobject.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/thermal.h>
+#include <linux/pm_wakeup.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include "thermal_risk_monitor.h"
+#include "mtk_thermal_ipi.h"
+
+/*==================================================
+ * Global variable
+ *==================================================
+ */
+static struct trm_data g_trm_data;
+static struct task_struct *thermal_awake_task;
+
+/*==================================================
+ * Platform data
+ *==================================================
+ */
+static char mt6880_id_to_tz_map[NUM_THERMAL_SENSOR][THERMAL_NAME_LENGTH] = {
+	"soc_max",
+	"cpu_little2",
+	"",
+	"",
+	"gpu1",
+	"md_4g",
+	"soc_dram_ntc",
+	"ltepa_ntc",
+	"nrpa_ntc",
+	"rf_ntc",
+};
+
+/*==================================================
+ * Local function
+ *==================================================
+ */
+static int trm_update_policy(enum thermal_sensor id, int threshold, int hysteresis)
+{
+	struct trm_sensor_data *sen_data;
+
+	mutex_lock(&g_trm_data.sen_list_lock);
+	list_for_each_entry(sen_data, &g_trm_data.sen_list, node) {
+		if (sen_data->id == id) {
+			sen_data->threshold = threshold;
+			sen_data->hysteresis = hysteresis;
+			mutex_unlock(&g_trm_data.sen_list_lock);
+			return 0;
+		}
+	}
+	mutex_unlock(&g_trm_data.sen_list_lock);
+
+	return -1;
+}
+
+static int trm_add_policy(enum thermal_sensor id, int threshold, int hysteresis)
+{
+	struct trm_sensor_data *sen_data;
+
+	sen_data = devm_kzalloc(g_trm_data.dev, sizeof(*sen_data), GFP_KERNEL);
+	if (!sen_data) {
+		dev_err(g_trm_data.dev,
+			"Failed to allocate memory, sensor id %u\n", id);
+		return -ENOMEM;
+	}
+
+	sen_data->id = id;
+	sen_data->threshold = threshold;
+	sen_data->hysteresis = hysteresis;
+
+	mutex_lock(&g_trm_data.sen_list_lock);
+	list_add(&sen_data->node, &g_trm_data.sen_list);
+	mutex_unlock(&g_trm_data.sen_list_lock);
+
+	return 0;
+}
+
+static ssize_t trm_policy_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int ret, threshold, hysteresis;
+	char cmd[THERMAL_NAME_LENGTH];
+	unsigned int i, id, len;
+	struct thermal_ipi_data thermal_data;
+
+	if (sscanf(buf, "%19s %d %d", cmd, &threshold, &hysteresis) == 3) {
+		len = strlen(cmd);
+		id = NUM_THERMAL_SENSOR;
+
+		for (i = 0; i < NUM_THERMAL_SENSOR; i++) {
+			if (!strncmp(cmd, g_trm_data.id_to_tz_map +
+				i * THERMAL_NAME_LENGTH, len)) {
+				id = i;
+				break;
+			}
+		}
+
+		if (id == NUM_THERMAL_SENSOR) {
+			dev_info(g_trm_data.dev, "Not support this tz %s\n",
+				cmd);
+			return -EINVAL;
+		}
+
+		ret = trm_update_policy(id, threshold, hysteresis);
+		if (ret == -1) {
+			ret = trm_add_policy(id, threshold, hysteresis);
+			if (ret)
+				return ret;
+		}
+
+		thermal_data.u.data.arg[0] = id;
+		thermal_data.u.data.arg[1] = threshold;
+		thermal_data.u.data.arg[2] = hysteresis;
+
+		while (thermal_to_sspm(TRM_IPI_INIT_GRP1, &thermal_data) !=
+			IPI_SUCCESS)
+			udelay(100);
+	} else if (sscanf(buf, "%19s", cmd) == 1) {
+		if (strncmp(cmd, "disabled", 8))
+			return -EINVAL;
+
+		thermal_data.u.data.arg[0] = 0;
+		thermal_data.u.data.arg[1] = 0;
+		thermal_data.u.data.arg[2] = 0;
+
+		while (thermal_to_sspm(TRM_IPI_DISABLE_CMD, &thermal_data) !=
+			IPI_SUCCESS)
+			udelay(100);
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+TRM_ATTR_WO(trm_policy);
+
+static struct attribute *trm_attrs[] = {
+	&trm_policy_attr.attr,
+	NULL
+};
+
+static struct attribute_group trm_attr_group = {
+	.name	= "trm",
+	.attrs	= trm_attrs,
+};
+
+static char * get_tz_name_by_id(enum thermal_sensor id)
+{
+	if (id >= NUM_THERMAL_SENSOR)
+		return NULL;
+
+	return g_trm_data.id_to_tz_map + id * THERMAL_NAME_LENGTH;
+}
+
+static int get_temp(enum thermal_sensor id, int *temp)
+{
+	struct thermal_zone_device *tzd;
+
+	tzd = thermal_zone_get_zone_by_name(get_tz_name_by_id(id));
+	if (IS_ERR(tzd))
+		return PTR_ERR(tzd);
+
+	return thermal_zone_get_temp(tzd, temp);
+}
+
+static int check_thermal_risk(void)
+{
+	struct trm_sensor_data *sen_data;
+	int ret, temp = 0;
+
+	mutex_lock(&g_trm_data.sen_list_lock);
+	list_for_each_entry(sen_data, &g_trm_data.sen_list, node) {
+		ret = get_temp(sen_data->id, &temp);
+
+		if (ret) {
+			dev_info(g_trm_data.dev,
+				"Error: Failed to get temp of tz %s, %d\n",
+				get_tz_name_by_id(sen_data->id), ret);
+			continue;
+		}
+
+		if (temp >= (sen_data->threshold - sen_data->hysteresis)) {
+			mutex_unlock(&g_trm_data.sen_list_lock);
+			return 1;
+		}
+	}
+	mutex_unlock(&g_trm_data.sen_list_lock);
+
+	return 0;
+}
+
+static int keep_ap_awake_thread(void *data)
+{
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule();
+
+	while (!kthread_should_stop()) {
+		pm_stay_awake(g_trm_data.dev);
+		while (1) {
+			if (!check_thermal_risk())
+				break;
+
+			msleep(1000);
+		}
+
+		pm_relax(g_trm_data.dev);
+		dev_info(g_trm_data.dev, "The thermal risk averted\n");
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule();
+	}
+
+	return 0;
+}
+
+static int trm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	mutex_init(&g_trm_data.sen_list_lock);
+	INIT_LIST_HEAD(&g_trm_data.sen_list);
+
+	g_trm_data.id_to_tz_map = (char *) of_device_get_match_data(dev);
+
+	if (!g_trm_data.id_to_tz_map) {
+		dev_err(dev, "Error: Failed to get trm platform data\n");
+		return -ENODATA;
+	}
+
+	g_trm_data.dev = &pdev->dev;
+	platform_set_drvdata(pdev, &g_trm_data);
+
+	ret = sysfs_create_group(kernel_kobj, &trm_attr_group);
+	if (ret) {
+		dev_err(dev, "Failed to create TRM sysfs, ret=%d!\n", ret);
+		return ret;
+	}
+
+	thermal_awake_task = kthread_run(keep_ap_awake_thread, NULL, "sspm_to_thermal");
+	if (IS_ERR(thermal_awake_task)) {
+		ret = PTR_ERR(thermal_awake_task);
+		thermal_awake_task = NULL;
+		dev_err(dev, "Fail to create a thread for thermal awake task, %d\n",
+			ret);
+
+		return ret;
+	}
+
+	return 0;
+}
+
+static int trm_remove(struct platform_device *pdev)
+{
+	sysfs_remove_group(kernel_kobj, &trm_attr_group);
+
+	return 0;
+}
+/*==================================================
+ * Extern function
+ *==================================================
+ */
+void keep_ap_wake(void)
+{
+	if (thermal_awake_task != NULL) {
+		dev_info(g_trm_data.dev, "Wake AP up for a thermal risk\n");
+		wake_up_process(thermal_awake_task);
+	}
+}
+/*==================================================
+ * Support chips
+ *==================================================
+ */
+static const struct of_device_id trm_of_match[] = {
+	{
+		.compatible = "mediatek,mt6880-trm",
+		.data = (void *)mt6880_id_to_tz_map,
+	},
+	{
+	},
+};
+MODULE_DEVICE_TABLE(of, trm_of_match);
+/*==================================================*/
+static struct platform_driver mtk_trm = {
+	.probe = trm_probe,
+	.remove = trm_remove,
+	.driver = {
+		.name = "mtk-trm",
+		.of_match_table = trm_of_match,
+	},
+};
+
+module_platform_driver(mtk_trm);
+MODULE_AUTHOR("Yu-Chia Chang <ethan.chang@mediatek.com>");
+MODULE_DESCRIPTION("Mediatek thermal risk monitor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/src/kernel/linux/v4.19/drivers/thermal/mediatek/thermal_risk_monitor.h b/src/kernel/linux/v4.19/drivers/thermal/mediatek/thermal_risk_monitor.h
new file mode 100644
index 0000000..9dc5492
--- /dev/null
+++ b/src/kernel/linux/v4.19/drivers/thermal/mediatek/thermal_risk_monitor.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2020 MediaTek Inc.
+ */
+
+#ifndef __THERMAL_RISK_MONITOR_H__
+#define __THERMAL_RISK_MONITOR_H__
+/*==================================================
+ * Definition or macro function
+ *==================================================
+ */
+enum thermal_sensor {
+	SEN_SOC_MAX,
+	SEN_CPU_LITTLE,
+	SEN_CPU_BIG,
+	SEN_CPU_BB,
+	SEN_GPU,
+	SEN_MD,
+	SEN_SOC_NTC,
+	SEN_LTEPA_NTC,
+	SEN_NRPA_NTC,
+	SEN_RF_NTC,
+	NUM_THERMAL_SENSOR
+};
+
+struct trm_sensor_data {
+	enum thermal_sensor id;
+	int threshold;                /* Threshold to wake up AP in m'C*/
+	int hysteresis;               /* To debounce in m'C */
+	struct list_head node;
+};
+
+struct trm_data {
+	struct list_head sen_list;
+	struct mutex sen_list_lock;
+	char *id_to_tz_map;
+	struct device *dev;
+};
+
+#define TRM_ATTR_RO(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_RO(_name)
+#define TRM_ATTR_WO(_name) \
+static struct kobj_attribute _name##_attr = __ATTR_WO(_name)
+/*==================================================
+ * Data structure
+ *==================================================
+ */
+/*==================================================
+ * Extern function
+ *==================================================
+ */
+extern void keep_ap_wake(void);
+#endif