[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