summaryrefslogtreecommitdiffstats
path: root/drivers/thermal/qcom
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thermal/qcom')
-rw-r--r--drivers/thermal/qcom/Kconfig43
-rw-r--r--drivers/thermal/qcom/Makefile8
-rw-r--r--drivers/thermal/qcom/lmh.c241
-rw-r--r--drivers/thermal/qcom/qcom-spmi-adc-tm5.c1081
-rw-r--r--drivers/thermal/qcom/qcom-spmi-temp-alarm.c494
-rw-r--r--drivers/thermal/qcom/tsens-8960.c282
-rw-r--r--drivers/thermal/qcom/tsens-v0_1.c648
-rw-r--r--drivers/thermal/qcom/tsens-v1.c389
-rw-r--r--drivers/thermal/qcom/tsens-v2.c110
-rw-r--r--drivers/thermal/qcom/tsens.c1187
-rw-r--r--drivers/thermal/qcom/tsens.h596
11 files changed, 5079 insertions, 0 deletions
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
new file mode 100644
index 000000000..2c7f3f9a2
--- /dev/null
+++ b/drivers/thermal/qcom/Kconfig
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config QCOM_TSENS
+ tristate "Qualcomm TSENS Temperature Alarm"
+ depends on NVMEM_QCOM_QFPROM
+ depends on ARCH_QCOM || COMPILE_TEST
+ help
+ This enables the thermal sysfs driver for the TSENS device. It shows
+ up in Sysfs as a thermal zone with multiple trip points. Disabling the
+ thermal zone device via the mode file results in disabling the sensor.
+ Also able to set threshold temperature for both hot and cold and update
+ when a threshold is reached.
+
+config QCOM_SPMI_ADC_TM5
+ tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5"
+ depends on OF && SPMI && IIO
+ select REGMAP_SPMI
+ select QCOM_VADC_COMMON
+ help
+ This enables the thermal driver for the ADC thermal monitoring
+ device. It shows up as a thermal zone with multiple trip points.
+ Thermal client sets threshold temperature for both warm and cool and
+ gets updated when a threshold is reached.
+
+config QCOM_SPMI_TEMP_ALARM
+ tristate "Qualcomm SPMI PMIC Temperature Alarm"
+ depends on OF && SPMI && IIO
+ select REGMAP_SPMI
+ help
+ This enables a thermal sysfs driver for Qualcomm plug-and-play (QPNP)
+ PMIC devices. It shows up in sysfs as a thermal sensor with multiple
+ trip points. The temperature reported by the thermal sensor reflects the
+ real time die temperature if an ADC is present or an estimate of the
+ temperature based upon the over temperature stage value.
+
+config QCOM_LMH
+ tristate "Qualcomm Limits Management Hardware"
+ depends on ARCH_QCOM && QCOM_SCM
+ help
+ This enables initialization of Qualcomm limits management
+ hardware(LMh). LMh allows for hardware-enforced mitigation for cpus based on
+ input from temperature and current sensors. On many newer Qualcomm SoCs
+ LMh is configured in the firmware and this feature need not be enabled.
+ However, on certain SoCs like sdm845 LMh has to be configured from kernel.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
new file mode 100644
index 000000000..0fa251204
--- /dev/null
+++ b/drivers/thermal/qcom/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
+
+qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
+ tsens-8960.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
+obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
+obj-$(CONFIG_QCOM_LMH) += lmh.o
diff --git a/drivers/thermal/qcom/lmh.c b/drivers/thermal/qcom/lmh.c
new file mode 100644
index 000000000..4122a51e9
--- /dev/null
+++ b/drivers/thermal/qcom/lmh.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (C) 2021, Linaro Limited. All rights reserved.
+ */
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/qcom_scm.h>
+
+#define LMH_NODE_DCVS 0x44435653
+#define LMH_CLUSTER0_NODE_ID 0x6370302D
+#define LMH_CLUSTER1_NODE_ID 0x6370312D
+
+#define LMH_SUB_FN_THERMAL 0x54484D4C
+#define LMH_SUB_FN_CRNT 0x43524E54
+#define LMH_SUB_FN_REL 0x52454C00
+#define LMH_SUB_FN_BCL 0x42434C00
+
+#define LMH_ALGO_MODE_ENABLE 0x454E424C
+#define LMH_TH_HI_THRESHOLD 0x48494748
+#define LMH_TH_LOW_THRESHOLD 0x4C4F5700
+#define LMH_TH_ARM_THRESHOLD 0x41524D00
+
+#define LMH_REG_DCVS_INTR_CLR 0x8
+
+#define LMH_ENABLE_ALGOS 1
+
+struct lmh_hw_data {
+ void __iomem *base;
+ struct irq_domain *domain;
+ int irq;
+};
+
+static irqreturn_t lmh_handle_irq(int hw_irq, void *data)
+{
+ struct lmh_hw_data *lmh_data = data;
+ int irq = irq_find_mapping(lmh_data->domain, 0);
+
+ /* Call the cpufreq driver to handle the interrupt */
+ if (irq)
+ generic_handle_irq(irq);
+
+ return IRQ_HANDLED;
+}
+
+static void lmh_enable_interrupt(struct irq_data *d)
+{
+ struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
+
+ /* Clear the existing interrupt */
+ writel(0xff, lmh_data->base + LMH_REG_DCVS_INTR_CLR);
+ enable_irq(lmh_data->irq);
+}
+
+static void lmh_disable_interrupt(struct irq_data *d)
+{
+ struct lmh_hw_data *lmh_data = irq_data_get_irq_chip_data(d);
+
+ disable_irq_nosync(lmh_data->irq);
+}
+
+static struct irq_chip lmh_irq_chip = {
+ .name = "lmh",
+ .irq_enable = lmh_enable_interrupt,
+ .irq_disable = lmh_disable_interrupt
+};
+
+static int lmh_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
+{
+ struct lmh_hw_data *lmh_data = d->host_data;
+
+ irq_set_chip_and_handler(irq, &lmh_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, lmh_data);
+
+ return 0;
+}
+
+static const struct irq_domain_ops lmh_irq_ops = {
+ .map = lmh_irq_map,
+ .xlate = irq_domain_xlate_onecell,
+};
+
+static int lmh_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *cpu_node;
+ struct lmh_hw_data *lmh_data;
+ int temp_low, temp_high, temp_arm, cpu_id, ret;
+ unsigned int enable_alg;
+ u32 node_id;
+
+ lmh_data = devm_kzalloc(dev, sizeof(*lmh_data), GFP_KERNEL);
+ if (!lmh_data)
+ return -ENOMEM;
+
+ lmh_data->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(lmh_data->base))
+ return PTR_ERR(lmh_data->base);
+
+ cpu_node = of_parse_phandle(np, "cpus", 0);
+ if (!cpu_node)
+ return -EINVAL;
+ cpu_id = of_cpu_node_to_id(cpu_node);
+ of_node_put(cpu_node);
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-high-millicelsius", &temp_high);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-high-millicelsius property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-low-millicelsius", &temp_low);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-low-millicelsius property\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "qcom,lmh-temp-arm-millicelsius", &temp_arm);
+ if (ret) {
+ dev_err(dev, "missing qcom,lmh-temp-arm-millicelsius property\n");
+ return ret;
+ }
+
+ /*
+ * Only sdm845 has lmh hardware currently enabled from hlos. If this is needed
+ * for other platforms, revisit this to check if the <cpu-id, node-id> should be part
+ * of a dt match table.
+ */
+ if (cpu_id == 0) {
+ node_id = LMH_CLUSTER0_NODE_ID;
+ } else if (cpu_id == 4) {
+ node_id = LMH_CLUSTER1_NODE_ID;
+ } else {
+ dev_err(dev, "Wrong CPU id associated with LMh node\n");
+ return -EINVAL;
+ }
+
+ if (!qcom_scm_lmh_dcvsh_available())
+ return -EINVAL;
+
+ enable_alg = (uintptr_t)of_device_get_match_data(dev);
+
+ if (enable_alg) {
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_CRNT, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling current subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_REL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling reliability subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_BCL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret)
+ dev_err(dev, "Error %d enabling BCL subfunction\n", ret);
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_ALGO_MODE_ENABLE, 1,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error %d enabling thermal subfunction\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_profile_change(0x1);
+ if (ret) {
+ dev_err(dev, "Error %d changing profile\n", ret);
+ return ret;
+ }
+ }
+
+ /* Set default thermal trips */
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_ARM_THRESHOLD, temp_arm,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_HI_THRESHOLD, temp_high,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal HI threshold%d\n", ret);
+ return ret;
+ }
+
+ ret = qcom_scm_lmh_dcvsh(LMH_SUB_FN_THERMAL, LMH_TH_LOW_THRESHOLD, temp_low,
+ LMH_NODE_DCVS, node_id, 0);
+ if (ret) {
+ dev_err(dev, "Error setting thermal ARM threshold%d\n", ret);
+ return ret;
+ }
+
+ lmh_data->irq = platform_get_irq(pdev, 0);
+ lmh_data->domain = irq_domain_add_linear(np, 1, &lmh_irq_ops, lmh_data);
+ if (!lmh_data->domain) {
+ dev_err(dev, "Error adding irq_domain\n");
+ return -EINVAL;
+ }
+
+ /* Disable the irq and let cpufreq enable it when ready to handle the interrupt */
+ irq_set_status_flags(lmh_data->irq, IRQ_NOAUTOEN);
+ ret = devm_request_irq(dev, lmh_data->irq, lmh_handle_irq,
+ IRQF_ONESHOT | IRQF_NO_SUSPEND,
+ "lmh-irq", lmh_data);
+ if (ret) {
+ dev_err(dev, "Error %d registering irq %x\n", ret, lmh_data->irq);
+ irq_domain_remove(lmh_data->domain);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id lmh_table[] = {
+ { .compatible = "qcom,sc8180x-lmh", },
+ { .compatible = "qcom,sdm845-lmh", .data = (void *)LMH_ENABLE_ALGOS},
+ { .compatible = "qcom,sm8150-lmh", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lmh_table);
+
+static struct platform_driver lmh_driver = {
+ .probe = lmh_probe,
+ .driver = {
+ .name = "qcom-lmh",
+ .of_match_table = lmh_table,
+ .suppress_bind_attrs = true,
+ },
+};
+module_platform_driver(lmh_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("QCOM LMh driver");
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
new file mode 100644
index 000000000..1b2c43eab
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
@@ -0,0 +1,1081 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 Linaro Limited
+ *
+ * Based on original driver:
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iio/adc/qcom-vadc-common.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include <asm-generic/unaligned.h>
+
+#include "../thermal_hwmon.h"
+
+/*
+ * Thermal monitoring block consists of 8 (ADC_TM5_NUM_CHANNELS) channels. Each
+ * channel is programmed to use one of ADC channels for voltage comparison.
+ * Voltages are programmed using ADC codes, so we have to convert temp to
+ * voltage and then to ADC code value.
+ *
+ * Configuration of TM channels must match configuration of corresponding ADC
+ * channels.
+ */
+
+#define ADC5_MAX_CHANNEL 0xc0
+#define ADC_TM5_NUM_CHANNELS 8
+
+#define ADC_TM5_STATUS_LOW 0x0a
+
+#define ADC_TM5_STATUS_HIGH 0x0b
+
+#define ADC_TM5_NUM_BTM 0x0f
+
+#define ADC_TM5_ADC_DIG_PARAM 0x42
+
+#define ADC_TM5_FAST_AVG_CTL (ADC_TM5_ADC_DIG_PARAM + 1)
+#define ADC_TM5_FAST_AVG_EN BIT(7)
+
+#define ADC_TM5_MEAS_INTERVAL_CTL (ADC_TM5_ADC_DIG_PARAM + 2)
+#define ADC_TM5_TIMER1 3 /* 3.9ms */
+
+#define ADC_TM5_MEAS_INTERVAL_CTL2 (ADC_TM5_ADC_DIG_PARAM + 3)
+#define ADC_TM5_MEAS_INTERVAL_CTL2_MASK 0xf0
+#define ADC_TM5_TIMER2 10 /* 1 second */
+#define ADC_TM5_MEAS_INTERVAL_CTL3_MASK 0xf
+#define ADC_TM5_TIMER3 4 /* 4 second */
+
+#define ADC_TM_EN_CTL1 0x46
+#define ADC_TM_EN BIT(7)
+#define ADC_TM_CONV_REQ 0x47
+#define ADC_TM_CONV_REQ_EN BIT(7)
+
+#define ADC_TM5_M_CHAN_BASE 0x60
+
+#define ADC_TM5_M_ADC_CH_SEL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 0)
+#define ADC_TM5_M_LOW_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 1)
+#define ADC_TM5_M_LOW_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 2)
+#define ADC_TM5_M_HIGH_THR0(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 3)
+#define ADC_TM5_M_HIGH_THR1(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 4)
+#define ADC_TM5_M_MEAS_INTERVAL_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 5)
+#define ADC_TM5_M_CTL(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 6)
+#define ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK 0xf
+#define ADC_TM5_M_CTL_CAL_SEL_MASK 0x30
+#define ADC_TM5_M_CTL_CAL_VAL 0x40
+#define ADC_TM5_M_EN(n) (ADC_TM5_M_CHAN_BASE + ((n) * 8) + 7)
+#define ADC_TM5_M_MEAS_EN BIT(7)
+#define ADC_TM5_M_HIGH_THR_INT_EN BIT(1)
+#define ADC_TM5_M_LOW_THR_INT_EN BIT(0)
+
+#define ADC_TM_GEN2_STATUS1 0x08
+#define ADC_TM_GEN2_STATUS_LOW_SET 0x09
+#define ADC_TM_GEN2_STATUS_LOW_CLR 0x0a
+#define ADC_TM_GEN2_STATUS_HIGH_SET 0x0b
+#define ADC_TM_GEN2_STATUS_HIGH_CLR 0x0c
+
+#define ADC_TM_GEN2_CFG_HS_SET 0x0d
+#define ADC_TM_GEN2_CFG_HS_FLAG BIT(0)
+#define ADC_TM_GEN2_CFG_HS_CLR 0x0e
+
+#define ADC_TM_GEN2_SID 0x40
+
+#define ADC_TM_GEN2_CH_CTL 0x41
+#define ADC_TM_GEN2_TM_CH_SEL GENMASK(7, 5)
+#define ADC_TM_GEN2_MEAS_INT_SEL GENMASK(3, 2)
+
+#define ADC_TM_GEN2_ADC_DIG_PARAM 0x42
+#define ADC_TM_GEN2_CTL_CAL_SEL GENMASK(5, 4)
+#define ADC_TM_GEN2_CTL_DEC_RATIO_MASK GENMASK(3, 2)
+
+#define ADC_TM_GEN2_FAST_AVG_CTL 0x43
+#define ADC_TM_GEN2_FAST_AVG_EN BIT(7)
+
+#define ADC_TM_GEN2_ADC_CH_SEL_CTL 0x44
+
+#define ADC_TM_GEN2_DELAY_CTL 0x45
+#define ADC_TM_GEN2_HW_SETTLE_DELAY GENMASK(3, 0)
+
+#define ADC_TM_GEN2_EN_CTL1 0x46
+#define ADC_TM_GEN2_EN BIT(7)
+
+#define ADC_TM_GEN2_CONV_REQ 0x47
+#define ADC_TM_GEN2_CONV_REQ_EN BIT(7)
+
+#define ADC_TM_GEN2_LOW_THR0 0x49
+#define ADC_TM_GEN2_LOW_THR1 0x4a
+#define ADC_TM_GEN2_HIGH_THR0 0x4b
+#define ADC_TM_GEN2_HIGH_THR1 0x4c
+#define ADC_TM_GEN2_LOWER_MASK(n) ((n) & GENMASK(7, 0))
+#define ADC_TM_GEN2_UPPER_MASK(n) (((n) & GENMASK(15, 8)) >> 8)
+
+#define ADC_TM_GEN2_MEAS_IRQ_EN 0x4d
+#define ADC_TM_GEN2_MEAS_EN BIT(7)
+#define ADC_TM5_GEN2_HIGH_THR_INT_EN BIT(1)
+#define ADC_TM5_GEN2_LOW_THR_INT_EN BIT(0)
+
+#define ADC_TM_GEN2_MEAS_INT_LSB 0x50
+#define ADC_TM_GEN2_MEAS_INT_MSB 0x51
+#define ADC_TM_GEN2_MEAS_INT_MODE 0x52
+
+#define ADC_TM_GEN2_Mn_DATA0(n) ((n * 2) + 0xa0)
+#define ADC_TM_GEN2_Mn_DATA1(n) ((n * 2) + 0xa1)
+#define ADC_TM_GEN2_DATA_SHIFT 8
+
+enum adc5_timer_select {
+ ADC5_TIMER_SEL_1 = 0,
+ ADC5_TIMER_SEL_2,
+ ADC5_TIMER_SEL_3,
+ ADC5_TIMER_SEL_NONE,
+};
+
+enum adc5_gen {
+ ADC_TM5,
+ ADC_TM_HC,
+ ADC_TM5_GEN2,
+ ADC_TM5_MAX
+};
+
+enum adc_tm5_cal_method {
+ ADC_TM5_NO_CAL = 0,
+ ADC_TM5_RATIOMETRIC_CAL,
+ ADC_TM5_ABSOLUTE_CAL
+};
+
+enum adc_tm_gen2_time_select {
+ MEAS_INT_50MS = 0,
+ MEAS_INT_100MS,
+ MEAS_INT_1S,
+ MEAS_INT_SET,
+ MEAS_INT_NONE,
+};
+
+struct adc_tm5_chip;
+struct adc_tm5_channel;
+
+struct adc_tm5_data {
+ const u32 full_scale_code_volt;
+ unsigned int *decimation;
+ unsigned int *hw_settle;
+ int (*disable_channel)(struct adc_tm5_channel *channel);
+ int (*configure)(struct adc_tm5_channel *channel, int low, int high);
+ irqreturn_t (*isr)(int irq, void *data);
+ int (*init)(struct adc_tm5_chip *chip);
+ char *irq_name;
+ int gen;
+};
+
+/**
+ * struct adc_tm5_channel - ADC Thermal Monitoring channel data.
+ * @channel: channel number.
+ * @adc_channel: corresponding ADC channel number.
+ * @cal_method: calibration method.
+ * @prescale: channel scaling performed on the input signal.
+ * @hw_settle_time: the time between AMUX being configured and the
+ * start of conversion.
+ * @decimation: sampling rate supported for the channel.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements.
+ * @high_thr_en: channel upper voltage threshold enable state.
+ * @low_thr_en: channel lower voltage threshold enable state.
+ * @meas_en: recurring measurement enable state
+ * @iio: IIO channel instance used by this channel.
+ * @chip: ADC TM chip instance.
+ * @tzd: thermal zone device used by this channel.
+ */
+struct adc_tm5_channel {
+ unsigned int channel;
+ unsigned int adc_channel;
+ enum adc_tm5_cal_method cal_method;
+ unsigned int prescale;
+ unsigned int hw_settle_time;
+ unsigned int decimation; /* For Gen2 ADC_TM */
+ unsigned int avg_samples; /* For Gen2 ADC_TM */
+ bool high_thr_en; /* For Gen2 ADC_TM */
+ bool low_thr_en; /* For Gen2 ADC_TM */
+ bool meas_en; /* For Gen2 ADC_TM */
+ struct iio_channel *iio;
+ struct adc_tm5_chip *chip;
+ struct thermal_zone_device *tzd;
+};
+
+/**
+ * struct adc_tm5_chip - ADC Thermal Monitoring properties
+ * @regmap: SPMI ADC5 Thermal Monitoring peripheral register map field.
+ * @dev: SPMI ADC5 device.
+ * @data: software configuration data.
+ * @channels: array of ADC TM channel data.
+ * @nchannels: amount of channels defined/allocated
+ * @decimation: sampling rate supported for the channel.
+ * Applies to all channels, used only on Gen1 ADC_TM.
+ * @avg_samples: ability to provide single result from the ADC
+ * that is an average of multiple measurements. Applies to all
+ * channels, used only on Gen1 ADC_TM.
+ * @base: base address of TM registers.
+ * @adc_mutex_lock: ADC_TM mutex lock, used only on Gen2 ADC_TM.
+ * It is used to ensure only one ADC channel configuration
+ * is done at a time using the shared set of configuration
+ * registers.
+ */
+struct adc_tm5_chip {
+ struct regmap *regmap;
+ struct device *dev;
+ const struct adc_tm5_data *data;
+ struct adc_tm5_channel *channels;
+ unsigned int nchannels;
+ unsigned int decimation;
+ unsigned int avg_samples;
+ u16 base;
+ struct mutex adc_mutex_lock;
+};
+
+static int adc_tm5_read(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_read(adc_tm->regmap, adc_tm->base + offset, data, len);
+}
+
+static int adc_tm5_write(struct adc_tm5_chip *adc_tm, u16 offset, u8 *data, int len)
+{
+ return regmap_bulk_write(adc_tm->regmap, adc_tm->base + offset, data, len);
+}
+
+static int adc_tm5_reg_update(struct adc_tm5_chip *adc_tm, u16 offset, u8 mask, u8 val)
+{
+ return regmap_write_bits(adc_tm->regmap, adc_tm->base + offset, mask, val);
+}
+
+static irqreturn_t adc_tm5_isr(int irq, void *data)
+{
+ struct adc_tm5_chip *chip = data;
+ u8 status_low, status_high, ctl;
+ int ret, i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, sizeof(status_low));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "read status low failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, sizeof(status_high));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "read status high failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ bool upper_set = false, lower_set = false;
+ unsigned int ch = chip->channels[i].channel;
+
+ /* No TZD, we warned at the boot time */
+ if (!chip->channels[i].tzd)
+ continue;
+
+ ret = adc_tm5_read(chip, ADC_TM5_M_EN(ch), &ctl, sizeof(ctl));
+ if (unlikely(ret)) {
+ dev_err(chip->dev, "ctl read failed: %d, channel %d\n", ret, i);
+ continue;
+ }
+
+ if (!(ctl & ADC_TM5_M_MEAS_EN))
+ continue;
+
+ lower_set = (status_low & BIT(ch)) &&
+ (ctl & ADC_TM5_M_LOW_THR_INT_EN);
+
+ upper_set = (status_high & BIT(ch)) &&
+ (ctl & ADC_TM5_M_HIGH_THR_INT_EN);
+
+ if (upper_set || lower_set)
+ thermal_zone_device_update(chip->channels[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t adc_tm5_gen2_isr(int irq, void *data)
+{
+ struct adc_tm5_chip *chip = data;
+ u8 status_low, status_high;
+ int ret, i;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
+ if (ret) {
+ dev_err(chip->dev, "read status_low failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
+ if (ret) {
+ dev_err(chip->dev, "read status_high failed: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_LOW_CLR, &status_low, sizeof(status_low));
+ if (ret < 0) {
+ dev_err(chip->dev, "clear status low failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_STATUS_HIGH_CLR, &status_high, sizeof(status_high));
+ if (ret < 0) {
+ dev_err(chip->dev, "clear status high failed with %d\n", ret);
+ return IRQ_HANDLED;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ bool upper_set = false, lower_set = false;
+ unsigned int ch = chip->channels[i].channel;
+
+ /* No TZD, we warned at the boot time */
+ if (!chip->channels[i].tzd)
+ continue;
+
+ if (!chip->channels[i].meas_en)
+ continue;
+
+ lower_set = (status_low & BIT(ch)) &&
+ (chip->channels[i].low_thr_en);
+
+ upper_set = (status_high & BIT(ch)) &&
+ (chip->channels[i].high_thr_en);
+
+ if (upper_set || lower_set)
+ thermal_zone_device_update(chip->channels[i].tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adc_tm5_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct adc_tm5_channel *channel = tz->devdata;
+ int ret;
+
+ if (!channel || !channel->iio)
+ return -EINVAL;
+
+ ret = iio_read_channel_processed(channel->iio, temp);
+ if (ret < 0)
+ return ret;
+
+ if (ret != IIO_VAL_INT)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int adc_tm5_disable_channel(struct adc_tm5_channel *channel)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ unsigned int reg = ADC_TM5_M_EN(channel->channel);
+
+ return adc_tm5_reg_update(chip, reg,
+ ADC_TM5_M_MEAS_EN |
+ ADC_TM5_M_HIGH_THR_INT_EN |
+ ADC_TM5_M_LOW_THR_INT_EN,
+ 0);
+}
+
+#define ADC_TM_GEN2_POLL_DELAY_MIN_US 100
+#define ADC_TM_GEN2_POLL_DELAY_MAX_US 110
+#define ADC_TM_GEN2_POLL_RETRY_COUNT 3
+
+static int32_t adc_tm5_gen2_conv_req(struct adc_tm5_chip *chip)
+{
+ int ret;
+ u8 data;
+ unsigned int count;
+
+ data = ADC_TM_GEN2_EN;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_EN_CTL1, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm enable failed with %d\n", ret);
+ return ret;
+ }
+
+ data = ADC_TM_GEN2_CFG_HS_FLAG;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CFG_HS_SET, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm handshake failed with %d\n", ret);
+ return ret;
+ }
+
+ data = ADC_TM_GEN2_CONV_REQ_EN;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CONV_REQ, &data, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm request conversion failed with %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * SW sets a handshake bit and waits for PBS to clear it
+ * before the next conversion request can be queued.
+ */
+
+ for (count = 0; count < ADC_TM_GEN2_POLL_RETRY_COUNT; count++) {
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_CFG_HS_SET, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm read failed with %d\n", ret);
+ return ret;
+ }
+
+ if (!(data & ADC_TM_GEN2_CFG_HS_FLAG))
+ return ret;
+ usleep_range(ADC_TM_GEN2_POLL_DELAY_MIN_US,
+ ADC_TM_GEN2_POLL_DELAY_MAX_US);
+ }
+
+ dev_err(chip->dev, "adc-tm conversion request handshake timed out\n");
+
+ return -ETIMEDOUT;
+}
+
+static int adc_tm5_gen2_disable_channel(struct adc_tm5_channel *channel)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ int ret;
+ u8 val;
+
+ mutex_lock(&chip->adc_mutex_lock);
+
+ channel->meas_en = false;
+ channel->high_thr_en = false;
+ channel->low_thr_en = false;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_CH_CTL, &val, sizeof(val));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+ val &= ~ADC_TM_GEN2_TM_CH_SEL;
+ val |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_CH_CTL, &val, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm channel disable failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+ val = 0;
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_MEAS_IRQ_EN, &val, 1);
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm interrupt disable failed with %d\n", ret);
+ goto disable_fail;
+ }
+
+
+ ret = adc_tm5_gen2_conv_req(channel->chip);
+ if (ret < 0)
+ dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
+
+disable_fail:
+ mutex_unlock(&chip->adc_mutex_lock);
+ return ret;
+}
+
+static int adc_tm5_enable(struct adc_tm5_chip *chip)
+{
+ int ret;
+ u8 data;
+
+ data = ADC_TM_EN;
+ ret = adc_tm5_write(chip, ADC_TM_EN_CTL1, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm enable failed\n");
+ return ret;
+ }
+
+ data = ADC_TM_CONV_REQ_EN;
+ ret = adc_tm5_write(chip, ADC_TM_CONV_REQ, &data, sizeof(data));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm request conversion failed\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adc_tm5_configure(struct adc_tm5_channel *channel, int low, int high)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ u8 buf[8];
+ u16 reg = ADC_TM5_M_ADC_CH_SEL_CTL(channel->channel);
+ int ret;
+
+ ret = adc_tm5_read(chip, reg, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params read failed: %d\n", channel->channel, ret);
+ return ret;
+ }
+
+ buf[0] = channel->adc_channel;
+
+ /* High temperature corresponds to low voltage threshold */
+ if (high != INT_MAX) {
+ u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+ chip->data->full_scale_code_volt, high);
+
+ put_unaligned_le16(adc_code, &buf[1]);
+ buf[7] |= ADC_TM5_M_LOW_THR_INT_EN;
+ } else {
+ buf[7] &= ~ADC_TM5_M_LOW_THR_INT_EN;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ if (low != -INT_MAX) {
+ u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+ chip->data->full_scale_code_volt, low);
+
+ put_unaligned_le16(adc_code, &buf[3]);
+ buf[7] |= ADC_TM5_M_HIGH_THR_INT_EN;
+ } else {
+ buf[7] &= ~ADC_TM5_M_HIGH_THR_INT_EN;
+ }
+
+ buf[5] = ADC5_TIMER_SEL_2;
+
+ /* Set calibration select, hw_settle delay */
+ buf[6] &= ~ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK;
+ buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_HW_SETTLE_DELAY_MASK, channel->hw_settle_time);
+ buf[6] &= ~ADC_TM5_M_CTL_CAL_SEL_MASK;
+ buf[6] |= FIELD_PREP(ADC_TM5_M_CTL_CAL_SEL_MASK, channel->cal_method);
+
+ buf[7] |= ADC_TM5_M_MEAS_EN;
+
+ ret = adc_tm5_write(chip, reg, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
+ return ret;
+ }
+
+ return adc_tm5_enable(chip);
+}
+
+static int adc_tm5_gen2_configure(struct adc_tm5_channel *channel, int low, int high)
+{
+ struct adc_tm5_chip *chip = channel->chip;
+ int ret;
+ u8 buf[14];
+ u16 adc_code;
+
+ mutex_lock(&chip->adc_mutex_lock);
+
+ channel->meas_en = true;
+
+ ret = adc_tm5_read(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
+ if (ret < 0) {
+ dev_err(chip->dev, "adc-tm block read failed with %d\n", ret);
+ goto config_fail;
+ }
+
+ /* Set SID from virtual channel number */
+ buf[0] = channel->adc_channel >> 8;
+
+ /* Set TM channel number used and measurement interval */
+ buf[1] &= ~ADC_TM_GEN2_TM_CH_SEL;
+ buf[1] |= FIELD_PREP(ADC_TM_GEN2_TM_CH_SEL, channel->channel);
+ buf[1] &= ~ADC_TM_GEN2_MEAS_INT_SEL;
+ buf[1] |= FIELD_PREP(ADC_TM_GEN2_MEAS_INT_SEL, MEAS_INT_1S);
+
+ buf[2] &= ~ADC_TM_GEN2_CTL_DEC_RATIO_MASK;
+ buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_DEC_RATIO_MASK, channel->decimation);
+ buf[2] &= ~ADC_TM_GEN2_CTL_CAL_SEL;
+ buf[2] |= FIELD_PREP(ADC_TM_GEN2_CTL_CAL_SEL, channel->cal_method);
+
+ buf[3] = channel->avg_samples | ADC_TM_GEN2_FAST_AVG_EN;
+
+ buf[4] = channel->adc_channel & 0xff;
+
+ buf[5] = channel->hw_settle_time & ADC_TM_GEN2_HW_SETTLE_DELAY;
+
+ /* High temperature corresponds to low voltage threshold */
+ if (high != INT_MAX) {
+ channel->low_thr_en = true;
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(high);
+ put_unaligned_le16(adc_code, &buf[9]);
+ } else {
+ channel->low_thr_en = false;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ if (low != -INT_MAX) {
+ channel->high_thr_en = true;
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(low);
+ put_unaligned_le16(adc_code, &buf[11]);
+ } else {
+ channel->high_thr_en = false;
+ }
+
+ buf[13] = ADC_TM_GEN2_MEAS_EN;
+ if (channel->high_thr_en)
+ buf[13] |= ADC_TM5_GEN2_HIGH_THR_INT_EN;
+ if (channel->low_thr_en)
+ buf[13] |= ADC_TM5_GEN2_LOW_THR_INT_EN;
+
+ ret = adc_tm5_write(chip, ADC_TM_GEN2_SID, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "channel %d params write failed: %d\n", channel->channel, ret);
+ goto config_fail;
+ }
+
+ ret = adc_tm5_gen2_conv_req(channel->chip);
+ if (ret < 0)
+ dev_err(chip->dev, "adc-tm channel configure failed with %d\n", ret);
+
+config_fail:
+ mutex_unlock(&chip->adc_mutex_lock);
+ return ret;
+}
+
+static int adc_tm5_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct adc_tm5_channel *channel = tz->devdata;
+ struct adc_tm5_chip *chip;
+ int ret;
+
+ if (!channel)
+ return -EINVAL;
+
+ chip = channel->chip;
+ dev_dbg(chip->dev, "%d:low(mdegC):%d, high(mdegC):%d\n",
+ channel->channel, low, high);
+
+ if (high == INT_MAX && low <= -INT_MAX)
+ ret = chip->data->disable_channel(channel);
+ else
+ ret = chip->data->configure(channel, low, high);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops adc_tm5_thermal_ops = {
+ .get_temp = adc_tm5_get_temp,
+ .set_trips = adc_tm5_set_trips,
+};
+
+static int adc_tm5_register_tzd(struct adc_tm5_chip *adc_tm)
+{
+ unsigned int i;
+ struct thermal_zone_device *tzd;
+
+ for (i = 0; i < adc_tm->nchannels; i++) {
+ adc_tm->channels[i].chip = adc_tm;
+ tzd = devm_thermal_of_zone_register(adc_tm->dev,
+ adc_tm->channels[i].channel,
+ &adc_tm->channels[i],
+ &adc_tm5_thermal_ops);
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -ENODEV) {
+ dev_warn(adc_tm->dev, "thermal sensor on channel %d is not used\n",
+ adc_tm->channels[i].channel);
+ continue;
+ }
+
+ dev_err(adc_tm->dev, "Error registering TZ zone for channel %d: %ld\n",
+ adc_tm->channels[i].channel, PTR_ERR(tzd));
+ return PTR_ERR(tzd);
+ }
+ adc_tm->channels[i].tzd = tzd;
+ if (devm_thermal_add_hwmon_sysfs(tzd))
+ dev_warn(adc_tm->dev,
+ "Failed to add hwmon sysfs attributes\n");
+ }
+
+ return 0;
+}
+
+static int adc_tm_hc_init(struct adc_tm5_chip *chip)
+{
+ unsigned int i;
+ u8 buf[2];
+ int ret;
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= ADC_TM5_NUM_CHANNELS) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ buf[0] = chip->decimation;
+ buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+
+ ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf));
+ if (ret)
+ dev_err(chip->dev, "block write failed: %d\n", ret);
+
+ return ret;
+}
+
+static int adc_tm5_init(struct adc_tm5_chip *chip)
+{
+ u8 buf[4], channels_available;
+ int ret;
+ unsigned int i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
+ &channels_available, sizeof(channels_available));
+ if (ret) {
+ dev_err(chip->dev, "read failed for BTM channels\n");
+ return ret;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= channels_available) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ buf[0] = chip->decimation;
+ buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+ buf[2] = ADC_TM5_TIMER1;
+ buf[3] = FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL2_MASK, ADC_TM5_TIMER2) |
+ FIELD_PREP(ADC_TM5_MEAS_INTERVAL_CTL3_MASK, ADC_TM5_TIMER3);
+
+ ret = adc_tm5_write(chip, ADC_TM5_ADC_DIG_PARAM, buf, sizeof(buf));
+ if (ret) {
+ dev_err(chip->dev, "block write failed: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int adc_tm5_gen2_init(struct adc_tm5_chip *chip)
+{
+ u8 channels_available;
+ int ret;
+ unsigned int i;
+
+ ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM,
+ &channels_available, sizeof(channels_available));
+ if (ret) {
+ dev_err(chip->dev, "read failed for BTM channels\n");
+ return ret;
+ }
+
+ for (i = 0; i < chip->nchannels; i++) {
+ if (chip->channels[i].channel >= channels_available) {
+ dev_err(chip->dev, "Invalid channel %d\n", chip->channels[i].channel);
+ return -EINVAL;
+ }
+ }
+
+ mutex_init(&chip->adc_mutex_lock);
+
+ return ret;
+}
+
+static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
+ struct adc_tm5_channel *channel,
+ struct device_node *node)
+{
+ const char *name = node->name;
+ u32 chan, value, adc_channel, varr[2];
+ int ret;
+ struct device *dev = adc_tm->dev;
+ struct of_phandle_args args;
+
+ ret = of_property_read_u32(node, "reg", &chan);
+ if (ret) {
+ dev_err(dev, "%s: invalid channel number %d\n", name, ret);
+ return ret;
+ }
+
+ if (chan >= ADC_TM5_NUM_CHANNELS) {
+ dev_err(dev, "%s: channel number too big: %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ channel->channel = chan;
+
+ /*
+ * We are tied to PMIC's ADC controller, which always use single
+ * argument for channel number. So don't bother parsing
+ * #io-channel-cells, just enforce cell_count = 1.
+ */
+ ret = of_parse_phandle_with_fixed_args(node, "io-channels", 1, 0, &args);
+ if (ret < 0) {
+ dev_err(dev, "%s: error parsing ADC channel number %d: %d\n", name, chan, ret);
+ return ret;
+ }
+ of_node_put(args.np);
+
+ if (args.args_count != 1) {
+ dev_err(dev, "%s: invalid args count for ADC channel %d\n", name, chan);
+ return -EINVAL;
+ }
+
+ adc_channel = args.args[0];
+ if (adc_tm->data->gen == ADC_TM5_GEN2)
+ adc_channel &= 0xff;
+
+ if (adc_channel >= ADC5_MAX_CHANNEL) {
+ dev_err(dev, "%s: invalid ADC channel number %d\n", name, chan);
+ return -EINVAL;
+ }
+ channel->adc_channel = args.args[0];
+
+ channel->iio = devm_fwnode_iio_channel_get_by_name(adc_tm->dev,
+ of_fwnode_handle(node), NULL);
+ if (IS_ERR(channel->iio)) {
+ ret = PTR_ERR(channel->iio);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "%s: error getting channel: %d\n", name, ret);
+ return ret;
+ }
+
+ ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
+ if (!ret) {
+ ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
+ if (ret < 0) {
+ dev_err(dev, "%s: invalid pre-scaling <%d %d>\n",
+ name, varr[0], varr[1]);
+ return ret;
+ }
+ channel->prescale = ret;
+ } else {
+ /* 1:1 prescale is index 0 */
+ channel->prescale = 0;
+ }
+
+ ret = of_property_read_u32(node, "qcom,hw-settle-time-us", &value);
+ if (!ret) {
+ ret = qcom_adc5_hw_settle_time_from_dt(value, adc_tm->data->hw_settle);
+ if (ret < 0) {
+ dev_err(dev, "%s invalid hw-settle-time-us %d us\n",
+ name, value);
+ return ret;
+ }
+ channel->hw_settle_time = ret;
+ } else {
+ channel->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
+ }
+
+ if (of_property_read_bool(node, "qcom,ratiometric"))
+ channel->cal_method = ADC_TM5_RATIOMETRIC_CAL;
+ else
+ channel->cal_method = ADC_TM5_ABSOLUTE_CAL;
+
+ if (adc_tm->data->gen == ADC_TM5_GEN2) {
+ ret = of_property_read_u32(node, "qcom,decimation", &value);
+ if (!ret) {
+ ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
+ if (ret < 0) {
+ dev_err(dev, "invalid decimation %d\n", value);
+ return ret;
+ }
+ channel->decimation = ret;
+ } else {
+ channel->decimation = ADC5_DECIMATION_DEFAULT;
+ }
+
+ ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+ if (!ret) {
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0) {
+ dev_err(dev, "invalid avg-samples %d\n", value);
+ return ret;
+ }
+ channel->avg_samples = ret;
+ } else {
+ channel->avg_samples = VADC_DEF_AVG_SAMPLES;
+ }
+ }
+
+ return 0;
+}
+
+static const struct adc_tm5_data adc_tm5_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 250, 420, 840 },
+ .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000, 16000, 32000,
+ 64000, 128000 },
+ .disable_channel = adc_tm5_disable_channel,
+ .configure = adc_tm5_configure,
+ .isr = adc_tm5_isr,
+ .init = adc_tm5_init,
+ .irq_name = "pm-adc-tm5",
+ .gen = ADC_TM5,
+};
+
+static const struct adc_tm5_data adc_tm_hc_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 256, 512, 1024 },
+ .hw_settle = (unsigned int []) { 0, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 6000, 8000, 10000 },
+ .disable_channel = adc_tm5_disable_channel,
+ .configure = adc_tm5_configure,
+ .isr = adc_tm5_isr,
+ .init = adc_tm_hc_init,
+ .irq_name = "pm-adc-tm5",
+ .gen = ADC_TM_HC,
+};
+
+static const struct adc_tm5_data adc_tm5_gen2_data_pmic = {
+ .full_scale_code_volt = 0x70e4,
+ .decimation = (unsigned int []) { 85, 340, 1360 },
+ .hw_settle = (unsigned int []) { 15, 100, 200, 300, 400, 500, 600, 700,
+ 1000, 2000, 4000, 8000, 16000, 32000,
+ 64000, 128000 },
+ .disable_channel = adc_tm5_gen2_disable_channel,
+ .configure = adc_tm5_gen2_configure,
+ .isr = adc_tm5_gen2_isr,
+ .init = adc_tm5_gen2_init,
+ .irq_name = "pm-adc-tm5-gen2",
+ .gen = ADC_TM5_GEN2,
+};
+
+static int adc_tm5_get_dt_data(struct adc_tm5_chip *adc_tm, struct device_node *node)
+{
+ struct adc_tm5_channel *channels;
+ struct device_node *child;
+ u32 value;
+ int ret;
+ struct device *dev = adc_tm->dev;
+
+ adc_tm->nchannels = of_get_available_child_count(node);
+ if (!adc_tm->nchannels)
+ return -EINVAL;
+
+ adc_tm->channels = devm_kcalloc(dev, adc_tm->nchannels,
+ sizeof(*adc_tm->channels), GFP_KERNEL);
+ if (!adc_tm->channels)
+ return -ENOMEM;
+
+ channels = adc_tm->channels;
+
+ adc_tm->data = of_device_get_match_data(dev);
+ if (!adc_tm->data)
+ adc_tm->data = &adc_tm5_data_pmic;
+
+ ret = of_property_read_u32(node, "qcom,decimation", &value);
+ if (!ret) {
+ ret = qcom_adc5_decimation_from_dt(value, adc_tm->data->decimation);
+ if (ret < 0) {
+ dev_err(dev, "invalid decimation %d\n", value);
+ return ret;
+ }
+ adc_tm->decimation = ret;
+ } else {
+ adc_tm->decimation = ADC5_DECIMATION_DEFAULT;
+ }
+
+ ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+ if (!ret) {
+ ret = qcom_adc5_avg_samples_from_dt(value);
+ if (ret < 0) {
+ dev_err(dev, "invalid avg-samples %d\n", value);
+ return ret;
+ }
+ adc_tm->avg_samples = ret;
+ } else {
+ adc_tm->avg_samples = VADC_DEF_AVG_SAMPLES;
+ }
+
+ for_each_available_child_of_node(node, child) {
+ ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child);
+ if (ret) {
+ of_node_put(child);
+ return ret;
+ }
+
+ channels++;
+ }
+
+ return 0;
+}
+
+static int adc_tm5_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct adc_tm5_chip *adc_tm;
+ struct regmap *regmap;
+ int ret, irq;
+ u32 reg;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ ret = of_property_read_u32(node, "reg", &reg);
+ if (ret)
+ return ret;
+
+ adc_tm = devm_kzalloc(&pdev->dev, sizeof(*adc_tm), GFP_KERNEL);
+ if (!adc_tm)
+ return -ENOMEM;
+
+ adc_tm->regmap = regmap;
+ adc_tm->dev = dev;
+ adc_tm->base = reg;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = adc_tm5_get_dt_data(adc_tm, node);
+ if (ret) {
+ dev_err(dev, "get dt data failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = adc_tm->data->init(adc_tm);
+ if (ret) {
+ dev_err(dev, "adc-tm init failed\n");
+ return ret;
+ }
+
+ ret = adc_tm5_register_tzd(adc_tm);
+ if (ret) {
+ dev_err(dev, "tzd register failed\n");
+ return ret;
+ }
+
+ return devm_request_threaded_irq(dev, irq, NULL, adc_tm->data->isr,
+ IRQF_ONESHOT, adc_tm->data->irq_name, adc_tm);
+}
+
+static const struct of_device_id adc_tm5_match_table[] = {
+ {
+ .compatible = "qcom,spmi-adc-tm5",
+ .data = &adc_tm5_data_pmic,
+ },
+ {
+ .compatible = "qcom,spmi-adc-tm-hc",
+ .data = &adc_tm_hc_data_pmic,
+ },
+ {
+ .compatible = "qcom,spmi-adc-tm5-gen2",
+ .data = &adc_tm5_gen2_data_pmic,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adc_tm5_match_table);
+
+static struct platform_driver adc_tm5_driver = {
+ .driver = {
+ .name = "qcom-spmi-adc-tm5",
+ .of_match_table = adc_tm5_match_table,
+ },
+ .probe = adc_tm5_probe,
+};
+module_platform_driver(adc_tm5_driver);
+
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/qcom/qcom-spmi-temp-alarm.c b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
new file mode 100644
index 000000000..ad8497810
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-temp-alarm.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2011-2015, 2017, 2020, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+#include "../thermal_core.h"
+#include "../thermal_hwmon.h"
+
+#define QPNP_TM_REG_DIG_MAJOR 0x01
+#define QPNP_TM_REG_TYPE 0x04
+#define QPNP_TM_REG_SUBTYPE 0x05
+#define QPNP_TM_REG_STATUS 0x08
+#define QPNP_TM_REG_SHUTDOWN_CTRL1 0x40
+#define QPNP_TM_REG_ALARM_CTRL 0x46
+
+#define QPNP_TM_TYPE 0x09
+#define QPNP_TM_SUBTYPE_GEN1 0x08
+#define QPNP_TM_SUBTYPE_GEN2 0x09
+
+#define STATUS_GEN1_STAGE_MASK GENMASK(1, 0)
+#define STATUS_GEN2_STATE_MASK GENMASK(6, 4)
+#define STATUS_GEN2_STATE_SHIFT 4
+
+#define SHUTDOWN_CTRL1_OVERRIDE_S2 BIT(6)
+#define SHUTDOWN_CTRL1_THRESHOLD_MASK GENMASK(1, 0)
+
+#define SHUTDOWN_CTRL1_RATE_25HZ BIT(3)
+
+#define ALARM_CTRL_FORCE_ENABLE BIT(7)
+
+#define THRESH_COUNT 4
+#define STAGE_COUNT 3
+
+/* Over-temperature trip point values in mC */
+static const long temp_map_gen1[THRESH_COUNT][STAGE_COUNT] = {
+ { 105000, 125000, 145000 },
+ { 110000, 130000, 150000 },
+ { 115000, 135000, 155000 },
+ { 120000, 140000, 160000 },
+};
+
+static const long temp_map_gen2_v1[THRESH_COUNT][STAGE_COUNT] = {
+ { 90000, 110000, 140000 },
+ { 95000, 115000, 145000 },
+ { 100000, 120000, 150000 },
+ { 105000, 125000, 155000 },
+};
+
+#define TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+
+#define THRESH_MIN 0
+#define THRESH_MAX 3
+
+#define TEMP_STAGE_HYSTERESIS 2000
+
+/* Temperature in Milli Celsius reported during stage 0 if no ADC is present */
+#define DEFAULT_TEMP 37000
+
+struct qpnp_tm_chip {
+ struct regmap *map;
+ struct device *dev;
+ struct thermal_zone_device *tz_dev;
+ unsigned int subtype;
+ long temp;
+ unsigned int thresh;
+ unsigned int stage;
+ unsigned int prev_stage;
+ unsigned int base;
+ /* protects .thresh, .stage and chip registers */
+ struct mutex lock;
+ bool initialized;
+
+ struct iio_channel *adc;
+ const long (*temp_map)[THRESH_COUNT][STAGE_COUNT];
+};
+
+/* This array maps from GEN2 alarm state to GEN1 alarm stage */
+static const unsigned int alarm_state_map[8] = {0, 1, 1, 2, 2, 3, 3, 3};
+
+static int qpnp_tm_read(struct qpnp_tm_chip *chip, u16 addr, u8 *data)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(chip->map, chip->base + addr, &val);
+ if (ret < 0)
+ return ret;
+
+ *data = val;
+ return 0;
+}
+
+static int qpnp_tm_write(struct qpnp_tm_chip *chip, u16 addr, u8 data)
+{
+ return regmap_write(chip->map, chip->base + addr, data);
+}
+
+/**
+ * qpnp_tm_decode_temp() - return temperature in mC corresponding to the
+ * specified over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ * @stage: Over-temperature stage
+ *
+ * Return: temperature in mC
+ */
+static long qpnp_tm_decode_temp(struct qpnp_tm_chip *chip, unsigned int stage)
+{
+ if (!chip->temp_map || chip->thresh >= THRESH_COUNT || stage == 0 ||
+ stage > STAGE_COUNT)
+ return 0;
+
+ return (*chip->temp_map)[chip->thresh][stage - 1];
+}
+
+/**
+ * qpnp_tm_get_temp_stage() - return over-temperature stage
+ * @chip: Pointer to the qpnp_tm chip
+ *
+ * Return: stage (GEN1) or state (GEN2) on success, or errno on failure.
+ */
+static int qpnp_tm_get_temp_stage(struct qpnp_tm_chip *chip)
+{
+ int ret;
+ u8 reg = 0;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_STATUS, &reg);
+ if (ret < 0)
+ return ret;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1)
+ ret = reg & STATUS_GEN1_STAGE_MASK;
+ else
+ ret = (reg & STATUS_GEN2_STATE_MASK) >> STATUS_GEN2_STATE_SHIFT;
+
+ return ret;
+}
+
+/*
+ * This function updates the internal temp value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int qpnp_tm_update_temp_no_adc(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage, stage_new, stage_old;
+ int ret;
+
+ WARN_ON(!mutex_is_locked(&chip->lock));
+
+ ret = qpnp_tm_get_temp_stage(chip);
+ if (ret < 0)
+ return ret;
+ stage = ret;
+
+ if (chip->subtype == QPNP_TM_SUBTYPE_GEN1) {
+ stage_new = stage;
+ stage_old = chip->stage;
+ } else {
+ stage_new = alarm_state_map[stage];
+ stage_old = alarm_state_map[chip->stage];
+ }
+
+ if (stage_new > stage_old) {
+ /* increasing stage, use lower bound */
+ chip->temp = qpnp_tm_decode_temp(chip, stage_new)
+ + TEMP_STAGE_HYSTERESIS;
+ } else if (stage_new < stage_old) {
+ /* decreasing stage, use upper bound */
+ chip->temp = qpnp_tm_decode_temp(chip, stage_new + 1)
+ - TEMP_STAGE_HYSTERESIS;
+ }
+
+ chip->stage = stage;
+
+ return 0;
+}
+
+static int qpnp_tm_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct qpnp_tm_chip *chip = tz->devdata;
+ int ret, mili_celsius;
+
+ if (!temp)
+ return -EINVAL;
+
+ if (!chip->initialized) {
+ *temp = DEFAULT_TEMP;
+ return 0;
+ }
+
+ if (!chip->adc) {
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_update_temp_no_adc(chip);
+ mutex_unlock(&chip->lock);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = iio_read_channel_processed(chip->adc, &mili_celsius);
+ if (ret < 0)
+ return ret;
+
+ chip->temp = mili_celsius;
+ }
+
+ *temp = chip->temp;
+
+ return 0;
+}
+
+static int qpnp_tm_update_critical_trip_temp(struct qpnp_tm_chip *chip,
+ int temp)
+{
+ long stage2_threshold_min = (*chip->temp_map)[THRESH_MIN][1];
+ long stage2_threshold_max = (*chip->temp_map)[THRESH_MAX][1];
+ bool disable_s2_shutdown = false;
+ u8 reg;
+
+ WARN_ON(!mutex_is_locked(&chip->lock));
+
+ /*
+ * Default: S2 and S3 shutdown enabled, thresholds at
+ * lowest threshold set, monitoring at 25Hz
+ */
+ reg = SHUTDOWN_CTRL1_RATE_25HZ;
+
+ if (temp == THERMAL_TEMP_INVALID ||
+ temp < stage2_threshold_min) {
+ chip->thresh = THRESH_MIN;
+ goto skip;
+ }
+
+ if (temp <= stage2_threshold_max) {
+ chip->thresh = THRESH_MAX -
+ ((stage2_threshold_max - temp) /
+ TEMP_THRESH_STEP);
+ disable_s2_shutdown = true;
+ } else {
+ chip->thresh = THRESH_MAX;
+
+ if (chip->adc)
+ disable_s2_shutdown = true;
+ else
+ dev_warn(chip->dev,
+ "No ADC is configured and critical temperature %d mC is above the maximum stage 2 threshold of %ld mC! Configuring stage 2 shutdown at %ld mC.\n",
+ temp, stage2_threshold_max, stage2_threshold_max);
+ }
+
+skip:
+ reg |= chip->thresh;
+ if (disable_s2_shutdown)
+ reg |= SHUTDOWN_CTRL1_OVERRIDE_S2;
+
+ return qpnp_tm_write(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, reg);
+}
+
+static int qpnp_tm_set_trip_temp(struct thermal_zone_device *tz, int trip, int temp)
+{
+ struct qpnp_tm_chip *chip = tz->devdata;
+ const struct thermal_trip *trip_points;
+ int ret;
+
+ trip_points = of_thermal_get_trip_points(chip->tz_dev);
+ if (!trip_points)
+ return -EINVAL;
+
+ if (trip_points[trip].type != THERMAL_TRIP_CRITICAL)
+ return 0;
+
+ mutex_lock(&chip->lock);
+ ret = qpnp_tm_update_critical_trip_temp(chip, temp);
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+
+static const struct thermal_zone_device_ops qpnp_tm_sensor_ops = {
+ .get_temp = qpnp_tm_get_temp,
+ .set_trip_temp = qpnp_tm_set_trip_temp,
+};
+
+static irqreturn_t qpnp_tm_isr(int irq, void *data)
+{
+ struct qpnp_tm_chip *chip = data;
+
+ thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+ return IRQ_HANDLED;
+}
+
+static int qpnp_tm_get_critical_trip_temp(struct qpnp_tm_chip *chip)
+{
+ int ntrips;
+ const struct thermal_trip *trips;
+ int i;
+
+ ntrips = of_thermal_get_ntrips(chip->tz_dev);
+ if (ntrips <= 0)
+ return THERMAL_TEMP_INVALID;
+
+ trips = of_thermal_get_trip_points(chip->tz_dev);
+ if (!trips)
+ return THERMAL_TEMP_INVALID;
+
+ for (i = 0; i < ntrips; i++) {
+ if (of_thermal_is_trip_valid(chip->tz_dev, i) &&
+ trips[i].type == THERMAL_TRIP_CRITICAL)
+ return trips[i].temperature;
+ }
+
+ return THERMAL_TEMP_INVALID;
+}
+
+/*
+ * This function initializes the internal temp value based on only the
+ * current thermal stage and threshold. Setup threshold control and
+ * disable shutdown override.
+ */
+static int qpnp_tm_init(struct qpnp_tm_chip *chip)
+{
+ unsigned int stage;
+ int ret;
+ u8 reg = 0;
+ int crit_temp;
+
+ mutex_lock(&chip->lock);
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SHUTDOWN_CTRL1, &reg);
+ if (ret < 0)
+ goto out;
+
+ chip->thresh = reg & SHUTDOWN_CTRL1_THRESHOLD_MASK;
+ chip->temp = DEFAULT_TEMP;
+
+ ret = qpnp_tm_get_temp_stage(chip);
+ if (ret < 0)
+ goto out;
+ chip->stage = ret;
+
+ stage = chip->subtype == QPNP_TM_SUBTYPE_GEN1
+ ? chip->stage : alarm_state_map[chip->stage];
+
+ if (stage)
+ chip->temp = qpnp_tm_decode_temp(chip, stage);
+
+ crit_temp = qpnp_tm_get_critical_trip_temp(chip);
+ ret = qpnp_tm_update_critical_trip_temp(chip, crit_temp);
+ if (ret < 0)
+ goto out;
+
+ /* Enable the thermal alarm PMIC module in always-on mode. */
+ reg = ALARM_CTRL_FORCE_ENABLE;
+ ret = qpnp_tm_write(chip, QPNP_TM_REG_ALARM_CTRL, reg);
+
+ chip->initialized = true;
+
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static int qpnp_tm_probe(struct platform_device *pdev)
+{
+ struct qpnp_tm_chip *chip;
+ struct device_node *node;
+ u8 type, subtype, dig_major;
+ u32 res;
+ int ret, irq;
+
+ node = pdev->dev.of_node;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ dev_set_drvdata(&pdev->dev, chip);
+ chip->dev = &pdev->dev;
+
+ mutex_init(&chip->lock);
+
+ chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->map)
+ return -ENXIO;
+
+ ret = of_property_read_u32(node, "reg", &res);
+ if (ret < 0)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ /* ADC based measurements are optional */
+ chip->adc = devm_iio_channel_get(&pdev->dev, "thermal");
+ if (IS_ERR(chip->adc)) {
+ ret = PTR_ERR(chip->adc);
+ chip->adc = NULL;
+ if (ret == -EPROBE_DEFER)
+ return ret;
+ }
+
+ chip->base = res;
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_TYPE, &type);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read type\n");
+ return ret;
+ }
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_SUBTYPE, &subtype);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read subtype\n");
+ return ret;
+ }
+
+ ret = qpnp_tm_read(chip, QPNP_TM_REG_DIG_MAJOR, &dig_major);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not read dig_major\n");
+ return ret;
+ }
+
+ if (type != QPNP_TM_TYPE || (subtype != QPNP_TM_SUBTYPE_GEN1
+ && subtype != QPNP_TM_SUBTYPE_GEN2)) {
+ dev_err(&pdev->dev, "invalid type 0x%02x or subtype 0x%02x\n",
+ type, subtype);
+ return -ENODEV;
+ }
+
+ chip->subtype = subtype;
+ if (subtype == QPNP_TM_SUBTYPE_GEN2 && dig_major >= 1)
+ chip->temp_map = &temp_map_gen2_v1;
+ else
+ chip->temp_map = &temp_map_gen1;
+
+ /*
+ * Register the sensor before initializing the hardware to be able to
+ * read the trip points. get_temp() returns the default temperature
+ * before the hardware initialization is completed.
+ */
+ chip->tz_dev = devm_thermal_of_zone_register(
+ &pdev->dev, 0, chip, &qpnp_tm_sensor_ops);
+ if (IS_ERR(chip->tz_dev)) {
+ dev_err(&pdev->dev, "failed to register sensor\n");
+ return PTR_ERR(chip->tz_dev);
+ }
+
+ ret = qpnp_tm_init(chip);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "init failed\n");
+ return ret;
+ }
+
+ if (devm_thermal_add_hwmon_sysfs(chip->tz_dev))
+ dev_warn(&pdev->dev,
+ "Failed to add hwmon sysfs attributes\n");
+
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qpnp_tm_isr,
+ IRQF_ONESHOT, node->name, chip);
+ if (ret < 0)
+ return ret;
+
+ thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+ return 0;
+}
+
+static const struct of_device_id qpnp_tm_match_table[] = {
+ { .compatible = "qcom,spmi-temp-alarm" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, qpnp_tm_match_table);
+
+static struct platform_driver qpnp_tm_driver = {
+ .driver = {
+ .name = "spmi-temp-alarm",
+ .of_match_table = qpnp_tm_match_table,
+ },
+ .probe = qpnp_tm_probe,
+};
+module_platform_driver(qpnp_tm_driver);
+
+MODULE_ALIAS("platform:spmi-temp-alarm");
+MODULE_DESCRIPTION("QPNP PMIC Temperature Alarm driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c
new file mode 100644
index 000000000..67c1748cd
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-8960.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+#include "tsens.h"
+
+#define CONFIG_ADDR 0x3640
+#define CONFIG_ADDR_8660 0x3620
+/* CONFIG_ADDR bitmasks */
+#define CONFIG 0x9b
+#define CONFIG_MASK 0xf
+#define CONFIG_8660 1
+#define CONFIG_SHIFT_8660 28
+#define CONFIG_MASK_8660 (3 << CONFIG_SHIFT_8660)
+
+#define CNTL_ADDR 0x3620
+/* CNTL_ADDR bitmasks */
+#define EN BIT(0)
+#define SW_RST BIT(1)
+
+#define MEASURE_PERIOD BIT(18)
+#define SLP_CLK_ENA BIT(26)
+#define SLP_CLK_ENA_8660 BIT(24)
+#define SENSOR0_SHIFT 3
+
+#define THRESHOLD_ADDR 0x3624
+
+#define INT_STATUS_ADDR 0x363c
+
+#define S0_STATUS_OFF 0x3628
+#define S1_STATUS_OFF 0x362c
+#define S2_STATUS_OFF 0x3630
+#define S3_STATUS_OFF 0x3634
+#define S4_STATUS_OFF 0x3638
+#define S5_STATUS_OFF 0x3664 /* Sensors 5-10 found on apq8064/msm8960 */
+#define S6_STATUS_OFF 0x3668
+#define S7_STATUS_OFF 0x366c
+#define S8_STATUS_OFF 0x3670
+#define S9_STATUS_OFF 0x3674
+#define S10_STATUS_OFF 0x3678
+
+/* Original slope - 350 to compensate mC to C inaccuracy */
+static u32 tsens_msm8960_slope[] = {
+ 826, 826, 804, 826,
+ 761, 782, 782, 849,
+ 782, 849, 782
+ };
+
+static int suspend_8960(struct tsens_priv *priv)
+{
+ int ret;
+ unsigned int mask;
+ struct regmap *map = priv->tm_map;
+
+ ret = regmap_read(map, THRESHOLD_ADDR, &priv->ctx.threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, CNTL_ADDR, &priv->ctx.control);
+ if (ret)
+ return ret;
+
+ if (priv->num_sensors > 1)
+ mask = SLP_CLK_ENA | EN;
+ else
+ mask = SLP_CLK_ENA_8660 | EN;
+
+ ret = regmap_update_bits(map, CNTL_ADDR, mask, 0);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int resume_8960(struct tsens_priv *priv)
+{
+ int ret;
+ struct regmap *map = priv->tm_map;
+
+ ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST);
+ if (ret)
+ return ret;
+
+ /*
+ * Separate CONFIG restore is not needed only for 8660 as
+ * config is part of CTRL Addr and its restored as such
+ */
+ if (priv->num_sensors > 1) {
+ ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(map, THRESHOLD_ADDR, priv->ctx.threshold);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(map, CNTL_ADDR, priv->ctx.control);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int enable_8960(struct tsens_priv *priv, int id)
+{
+ int ret;
+ u32 reg, mask = BIT(id);
+
+ ret = regmap_read(priv->tm_map, CNTL_ADDR, &reg);
+ if (ret)
+ return ret;
+
+ /* HARDWARE BUG:
+ * On platforms with more than 6 sensors, all remaining sensors
+ * must be enabled together, otherwise undefined results are expected.
+ * (Sensor 6-7 disabled, Sensor 3 disabled...) In the original driver,
+ * all the sensors are enabled in one step hence this bug is not
+ * triggered.
+ */
+ if (id > 5)
+ mask = GENMASK(10, 6);
+
+ mask <<= SENSOR0_SHIFT;
+
+ /* Sensors already enabled. Skip. */
+ if ((reg & mask) == mask)
+ return 0;
+
+ ret = regmap_write(priv->tm_map, CNTL_ADDR, reg | SW_RST);
+ if (ret)
+ return ret;
+
+ reg |= MEASURE_PERIOD;
+
+ if (priv->num_sensors > 1)
+ reg |= mask | SLP_CLK_ENA | EN;
+ else
+ reg |= mask | SLP_CLK_ENA_8660 | EN;
+
+ ret = regmap_write(priv->tm_map, CNTL_ADDR, reg);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void disable_8960(struct tsens_priv *priv)
+{
+ int ret;
+ u32 reg_cntl;
+ u32 mask;
+
+ mask = GENMASK(priv->num_sensors - 1, 0);
+ mask <<= SENSOR0_SHIFT;
+ mask |= EN;
+
+ ret = regmap_read(priv->tm_map, CNTL_ADDR, &reg_cntl);
+ if (ret)
+ return;
+
+ reg_cntl &= ~mask;
+
+ if (priv->num_sensors > 1)
+ reg_cntl &= ~SLP_CLK_ENA;
+ else
+ reg_cntl &= ~SLP_CLK_ENA_8660;
+
+ regmap_write(priv->tm_map, CNTL_ADDR, reg_cntl);
+}
+
+static int calibrate_8960(struct tsens_priv *priv)
+{
+ int i;
+ char *data;
+ u32 p1[11];
+
+ data = qfprom_read(priv->dev, "calib");
+ if (IS_ERR(data))
+ data = qfprom_read(priv->dev, "calib_backup");
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = data[i];
+ priv->sensor[i].slope = tsens_msm8960_slope[i];
+ }
+
+ compute_intercept_slope(priv, p1, NULL, ONE_PT_CALIB);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct reg_field tsens_8960_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* No VERSION information */
+
+ /* CNTL */
+ [TSENS_EN] = REG_FIELD(CNTL_ADDR, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(CNTL_ADDR, 1, 1),
+ /* 8960 has 5 sensors, 8660 has 11, we only handle 5 */
+ [SENSOR_EN] = REG_FIELD(CNTL_ADDR, 3, 7),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ /* NO INTERRUPT ENABLE */
+
+ /* Single UPPER/LOWER TEMPERATURE THRESHOLD for all sensors */
+ [LOW_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 0, 7),
+ [UP_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 8, 15),
+ /* MIN_THRESH_0 and MAX_THRESH_0 are not present in the regfield
+ * Recycle CRIT_THRESH_0 and 1 to set the required regs to hardcoded temp
+ * MIN_THRESH_0 -> CRIT_THRESH_1
+ * MAX_THRESH_0 -> CRIT_THRESH_0
+ */
+ [CRIT_THRESH_1] = REG_FIELD(THRESHOLD_ADDR, 16, 23),
+ [CRIT_THRESH_0] = REG_FIELD(THRESHOLD_ADDR, 24, 31),
+
+ /* UPPER/LOWER INTERRUPT [CLEAR/STATUS] */
+ /* 1 == clear, 0 == normal operation */
+ [LOW_INT_CLEAR_0] = REG_FIELD(CNTL_ADDR, 9, 9),
+ [UP_INT_CLEAR_0] = REG_FIELD(CNTL_ADDR, 10, 10),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on 8960 */
+
+ /* Sn_STATUS */
+ [LAST_TEMP_0] = REG_FIELD(S0_STATUS_OFF, 0, 7),
+ [LAST_TEMP_1] = REG_FIELD(S1_STATUS_OFF, 0, 7),
+ [LAST_TEMP_2] = REG_FIELD(S2_STATUS_OFF, 0, 7),
+ [LAST_TEMP_3] = REG_FIELD(S3_STATUS_OFF, 0, 7),
+ [LAST_TEMP_4] = REG_FIELD(S4_STATUS_OFF, 0, 7),
+ [LAST_TEMP_5] = REG_FIELD(S5_STATUS_OFF, 0, 7),
+ [LAST_TEMP_6] = REG_FIELD(S6_STATUS_OFF, 0, 7),
+ [LAST_TEMP_7] = REG_FIELD(S7_STATUS_OFF, 0, 7),
+ [LAST_TEMP_8] = REG_FIELD(S8_STATUS_OFF, 0, 7),
+ [LAST_TEMP_9] = REG_FIELD(S9_STATUS_OFF, 0, 7),
+ [LAST_TEMP_10] = REG_FIELD(S10_STATUS_OFF, 0, 7),
+
+ /* No VALID field on 8960 */
+ /* TSENS_INT_STATUS bits: 1 == threshold violated */
+ [MIN_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 0, 0),
+ [LOWER_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 1, 1),
+ [UPPER_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 2, 2),
+ /* No CRITICAL field on 8960 */
+ [MAX_STATUS_0] = REG_FIELD(INT_STATUS_ADDR, 3, 3),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(INT_STATUS_ADDR, 7, 7),
+};
+
+static const struct tsens_ops ops_8960 = {
+ .init = init_common,
+ .calibrate = calibrate_8960,
+ .get_temp = get_temp_common,
+ .enable = enable_8960,
+ .disable = disable_8960,
+ .suspend = suspend_8960,
+ .resume = resume_8960,
+};
+
+static struct tsens_features tsens_8960_feat = {
+ .ver_major = VER_0,
+ .crit_int = 0,
+ .adc = 1,
+ .srot_split = 0,
+ .max_sensors = 11,
+};
+
+struct tsens_plat_data data_8960 = {
+ .num_sensors = 11,
+ .ops = &ops_8960,
+ .feat = &tsens_8960_feat,
+ .fields = tsens_8960_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens-v0_1.c b/drivers/thermal/qcom/tsens-v0_1.c
new file mode 100644
index 000000000..8d036727b
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v0_1.c
@@ -0,0 +1,648 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/platform_device.h>
+#include "tsens.h"
+
+/* ----- SROT ------ */
+#define SROT_CTRL_OFF 0x0000
+
+/* ----- TM ------ */
+#define TM_INT_EN_OFF 0x0000
+#define TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF 0x0004
+#define TM_Sn_STATUS_OFF 0x0030
+#define TM_TRDY_OFF 0x005c
+
+/* eeprom layout data for 8916 */
+#define MSM8916_BASE0_MASK 0x0000007f
+#define MSM8916_BASE1_MASK 0xfe000000
+#define MSM8916_BASE0_SHIFT 0
+#define MSM8916_BASE1_SHIFT 25
+
+#define MSM8916_S0_P1_MASK 0x00000f80
+#define MSM8916_S1_P1_MASK 0x003e0000
+#define MSM8916_S2_P1_MASK 0xf8000000
+#define MSM8916_S3_P1_MASK 0x000003e0
+#define MSM8916_S4_P1_MASK 0x000f8000
+
+#define MSM8916_S0_P2_MASK 0x0001f000
+#define MSM8916_S1_P2_MASK 0x07c00000
+#define MSM8916_S2_P2_MASK 0x0000001f
+#define MSM8916_S3_P2_MASK 0x00007c00
+#define MSM8916_S4_P2_MASK 0x01f00000
+
+#define MSM8916_S0_P1_SHIFT 7
+#define MSM8916_S1_P1_SHIFT 17
+#define MSM8916_S2_P1_SHIFT 27
+#define MSM8916_S3_P1_SHIFT 5
+#define MSM8916_S4_P1_SHIFT 15
+
+#define MSM8916_S0_P2_SHIFT 12
+#define MSM8916_S1_P2_SHIFT 22
+#define MSM8916_S2_P2_SHIFT 0
+#define MSM8916_S3_P2_SHIFT 10
+#define MSM8916_S4_P2_SHIFT 20
+
+#define MSM8916_CAL_SEL_MASK 0xe0000000
+#define MSM8916_CAL_SEL_SHIFT 29
+
+/* eeprom layout data for 8939 */
+#define MSM8939_BASE0_MASK 0x000000ff
+#define MSM8939_BASE1_MASK 0xff000000
+#define MSM8939_BASE0_SHIFT 0
+#define MSM8939_BASE1_SHIFT 24
+
+#define MSM8939_S0_P1_MASK 0x000001f8
+#define MSM8939_S1_P1_MASK 0x001f8000
+#define MSM8939_S2_P1_MASK_0_4 0xf8000000
+#define MSM8939_S2_P1_MASK_5 0x00000001
+#define MSM8939_S3_P1_MASK 0x00001f80
+#define MSM8939_S4_P1_MASK 0x01f80000
+#define MSM8939_S5_P1_MASK 0x00003f00
+#define MSM8939_S6_P1_MASK 0x03f00000
+#define MSM8939_S7_P1_MASK 0x0000003f
+#define MSM8939_S8_P1_MASK 0x0003f000
+#define MSM8939_S9_P1_MASK 0x07e00000
+
+#define MSM8939_S0_P2_MASK 0x00007e00
+#define MSM8939_S1_P2_MASK 0x07e00000
+#define MSM8939_S2_P2_MASK 0x0000007e
+#define MSM8939_S3_P2_MASK 0x0007e000
+#define MSM8939_S4_P2_MASK 0x7e000000
+#define MSM8939_S5_P2_MASK 0x000fc000
+#define MSM8939_S6_P2_MASK 0xfc000000
+#define MSM8939_S7_P2_MASK 0x00000fc0
+#define MSM8939_S8_P2_MASK 0x00fc0000
+#define MSM8939_S9_P2_MASK_0_4 0xf8000000
+#define MSM8939_S9_P2_MASK_5 0x00002000
+
+#define MSM8939_S0_P1_SHIFT 3
+#define MSM8939_S1_P1_SHIFT 15
+#define MSM8939_S2_P1_SHIFT_0_4 27
+#define MSM8939_S2_P1_SHIFT_5 0
+#define MSM8939_S3_P1_SHIFT 7
+#define MSM8939_S4_P1_SHIFT 19
+#define MSM8939_S5_P1_SHIFT 8
+#define MSM8939_S6_P1_SHIFT 20
+#define MSM8939_S7_P1_SHIFT 0
+#define MSM8939_S8_P1_SHIFT 12
+#define MSM8939_S9_P1_SHIFT 21
+
+#define MSM8939_S0_P2_SHIFT 9
+#define MSM8939_S1_P2_SHIFT 21
+#define MSM8939_S2_P2_SHIFT 1
+#define MSM8939_S3_P2_SHIFT 13
+#define MSM8939_S4_P2_SHIFT 25
+#define MSM8939_S5_P2_SHIFT 14
+#define MSM8939_S6_P2_SHIFT 26
+#define MSM8939_S7_P2_SHIFT 6
+#define MSM8939_S8_P2_SHIFT 18
+#define MSM8939_S9_P2_SHIFT_0_4 27
+#define MSM8939_S9_P2_SHIFT_5 13
+
+#define MSM8939_CAL_SEL_MASK 0x7
+#define MSM8939_CAL_SEL_SHIFT 0
+
+/* eeprom layout data for 8974 */
+#define BASE1_MASK 0xff
+#define S0_P1_MASK 0x3f00
+#define S1_P1_MASK 0xfc000
+#define S2_P1_MASK 0x3f00000
+#define S3_P1_MASK 0xfc000000
+#define S4_P1_MASK 0x3f
+#define S5_P1_MASK 0xfc0
+#define S6_P1_MASK 0x3f000
+#define S7_P1_MASK 0xfc0000
+#define S8_P1_MASK 0x3f000000
+#define S8_P1_MASK_BKP 0x3f
+#define S9_P1_MASK 0x3f
+#define S9_P1_MASK_BKP 0xfc0
+#define S10_P1_MASK 0xfc0
+#define S10_P1_MASK_BKP 0x3f000
+#define CAL_SEL_0_1 0xc0000000
+#define CAL_SEL_2 0x40000000
+#define CAL_SEL_SHIFT 30
+#define CAL_SEL_SHIFT_2 28
+
+#define S0_P1_SHIFT 8
+#define S1_P1_SHIFT 14
+#define S2_P1_SHIFT 20
+#define S3_P1_SHIFT 26
+#define S5_P1_SHIFT 6
+#define S6_P1_SHIFT 12
+#define S7_P1_SHIFT 18
+#define S8_P1_SHIFT 24
+#define S9_P1_BKP_SHIFT 6
+#define S10_P1_SHIFT 6
+#define S10_P1_BKP_SHIFT 12
+
+#define BASE2_SHIFT 12
+#define BASE2_BKP_SHIFT 18
+#define S0_P2_SHIFT 20
+#define S0_P2_BKP_SHIFT 26
+#define S1_P2_SHIFT 26
+#define S2_P2_BKP_SHIFT 6
+#define S3_P2_SHIFT 6
+#define S3_P2_BKP_SHIFT 12
+#define S4_P2_SHIFT 12
+#define S4_P2_BKP_SHIFT 18
+#define S5_P2_SHIFT 18
+#define S5_P2_BKP_SHIFT 24
+#define S6_P2_SHIFT 24
+#define S7_P2_BKP_SHIFT 6
+#define S8_P2_SHIFT 6
+#define S8_P2_BKP_SHIFT 12
+#define S9_P2_SHIFT 12
+#define S9_P2_BKP_SHIFT 18
+#define S10_P2_SHIFT 18
+#define S10_P2_BKP_SHIFT 24
+
+#define BASE2_MASK 0xff000
+#define BASE2_BKP_MASK 0xfc0000
+#define S0_P2_MASK 0x3f00000
+#define S0_P2_BKP_MASK 0xfc000000
+#define S1_P2_MASK 0xfc000000
+#define S1_P2_BKP_MASK 0x3f
+#define S2_P2_MASK 0x3f
+#define S2_P2_BKP_MASK 0xfc0
+#define S3_P2_MASK 0xfc0
+#define S3_P2_BKP_MASK 0x3f000
+#define S4_P2_MASK 0x3f000
+#define S4_P2_BKP_MASK 0xfc0000
+#define S5_P2_MASK 0xfc0000
+#define S5_P2_BKP_MASK 0x3f000000
+#define S6_P2_MASK 0x3f000000
+#define S6_P2_BKP_MASK 0x3f
+#define S7_P2_MASK 0x3f
+#define S7_P2_BKP_MASK 0xfc0
+#define S8_P2_MASK 0xfc0
+#define S8_P2_BKP_MASK 0x3f000
+#define S9_P2_MASK 0x3f000
+#define S9_P2_BKP_MASK 0xfc0000
+#define S10_P2_MASK 0xfc0000
+#define S10_P2_BKP_MASK 0x3f000000
+
+#define BKP_SEL 0x3
+#define BKP_REDUN_SEL 0xe0000000
+#define BKP_REDUN_SHIFT 29
+
+#define BIT_APPEND 0x3
+
+/* eeprom layout data for mdm9607 */
+#define MDM9607_BASE0_MASK 0x000000ff
+#define MDM9607_BASE1_MASK 0x000ff000
+#define MDM9607_BASE0_SHIFT 0
+#define MDM9607_BASE1_SHIFT 12
+
+#define MDM9607_S0_P1_MASK 0x00003f00
+#define MDM9607_S1_P1_MASK 0x03f00000
+#define MDM9607_S2_P1_MASK 0x0000003f
+#define MDM9607_S3_P1_MASK 0x0003f000
+#define MDM9607_S4_P1_MASK 0x0000003f
+
+#define MDM9607_S0_P2_MASK 0x000fc000
+#define MDM9607_S1_P2_MASK 0xfc000000
+#define MDM9607_S2_P2_MASK 0x00000fc0
+#define MDM9607_S3_P2_MASK 0x00fc0000
+#define MDM9607_S4_P2_MASK 0x00000fc0
+
+#define MDM9607_S0_P1_SHIFT 8
+#define MDM9607_S1_P1_SHIFT 20
+#define MDM9607_S2_P1_SHIFT 0
+#define MDM9607_S3_P1_SHIFT 12
+#define MDM9607_S4_P1_SHIFT 0
+
+#define MDM9607_S0_P2_SHIFT 14
+#define MDM9607_S1_P2_SHIFT 26
+#define MDM9607_S2_P2_SHIFT 6
+#define MDM9607_S3_P2_SHIFT 18
+#define MDM9607_S4_P2_SHIFT 6
+
+#define MDM9607_CAL_SEL_MASK 0x00700000
+#define MDM9607_CAL_SEL_SHIFT 20
+
+static int calibrate_8916(struct tsens_priv *priv)
+{
+ int base0 = 0, base1 = 0, i;
+ u32 p1[5], p2[5];
+ int mode = 0;
+ u32 *qfprom_cdata, *qfprom_csel;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ qfprom_csel = (u32 *)qfprom_read(priv->dev, "calib_sel");
+ if (IS_ERR(qfprom_csel)) {
+ kfree(qfprom_cdata);
+ return PTR_ERR(qfprom_csel);
+ }
+
+ mode = (qfprom_csel[0] & MSM8916_CAL_SEL_MASK) >> MSM8916_CAL_SEL_SHIFT;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base1 = (qfprom_cdata[1] & MSM8916_BASE1_MASK) >> MSM8916_BASE1_SHIFT;
+ p2[0] = (qfprom_cdata[0] & MSM8916_S0_P2_MASK) >> MSM8916_S0_P2_SHIFT;
+ p2[1] = (qfprom_cdata[0] & MSM8916_S1_P2_MASK) >> MSM8916_S1_P2_SHIFT;
+ p2[2] = (qfprom_cdata[1] & MSM8916_S2_P2_MASK) >> MSM8916_S2_P2_SHIFT;
+ p2[3] = (qfprom_cdata[1] & MSM8916_S3_P2_MASK) >> MSM8916_S3_P2_SHIFT;
+ p2[4] = (qfprom_cdata[1] & MSM8916_S4_P2_MASK) >> MSM8916_S4_P2_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = ((base1 + p2[i]) << 3);
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base0 = (qfprom_cdata[0] & MSM8916_BASE0_MASK);
+ p1[0] = (qfprom_cdata[0] & MSM8916_S0_P1_MASK) >> MSM8916_S0_P1_SHIFT;
+ p1[1] = (qfprom_cdata[0] & MSM8916_S1_P1_MASK) >> MSM8916_S1_P1_SHIFT;
+ p1[2] = (qfprom_cdata[0] & MSM8916_S2_P1_MASK) >> MSM8916_S2_P1_SHIFT;
+ p1[3] = (qfprom_cdata[1] & MSM8916_S3_P1_MASK) >> MSM8916_S3_P1_SHIFT;
+ p1[4] = (qfprom_cdata[1] & MSM8916_S4_P1_MASK) >> MSM8916_S4_P1_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = (((base0) + p1[i]) << 3);
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+ kfree(qfprom_csel);
+
+ return 0;
+}
+
+static int calibrate_8939(struct tsens_priv *priv)
+{
+ int base0 = 0, base1 = 0, i;
+ u32 p1[10], p2[10];
+ int mode = 0;
+ u32 *qfprom_cdata;
+ u32 cdata[4];
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ /* Mapping between qfprom nvmem and calibration data */
+ cdata[0] = qfprom_cdata[12];
+ cdata[1] = qfprom_cdata[13];
+ cdata[2] = qfprom_cdata[0];
+ cdata[3] = qfprom_cdata[1];
+
+ mode = (cdata[0] & MSM8939_CAL_SEL_MASK) >> MSM8939_CAL_SEL_SHIFT;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base1 = (cdata[3] & MSM8939_BASE1_MASK) >> MSM8939_BASE1_SHIFT;
+ p2[0] = (cdata[0] & MSM8939_S0_P2_MASK) >> MSM8939_S0_P2_SHIFT;
+ p2[1] = (cdata[0] & MSM8939_S1_P2_MASK) >> MSM8939_S1_P2_SHIFT;
+ p2[2] = (cdata[1] & MSM8939_S2_P2_MASK) >> MSM8939_S2_P2_SHIFT;
+ p2[3] = (cdata[1] & MSM8939_S3_P2_MASK) >> MSM8939_S3_P2_SHIFT;
+ p2[4] = (cdata[1] & MSM8939_S4_P2_MASK) >> MSM8939_S4_P2_SHIFT;
+ p2[5] = (cdata[2] & MSM8939_S5_P2_MASK) >> MSM8939_S5_P2_SHIFT;
+ p2[6] = (cdata[2] & MSM8939_S6_P2_MASK) >> MSM8939_S6_P2_SHIFT;
+ p2[7] = (cdata[3] & MSM8939_S7_P2_MASK) >> MSM8939_S7_P2_SHIFT;
+ p2[8] = (cdata[3] & MSM8939_S8_P2_MASK) >> MSM8939_S8_P2_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = (base1 + p2[i]) << 2;
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base0 = (cdata[2] & MSM8939_BASE0_MASK) >> MSM8939_BASE0_SHIFT;
+ p1[0] = (cdata[0] & MSM8939_S0_P1_MASK) >> MSM8939_S0_P1_SHIFT;
+ p1[1] = (cdata[0] & MSM8939_S1_P1_MASK) >> MSM8939_S1_P1_SHIFT;
+ p1[2] = (cdata[0] & MSM8939_S2_P1_MASK_0_4) >> MSM8939_S2_P1_SHIFT_0_4;
+ p1[2] |= ((cdata[1] & MSM8939_S2_P1_MASK_5) >> MSM8939_S2_P1_SHIFT_5) << 5;
+ p1[3] = (cdata[1] & MSM8939_S3_P1_MASK) >> MSM8939_S3_P1_SHIFT;
+ p1[4] = (cdata[1] & MSM8939_S4_P1_MASK) >> MSM8939_S4_P1_SHIFT;
+ p1[5] = (cdata[2] & MSM8939_S5_P1_MASK) >> MSM8939_S5_P1_SHIFT;
+ p1[6] = (cdata[2] & MSM8939_S6_P1_MASK) >> MSM8939_S6_P1_SHIFT;
+ p1[7] = (cdata[3] & MSM8939_S7_P1_MASK) >> MSM8939_S7_P1_SHIFT;
+ p1[8] = (cdata[3] & MSM8939_S8_P1_MASK) >> MSM8939_S8_P1_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = ((base0) + p1[i]) << 2;
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
+static int calibrate_8974(struct tsens_priv *priv)
+{
+ int base1 = 0, base2 = 0, i;
+ u32 p1[11], p2[11];
+ int mode = 0;
+ u32 *calib, *bkp;
+ u32 calib_redun_sel;
+
+ calib = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(calib))
+ return PTR_ERR(calib);
+
+ bkp = (u32 *)qfprom_read(priv->dev, "calib_backup");
+ if (IS_ERR(bkp)) {
+ kfree(calib);
+ return PTR_ERR(bkp);
+ }
+
+ calib_redun_sel = bkp[1] & BKP_REDUN_SEL;
+ calib_redun_sel >>= BKP_REDUN_SHIFT;
+
+ if (calib_redun_sel == BKP_SEL) {
+ mode = (calib[4] & CAL_SEL_0_1) >> CAL_SEL_SHIFT;
+ mode |= (calib[5] & CAL_SEL_2) >> CAL_SEL_SHIFT_2;
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base2 = (bkp[2] & BASE2_BKP_MASK) >> BASE2_BKP_SHIFT;
+ p2[0] = (bkp[2] & S0_P2_BKP_MASK) >> S0_P2_BKP_SHIFT;
+ p2[1] = (bkp[3] & S1_P2_BKP_MASK);
+ p2[2] = (bkp[3] & S2_P2_BKP_MASK) >> S2_P2_BKP_SHIFT;
+ p2[3] = (bkp[3] & S3_P2_BKP_MASK) >> S3_P2_BKP_SHIFT;
+ p2[4] = (bkp[3] & S4_P2_BKP_MASK) >> S4_P2_BKP_SHIFT;
+ p2[5] = (calib[4] & S5_P2_BKP_MASK) >> S5_P2_BKP_SHIFT;
+ p2[6] = (calib[5] & S6_P2_BKP_MASK);
+ p2[7] = (calib[5] & S7_P2_BKP_MASK) >> S7_P2_BKP_SHIFT;
+ p2[8] = (calib[5] & S8_P2_BKP_MASK) >> S8_P2_BKP_SHIFT;
+ p2[9] = (calib[5] & S9_P2_BKP_MASK) >> S9_P2_BKP_SHIFT;
+ p2[10] = (calib[5] & S10_P2_BKP_MASK) >> S10_P2_BKP_SHIFT;
+ fallthrough;
+ case ONE_PT_CALIB:
+ case ONE_PT_CALIB2:
+ base1 = bkp[0] & BASE1_MASK;
+ p1[0] = (bkp[0] & S0_P1_MASK) >> S0_P1_SHIFT;
+ p1[1] = (bkp[0] & S1_P1_MASK) >> S1_P1_SHIFT;
+ p1[2] = (bkp[0] & S2_P1_MASK) >> S2_P1_SHIFT;
+ p1[3] = (bkp[0] & S3_P1_MASK) >> S3_P1_SHIFT;
+ p1[4] = (bkp[1] & S4_P1_MASK);
+ p1[5] = (bkp[1] & S5_P1_MASK) >> S5_P1_SHIFT;
+ p1[6] = (bkp[1] & S6_P1_MASK) >> S6_P1_SHIFT;
+ p1[7] = (bkp[1] & S7_P1_MASK) >> S7_P1_SHIFT;
+ p1[8] = (bkp[2] & S8_P1_MASK_BKP) >> S8_P1_SHIFT;
+ p1[9] = (bkp[2] & S9_P1_MASK_BKP) >> S9_P1_BKP_SHIFT;
+ p1[10] = (bkp[2] & S10_P1_MASK_BKP) >> S10_P1_BKP_SHIFT;
+ break;
+ }
+ } else {
+ mode = (calib[1] & CAL_SEL_0_1) >> CAL_SEL_SHIFT;
+ mode |= (calib[3] & CAL_SEL_2) >> CAL_SEL_SHIFT_2;
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base2 = (calib[2] & BASE2_MASK) >> BASE2_SHIFT;
+ p2[0] = (calib[2] & S0_P2_MASK) >> S0_P2_SHIFT;
+ p2[1] = (calib[2] & S1_P2_MASK) >> S1_P2_SHIFT;
+ p2[2] = (calib[3] & S2_P2_MASK);
+ p2[3] = (calib[3] & S3_P2_MASK) >> S3_P2_SHIFT;
+ p2[4] = (calib[3] & S4_P2_MASK) >> S4_P2_SHIFT;
+ p2[5] = (calib[3] & S5_P2_MASK) >> S5_P2_SHIFT;
+ p2[6] = (calib[3] & S6_P2_MASK) >> S6_P2_SHIFT;
+ p2[7] = (calib[4] & S7_P2_MASK);
+ p2[8] = (calib[4] & S8_P2_MASK) >> S8_P2_SHIFT;
+ p2[9] = (calib[4] & S9_P2_MASK) >> S9_P2_SHIFT;
+ p2[10] = (calib[4] & S10_P2_MASK) >> S10_P2_SHIFT;
+ fallthrough;
+ case ONE_PT_CALIB:
+ case ONE_PT_CALIB2:
+ base1 = calib[0] & BASE1_MASK;
+ p1[0] = (calib[0] & S0_P1_MASK) >> S0_P1_SHIFT;
+ p1[1] = (calib[0] & S1_P1_MASK) >> S1_P1_SHIFT;
+ p1[2] = (calib[0] & S2_P1_MASK) >> S2_P1_SHIFT;
+ p1[3] = (calib[0] & S3_P1_MASK) >> S3_P1_SHIFT;
+ p1[4] = (calib[1] & S4_P1_MASK);
+ p1[5] = (calib[1] & S5_P1_MASK) >> S5_P1_SHIFT;
+ p1[6] = (calib[1] & S6_P1_MASK) >> S6_P1_SHIFT;
+ p1[7] = (calib[1] & S7_P1_MASK) >> S7_P1_SHIFT;
+ p1[8] = (calib[1] & S8_P1_MASK) >> S8_P1_SHIFT;
+ p1[9] = (calib[2] & S9_P1_MASK);
+ p1[10] = (calib[2] & S10_P1_MASK) >> S10_P1_SHIFT;
+ break;
+ }
+ }
+
+ switch (mode) {
+ case ONE_PT_CALIB:
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] += (base1 << 2) | BIT_APPEND;
+ break;
+ case TWO_PT_CALIB:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p2[i] += base2;
+ p2[i] <<= 2;
+ p2[i] |= BIT_APPEND;
+ }
+ fallthrough;
+ case ONE_PT_CALIB2:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] += base1;
+ p1[i] <<= 2;
+ p1[i] |= BIT_APPEND;
+ }
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = 780;
+ p1[0] = 502;
+ p1[1] = 509;
+ p1[2] = 503;
+ p1[3] = 509;
+ p1[4] = 505;
+ p1[5] = 509;
+ p1[6] = 507;
+ p1[7] = 510;
+ p1[8] = 508;
+ p1[9] = 509;
+ p1[10] = 508;
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(calib);
+ kfree(bkp);
+
+ return 0;
+}
+
+static int calibrate_9607(struct tsens_priv *priv)
+{
+ int base, i;
+ u32 p1[5], p2[5];
+ int mode = 0;
+ u32 *qfprom_cdata;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ mode = (qfprom_cdata[2] & MDM9607_CAL_SEL_MASK) >> MDM9607_CAL_SEL_SHIFT;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base = (qfprom_cdata[2] & MDM9607_BASE1_MASK) >> MDM9607_BASE1_SHIFT;
+ p2[0] = (qfprom_cdata[0] & MDM9607_S0_P2_MASK) >> MDM9607_S0_P2_SHIFT;
+ p2[1] = (qfprom_cdata[0] & MDM9607_S1_P2_MASK) >> MDM9607_S1_P2_SHIFT;
+ p2[2] = (qfprom_cdata[1] & MDM9607_S2_P2_MASK) >> MDM9607_S2_P2_SHIFT;
+ p2[3] = (qfprom_cdata[1] & MDM9607_S3_P2_MASK) >> MDM9607_S3_P2_SHIFT;
+ p2[4] = (qfprom_cdata[2] & MDM9607_S4_P2_MASK) >> MDM9607_S4_P2_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = ((base + p2[i]) << 2);
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base = (qfprom_cdata[0] & MDM9607_BASE0_MASK);
+ p1[0] = (qfprom_cdata[0] & MDM9607_S0_P1_MASK) >> MDM9607_S0_P1_SHIFT;
+ p1[1] = (qfprom_cdata[0] & MDM9607_S1_P1_MASK) >> MDM9607_S1_P1_SHIFT;
+ p1[2] = (qfprom_cdata[1] & MDM9607_S2_P1_MASK) >> MDM9607_S2_P1_SHIFT;
+ p1[3] = (qfprom_cdata[1] & MDM9607_S3_P1_MASK) >> MDM9607_S3_P1_SHIFT;
+ p1[4] = (qfprom_cdata[2] & MDM9607_S4_P1_MASK) >> MDM9607_S4_P1_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = ((base + p1[i]) << 2);
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
+static int __init init_8939(struct tsens_priv *priv) {
+ priv->sensor[0].slope = 2911;
+ priv->sensor[1].slope = 2789;
+ priv->sensor[2].slope = 2906;
+ priv->sensor[3].slope = 2763;
+ priv->sensor[4].slope = 2922;
+ priv->sensor[5].slope = 2867;
+ priv->sensor[6].slope = 2833;
+ priv->sensor[7].slope = 2838;
+ priv->sensor[8].slope = 2840;
+ /* priv->sensor[9].slope = 2852; */
+
+ return init_common(priv);
+}
+
+/* v0.1: 8916, 8939, 8974, 9607 */
+
+static struct tsens_features tsens_v0_1_feat = {
+ .ver_major = VER_0_1,
+ .crit_int = 0,
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
+};
+
+static const struct reg_field tsens_v0_1_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* No VERSION information */
+
+ /* CTRL_OFFSET */
+ [TSENS_EN] = REG_FIELD(SROT_CTRL_OFF, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(SROT_CTRL_OFF, 1, 1),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ [INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 0),
+
+ /* UPPER/LOWER TEMPERATURE THRESHOLDS */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 0, 9),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 10, 19),
+
+ /* UPPER/LOWER INTERRUPTS [CLEAR/STATUS] */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 20, 20),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 21, 21),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on v0.1 */
+
+ /* Sn_STATUS */
+ REG_FIELD_FOR_EACH_SENSOR11(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 9),
+ /* No VALID field on v0.1 */
+ /* xxx_STATUS bits: 1 == threshold violated */
+ REG_FIELD_FOR_EACH_SENSOR11(MIN_STATUS, TM_Sn_STATUS_OFF, 10, 10),
+ REG_FIELD_FOR_EACH_SENSOR11(LOWER_STATUS, TM_Sn_STATUS_OFF, 11, 11),
+ REG_FIELD_FOR_EACH_SENSOR11(UPPER_STATUS, TM_Sn_STATUS_OFF, 12, 12),
+ /* No CRITICAL field on v0.1 */
+ REG_FIELD_FOR_EACH_SENSOR11(MAX_STATUS, TM_Sn_STATUS_OFF, 13, 13),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
+};
+
+static const struct tsens_ops ops_8916 = {
+ .init = init_common,
+ .calibrate = calibrate_8916,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8916 = {
+ .num_sensors = 5,
+ .ops = &ops_8916,
+ .hw_ids = (unsigned int []){0, 1, 2, 4, 5 },
+
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_8939 = {
+ .init = init_8939,
+ .calibrate = calibrate_8939,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8939 = {
+ .num_sensors = 9,
+ .ops = &ops_8939,
+ .hw_ids = (unsigned int []){ 0, 1, 2, 3, 5, 6, 7, 8, 9, /* 10 */ },
+
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_8974 = {
+ .init = init_common,
+ .calibrate = calibrate_8974,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_8974 = {
+ .num_sensors = 11,
+ .ops = &ops_8974,
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
+
+static const struct tsens_ops ops_9607 = {
+ .init = init_common,
+ .calibrate = calibrate_9607,
+ .get_temp = get_temp_common,
+};
+
+struct tsens_plat_data data_9607 = {
+ .num_sensors = 5,
+ .ops = &ops_9607,
+ .hw_ids = (unsigned int []){ 0, 1, 2, 3, 4 },
+ .feat = &tsens_v0_1_feat,
+ .fields = tsens_v0_1_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c
new file mode 100644
index 000000000..faa4576fa
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v1.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019, Linaro Limited
+ */
+
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include "tsens.h"
+
+/* ----- SROT ------ */
+#define SROT_HW_VER_OFF 0x0000
+#define SROT_CTRL_OFF 0x0004
+
+/* ----- TM ------ */
+#define TM_INT_EN_OFF 0x0000
+#define TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF 0x0004
+#define TM_Sn_STATUS_OFF 0x0044
+#define TM_TRDY_OFF 0x0084
+#define TM_HIGH_LOW_INT_STATUS_OFF 0x0088
+#define TM_HIGH_LOW_Sn_INT_THRESHOLD_OFF 0x0090
+
+/* eeprom layout data for msm8956/76 (v1) */
+#define MSM8976_BASE0_MASK 0xff
+#define MSM8976_BASE1_MASK 0xff
+#define MSM8976_BASE1_SHIFT 8
+
+#define MSM8976_S0_P1_MASK 0x3f00
+#define MSM8976_S1_P1_MASK 0x3f00000
+#define MSM8976_S2_P1_MASK 0x3f
+#define MSM8976_S3_P1_MASK 0x3f000
+#define MSM8976_S4_P1_MASK 0x3f00
+#define MSM8976_S5_P1_MASK 0x3f00000
+#define MSM8976_S6_P1_MASK 0x3f
+#define MSM8976_S7_P1_MASK 0x3f000
+#define MSM8976_S8_P1_MASK 0x1f8
+#define MSM8976_S9_P1_MASK 0x1f8000
+#define MSM8976_S10_P1_MASK 0xf8000000
+#define MSM8976_S10_P1_MASK_1 0x1
+
+#define MSM8976_S0_P2_MASK 0xfc000
+#define MSM8976_S1_P2_MASK 0xfc000000
+#define MSM8976_S2_P2_MASK 0xfc0
+#define MSM8976_S3_P2_MASK 0xfc0000
+#define MSM8976_S4_P2_MASK 0xfc000
+#define MSM8976_S5_P2_MASK 0xfc000000
+#define MSM8976_S6_P2_MASK 0xfc0
+#define MSM8976_S7_P2_MASK 0xfc0000
+#define MSM8976_S8_P2_MASK 0x7e00
+#define MSM8976_S9_P2_MASK 0x7e00000
+#define MSM8976_S10_P2_MASK 0x7e
+
+#define MSM8976_S0_P1_SHIFT 8
+#define MSM8976_S1_P1_SHIFT 20
+#define MSM8976_S2_P1_SHIFT 0
+#define MSM8976_S3_P1_SHIFT 12
+#define MSM8976_S4_P1_SHIFT 8
+#define MSM8976_S5_P1_SHIFT 20
+#define MSM8976_S6_P1_SHIFT 0
+#define MSM8976_S7_P1_SHIFT 12
+#define MSM8976_S8_P1_SHIFT 3
+#define MSM8976_S9_P1_SHIFT 15
+#define MSM8976_S10_P1_SHIFT 27
+#define MSM8976_S10_P1_SHIFT_1 0
+
+#define MSM8976_S0_P2_SHIFT 14
+#define MSM8976_S1_P2_SHIFT 26
+#define MSM8976_S2_P2_SHIFT 6
+#define MSM8976_S3_P2_SHIFT 18
+#define MSM8976_S4_P2_SHIFT 14
+#define MSM8976_S5_P2_SHIFT 26
+#define MSM8976_S6_P2_SHIFT 6
+#define MSM8976_S7_P2_SHIFT 18
+#define MSM8976_S8_P2_SHIFT 9
+#define MSM8976_S9_P2_SHIFT 21
+#define MSM8976_S10_P2_SHIFT 1
+
+#define MSM8976_CAL_SEL_MASK 0x3
+
+/* eeprom layout data for qcs404/405 (v1) */
+#define BASE0_MASK 0x000007f8
+#define BASE1_MASK 0x0007f800
+#define BASE0_SHIFT 3
+#define BASE1_SHIFT 11
+
+#define S0_P1_MASK 0x0000003f
+#define S1_P1_MASK 0x0003f000
+#define S2_P1_MASK 0x3f000000
+#define S3_P1_MASK 0x000003f0
+#define S4_P1_MASK 0x003f0000
+#define S5_P1_MASK 0x0000003f
+#define S6_P1_MASK 0x0003f000
+#define S7_P1_MASK 0x3f000000
+#define S8_P1_MASK 0x000003f0
+#define S9_P1_MASK 0x003f0000
+
+#define S0_P2_MASK 0x00000fc0
+#define S1_P2_MASK 0x00fc0000
+#define S2_P2_MASK_1_0 0xc0000000
+#define S2_P2_MASK_5_2 0x0000000f
+#define S3_P2_MASK 0x0000fc00
+#define S4_P2_MASK 0x0fc00000
+#define S5_P2_MASK 0x00000fc0
+#define S6_P2_MASK 0x00fc0000
+#define S7_P2_MASK_1_0 0xc0000000
+#define S7_P2_MASK_5_2 0x0000000f
+#define S8_P2_MASK 0x0000fc00
+#define S9_P2_MASK 0x0fc00000
+
+#define S0_P1_SHIFT 0
+#define S0_P2_SHIFT 6
+#define S1_P1_SHIFT 12
+#define S1_P2_SHIFT 18
+#define S2_P1_SHIFT 24
+#define S2_P2_SHIFT_1_0 30
+
+#define S2_P2_SHIFT_5_2 0
+#define S3_P1_SHIFT 4
+#define S3_P2_SHIFT 10
+#define S4_P1_SHIFT 16
+#define S4_P2_SHIFT 22
+
+#define S5_P1_SHIFT 0
+#define S5_P2_SHIFT 6
+#define S6_P1_SHIFT 12
+#define S6_P2_SHIFT 18
+#define S7_P1_SHIFT 24
+#define S7_P2_SHIFT_1_0 30
+
+#define S7_P2_SHIFT_5_2 0
+#define S8_P1_SHIFT 4
+#define S8_P2_SHIFT 10
+#define S9_P1_SHIFT 16
+#define S9_P2_SHIFT 22
+
+#define CAL_SEL_MASK 7
+#define CAL_SEL_SHIFT 0
+
+static int calibrate_v1(struct tsens_priv *priv)
+{
+ u32 base0 = 0, base1 = 0;
+ u32 p1[10], p2[10];
+ u32 mode = 0, lsb = 0, msb = 0;
+ u32 *qfprom_cdata;
+ int i;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ mode = (qfprom_cdata[4] & CAL_SEL_MASK) >> CAL_SEL_SHIFT;
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base1 = (qfprom_cdata[4] & BASE1_MASK) >> BASE1_SHIFT;
+ p2[0] = (qfprom_cdata[0] & S0_P2_MASK) >> S0_P2_SHIFT;
+ p2[1] = (qfprom_cdata[0] & S1_P2_MASK) >> S1_P2_SHIFT;
+ /* This value is split over two registers, 2 bits and 4 bits */
+ lsb = (qfprom_cdata[0] & S2_P2_MASK_1_0) >> S2_P2_SHIFT_1_0;
+ msb = (qfprom_cdata[1] & S2_P2_MASK_5_2) >> S2_P2_SHIFT_5_2;
+ p2[2] = msb << 2 | lsb;
+ p2[3] = (qfprom_cdata[1] & S3_P2_MASK) >> S3_P2_SHIFT;
+ p2[4] = (qfprom_cdata[1] & S4_P2_MASK) >> S4_P2_SHIFT;
+ p2[5] = (qfprom_cdata[2] & S5_P2_MASK) >> S5_P2_SHIFT;
+ p2[6] = (qfprom_cdata[2] & S6_P2_MASK) >> S6_P2_SHIFT;
+ /* This value is split over two registers, 2 bits and 4 bits */
+ lsb = (qfprom_cdata[2] & S7_P2_MASK_1_0) >> S7_P2_SHIFT_1_0;
+ msb = (qfprom_cdata[3] & S7_P2_MASK_5_2) >> S7_P2_SHIFT_5_2;
+ p2[7] = msb << 2 | lsb;
+ p2[8] = (qfprom_cdata[3] & S8_P2_MASK) >> S8_P2_SHIFT;
+ p2[9] = (qfprom_cdata[3] & S9_P2_MASK) >> S9_P2_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = ((base1 + p2[i]) << 2);
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base0 = (qfprom_cdata[4] & BASE0_MASK) >> BASE0_SHIFT;
+ p1[0] = (qfprom_cdata[0] & S0_P1_MASK) >> S0_P1_SHIFT;
+ p1[1] = (qfprom_cdata[0] & S1_P1_MASK) >> S1_P1_SHIFT;
+ p1[2] = (qfprom_cdata[0] & S2_P1_MASK) >> S2_P1_SHIFT;
+ p1[3] = (qfprom_cdata[1] & S3_P1_MASK) >> S3_P1_SHIFT;
+ p1[4] = (qfprom_cdata[1] & S4_P1_MASK) >> S4_P1_SHIFT;
+ p1[5] = (qfprom_cdata[2] & S5_P1_MASK) >> S5_P1_SHIFT;
+ p1[6] = (qfprom_cdata[2] & S6_P1_MASK) >> S6_P1_SHIFT;
+ p1[7] = (qfprom_cdata[2] & S7_P1_MASK) >> S7_P1_SHIFT;
+ p1[8] = (qfprom_cdata[3] & S8_P1_MASK) >> S8_P1_SHIFT;
+ p1[9] = (qfprom_cdata[3] & S9_P1_MASK) >> S9_P1_SHIFT;
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = (((base0) + p1[i]) << 2);
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
+static int calibrate_8976(struct tsens_priv *priv)
+{
+ int base0 = 0, base1 = 0, i;
+ u32 p1[11], p2[11];
+ int mode = 0, tmp = 0;
+ u32 *qfprom_cdata;
+
+ qfprom_cdata = (u32 *)qfprom_read(priv->dev, "calib");
+ if (IS_ERR(qfprom_cdata))
+ return PTR_ERR(qfprom_cdata);
+
+ mode = (qfprom_cdata[4] & MSM8976_CAL_SEL_MASK);
+ dev_dbg(priv->dev, "calibration mode is %d\n", mode);
+
+ switch (mode) {
+ case TWO_PT_CALIB:
+ base1 = (qfprom_cdata[2] & MSM8976_BASE1_MASK) >> MSM8976_BASE1_SHIFT;
+ p2[0] = (qfprom_cdata[0] & MSM8976_S0_P2_MASK) >> MSM8976_S0_P2_SHIFT;
+ p2[1] = (qfprom_cdata[0] & MSM8976_S1_P2_MASK) >> MSM8976_S1_P2_SHIFT;
+ p2[2] = (qfprom_cdata[1] & MSM8976_S2_P2_MASK) >> MSM8976_S2_P2_SHIFT;
+ p2[3] = (qfprom_cdata[1] & MSM8976_S3_P2_MASK) >> MSM8976_S3_P2_SHIFT;
+ p2[4] = (qfprom_cdata[2] & MSM8976_S4_P2_MASK) >> MSM8976_S4_P2_SHIFT;
+ p2[5] = (qfprom_cdata[2] & MSM8976_S5_P2_MASK) >> MSM8976_S5_P2_SHIFT;
+ p2[6] = (qfprom_cdata[3] & MSM8976_S6_P2_MASK) >> MSM8976_S6_P2_SHIFT;
+ p2[7] = (qfprom_cdata[3] & MSM8976_S7_P2_MASK) >> MSM8976_S7_P2_SHIFT;
+ p2[8] = (qfprom_cdata[4] & MSM8976_S8_P2_MASK) >> MSM8976_S8_P2_SHIFT;
+ p2[9] = (qfprom_cdata[4] & MSM8976_S9_P2_MASK) >> MSM8976_S9_P2_SHIFT;
+ p2[10] = (qfprom_cdata[5] & MSM8976_S10_P2_MASK) >> MSM8976_S10_P2_SHIFT;
+
+ for (i = 0; i < priv->num_sensors; i++)
+ p2[i] = ((base1 + p2[i]) << 2);
+ fallthrough;
+ case ONE_PT_CALIB2:
+ base0 = qfprom_cdata[0] & MSM8976_BASE0_MASK;
+ p1[0] = (qfprom_cdata[0] & MSM8976_S0_P1_MASK) >> MSM8976_S0_P1_SHIFT;
+ p1[1] = (qfprom_cdata[0] & MSM8976_S1_P1_MASK) >> MSM8976_S1_P1_SHIFT;
+ p1[2] = (qfprom_cdata[1] & MSM8976_S2_P1_MASK) >> MSM8976_S2_P1_SHIFT;
+ p1[3] = (qfprom_cdata[1] & MSM8976_S3_P1_MASK) >> MSM8976_S3_P1_SHIFT;
+ p1[4] = (qfprom_cdata[2] & MSM8976_S4_P1_MASK) >> MSM8976_S4_P1_SHIFT;
+ p1[5] = (qfprom_cdata[2] & MSM8976_S5_P1_MASK) >> MSM8976_S5_P1_SHIFT;
+ p1[6] = (qfprom_cdata[3] & MSM8976_S6_P1_MASK) >> MSM8976_S6_P1_SHIFT;
+ p1[7] = (qfprom_cdata[3] & MSM8976_S7_P1_MASK) >> MSM8976_S7_P1_SHIFT;
+ p1[8] = (qfprom_cdata[4] & MSM8976_S8_P1_MASK) >> MSM8976_S8_P1_SHIFT;
+ p1[9] = (qfprom_cdata[4] & MSM8976_S9_P1_MASK) >> MSM8976_S9_P1_SHIFT;
+ p1[10] = (qfprom_cdata[4] & MSM8976_S10_P1_MASK) >> MSM8976_S10_P1_SHIFT;
+ tmp = (qfprom_cdata[5] & MSM8976_S10_P1_MASK_1) << MSM8976_S10_P1_SHIFT_1;
+ p1[10] |= tmp;
+
+ for (i = 0; i < priv->num_sensors; i++)
+ p1[i] = (((base0) + p1[i]) << 2);
+ break;
+ default:
+ for (i = 0; i < priv->num_sensors; i++) {
+ p1[i] = 500;
+ p2[i] = 780;
+ }
+ break;
+ }
+
+ compute_intercept_slope(priv, p1, p2, mode);
+ kfree(qfprom_cdata);
+
+ return 0;
+}
+
+/* v1.x: msm8956,8976,qcs404,405 */
+
+static struct tsens_features tsens_v1_feat = {
+ .ver_major = VER_1_X,
+ .crit_int = 0,
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
+};
+
+static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* VERSION */
+ [VER_MAJOR] = REG_FIELD(SROT_HW_VER_OFF, 28, 31),
+ [VER_MINOR] = REG_FIELD(SROT_HW_VER_OFF, 16, 27),
+ [VER_STEP] = REG_FIELD(SROT_HW_VER_OFF, 0, 15),
+ /* CTRL_OFFSET */
+ [TSENS_EN] = REG_FIELD(SROT_CTRL_OFF, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(SROT_CTRL_OFF, 1, 1),
+ [SENSOR_EN] = REG_FIELD(SROT_CTRL_OFF, 3, 13),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ [INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 0),
+
+ /* UPPER/LOWER TEMPERATURE THRESHOLDS */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 0, 9),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_THRESH, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 10, 19),
+
+ /* UPPER/LOWER INTERRUPTS [CLEAR/STATUS] */
+ REG_FIELD_FOR_EACH_SENSOR11(LOW_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 20, 20),
+ REG_FIELD_FOR_EACH_SENSOR11(UP_INT_CLEAR, TM_Sn_UPPER_LOWER_STATUS_CTRL_OFF, 21, 21),
+ [LOW_INT_STATUS_0] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 0, 0),
+ [LOW_INT_STATUS_1] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 1, 1),
+ [LOW_INT_STATUS_2] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 2, 2),
+ [LOW_INT_STATUS_3] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 3, 3),
+ [LOW_INT_STATUS_4] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 4, 4),
+ [LOW_INT_STATUS_5] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 5, 5),
+ [LOW_INT_STATUS_6] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 6, 6),
+ [LOW_INT_STATUS_7] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 7, 7),
+ [UP_INT_STATUS_0] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 8, 8),
+ [UP_INT_STATUS_1] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 9, 9),
+ [UP_INT_STATUS_2] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 10, 10),
+ [UP_INT_STATUS_3] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 11, 11),
+ [UP_INT_STATUS_4] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 12, 12),
+ [UP_INT_STATUS_5] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 13, 13),
+ [UP_INT_STATUS_6] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 14, 14),
+ [UP_INT_STATUS_7] = REG_FIELD(TM_HIGH_LOW_INT_STATUS_OFF, 15, 15),
+
+ /* NO CRITICAL INTERRUPT SUPPORT on v1 */
+
+ /* Sn_STATUS */
+ REG_FIELD_FOR_EACH_SENSOR11(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 9),
+ REG_FIELD_FOR_EACH_SENSOR11(VALID, TM_Sn_STATUS_OFF, 14, 14),
+ /* xxx_STATUS bits: 1 == threshold violated */
+ REG_FIELD_FOR_EACH_SENSOR11(MIN_STATUS, TM_Sn_STATUS_OFF, 10, 10),
+ REG_FIELD_FOR_EACH_SENSOR11(LOWER_STATUS, TM_Sn_STATUS_OFF, 11, 11),
+ REG_FIELD_FOR_EACH_SENSOR11(UPPER_STATUS, TM_Sn_STATUS_OFF, 12, 12),
+ /* No CRITICAL field on v1.x */
+ REG_FIELD_FOR_EACH_SENSOR11(MAX_STATUS, TM_Sn_STATUS_OFF, 13, 13),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
+};
+
+static int __init init_8956(struct tsens_priv *priv) {
+ priv->sensor[0].slope = 3313;
+ priv->sensor[1].slope = 3275;
+ priv->sensor[2].slope = 3320;
+ priv->sensor[3].slope = 3246;
+ priv->sensor[4].slope = 3279;
+ priv->sensor[5].slope = 3257;
+ priv->sensor[6].slope = 3234;
+ priv->sensor[7].slope = 3269;
+ priv->sensor[8].slope = 3255;
+ priv->sensor[9].slope = 3239;
+ priv->sensor[10].slope = 3286;
+
+ return init_common(priv);
+}
+
+static const struct tsens_ops ops_generic_v1 = {
+ .init = init_common,
+ .calibrate = calibrate_v1,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_tsens_v1 = {
+ .ops = &ops_generic_v1,
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
+
+static const struct tsens_ops ops_8956 = {
+ .init = init_8956,
+ .calibrate = calibrate_8976,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_8956 = {
+ .num_sensors = 11,
+ .ops = &ops_8956,
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
+
+static const struct tsens_ops ops_8976 = {
+ .init = init_common,
+ .calibrate = calibrate_8976,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_8976 = {
+ .num_sensors = 11,
+ .ops = &ops_8976,
+ .hw_ids = (unsigned int[]){0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
+ .feat = &tsens_v1_feat,
+ .fields = tsens_v1_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c
new file mode 100644
index 000000000..b293ed321
--- /dev/null
+++ b/drivers/thermal/qcom/tsens-v2.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2018, Linaro Limited
+ */
+
+#include <linux/bitops.h>
+#include <linux/regmap.h>
+#include "tsens.h"
+
+/* ----- SROT ------ */
+#define SROT_HW_VER_OFF 0x0000
+#define SROT_CTRL_OFF 0x0004
+
+/* ----- TM ------ */
+#define TM_INT_EN_OFF 0x0004
+#define TM_UPPER_LOWER_INT_STATUS_OFF 0x0008
+#define TM_UPPER_LOWER_INT_CLEAR_OFF 0x000c
+#define TM_UPPER_LOWER_INT_MASK_OFF 0x0010
+#define TM_CRITICAL_INT_STATUS_OFF 0x0014
+#define TM_CRITICAL_INT_CLEAR_OFF 0x0018
+#define TM_CRITICAL_INT_MASK_OFF 0x001c
+#define TM_Sn_UPPER_LOWER_THRESHOLD_OFF 0x0020
+#define TM_Sn_CRITICAL_THRESHOLD_OFF 0x0060
+#define TM_Sn_STATUS_OFF 0x00a0
+#define TM_TRDY_OFF 0x00e4
+#define TM_WDOG_LOG_OFF 0x013c
+
+/* v2.x: 8996, 8998, sdm845 */
+
+static struct tsens_features tsens_v2_feat = {
+ .ver_major = VER_2_X,
+ .crit_int = 1,
+ .adc = 0,
+ .srot_split = 1,
+ .max_sensors = 16,
+};
+
+static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* VERSION */
+ [VER_MAJOR] = REG_FIELD(SROT_HW_VER_OFF, 28, 31),
+ [VER_MINOR] = REG_FIELD(SROT_HW_VER_OFF, 16, 27),
+ [VER_STEP] = REG_FIELD(SROT_HW_VER_OFF, 0, 15),
+ /* CTRL_OFF */
+ [TSENS_EN] = REG_FIELD(SROT_CTRL_OFF, 0, 0),
+ [TSENS_SW_RST] = REG_FIELD(SROT_CTRL_OFF, 1, 1),
+
+ /* ----- TM ------ */
+ /* INTERRUPT ENABLE */
+ /* v2 has separate enables for UPPER/LOWER/CRITICAL interrupts */
+ [INT_EN] = REG_FIELD(TM_INT_EN_OFF, 0, 2),
+
+ /* TEMPERATURE THRESHOLDS */
+ REG_FIELD_FOR_EACH_SENSOR16(LOW_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 0, 11),
+ REG_FIELD_FOR_EACH_SENSOR16(UP_THRESH, TM_Sn_UPPER_LOWER_THRESHOLD_OFF, 12, 23),
+ REG_FIELD_FOR_EACH_SENSOR16(CRIT_THRESH, TM_Sn_CRITICAL_THRESHOLD_OFF, 0, 11),
+
+ /* INTERRUPTS [CLEAR/STATUS/MASK] */
+ REG_FIELD_SPLIT_BITS_0_15(LOW_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(LOW_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(LOW_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
+ REG_FIELD_SPLIT_BITS_16_31(UP_INT_STATUS, TM_UPPER_LOWER_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_16_31(UP_INT_CLEAR, TM_UPPER_LOWER_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_16_31(UP_INT_MASK, TM_UPPER_LOWER_INT_MASK_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_STATUS, TM_CRITICAL_INT_STATUS_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_CLEAR, TM_CRITICAL_INT_CLEAR_OFF),
+ REG_FIELD_SPLIT_BITS_0_15(CRIT_INT_MASK, TM_CRITICAL_INT_MASK_OFF),
+
+ /* WATCHDOG on v2.3 or later */
+ [WDOG_BARK_STATUS] = REG_FIELD(TM_CRITICAL_INT_STATUS_OFF, 31, 31),
+ [WDOG_BARK_CLEAR] = REG_FIELD(TM_CRITICAL_INT_CLEAR_OFF, 31, 31),
+ [WDOG_BARK_MASK] = REG_FIELD(TM_CRITICAL_INT_MASK_OFF, 31, 31),
+ [CC_MON_STATUS] = REG_FIELD(TM_CRITICAL_INT_STATUS_OFF, 30, 30),
+ [CC_MON_CLEAR] = REG_FIELD(TM_CRITICAL_INT_CLEAR_OFF, 30, 30),
+ [CC_MON_MASK] = REG_FIELD(TM_CRITICAL_INT_MASK_OFF, 30, 30),
+ [WDOG_BARK_COUNT] = REG_FIELD(TM_WDOG_LOG_OFF, 0, 7),
+
+ /* Sn_STATUS */
+ REG_FIELD_FOR_EACH_SENSOR16(LAST_TEMP, TM_Sn_STATUS_OFF, 0, 11),
+ REG_FIELD_FOR_EACH_SENSOR16(VALID, TM_Sn_STATUS_OFF, 21, 21),
+ /* xxx_STATUS bits: 1 == threshold violated */
+ REG_FIELD_FOR_EACH_SENSOR16(MIN_STATUS, TM_Sn_STATUS_OFF, 16, 16),
+ REG_FIELD_FOR_EACH_SENSOR16(LOWER_STATUS, TM_Sn_STATUS_OFF, 17, 17),
+ REG_FIELD_FOR_EACH_SENSOR16(UPPER_STATUS, TM_Sn_STATUS_OFF, 18, 18),
+ REG_FIELD_FOR_EACH_SENSOR16(CRITICAL_STATUS, TM_Sn_STATUS_OFF, 19, 19),
+ REG_FIELD_FOR_EACH_SENSOR16(MAX_STATUS, TM_Sn_STATUS_OFF, 20, 20),
+
+ /* TRDY: 1=ready, 0=in progress */
+ [TRDY] = REG_FIELD(TM_TRDY_OFF, 0, 0),
+};
+
+static const struct tsens_ops ops_generic_v2 = {
+ .init = init_common,
+ .get_temp = get_temp_tsens_valid,
+};
+
+struct tsens_plat_data data_tsens_v2 = {
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+};
+
+/* Kept around for backward compatibility with old msm8996.dtsi */
+struct tsens_plat_data data_8996 = {
+ .num_sensors = 13,
+ .ops = &ops_generic_v2,
+ .feat = &tsens_v2_feat,
+ .fields = tsens_v2_regfields,
+};
diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c
new file mode 100644
index 000000000..252c5ffdd
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.c
@@ -0,0 +1,1187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2019, 2020, Linaro Ltd.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/thermal.h>
+#include "../thermal_hwmon.h"
+#include "tsens.h"
+
+/**
+ * struct tsens_irq_data - IRQ status and temperature violations
+ * @up_viol: upper threshold violated
+ * @up_thresh: upper threshold temperature value
+ * @up_irq_mask: mask register for upper threshold irqs
+ * @up_irq_clear: clear register for uppper threshold irqs
+ * @low_viol: lower threshold violated
+ * @low_thresh: lower threshold temperature value
+ * @low_irq_mask: mask register for lower threshold irqs
+ * @low_irq_clear: clear register for lower threshold irqs
+ * @crit_viol: critical threshold violated
+ * @crit_thresh: critical threshold temperature value
+ * @crit_irq_mask: mask register for critical threshold irqs
+ * @crit_irq_clear: clear register for critical threshold irqs
+ *
+ * Structure containing data about temperature threshold settings and
+ * irq status if they were violated.
+ */
+struct tsens_irq_data {
+ u32 up_viol;
+ int up_thresh;
+ u32 up_irq_mask;
+ u32 up_irq_clear;
+ u32 low_viol;
+ int low_thresh;
+ u32 low_irq_mask;
+ u32 low_irq_clear;
+ u32 crit_viol;
+ u32 crit_thresh;
+ u32 crit_irq_mask;
+ u32 crit_irq_clear;
+};
+
+char *qfprom_read(struct device *dev, const char *cname)
+{
+ struct nvmem_cell *cell;
+ ssize_t data;
+ char *ret;
+
+ cell = nvmem_cell_get(dev, cname);
+ if (IS_ERR(cell))
+ return ERR_CAST(cell);
+
+ ret = nvmem_cell_read(cell, &data);
+ nvmem_cell_put(cell);
+
+ return ret;
+}
+
+/*
+ * Use this function on devices where slope and offset calculations
+ * depend on calibration data read from qfprom. On others the slope
+ * and offset values are derived from tz->tzp->slope and tz->tzp->offset
+ * resp.
+ */
+void compute_intercept_slope(struct tsens_priv *priv, u32 *p1,
+ u32 *p2, u32 mode)
+{
+ int i;
+ int num, den;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ dev_dbg(priv->dev,
+ "%s: sensor%d - data_point1:%#x data_point2:%#x\n",
+ __func__, i, p1[i], p2[i]);
+
+ if (!priv->sensor[i].slope)
+ priv->sensor[i].slope = SLOPE_DEFAULT;
+ if (mode == TWO_PT_CALIB) {
+ /*
+ * slope (m) = adc_code2 - adc_code1 (y2 - y1)/
+ * temp_120_degc - temp_30_degc (x2 - x1)
+ */
+ num = p2[i] - p1[i];
+ num *= SLOPE_FACTOR;
+ den = CAL_DEGC_PT2 - CAL_DEGC_PT1;
+ priv->sensor[i].slope = num / den;
+ }
+
+ priv->sensor[i].offset = (p1[i] * SLOPE_FACTOR) -
+ (CAL_DEGC_PT1 *
+ priv->sensor[i].slope);
+ dev_dbg(priv->dev, "%s: offset:%d\n", __func__,
+ priv->sensor[i].offset);
+ }
+}
+
+static inline u32 degc_to_code(int degc, const struct tsens_sensor *s)
+{
+ u64 code = div_u64(((u64)degc * s->slope + s->offset), SLOPE_FACTOR);
+
+ pr_debug("%s: raw_code: 0x%llx, degc:%d\n", __func__, code, degc);
+ return clamp_val(code, THRESHOLD_MIN_ADC_CODE, THRESHOLD_MAX_ADC_CODE);
+}
+
+static inline int code_to_degc(u32 adc_code, const struct tsens_sensor *s)
+{
+ int degc, num, den;
+
+ num = (adc_code * SLOPE_FACTOR) - s->offset;
+ den = s->slope;
+
+ if (num > 0)
+ degc = num + (den / 2);
+ else if (num < 0)
+ degc = num - (den / 2);
+ else
+ degc = num;
+
+ degc /= den;
+
+ return degc;
+}
+
+/**
+ * tsens_hw_to_mC - Return sign-extended temperature in mCelsius.
+ * @s: Pointer to sensor struct
+ * @field: Index into regmap_field array pointing to temperature data
+ *
+ * This function handles temperature returned in ADC code or deciCelsius
+ * depending on IP version.
+ *
+ * Return: Temperature in milliCelsius on success, a negative errno will
+ * be returned in error cases
+ */
+static int tsens_hw_to_mC(const struct tsens_sensor *s, int field)
+{
+ struct tsens_priv *priv = s->priv;
+ u32 resolution;
+ u32 temp = 0;
+ int ret;
+
+ resolution = priv->fields[LAST_TEMP_0].msb -
+ priv->fields[LAST_TEMP_0].lsb;
+
+ ret = regmap_field_read(priv->rf[field], &temp);
+ if (ret)
+ return ret;
+
+ /* Convert temperature from ADC code to milliCelsius */
+ if (priv->feat->adc)
+ return code_to_degc(temp, s) * 1000;
+
+ /* deciCelsius -> milliCelsius along with sign extension */
+ return sign_extend32(temp, resolution) * 100;
+}
+
+/**
+ * tsens_mC_to_hw - Convert temperature to hardware register value
+ * @s: Pointer to sensor struct
+ * @temp: temperature in milliCelsius to be programmed to hardware
+ *
+ * This function outputs the value to be written to hardware in ADC code
+ * or deciCelsius depending on IP version.
+ *
+ * Return: ADC code or temperature in deciCelsius.
+ */
+static int tsens_mC_to_hw(const struct tsens_sensor *s, int temp)
+{
+ struct tsens_priv *priv = s->priv;
+
+ /* milliC to adc code */
+ if (priv->feat->adc)
+ return degc_to_code(temp / 1000, s);
+
+ /* milliC to deciC */
+ return temp / 100;
+}
+
+static inline enum tsens_ver tsens_version(struct tsens_priv *priv)
+{
+ return priv->feat->ver_major;
+}
+
+static void tsens_set_interrupt_v1(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ u32 index = 0;
+
+ switch (irq_type) {
+ case UPPER:
+ index = UP_INT_CLEAR_0 + hw_id;
+ break;
+ case LOWER:
+ index = LOW_INT_CLEAR_0 + hw_id;
+ break;
+ case CRITICAL:
+ /* No critical interrupts before v2 */
+ return;
+ }
+ regmap_field_write(priv->rf[index], enable ? 0 : 1);
+}
+
+static void tsens_set_interrupt_v2(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ u32 index_mask = 0, index_clear = 0;
+
+ /*
+ * To enable the interrupt flag for a sensor:
+ * - clear the mask bit
+ * To disable the interrupt flag for a sensor:
+ * - Mask further interrupts for this sensor
+ * - Write 1 followed by 0 to clear the interrupt
+ */
+ switch (irq_type) {
+ case UPPER:
+ index_mask = UP_INT_MASK_0 + hw_id;
+ index_clear = UP_INT_CLEAR_0 + hw_id;
+ break;
+ case LOWER:
+ index_mask = LOW_INT_MASK_0 + hw_id;
+ index_clear = LOW_INT_CLEAR_0 + hw_id;
+ break;
+ case CRITICAL:
+ index_mask = CRIT_INT_MASK_0 + hw_id;
+ index_clear = CRIT_INT_CLEAR_0 + hw_id;
+ break;
+ }
+
+ if (enable) {
+ regmap_field_write(priv->rf[index_mask], 0);
+ } else {
+ regmap_field_write(priv->rf[index_mask], 1);
+ regmap_field_write(priv->rf[index_clear], 1);
+ regmap_field_write(priv->rf[index_clear], 0);
+ }
+}
+
+/**
+ * tsens_set_interrupt - Set state of an interrupt
+ * @priv: Pointer to tsens controller private data
+ * @hw_id: Hardware ID aka. sensor number
+ * @irq_type: irq_type from enum tsens_irq_type
+ * @enable: false = disable, true = enable
+ *
+ * Call IP-specific function to set state of an interrupt
+ *
+ * Return: void
+ */
+static void tsens_set_interrupt(struct tsens_priv *priv, u32 hw_id,
+ enum tsens_irq_type irq_type, bool enable)
+{
+ dev_dbg(priv->dev, "[%u] %s: %s -> %s\n", hw_id, __func__,
+ irq_type ? ((irq_type == 1) ? "UP" : "CRITICAL") : "LOW",
+ enable ? "en" : "dis");
+ if (tsens_version(priv) > VER_1_X)
+ tsens_set_interrupt_v2(priv, hw_id, irq_type, enable);
+ else
+ tsens_set_interrupt_v1(priv, hw_id, irq_type, enable);
+}
+
+/**
+ * tsens_threshold_violated - Check if a sensor temperature violated a preset threshold
+ * @priv: Pointer to tsens controller private data
+ * @hw_id: Hardware ID aka. sensor number
+ * @d: Pointer to irq state data
+ *
+ * Return: 0 if threshold was not violated, 1 if it was violated and negative
+ * errno in case of errors
+ */
+static int tsens_threshold_violated(struct tsens_priv *priv, u32 hw_id,
+ struct tsens_irq_data *d)
+{
+ int ret;
+
+ ret = regmap_field_read(priv->rf[UPPER_STATUS_0 + hw_id], &d->up_viol);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOWER_STATUS_0 + hw_id], &d->low_viol);
+ if (ret)
+ return ret;
+
+ if (priv->feat->crit_int) {
+ ret = regmap_field_read(priv->rf[CRITICAL_STATUS_0 + hw_id],
+ &d->crit_viol);
+ if (ret)
+ return ret;
+ }
+
+ if (d->up_viol || d->low_viol || d->crit_viol)
+ return 1;
+
+ return 0;
+}
+
+static int tsens_read_irq_state(struct tsens_priv *priv, u32 hw_id,
+ const struct tsens_sensor *s,
+ struct tsens_irq_data *d)
+{
+ int ret;
+
+ ret = regmap_field_read(priv->rf[UP_INT_CLEAR_0 + hw_id], &d->up_irq_clear);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOW_INT_CLEAR_0 + hw_id], &d->low_irq_clear);
+ if (ret)
+ return ret;
+ if (tsens_version(priv) > VER_1_X) {
+ ret = regmap_field_read(priv->rf[UP_INT_MASK_0 + hw_id], &d->up_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id],
+ &d->crit_irq_clear);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id],
+ &d->crit_irq_mask);
+ if (ret)
+ return ret;
+
+ d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id);
+ } else {
+ /* No mask register on older TSENS */
+ d->up_irq_mask = 0;
+ d->low_irq_mask = 0;
+ d->crit_irq_clear = 0;
+ d->crit_irq_mask = 0;
+ d->crit_thresh = 0;
+ }
+
+ d->up_thresh = tsens_hw_to_mC(s, UP_THRESH_0 + hw_id);
+ d->low_thresh = tsens_hw_to_mC(s, LOW_THRESH_0 + hw_id);
+
+ dev_dbg(priv->dev, "[%u] %s%s: status(%u|%u|%u) | clr(%u|%u|%u) | mask(%u|%u|%u)\n",
+ hw_id, __func__,
+ (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
+ d->low_viol, d->up_viol, d->crit_viol,
+ d->low_irq_clear, d->up_irq_clear, d->crit_irq_clear,
+ d->low_irq_mask, d->up_irq_mask, d->crit_irq_mask);
+ dev_dbg(priv->dev, "[%u] %s%s: thresh: (%d:%d:%d)\n", hw_id, __func__,
+ (d->up_viol || d->low_viol || d->crit_viol) ? "(V)" : "",
+ d->low_thresh, d->up_thresh, d->crit_thresh);
+
+ return 0;
+}
+
+static inline u32 masked_irq(u32 hw_id, u32 mask, enum tsens_ver ver)
+{
+ if (ver > VER_1_X)
+ return mask & (1 << hw_id);
+
+ /* v1, v0.1 don't have a irq mask register */
+ return 0;
+}
+
+/**
+ * tsens_critical_irq_thread() - Threaded handler for critical interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Check FSM watchdog bark status and clear if needed.
+ * Check all sensors to find ones that violated their critical threshold limits.
+ * Clear and then re-enable the interrupt.
+ *
+ * The level-triggered interrupt might deassert if the temperature returned to
+ * within the threshold limits by the time the handler got scheduled. We
+ * consider the irq to have been handled in that case.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_critical_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_irq_data d;
+ int temp, ret, i;
+ u32 wdog_status, wdog_count;
+
+ if (priv->feat->has_watchdog) {
+ ret = regmap_field_read(priv->rf[WDOG_BARK_STATUS],
+ &wdog_status);
+ if (ret)
+ return ret;
+
+ if (wdog_status) {
+ /* Clear WDOG interrupt */
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 1);
+ regmap_field_write(priv->rf[WDOG_BARK_CLEAR], 0);
+ ret = regmap_field_read(priv->rf[WDOG_BARK_COUNT],
+ &wdog_count);
+ if (ret)
+ return ret;
+ if (wdog_count)
+ dev_dbg(priv->dev, "%s: watchdog count: %d\n",
+ __func__, wdog_count);
+
+ /* Fall through to handle critical interrupts if any */
+ }
+ }
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+ ret = get_temp_tsens_valid(s, &temp);
+ if (ret) {
+ dev_err(priv->dev, "[%u] %s: error reading sensor\n",
+ hw_id, __func__);
+ continue;
+ }
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+ if (d.crit_viol &&
+ !masked_irq(hw_id, d.crit_irq_mask, tsens_version(priv))) {
+ /* Mask critical interrupts, unused on Linux */
+ tsens_set_interrupt(priv, hw_id, CRITICAL, false);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsens_irq_thread - Threaded interrupt handler for uplow interrupts
+ * @irq: irq number
+ * @data: tsens controller private data
+ *
+ * Check all sensors to find ones that violated their threshold limits. If the
+ * temperature is still outside the limits, call thermal_zone_device_update() to
+ * update the thresholds, else re-enable the interrupts.
+ *
+ * The level-triggered interrupt might deassert if the temperature returned to
+ * within the threshold limits by the time the handler got scheduled. We
+ * consider the irq to have been handled in that case.
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsens_irq_thread(int irq, void *data)
+{
+ struct tsens_priv *priv = data;
+ struct tsens_irq_data d;
+ bool enable = true, disable = false;
+ unsigned long flags;
+ int temp, ret, i;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ bool trigger = false;
+ const struct tsens_sensor *s = &priv->sensor[i];
+ u32 hw_id = s->hw_id;
+
+ if (!s->tzd)
+ continue;
+ if (!tsens_threshold_violated(priv, hw_id, &d))
+ continue;
+ ret = get_temp_tsens_valid(s, &temp);
+ if (ret) {
+ dev_err(priv->dev, "[%u] %s: error reading sensor\n",
+ hw_id, __func__);
+ continue;
+ }
+
+ spin_lock_irqsave(&priv->ul_lock, flags);
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+
+ if (d.up_viol &&
+ !masked_irq(hw_id, d.up_irq_mask, tsens_version(priv))) {
+ tsens_set_interrupt(priv, hw_id, UPPER, disable);
+ if (d.up_thresh > temp) {
+ dev_dbg(priv->dev, "[%u] %s: re-arm upper\n",
+ hw_id, __func__);
+ tsens_set_interrupt(priv, hw_id, UPPER, enable);
+ } else {
+ trigger = true;
+ /* Keep irq masked */
+ }
+ } else if (d.low_viol &&
+ !masked_irq(hw_id, d.low_irq_mask, tsens_version(priv))) {
+ tsens_set_interrupt(priv, hw_id, LOWER, disable);
+ if (d.low_thresh < temp) {
+ dev_dbg(priv->dev, "[%u] %s: re-arm low\n",
+ hw_id, __func__);
+ tsens_set_interrupt(priv, hw_id, LOWER, enable);
+ } else {
+ trigger = true;
+ /* Keep irq masked */
+ }
+ }
+
+ spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+ if (trigger) {
+ dev_dbg(priv->dev, "[%u] %s: TZ update trigger (%d mC)\n",
+ hw_id, __func__, temp);
+ thermal_zone_device_update(s->tzd,
+ THERMAL_EVENT_UNSPECIFIED);
+ } else {
+ dev_dbg(priv->dev, "[%u] %s: no violation: %d\n",
+ hw_id, __func__, temp);
+ }
+
+ if (tsens_version(priv) < VER_0_1) {
+ /* Constraint: There is only 1 interrupt control register for all
+ * 11 temperature sensor. So monitoring more than 1 sensor based
+ * on interrupts will yield inconsistent result. To overcome this
+ * issue we will monitor only sensor 0 which is the master sensor.
+ */
+ break;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high)
+{
+ struct tsens_sensor *s = tz->devdata;
+ struct tsens_priv *priv = s->priv;
+ struct device *dev = priv->dev;
+ struct tsens_irq_data d;
+ unsigned long flags;
+ int high_val, low_val, cl_high, cl_low;
+ u32 hw_id = s->hw_id;
+
+ if (tsens_version(priv) < VER_0_1) {
+ /* Pre v0.1 IP had a single register for each type of interrupt
+ * and thresholds
+ */
+ hw_id = 0;
+ }
+
+ dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
+ hw_id, __func__, low, high);
+
+ cl_high = clamp_val(high, -40000, 120000);
+ cl_low = clamp_val(low, -40000, 120000);
+
+ high_val = tsens_mC_to_hw(s, cl_high);
+ low_val = tsens_mC_to_hw(s, cl_low);
+
+ spin_lock_irqsave(&priv->ul_lock, flags);
+
+ tsens_read_irq_state(priv, hw_id, s, &d);
+
+ /* Write the new thresholds and clear the status */
+ regmap_field_write(priv->rf[LOW_THRESH_0 + hw_id], low_val);
+ regmap_field_write(priv->rf[UP_THRESH_0 + hw_id], high_val);
+ tsens_set_interrupt(priv, hw_id, LOWER, true);
+ tsens_set_interrupt(priv, hw_id, UPPER, true);
+
+ spin_unlock_irqrestore(&priv->ul_lock, flags);
+
+ dev_dbg(dev, "[%u] %s: (%d:%d)->(%d:%d)\n",
+ hw_id, __func__, d.low_thresh, d.up_thresh, cl_low, cl_high);
+
+ return 0;
+}
+
+static int tsens_enable_irq(struct tsens_priv *priv)
+{
+ int ret;
+ int val = tsens_version(priv) > VER_1_X ? 7 : 1;
+
+ ret = regmap_field_write(priv->rf[INT_EN], val);
+ if (ret < 0)
+ dev_err(priv->dev, "%s: failed to enable interrupts\n",
+ __func__);
+
+ return ret;
+}
+
+static void tsens_disable_irq(struct tsens_priv *priv)
+{
+ regmap_field_write(priv->rf[INT_EN], 0);
+}
+
+int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int hw_id = s->hw_id;
+ u32 temp_idx = LAST_TEMP_0 + hw_id;
+ u32 valid_idx = VALID_0 + hw_id;
+ u32 valid;
+ int ret;
+
+ /* VER_0 doesn't have VALID bit */
+ if (tsens_version(priv) == VER_0)
+ goto get_temp;
+
+ /* Valid bit is 0 for 6 AHB clock cycles.
+ * At 19.2MHz, 1 AHB clock is ~60ns.
+ * We should enter this loop very, very rarely.
+ * Wait 1 us since it's the min of poll_timeout macro.
+ * Old value was 400 ns.
+ */
+ ret = regmap_field_read_poll_timeout(priv->rf[valid_idx], valid,
+ valid, 1, 20 * USEC_PER_MSEC);
+ if (ret)
+ return ret;
+
+get_temp:
+ /* Valid bit is set, OK to read the temperature */
+ *temp = tsens_hw_to_mC(s, temp_idx);
+
+ return 0;
+}
+
+int get_temp_common(const struct tsens_sensor *s, int *temp)
+{
+ struct tsens_priv *priv = s->priv;
+ int hw_id = s->hw_id;
+ int last_temp = 0, ret, trdy;
+ unsigned long timeout;
+
+ timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
+ do {
+ if (tsens_version(priv) == VER_0) {
+ ret = regmap_field_read(priv->rf[TRDY], &trdy);
+ if (ret)
+ return ret;
+ if (!trdy)
+ continue;
+ }
+
+ ret = regmap_field_read(priv->rf[LAST_TEMP_0 + hw_id], &last_temp);
+ if (ret)
+ return ret;
+
+ *temp = code_to_degc(last_temp, s) * 1000;
+
+ return 0;
+ } while (time_before(jiffies, timeout));
+
+ return -ETIMEDOUT;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int dbg_sensors_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ int i;
+
+ seq_printf(s, "max: %2d\nnum: %2d\n\n",
+ priv->feat->max_sensors, priv->num_sensors);
+
+ seq_puts(s, " id slope offset\n--------------------------\n");
+ for (i = 0; i < priv->num_sensors; i++) {
+ seq_printf(s, "%8d %8d %8d\n", priv->sensor[i].hw_id,
+ priv->sensor[i].slope, priv->sensor[i].offset);
+ }
+
+ return 0;
+}
+
+static int dbg_version_show(struct seq_file *s, void *data)
+{
+ struct platform_device *pdev = s->private;
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ u32 maj_ver, min_ver, step_ver;
+ int ret;
+
+ if (tsens_version(priv) > VER_0_1) {
+ ret = regmap_field_read(priv->rf[VER_MAJOR], &maj_ver);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[VER_MINOR], &min_ver);
+ if (ret)
+ return ret;
+ ret = regmap_field_read(priv->rf[VER_STEP], &step_ver);
+ if (ret)
+ return ret;
+ seq_printf(s, "%d.%d.%d\n", maj_ver, min_ver, step_ver);
+ } else {
+ seq_puts(s, "0.1.0\n");
+ }
+
+ return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(dbg_version);
+DEFINE_SHOW_ATTRIBUTE(dbg_sensors);
+
+static void tsens_debug_init(struct platform_device *pdev)
+{
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+ struct dentry *root, *file;
+
+ root = debugfs_lookup("tsens", NULL);
+ if (!root)
+ priv->debug_root = debugfs_create_dir("tsens", NULL);
+ else
+ priv->debug_root = root;
+
+ file = debugfs_lookup("version", priv->debug_root);
+ if (!file)
+ debugfs_create_file("version", 0444, priv->debug_root,
+ pdev, &dbg_version_fops);
+
+ /* A directory for each instance of the TSENS IP */
+ priv->debug = debugfs_create_dir(dev_name(&pdev->dev), priv->debug_root);
+ debugfs_create_file("sensors", 0444, priv->debug, pdev, &dbg_sensors_fops);
+}
+#else
+static inline void tsens_debug_init(struct platform_device *pdev) {}
+#endif
+
+static const struct regmap_config tsens_config = {
+ .name = "tm",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static const struct regmap_config tsens_srot_config = {
+ .name = "srot",
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+int __init init_common(struct tsens_priv *priv)
+{
+ void __iomem *tm_base, *srot_base;
+ struct device *dev = priv->dev;
+ u32 ver_minor;
+ struct resource *res;
+ u32 enabled;
+ int ret, i, j;
+ struct platform_device *op = of_find_device_by_node(priv->dev->of_node);
+
+ if (!op)
+ return -EINVAL;
+
+ if (op->num_resources > 1) {
+ /* DT with separate SROT and TM address space */
+ priv->tm_offset = 0;
+ res = platform_get_resource(op, IORESOURCE_MEM, 1);
+ srot_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(srot_base)) {
+ ret = PTR_ERR(srot_base);
+ goto err_put_device;
+ }
+
+ priv->srot_map = devm_regmap_init_mmio(dev, srot_base,
+ &tsens_srot_config);
+ if (IS_ERR(priv->srot_map)) {
+ ret = PTR_ERR(priv->srot_map);
+ goto err_put_device;
+ }
+ } else {
+ /* old DTs where SROT and TM were in a contiguous 2K block */
+ priv->tm_offset = 0x1000;
+ }
+
+ if (tsens_version(priv) >= VER_0_1) {
+ res = platform_get_resource(op, IORESOURCE_MEM, 0);
+ tm_base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(tm_base)) {
+ ret = PTR_ERR(tm_base);
+ goto err_put_device;
+ }
+
+ priv->tm_map = devm_regmap_init_mmio(dev, tm_base, &tsens_config);
+ } else { /* VER_0 share the same gcc regs using a syscon */
+ struct device *parent = priv->dev->parent;
+
+ if (parent)
+ priv->tm_map = syscon_node_to_regmap(parent->of_node);
+ }
+
+ if (IS_ERR_OR_NULL(priv->tm_map)) {
+ if (!priv->tm_map)
+ ret = -ENODEV;
+ else
+ ret = PTR_ERR(priv->tm_map);
+ goto err_put_device;
+ }
+
+ /* VER_0 have only tm_map */
+ if (!priv->srot_map)
+ priv->srot_map = priv->tm_map;
+
+ if (tsens_version(priv) > VER_0_1) {
+ for (i = VER_MAJOR; i <= VER_STEP; i++) {
+ priv->rf[i] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[i]);
+ if (IS_ERR(priv->rf[i])) {
+ ret = PTR_ERR(priv->rf[i]);
+ goto err_put_device;
+ }
+ }
+ ret = regmap_field_read(priv->rf[VER_MINOR], &ver_minor);
+ if (ret)
+ goto err_put_device;
+ }
+
+ priv->rf[TSENS_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[TSENS_EN]);
+ if (IS_ERR(priv->rf[TSENS_EN])) {
+ ret = PTR_ERR(priv->rf[TSENS_EN]);
+ goto err_put_device;
+ }
+ /* in VER_0 TSENS need to be explicitly enabled */
+ if (tsens_version(priv) == VER_0)
+ regmap_field_write(priv->rf[TSENS_EN], 1);
+
+ ret = regmap_field_read(priv->rf[TSENS_EN], &enabled);
+ if (ret)
+ goto err_put_device;
+ if (!enabled) {
+ dev_err(dev, "%s: device not enabled\n", __func__);
+ ret = -ENODEV;
+ goto err_put_device;
+ }
+
+ priv->rf[SENSOR_EN] = devm_regmap_field_alloc(dev, priv->srot_map,
+ priv->fields[SENSOR_EN]);
+ if (IS_ERR(priv->rf[SENSOR_EN])) {
+ ret = PTR_ERR(priv->rf[SENSOR_EN]);
+ goto err_put_device;
+ }
+ priv->rf[INT_EN] = devm_regmap_field_alloc(dev, priv->tm_map,
+ priv->fields[INT_EN]);
+ if (IS_ERR(priv->rf[INT_EN])) {
+ ret = PTR_ERR(priv->rf[INT_EN]);
+ goto err_put_device;
+ }
+
+ priv->rf[TSENS_SW_RST] =
+ devm_regmap_field_alloc(dev, priv->srot_map, priv->fields[TSENS_SW_RST]);
+ if (IS_ERR(priv->rf[TSENS_SW_RST])) {
+ ret = PTR_ERR(priv->rf[TSENS_SW_RST]);
+ goto err_put_device;
+ }
+
+ priv->rf[TRDY] = devm_regmap_field_alloc(dev, priv->tm_map, priv->fields[TRDY]);
+ if (IS_ERR(priv->rf[TRDY])) {
+ ret = PTR_ERR(priv->rf[TRDY]);
+ goto err_put_device;
+ }
+
+ /* This loop might need changes if enum regfield_ids is reordered */
+ for (j = LAST_TEMP_0; j <= UP_THRESH_15; j += 16) {
+ for (i = 0; i < priv->feat->max_sensors; i++) {
+ int idx = j + i;
+
+ priv->rf[idx] = devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[idx]);
+ if (IS_ERR(priv->rf[idx])) {
+ ret = PTR_ERR(priv->rf[idx]);
+ goto err_put_device;
+ }
+ }
+ }
+
+ if (priv->feat->crit_int || tsens_version(priv) < VER_0_1) {
+ /* Loop might need changes if enum regfield_ids is reordered */
+ for (j = CRITICAL_STATUS_0; j <= CRIT_THRESH_15; j += 16) {
+ for (i = 0; i < priv->feat->max_sensors; i++) {
+ int idx = j + i;
+
+ priv->rf[idx] =
+ devm_regmap_field_alloc(dev,
+ priv->tm_map,
+ priv->fields[idx]);
+ if (IS_ERR(priv->rf[idx])) {
+ ret = PTR_ERR(priv->rf[idx]);
+ goto err_put_device;
+ }
+ }
+ }
+ }
+
+ if (tsens_version(priv) > VER_1_X && ver_minor > 2) {
+ /* Watchdog is present only on v2.3+ */
+ priv->feat->has_watchdog = 1;
+ for (i = WDOG_BARK_STATUS; i <= CC_MON_MASK; i++) {
+ priv->rf[i] = devm_regmap_field_alloc(dev, priv->tm_map,
+ priv->fields[i]);
+ if (IS_ERR(priv->rf[i])) {
+ ret = PTR_ERR(priv->rf[i]);
+ goto err_put_device;
+ }
+ }
+ /*
+ * Watchdog is already enabled, unmask the bark.
+ * Disable cycle completion monitoring
+ */
+ regmap_field_write(priv->rf[WDOG_BARK_MASK], 0);
+ regmap_field_write(priv->rf[CC_MON_MASK], 1);
+ }
+
+ spin_lock_init(&priv->ul_lock);
+
+ /* VER_0 interrupt doesn't need to be enabled */
+ if (tsens_version(priv) >= VER_0_1)
+ tsens_enable_irq(priv);
+
+ tsens_debug_init(op);
+
+err_put_device:
+ put_device(&op->dev);
+ return ret;
+}
+
+static int tsens_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct tsens_sensor *s = tz->devdata;
+ struct tsens_priv *priv = s->priv;
+
+ return priv->ops->get_temp(s, temp);
+}
+
+static int __maybe_unused tsens_suspend(struct device *dev)
+{
+ struct tsens_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->ops && priv->ops->suspend)
+ return priv->ops->suspend(priv);
+
+ return 0;
+}
+
+static int __maybe_unused tsens_resume(struct device *dev)
+{
+ struct tsens_priv *priv = dev_get_drvdata(dev);
+
+ if (priv->ops && priv->ops->resume)
+ return priv->ops->resume(priv);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(tsens_pm_ops, tsens_suspend, tsens_resume);
+
+static const struct of_device_id tsens_table[] = {
+ {
+ .compatible = "qcom,ipq8064-tsens",
+ .data = &data_8960,
+ }, {
+ .compatible = "qcom,mdm9607-tsens",
+ .data = &data_9607,
+ }, {
+ .compatible = "qcom,msm8916-tsens",
+ .data = &data_8916,
+ }, {
+ .compatible = "qcom,msm8939-tsens",
+ .data = &data_8939,
+ }, {
+ .compatible = "qcom,msm8956-tsens",
+ .data = &data_8956,
+ }, {
+ .compatible = "qcom,msm8960-tsens",
+ .data = &data_8960,
+ }, {
+ .compatible = "qcom,msm8974-tsens",
+ .data = &data_8974,
+ }, {
+ .compatible = "qcom,msm8976-tsens",
+ .data = &data_8976,
+ }, {
+ .compatible = "qcom,msm8996-tsens",
+ .data = &data_8996,
+ }, {
+ .compatible = "qcom,tsens-v1",
+ .data = &data_tsens_v1,
+ }, {
+ .compatible = "qcom,tsens-v2",
+ .data = &data_tsens_v2,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, tsens_table);
+
+static const struct thermal_zone_device_ops tsens_of_ops = {
+ .get_temp = tsens_get_temp,
+ .set_trips = tsens_set_trips,
+};
+
+static int tsens_register_irq(struct tsens_priv *priv, char *irqname,
+ irq_handler_t thread_fn)
+{
+ struct platform_device *pdev;
+ int ret, irq;
+
+ pdev = of_find_device_by_node(priv->dev->of_node);
+ if (!pdev)
+ return -ENODEV;
+
+ irq = platform_get_irq_byname(pdev, irqname);
+ if (irq < 0) {
+ ret = irq;
+ /* For old DTs with no IRQ defined */
+ if (irq == -ENXIO)
+ ret = 0;
+ } else {
+ /* VER_0 interrupt is TRIGGER_RISING, VER_0_1 and up is ONESHOT */
+ if (tsens_version(priv) == VER_0)
+ ret = devm_request_threaded_irq(&pdev->dev, irq,
+ thread_fn, NULL,
+ IRQF_TRIGGER_RISING,
+ dev_name(&pdev->dev),
+ priv);
+ else
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ thread_fn, IRQF_ONESHOT,
+ dev_name(&pdev->dev),
+ priv);
+
+ if (ret)
+ dev_err(&pdev->dev, "%s: failed to get irq\n",
+ __func__);
+ else
+ enable_irq_wake(irq);
+ }
+
+ put_device(&pdev->dev);
+ return ret;
+}
+
+static int tsens_register(struct tsens_priv *priv)
+{
+ int i, ret;
+ struct thermal_zone_device *tzd;
+
+ for (i = 0; i < priv->num_sensors; i++) {
+ priv->sensor[i].priv = priv;
+ tzd = devm_thermal_of_zone_register(priv->dev, priv->sensor[i].hw_id,
+ &priv->sensor[i],
+ &tsens_of_ops);
+ if (IS_ERR(tzd))
+ continue;
+ priv->sensor[i].tzd = tzd;
+ if (priv->ops->enable)
+ priv->ops->enable(priv, i);
+
+ if (devm_thermal_add_hwmon_sysfs(tzd))
+ dev_warn(priv->dev,
+ "Failed to add hwmon sysfs attributes\n");
+ }
+
+ /* VER_0 require to set MIN and MAX THRESH
+ * These 2 regs are set using the:
+ * - CRIT_THRESH_0 for MAX THRESH hardcoded to 120°C
+ * - CRIT_THRESH_1 for MIN THRESH hardcoded to 0°C
+ */
+ if (tsens_version(priv) < VER_0_1) {
+ regmap_field_write(priv->rf[CRIT_THRESH_0],
+ tsens_mC_to_hw(priv->sensor, 120000));
+
+ regmap_field_write(priv->rf[CRIT_THRESH_1],
+ tsens_mC_to_hw(priv->sensor, 0));
+ }
+
+ ret = tsens_register_irq(priv, "uplow", tsens_irq_thread);
+ if (ret < 0)
+ return ret;
+
+ if (priv->feat->crit_int)
+ ret = tsens_register_irq(priv, "critical",
+ tsens_critical_irq_thread);
+
+ return ret;
+}
+
+static int tsens_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct device *dev;
+ struct device_node *np;
+ struct tsens_priv *priv;
+ const struct tsens_plat_data *data;
+ const struct of_device_id *id;
+ u32 num_sensors;
+
+ if (pdev->dev.of_node)
+ dev = &pdev->dev;
+ else
+ dev = pdev->dev.parent;
+
+ np = dev->of_node;
+
+ id = of_match_node(tsens_table, np);
+ if (id)
+ data = id->data;
+ else
+ data = &data_8960;
+
+ num_sensors = data->num_sensors;
+
+ if (np)
+ of_property_read_u32(np, "#qcom,sensors", &num_sensors);
+
+ if (num_sensors <= 0) {
+ dev_err(dev, "%s: invalid number of sensors\n", __func__);
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(dev,
+ struct_size(priv, sensor, num_sensors),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->num_sensors = num_sensors;
+ priv->ops = data->ops;
+ for (i = 0; i < priv->num_sensors; i++) {
+ if (data->hw_ids)
+ priv->sensor[i].hw_id = data->hw_ids[i];
+ else
+ priv->sensor[i].hw_id = i;
+ }
+ priv->feat = data->feat;
+ priv->fields = data->fields;
+
+ platform_set_drvdata(pdev, priv);
+
+ if (!priv->ops || !priv->ops->init || !priv->ops->get_temp)
+ return -EINVAL;
+
+ ret = priv->ops->init(priv);
+ if (ret < 0) {
+ dev_err(dev, "%s: init failed\n", __func__);
+ return ret;
+ }
+
+ if (priv->ops->calibrate) {
+ ret = priv->ops->calibrate(priv);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "%s: calibration failed\n", __func__);
+ return ret;
+ }
+ }
+
+ return tsens_register(priv);
+}
+
+static int tsens_remove(struct platform_device *pdev)
+{
+ struct tsens_priv *priv = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(priv->debug_root);
+ tsens_disable_irq(priv);
+ if (priv->ops->disable)
+ priv->ops->disable(priv);
+
+ return 0;
+}
+
+static struct platform_driver tsens_driver = {
+ .probe = tsens_probe,
+ .remove = tsens_remove,
+ .driver = {
+ .name = "qcom-tsens",
+ .pm = &tsens_pm_ops,
+ .of_match_table = tsens_table,
+ },
+};
+module_platform_driver(tsens_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("QCOM Temperature Sensor driver");
+MODULE_ALIAS("platform:qcom-tsens");
diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h
new file mode 100644
index 000000000..4f969dd7d
--- /dev/null
+++ b/drivers/thermal/qcom/tsens.h
@@ -0,0 +1,596 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef __QCOM_TSENS_H__
+#define __QCOM_TSENS_H__
+
+#define ONE_PT_CALIB 0x1
+#define ONE_PT_CALIB2 0x2
+#define TWO_PT_CALIB 0x3
+#define CAL_DEGC_PT1 30
+#define CAL_DEGC_PT2 120
+#define SLOPE_FACTOR 1000
+#define SLOPE_DEFAULT 3200
+#define TIMEOUT_US 100
+#define THRESHOLD_MAX_ADC_CODE 0x3ff
+#define THRESHOLD_MIN_ADC_CODE 0x0
+
+#include <linux/interrupt.h>
+#include <linux/thermal.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+struct tsens_priv;
+
+/* IP version numbers in ascending order */
+enum tsens_ver {
+ VER_0 = 0,
+ VER_0_1,
+ VER_1_X,
+ VER_2_X,
+};
+
+enum tsens_irq_type {
+ LOWER,
+ UPPER,
+ CRITICAL,
+};
+
+/**
+ * struct tsens_sensor - data for each sensor connected to the tsens device
+ * @priv: tsens device instance that this sensor is connected to
+ * @tzd: pointer to the thermal zone that this sensor is in
+ * @offset: offset of temperature adjustment curve
+ * @hw_id: HW ID can be used in case of platform-specific IDs
+ * @slope: slope of temperature adjustment curve
+ * @status: 8960-specific variable to track 8960 and 8660 status register offset
+ */
+struct tsens_sensor {
+ struct tsens_priv *priv;
+ struct thermal_zone_device *tzd;
+ int offset;
+ unsigned int hw_id;
+ int slope;
+ u32 status;
+};
+
+/**
+ * struct tsens_ops - operations as supported by the tsens device
+ * @init: Function to initialize the tsens device
+ * @calibrate: Function to calibrate the tsens device
+ * @get_temp: Function which returns the temp in millidegC
+ * @enable: Function to enable (clocks/power) tsens device
+ * @disable: Function to disable the tsens device
+ * @suspend: Function to suspend the tsens device
+ * @resume: Function to resume the tsens device
+ */
+struct tsens_ops {
+ /* mandatory callbacks */
+ int (*init)(struct tsens_priv *priv);
+ int (*calibrate)(struct tsens_priv *priv);
+ int (*get_temp)(const struct tsens_sensor *s, int *temp);
+ /* optional callbacks */
+ int (*enable)(struct tsens_priv *priv, int i);
+ void (*disable)(struct tsens_priv *priv);
+ int (*suspend)(struct tsens_priv *priv);
+ int (*resume)(struct tsens_priv *priv);
+};
+
+#define REG_FIELD_FOR_EACH_SENSOR11(_name, _offset, _startbit, _stopbit) \
+ [_name##_##0] = REG_FIELD(_offset, _startbit, _stopbit), \
+ [_name##_##1] = REG_FIELD(_offset + 4, _startbit, _stopbit), \
+ [_name##_##2] = REG_FIELD(_offset + 8, _startbit, _stopbit), \
+ [_name##_##3] = REG_FIELD(_offset + 12, _startbit, _stopbit), \
+ [_name##_##4] = REG_FIELD(_offset + 16, _startbit, _stopbit), \
+ [_name##_##5] = REG_FIELD(_offset + 20, _startbit, _stopbit), \
+ [_name##_##6] = REG_FIELD(_offset + 24, _startbit, _stopbit), \
+ [_name##_##7] = REG_FIELD(_offset + 28, _startbit, _stopbit), \
+ [_name##_##8] = REG_FIELD(_offset + 32, _startbit, _stopbit), \
+ [_name##_##9] = REG_FIELD(_offset + 36, _startbit, _stopbit), \
+ [_name##_##10] = REG_FIELD(_offset + 40, _startbit, _stopbit)
+
+#define REG_FIELD_FOR_EACH_SENSOR16(_name, _offset, _startbit, _stopbit) \
+ [_name##_##0] = REG_FIELD(_offset, _startbit, _stopbit), \
+ [_name##_##1] = REG_FIELD(_offset + 4, _startbit, _stopbit), \
+ [_name##_##2] = REG_FIELD(_offset + 8, _startbit, _stopbit), \
+ [_name##_##3] = REG_FIELD(_offset + 12, _startbit, _stopbit), \
+ [_name##_##4] = REG_FIELD(_offset + 16, _startbit, _stopbit), \
+ [_name##_##5] = REG_FIELD(_offset + 20, _startbit, _stopbit), \
+ [_name##_##6] = REG_FIELD(_offset + 24, _startbit, _stopbit), \
+ [_name##_##7] = REG_FIELD(_offset + 28, _startbit, _stopbit), \
+ [_name##_##8] = REG_FIELD(_offset + 32, _startbit, _stopbit), \
+ [_name##_##9] = REG_FIELD(_offset + 36, _startbit, _stopbit), \
+ [_name##_##10] = REG_FIELD(_offset + 40, _startbit, _stopbit), \
+ [_name##_##11] = REG_FIELD(_offset + 44, _startbit, _stopbit), \
+ [_name##_##12] = REG_FIELD(_offset + 48, _startbit, _stopbit), \
+ [_name##_##13] = REG_FIELD(_offset + 52, _startbit, _stopbit), \
+ [_name##_##14] = REG_FIELD(_offset + 56, _startbit, _stopbit), \
+ [_name##_##15] = REG_FIELD(_offset + 60, _startbit, _stopbit)
+
+#define REG_FIELD_SPLIT_BITS_0_15(_name, _offset) \
+ [_name##_##0] = REG_FIELD(_offset, 0, 0), \
+ [_name##_##1] = REG_FIELD(_offset, 1, 1), \
+ [_name##_##2] = REG_FIELD(_offset, 2, 2), \
+ [_name##_##3] = REG_FIELD(_offset, 3, 3), \
+ [_name##_##4] = REG_FIELD(_offset, 4, 4), \
+ [_name##_##5] = REG_FIELD(_offset, 5, 5), \
+ [_name##_##6] = REG_FIELD(_offset, 6, 6), \
+ [_name##_##7] = REG_FIELD(_offset, 7, 7), \
+ [_name##_##8] = REG_FIELD(_offset, 8, 8), \
+ [_name##_##9] = REG_FIELD(_offset, 9, 9), \
+ [_name##_##10] = REG_FIELD(_offset, 10, 10), \
+ [_name##_##11] = REG_FIELD(_offset, 11, 11), \
+ [_name##_##12] = REG_FIELD(_offset, 12, 12), \
+ [_name##_##13] = REG_FIELD(_offset, 13, 13), \
+ [_name##_##14] = REG_FIELD(_offset, 14, 14), \
+ [_name##_##15] = REG_FIELD(_offset, 15, 15)
+
+#define REG_FIELD_SPLIT_BITS_16_31(_name, _offset) \
+ [_name##_##0] = REG_FIELD(_offset, 16, 16), \
+ [_name##_##1] = REG_FIELD(_offset, 17, 17), \
+ [_name##_##2] = REG_FIELD(_offset, 18, 18), \
+ [_name##_##3] = REG_FIELD(_offset, 19, 19), \
+ [_name##_##4] = REG_FIELD(_offset, 20, 20), \
+ [_name##_##5] = REG_FIELD(_offset, 21, 21), \
+ [_name##_##6] = REG_FIELD(_offset, 22, 22), \
+ [_name##_##7] = REG_FIELD(_offset, 23, 23), \
+ [_name##_##8] = REG_FIELD(_offset, 24, 24), \
+ [_name##_##9] = REG_FIELD(_offset, 25, 25), \
+ [_name##_##10] = REG_FIELD(_offset, 26, 26), \
+ [_name##_##11] = REG_FIELD(_offset, 27, 27), \
+ [_name##_##12] = REG_FIELD(_offset, 28, 28), \
+ [_name##_##13] = REG_FIELD(_offset, 29, 29), \
+ [_name##_##14] = REG_FIELD(_offset, 30, 30), \
+ [_name##_##15] = REG_FIELD(_offset, 31, 31)
+
+/*
+ * reg_field IDs to use as an index into an array
+ * If you change the order of the entries, check the devm_regmap_field_alloc()
+ * calls in init_common()
+ */
+enum regfield_ids {
+ /* ----- SROT ------ */
+ /* HW_VER */
+ VER_MAJOR,
+ VER_MINOR,
+ VER_STEP,
+ /* CTRL_OFFSET */
+ TSENS_EN,
+ TSENS_SW_RST,
+ SENSOR_EN,
+ CODE_OR_TEMP,
+
+ /* ----- TM ------ */
+ /* TRDY */
+ TRDY,
+ /* INTERRUPT ENABLE */
+ INT_EN, /* v2+ has separate enables for crit, upper and lower irq */
+ /* STATUS */
+ LAST_TEMP_0, /* Last temperature reading */
+ LAST_TEMP_1,
+ LAST_TEMP_2,
+ LAST_TEMP_3,
+ LAST_TEMP_4,
+ LAST_TEMP_5,
+ LAST_TEMP_6,
+ LAST_TEMP_7,
+ LAST_TEMP_8,
+ LAST_TEMP_9,
+ LAST_TEMP_10,
+ LAST_TEMP_11,
+ LAST_TEMP_12,
+ LAST_TEMP_13,
+ LAST_TEMP_14,
+ LAST_TEMP_15,
+ VALID_0, /* VALID reading or not */
+ VALID_1,
+ VALID_2,
+ VALID_3,
+ VALID_4,
+ VALID_5,
+ VALID_6,
+ VALID_7,
+ VALID_8,
+ VALID_9,
+ VALID_10,
+ VALID_11,
+ VALID_12,
+ VALID_13,
+ VALID_14,
+ VALID_15,
+ LOWER_STATUS_0, /* LOWER threshold violated */
+ LOWER_STATUS_1,
+ LOWER_STATUS_2,
+ LOWER_STATUS_3,
+ LOWER_STATUS_4,
+ LOWER_STATUS_5,
+ LOWER_STATUS_6,
+ LOWER_STATUS_7,
+ LOWER_STATUS_8,
+ LOWER_STATUS_9,
+ LOWER_STATUS_10,
+ LOWER_STATUS_11,
+ LOWER_STATUS_12,
+ LOWER_STATUS_13,
+ LOWER_STATUS_14,
+ LOWER_STATUS_15,
+ LOW_INT_STATUS_0, /* LOWER interrupt status */
+ LOW_INT_STATUS_1,
+ LOW_INT_STATUS_2,
+ LOW_INT_STATUS_3,
+ LOW_INT_STATUS_4,
+ LOW_INT_STATUS_5,
+ LOW_INT_STATUS_6,
+ LOW_INT_STATUS_7,
+ LOW_INT_STATUS_8,
+ LOW_INT_STATUS_9,
+ LOW_INT_STATUS_10,
+ LOW_INT_STATUS_11,
+ LOW_INT_STATUS_12,
+ LOW_INT_STATUS_13,
+ LOW_INT_STATUS_14,
+ LOW_INT_STATUS_15,
+ LOW_INT_CLEAR_0, /* LOWER interrupt clear */
+ LOW_INT_CLEAR_1,
+ LOW_INT_CLEAR_2,
+ LOW_INT_CLEAR_3,
+ LOW_INT_CLEAR_4,
+ LOW_INT_CLEAR_5,
+ LOW_INT_CLEAR_6,
+ LOW_INT_CLEAR_7,
+ LOW_INT_CLEAR_8,
+ LOW_INT_CLEAR_9,
+ LOW_INT_CLEAR_10,
+ LOW_INT_CLEAR_11,
+ LOW_INT_CLEAR_12,
+ LOW_INT_CLEAR_13,
+ LOW_INT_CLEAR_14,
+ LOW_INT_CLEAR_15,
+ LOW_INT_MASK_0, /* LOWER interrupt mask */
+ LOW_INT_MASK_1,
+ LOW_INT_MASK_2,
+ LOW_INT_MASK_3,
+ LOW_INT_MASK_4,
+ LOW_INT_MASK_5,
+ LOW_INT_MASK_6,
+ LOW_INT_MASK_7,
+ LOW_INT_MASK_8,
+ LOW_INT_MASK_9,
+ LOW_INT_MASK_10,
+ LOW_INT_MASK_11,
+ LOW_INT_MASK_12,
+ LOW_INT_MASK_13,
+ LOW_INT_MASK_14,
+ LOW_INT_MASK_15,
+ LOW_THRESH_0, /* LOWER threshold values */
+ LOW_THRESH_1,
+ LOW_THRESH_2,
+ LOW_THRESH_3,
+ LOW_THRESH_4,
+ LOW_THRESH_5,
+ LOW_THRESH_6,
+ LOW_THRESH_7,
+ LOW_THRESH_8,
+ LOW_THRESH_9,
+ LOW_THRESH_10,
+ LOW_THRESH_11,
+ LOW_THRESH_12,
+ LOW_THRESH_13,
+ LOW_THRESH_14,
+ LOW_THRESH_15,
+ UPPER_STATUS_0, /* UPPER threshold violated */
+ UPPER_STATUS_1,
+ UPPER_STATUS_2,
+ UPPER_STATUS_3,
+ UPPER_STATUS_4,
+ UPPER_STATUS_5,
+ UPPER_STATUS_6,
+ UPPER_STATUS_7,
+ UPPER_STATUS_8,
+ UPPER_STATUS_9,
+ UPPER_STATUS_10,
+ UPPER_STATUS_11,
+ UPPER_STATUS_12,
+ UPPER_STATUS_13,
+ UPPER_STATUS_14,
+ UPPER_STATUS_15,
+ UP_INT_STATUS_0, /* UPPER interrupt status */
+ UP_INT_STATUS_1,
+ UP_INT_STATUS_2,
+ UP_INT_STATUS_3,
+ UP_INT_STATUS_4,
+ UP_INT_STATUS_5,
+ UP_INT_STATUS_6,
+ UP_INT_STATUS_7,
+ UP_INT_STATUS_8,
+ UP_INT_STATUS_9,
+ UP_INT_STATUS_10,
+ UP_INT_STATUS_11,
+ UP_INT_STATUS_12,
+ UP_INT_STATUS_13,
+ UP_INT_STATUS_14,
+ UP_INT_STATUS_15,
+ UP_INT_CLEAR_0, /* UPPER interrupt clear */
+ UP_INT_CLEAR_1,
+ UP_INT_CLEAR_2,
+ UP_INT_CLEAR_3,
+ UP_INT_CLEAR_4,
+ UP_INT_CLEAR_5,
+ UP_INT_CLEAR_6,
+ UP_INT_CLEAR_7,
+ UP_INT_CLEAR_8,
+ UP_INT_CLEAR_9,
+ UP_INT_CLEAR_10,
+ UP_INT_CLEAR_11,
+ UP_INT_CLEAR_12,
+ UP_INT_CLEAR_13,
+ UP_INT_CLEAR_14,
+ UP_INT_CLEAR_15,
+ UP_INT_MASK_0, /* UPPER interrupt mask */
+ UP_INT_MASK_1,
+ UP_INT_MASK_2,
+ UP_INT_MASK_3,
+ UP_INT_MASK_4,
+ UP_INT_MASK_5,
+ UP_INT_MASK_6,
+ UP_INT_MASK_7,
+ UP_INT_MASK_8,
+ UP_INT_MASK_9,
+ UP_INT_MASK_10,
+ UP_INT_MASK_11,
+ UP_INT_MASK_12,
+ UP_INT_MASK_13,
+ UP_INT_MASK_14,
+ UP_INT_MASK_15,
+ UP_THRESH_0, /* UPPER threshold values */
+ UP_THRESH_1,
+ UP_THRESH_2,
+ UP_THRESH_3,
+ UP_THRESH_4,
+ UP_THRESH_5,
+ UP_THRESH_6,
+ UP_THRESH_7,
+ UP_THRESH_8,
+ UP_THRESH_9,
+ UP_THRESH_10,
+ UP_THRESH_11,
+ UP_THRESH_12,
+ UP_THRESH_13,
+ UP_THRESH_14,
+ UP_THRESH_15,
+ CRITICAL_STATUS_0, /* CRITICAL threshold violated */
+ CRITICAL_STATUS_1,
+ CRITICAL_STATUS_2,
+ CRITICAL_STATUS_3,
+ CRITICAL_STATUS_4,
+ CRITICAL_STATUS_5,
+ CRITICAL_STATUS_6,
+ CRITICAL_STATUS_7,
+ CRITICAL_STATUS_8,
+ CRITICAL_STATUS_9,
+ CRITICAL_STATUS_10,
+ CRITICAL_STATUS_11,
+ CRITICAL_STATUS_12,
+ CRITICAL_STATUS_13,
+ CRITICAL_STATUS_14,
+ CRITICAL_STATUS_15,
+ CRIT_INT_STATUS_0, /* CRITICAL interrupt status */
+ CRIT_INT_STATUS_1,
+ CRIT_INT_STATUS_2,
+ CRIT_INT_STATUS_3,
+ CRIT_INT_STATUS_4,
+ CRIT_INT_STATUS_5,
+ CRIT_INT_STATUS_6,
+ CRIT_INT_STATUS_7,
+ CRIT_INT_STATUS_8,
+ CRIT_INT_STATUS_9,
+ CRIT_INT_STATUS_10,
+ CRIT_INT_STATUS_11,
+ CRIT_INT_STATUS_12,
+ CRIT_INT_STATUS_13,
+ CRIT_INT_STATUS_14,
+ CRIT_INT_STATUS_15,
+ CRIT_INT_CLEAR_0, /* CRITICAL interrupt clear */
+ CRIT_INT_CLEAR_1,
+ CRIT_INT_CLEAR_2,
+ CRIT_INT_CLEAR_3,
+ CRIT_INT_CLEAR_4,
+ CRIT_INT_CLEAR_5,
+ CRIT_INT_CLEAR_6,
+ CRIT_INT_CLEAR_7,
+ CRIT_INT_CLEAR_8,
+ CRIT_INT_CLEAR_9,
+ CRIT_INT_CLEAR_10,
+ CRIT_INT_CLEAR_11,
+ CRIT_INT_CLEAR_12,
+ CRIT_INT_CLEAR_13,
+ CRIT_INT_CLEAR_14,
+ CRIT_INT_CLEAR_15,
+ CRIT_INT_MASK_0, /* CRITICAL interrupt mask */
+ CRIT_INT_MASK_1,
+ CRIT_INT_MASK_2,
+ CRIT_INT_MASK_3,
+ CRIT_INT_MASK_4,
+ CRIT_INT_MASK_5,
+ CRIT_INT_MASK_6,
+ CRIT_INT_MASK_7,
+ CRIT_INT_MASK_8,
+ CRIT_INT_MASK_9,
+ CRIT_INT_MASK_10,
+ CRIT_INT_MASK_11,
+ CRIT_INT_MASK_12,
+ CRIT_INT_MASK_13,
+ CRIT_INT_MASK_14,
+ CRIT_INT_MASK_15,
+ CRIT_THRESH_0, /* CRITICAL threshold values */
+ CRIT_THRESH_1,
+ CRIT_THRESH_2,
+ CRIT_THRESH_3,
+ CRIT_THRESH_4,
+ CRIT_THRESH_5,
+ CRIT_THRESH_6,
+ CRIT_THRESH_7,
+ CRIT_THRESH_8,
+ CRIT_THRESH_9,
+ CRIT_THRESH_10,
+ CRIT_THRESH_11,
+ CRIT_THRESH_12,
+ CRIT_THRESH_13,
+ CRIT_THRESH_14,
+ CRIT_THRESH_15,
+
+ /* WATCHDOG */
+ WDOG_BARK_STATUS,
+ WDOG_BARK_CLEAR,
+ WDOG_BARK_MASK,
+ WDOG_BARK_COUNT,
+
+ /* CYCLE COMPLETION MONITOR */
+ CC_MON_STATUS,
+ CC_MON_CLEAR,
+ CC_MON_MASK,
+
+ MIN_STATUS_0, /* MIN threshold violated */
+ MIN_STATUS_1,
+ MIN_STATUS_2,
+ MIN_STATUS_3,
+ MIN_STATUS_4,
+ MIN_STATUS_5,
+ MIN_STATUS_6,
+ MIN_STATUS_7,
+ MIN_STATUS_8,
+ MIN_STATUS_9,
+ MIN_STATUS_10,
+ MIN_STATUS_11,
+ MIN_STATUS_12,
+ MIN_STATUS_13,
+ MIN_STATUS_14,
+ MIN_STATUS_15,
+ MAX_STATUS_0, /* MAX threshold violated */
+ MAX_STATUS_1,
+ MAX_STATUS_2,
+ MAX_STATUS_3,
+ MAX_STATUS_4,
+ MAX_STATUS_5,
+ MAX_STATUS_6,
+ MAX_STATUS_7,
+ MAX_STATUS_8,
+ MAX_STATUS_9,
+ MAX_STATUS_10,
+ MAX_STATUS_11,
+ MAX_STATUS_12,
+ MAX_STATUS_13,
+ MAX_STATUS_14,
+ MAX_STATUS_15,
+
+ /* Keep last */
+ MAX_REGFIELDS
+};
+
+/**
+ * struct tsens_features - Features supported by the IP
+ * @ver_major: Major number of IP version
+ * @crit_int: does the IP support critical interrupts?
+ * @adc: do the sensors only output adc code (instead of temperature)?
+ * @srot_split: does the IP neatly splits the register space into SROT and TM,
+ * with SROT only being available to secure boot firmware?
+ * @has_watchdog: does this IP support watchdog functionality?
+ * @max_sensors: maximum sensors supported by this version of the IP
+ */
+struct tsens_features {
+ unsigned int ver_major;
+ unsigned int crit_int:1;
+ unsigned int adc:1;
+ unsigned int srot_split:1;
+ unsigned int has_watchdog:1;
+ unsigned int max_sensors;
+};
+
+/**
+ * struct tsens_plat_data - tsens compile-time platform data
+ * @num_sensors: Number of sensors supported by platform
+ * @ops: operations the tsens instance supports
+ * @hw_ids: Subset of sensors ids supported by platform, if not the first n
+ * @feat: features of the IP
+ * @fields: bitfield locations
+ */
+struct tsens_plat_data {
+ const u32 num_sensors;
+ const struct tsens_ops *ops;
+ unsigned int *hw_ids;
+ struct tsens_features *feat;
+ const struct reg_field *fields;
+};
+
+/**
+ * struct tsens_context - Registers to be saved/restored across a context loss
+ * @threshold: Threshold register value
+ * @control: Control register value
+ */
+struct tsens_context {
+ int threshold;
+ int control;
+};
+
+/**
+ * struct tsens_priv - private data for each instance of the tsens IP
+ * @dev: pointer to struct device
+ * @num_sensors: number of sensors enabled on this device
+ * @tm_map: pointer to TM register address space
+ * @srot_map: pointer to SROT register address space
+ * @tm_offset: deal with old device trees that don't address TM and SROT
+ * address space separately
+ * @ul_lock: lock while processing upper/lower threshold interrupts
+ * @crit_lock: lock while processing critical threshold interrupts
+ * @rf: array of regmap_fields used to store value of the field
+ * @ctx: registers to be saved and restored during suspend/resume
+ * @feat: features of the IP
+ * @fields: bitfield locations
+ * @ops: pointer to list of callbacks supported by this device
+ * @debug_root: pointer to debugfs dentry for all tsens
+ * @debug: pointer to debugfs dentry for tsens controller
+ * @sensor: list of sensors attached to this device
+ */
+struct tsens_priv {
+ struct device *dev;
+ u32 num_sensors;
+ struct regmap *tm_map;
+ struct regmap *srot_map;
+ u32 tm_offset;
+
+ /* lock for upper/lower threshold interrupts */
+ spinlock_t ul_lock;
+
+ struct regmap_field *rf[MAX_REGFIELDS];
+ struct tsens_context ctx;
+ struct tsens_features *feat;
+ const struct reg_field *fields;
+ const struct tsens_ops *ops;
+
+ struct dentry *debug_root;
+ struct dentry *debug;
+
+ struct tsens_sensor sensor[];
+};
+
+char *qfprom_read(struct device *dev, const char *cname);
+void compute_intercept_slope(struct tsens_priv *priv, u32 *pt1, u32 *pt2, u32 mode);
+int init_common(struct tsens_priv *priv);
+int get_temp_tsens_valid(const struct tsens_sensor *s, int *temp);
+int get_temp_common(const struct tsens_sensor *s, int *temp);
+
+/* TSENS target */
+extern struct tsens_plat_data data_8960;
+
+/* TSENS v0.1 targets */
+extern struct tsens_plat_data data_8916, data_8939, data_8974, data_9607;
+
+/* TSENS v1 targets */
+extern struct tsens_plat_data data_tsens_v1, data_8976, data_8956;
+
+/* TSENS v2 targets */
+extern struct tsens_plat_data data_8996, data_tsens_v2;
+
+#endif /* __QCOM_TSENS_H__ */