Linux-IIO Archive on lore.kernel.org
 help / color / Atom feed
* [PATCH 0/5] qcom: pm8150: add support for thermal monitoring
@ 2020-06-21 19:35 Dmitry Baryshkov
  2020-06-21 19:35 ` [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings Dmitry Baryshkov
                   ` (4 more replies)
  0 siblings, 5 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-21 19:35 UTC (permalink / raw)
  To: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-arm-msm, devicetree, Vinod Koul, linux-iio

This patch serie adds support for thermal monitoring block on Qualcomm's
PMIC5 chips. PM8150{,b,l} and sm8250-mtp board device trees are extended
to support thermal zones provided by this thermal monitoring block.

-- 
With best wishes
Dmitry



^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings
  2020-06-21 19:35 [PATCH 0/5] qcom: pm8150: add support for thermal monitoring Dmitry Baryshkov
@ 2020-06-21 19:35 ` Dmitry Baryshkov
  2020-06-29 21:57   ` Rob Herring
  2020-06-21 19:35 ` [PATCH 2/5] iio: adc: qcom-vadc: move several adc5 functions to common file Dmitry Baryshkov
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-21 19:35 UTC (permalink / raw)
  To: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-arm-msm, devicetree, Vinod Koul, linux-iio

Add bindings for thermal monitor, part of Qualcomm PMIC5 chips. It is a
close counterpart of VADC part of those PMICs.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
 .../bindings/thermal/qcom-spmi-adc-tm5.yaml   | 143 ++++++++++++++++++
 1 file changed, 143 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml

diff --git a/Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml b/Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml
new file mode 100644
index 000000000000..16d3f61d692a
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml
@@ -0,0 +1,143 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/qcom-spmi-adc-tm5.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm's SPMI PMIC ADC-TM
+maintainers:
+  - Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+
+properties:
+  compatible:
+    const: qcom,spmi-adc-tm5
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  "#thermal-sensor-cells":
+    const: 1
+    description:
+      Number of cells required to uniquely identify the thermal sensors. Since
+      we have multiple sensors this is set to 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  io-channels:
+    description:
+      From common IIO binding. Used to pipe PMIC ADC channel to thermal monitor
+
+  io-channel-names:
+    description:
+      From common IIO binding. Names each of IIO channels. The name should
+      be equal to the sensor's subnode name.
+
+  qcom,avg-samples:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: Number of samples to be used for measurement.
+    enum:
+      - 1
+      - 2
+      - 4
+      - 8
+      - 16
+    default: 1
+
+  qcom,decimation:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: This parameter is used to decrease ADC sampling rate.
+    enum:
+      - 250
+      - 420
+      - 840
+    default: 840
+
+patternProperties:
+  "^([-a-z0-9]*)@[0-9]+$":
+    type: object
+    description:
+      Represent one thermal sensor.
+
+    properties:
+      reg:
+        description: Specify the sensor channel.
+        maxItems: 1
+
+      qcom,adc-channel:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description: Corresponding ADC channel ID.
+
+      qcom,ratiometric:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          Channel calibration type.
+          If this property is specified VADC will use the VDD reference
+          (1.875V) and GND for channel calibration. If property is not found,
+          channel will be calibrated with 0V and 1.25V reference channels,
+          also known as absolute calibration.
+
+      qcom,hw-settle-time:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description: Time between AMUX getting configured and the ADC starting conversion.
+
+      qcom,pre-scaling:
+        $ref: /schemas/types.yaml#/definitions/uint32-array
+        description: Used for scaling the channel input signal before the signal is fed to VADC. See qcom,spi-vadc specification for the list of possible values.
+        minItems: 2
+        maxItems: 2
+
+    required:
+      - reg
+      - qcom,adc-channel
+
+    additionalProperties:
+      false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - "#address-cells"
+  - "#size-cells"
+  - "#thermal-sensor-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+		pm8150b_adc: adc@3100 {
+			compatible = "qcom,spmi-adc5";
+			/* Other propreties are omitted */
+			conn-therm@4f {
+				reg = <ADC5_AMUX_THM3_100K_PU>;
+				qcom,ratiometric;
+				qcom,hw-settle-time = <200>;
+			};
+		};
+		pm8150b_adc_tm: adc-tm@3500 {
+			compatible = "qcom,spmi-adc-tm5";
+			reg = <0x3500>;
+			interrupts = <0x2 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
+			#thermal-sensor-cells = <1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			io-channels = <&pm8150b_adc ADC5_AMUX_THM3_100K_PU>;
+			io-channel-names = "conn-therm";
+
+			conn-therm@0 {
+				reg = <0>;
+				qcom,adc-channel = <ADC5_AMUX_THM3_100K_PU>;
+				qcom,ratiometric;
+				qcom,hw-settle-time = <200>;
+			};
+		};
+
+
+...
-- 
2.27.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 2/5] iio: adc: qcom-vadc: move several adc5 functions to common file
  2020-06-21 19:35 [PATCH 0/5] qcom: pm8150: add support for thermal monitoring Dmitry Baryshkov
  2020-06-21 19:35 ` [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings Dmitry Baryshkov
@ 2020-06-21 19:35 ` Dmitry Baryshkov
  2020-06-21 19:43   ` Bjorn Andersson
  2020-06-21 19:35 ` [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor Dmitry Baryshkov
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-21 19:35 UTC (permalink / raw)
  To: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-arm-msm, devicetree, Vinod Koul, linux-iio

ADC-TM5 driver will make use of several functions from ADC5 driver. Move
them to qcom-vadc-common driver.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
 drivers/iio/adc/qcom-spmi-adc5.c   | 73 +++---------------------------
 drivers/iio/adc/qcom-vadc-common.c | 69 +++++++++++++++++++++++++++-
 drivers/iio/adc/qcom-vadc-common.h | 12 ++++-
 3 files changed, 85 insertions(+), 69 deletions(-)

diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
index 21fdcde77883..10ca0bf77160 100644
--- a/drivers/iio/adc/qcom-spmi-adc5.c
+++ b/drivers/iio/adc/qcom-spmi-adc5.c
@@ -143,18 +143,6 @@ struct adc5_chip {
 	const struct adc5_data	*data;
 };
 
-static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
-	{.num =  1, .den =  1},
-	{.num =  1, .den =  3},
-	{.num =  1, .den =  4},
-	{.num =  1, .den =  6},
-	{.num =  1, .den = 20},
-	{.num =  1, .den =  8},
-	{.num = 10, .den = 81},
-	{.num =  1, .den = 10},
-	{.num =  1, .den = 16}
-};
-
 static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len)
 {
 	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
@@ -165,55 +153,6 @@ static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
 	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
 }
 
-static int adc5_prescaling_from_dt(u32 num, u32 den)
-{
-	unsigned int pre;
-
-	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
-		if (adc5_prescale_ratios[pre].num == num &&
-		    adc5_prescale_ratios[pre].den == den)
-			break;
-
-	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
-		return -EINVAL;
-
-	return pre;
-}
-
-static int adc5_hw_settle_time_from_dt(u32 value,
-					const unsigned int *hw_settle)
-{
-	unsigned int i;
-
-	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
-		if (value == hw_settle[i])
-			return i;
-	}
-
-	return -EINVAL;
-}
-
-static int adc5_avg_samples_from_dt(u32 value)
-{
-	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
-		return -EINVAL;
-
-	return __ffs(value);
-}
-
-static int adc5_decimation_from_dt(u32 value,
-					const unsigned int *decimation)
-{
-	unsigned int i;
-
-	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
-		if (value == decimation[i])
-			return i;
-	}
-
-	return -EINVAL;
-}
-
 static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
 {
 	int ret;
@@ -396,7 +335,7 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
 			return ret;
 
 		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
-			&adc5_prescale_ratios[prop->prescale],
+			prop->prescale,
 			adc->data,
 			adc_code_volt, val);
 		if (ret)
@@ -539,7 +478,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
 
 	ret = of_property_read_u32(node, "qcom,decimation", &value);
 	if (!ret) {
-		ret = adc5_decimation_from_dt(value, data->decimation);
+		ret = qcom_adc5_decimation_from_dt(value, data->decimation);
 		if (ret < 0) {
 			dev_err(dev, "%02x invalid decimation %d\n",
 				chan, value);
@@ -552,7 +491,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
 
 	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
 	if (!ret) {
-		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
+		ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
 		if (ret < 0) {
 			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
 				chan, varr[0], varr[1]);
@@ -580,10 +519,10 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
 		/* Digital controller >= 5.3 have hw_settle_2 option */
 		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
 			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
-			ret = adc5_hw_settle_time_from_dt(value,
+			ret = qcom_adc5_hw_settle_time_from_dt(value,
 							data->hw_settle_2);
 		else
-			ret = adc5_hw_settle_time_from_dt(value,
+			ret = qcom_adc5_hw_settle_time_from_dt(value,
 							data->hw_settle_1);
 
 		if (ret < 0) {
@@ -598,7 +537,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
 
 	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
 	if (!ret) {
-		ret = adc5_avg_samples_from_dt(value);
+		ret = qcom_adc5_avg_samples_from_dt(value);
 		if (ret < 0) {
 			dev_err(dev, "%02x invalid avg-samples %d\n",
 				chan, value);
diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
index 2bb78d1c4daa..ffa578ce76db 100644
--- a/drivers/iio/adc/qcom-vadc-common.c
+++ b/drivers/iio/adc/qcom-vadc-common.c
@@ -89,6 +89,18 @@ static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = {
 	{ 46,	125000 },
 };
 
+static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
+	{.num =  1, .den =  1},
+	{.num =  1, .den =  3},
+	{.num =  1, .den =  4},
+	{.num =  1, .den =  6},
+	{.num =  1, .den = 20},
+	{.num =  1, .den =  8},
+	{.num = 10, .den = 81},
+	{.num =  1, .den = 10},
+	{.num =  1, .den = 16}
+};
+
 static int qcom_vadc_scale_hw_calib_volt(
 				const struct vadc_prescale_ratio *prescale,
 				const struct adc5_data *data,
@@ -385,10 +397,12 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 EXPORT_SYMBOL(qcom_vadc_scale);
 
 int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
-		    const struct vadc_prescale_ratio *prescale,
+		    unsigned int prescale_ratio,
 		    const struct adc5_data *data,
 		    u16 adc_code, int *result)
 {
+	const struct vadc_prescale_ratio *prescale = &adc5_prescale_ratios[prescale_ratio];
+
 	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
 		scaletype < SCALE_HW_CALIB_INVALID)) {
 		pr_err("Invalid scale type %d\n", scaletype);
@@ -400,6 +414,59 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
 }
 EXPORT_SYMBOL(qcom_adc5_hw_scale);
 
+int qcom_adc5_prescaling_from_dt(u32 num, u32 den)
+{
+	unsigned int pre;
+
+	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
+		if (adc5_prescale_ratios[pre].num == num &&
+		    adc5_prescale_ratios[pre].den == den)
+			break;
+
+	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
+		return -EINVAL;
+
+	return pre;
+}
+EXPORT_SYMBOL(qcom_adc5_prescaling_from_dt);
+
+int qcom_adc5_hw_settle_time_from_dt(u32 value,
+				     const unsigned int *hw_settle)
+{
+	unsigned int i;
+
+	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
+		if (value == hw_settle[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL(qcom_adc5_hw_settle_time_from_dt);
+
+int qcom_adc5_avg_samples_from_dt(u32 value)
+{
+	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
+		return -EINVAL;
+
+	return __ffs(value);
+}
+EXPORT_SYMBOL(qcom_adc5_avg_samples_from_dt);
+
+int qcom_adc5_decimation_from_dt(u32 value,
+			    const unsigned int *decimation)
+{
+	unsigned int i;
+
+	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
+		if (value == decimation[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL(qcom_adc5_decimation_from_dt);
+
 int qcom_vadc_decimation_from_dt(u32 value)
 {
 	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
index e074902a24cc..2c65ddc98696 100644
--- a/drivers/iio/adc/qcom-vadc-common.h
+++ b/drivers/iio/adc/qcom-vadc-common.h
@@ -153,10 +153,20 @@ struct qcom_adc5_scale_type {
 };
 
 int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
-		    const struct vadc_prescale_ratio *prescale,
+		    unsigned int prescale_ratio,
 		    const struct adc5_data *data,
 		    u16 adc_code, int *result_mdec);
 
+int qcom_adc5_prescaling_from_dt(u32 num, u32 den);
+
+int qcom_adc5_hw_settle_time_from_dt(u32 value,
+		const unsigned int *hw_settle);
+
+int qcom_adc5_avg_samples_from_dt(u32 value);
+
+int qcom_adc5_decimation_from_dt(u32 value,
+			    const unsigned int *decimation);
+
 int qcom_vadc_decimation_from_dt(u32 value);
 
 #endif /* QCOM_VADC_COMMON_H */
-- 
2.27.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor
  2020-06-21 19:35 [PATCH 0/5] qcom: pm8150: add support for thermal monitoring Dmitry Baryshkov
  2020-06-21 19:35 ` [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings Dmitry Baryshkov
  2020-06-21 19:35 ` [PATCH 2/5] iio: adc: qcom-vadc: move several adc5 functions to common file Dmitry Baryshkov
@ 2020-06-21 19:35 ` Dmitry Baryshkov
  2020-06-22  5:39   ` Bjorn Andersson
  2020-06-27 14:42   ` Jonathan Cameron
  2020-06-21 19:35 ` [PATCH 4/5] arm64: dts: qcom: pm8150x: add definitions for adc-tm5 part Dmitry Baryshkov
  2020-06-21 19:35 ` [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5 Dmitry Baryshkov
  4 siblings, 2 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-21 19:35 UTC (permalink / raw)
  To: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-arm-msm, devicetree, Vinod Koul, linux-iio

Add support for Thermal Monitoring part of PMIC5. This part is closely
coupled with ADC, using it's channels directly. ADC-TM support
generating interrupts on ADC value crossing low or high voltage bounds,
which is used to support thermal trip points.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
 drivers/iio/adc/qcom-vadc-common.c       |  67 +++
 drivers/iio/adc/qcom-vadc-common.h       |   3 +
 drivers/thermal/qcom/Kconfig             |  11 +
 drivers/thermal/qcom/Makefile            |   1 +
 drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 617 +++++++++++++++++++++++
 5 files changed, 699 insertions(+)
 create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5.c

diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
index ffa578ce76db..e470beb8c6f9 100644
--- a/drivers/iio/adc/qcom-vadc-common.c
+++ b/drivers/iio/adc/qcom-vadc-common.c
@@ -176,6 +176,47 @@ static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
 	return 0;
 }
 
+static s32 qcom_vadc_map_temp_voltage(const struct vadc_map_pt *pts,
+				      u32 tablesize, int input)
+{
+	bool descending = 1;
+	u32 i = 0;
+
+	/* Check if table is descending or ascending */
+	if (tablesize > 1) {
+		if (pts[0].y < pts[1].y)
+			descending = 0;
+	}
+
+	while (i < tablesize) {
+		if ((descending) && (pts[i].y < input)) {
+			/* table entry is less than measured*/
+			 /* value and table is descending, stop */
+			break;
+		} else if ((!descending) &&
+				(pts[i].y > input)) {
+			/* table entry is greater than measured*/
+			/*value and table is ascending, stop */
+			break;
+		}
+		i++;
+	}
+
+	if (i == 0)
+		return pts[0].x;
+	if (i == tablesize)
+		return pts[tablesize - 1].x;
+
+	/* result is between search_index and search_index-1 */
+	/* interpolate linearly */
+	return (((s32)((pts[i].x - pts[i - 1].x) *
+		(input - pts[i - 1].y)) /
+		(pts[i].y - pts[i - 1].y)) +
+		pts[i - 1].x);
+
+	return 0;
+}
+
 static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph,
 				  u16 adc_code,
 				  bool absolute,
@@ -273,6 +314,19 @@ static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph,
 	return 0;
 }
 
+static u16 qcom_vadc_scale_voltage_code(int voltage,
+				const struct vadc_prescale_ratio *prescale,
+				const u32 full_scale_code_volt,
+				unsigned int factor)
+{
+	s64 volt = voltage, adc_vdd_ref_mv = 1875;
+
+	volt *= prescale->num * factor * full_scale_code_volt;
+	volt = div64_s64(volt, (s64)prescale->den * adc_vdd_ref_mv * 1000);
+
+	return volt;
+}
+
 static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
 				const struct vadc_prescale_ratio *prescale,
 				const struct adc5_data *data,
@@ -396,6 +450,19 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 }
 EXPORT_SYMBOL(qcom_vadc_scale);
 
+u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
+		u32 full_scale_code_volt, int temp)
+{
+	const struct vadc_prescale_ratio *prescale = &adc5_prescale_ratios[prescale_ratio];
+	s32 voltage;
+
+	voltage = qcom_vadc_map_temp_voltage(adcmap_100k_104ef_104fb_1875_vref,
+				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
+				 temp);
+	return qcom_vadc_scale_voltage_code(voltage, prescale, full_scale_code_volt, 1000);
+}
+EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale);
+
 int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
 		    unsigned int prescale_ratio,
 		    const struct adc5_data *data,
diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
index 2c65ddc98696..978820443d8a 100644
--- a/drivers/iio/adc/qcom-vadc-common.h
+++ b/drivers/iio/adc/qcom-vadc-common.h
@@ -157,6 +157,9 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
 		    const struct adc5_data *data,
 		    u16 adc_code, int *result_mdec);
 
+u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
+		u32 full_scale_code_volt, int temp);
+
 int qcom_adc5_prescaling_from_dt(u32 num, u32 den);
 
 int qcom_adc5_hw_settle_time_from_dt(u32 value,
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index aa9c1d80fae4..c61df55760e9 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -20,3 +20,14 @@ config QCOM_SPMI_TEMP_ALARM
 	  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_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.
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index ec86eef7f6a6..5b9445a3fd26 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -4,3 +4,4 @@ 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_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5)	+= qcom-spmi-adc-tm5.o
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 000000000000..ebfe0c9fbf96
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
@@ -0,0 +1,617 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2020 Linaro Limited
+ */
+
+#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 "../../iio/adc/qcom-vadc-common.h"
+
+#define ADC5_MAX_CHANNEL                        0xc0
+#define ADC_TM5_NUM_CHANNELS		8
+#define ADC_TM5_TIMER1			3 /* 3.9ms */
+#define ADC_TM5_TIMER2			10 /* 1 second */
+#define ADC_TM5_TIMER3			4 /* 4 second */
+
+#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			0x43
+#define ADC_TM5_FAST_AVG_EN			BIT(7)
+
+#define ADC_TM5_MEAS_INTERVAL_CTL		0x44
+
+#define ADC_TM5_MEAS_INTERVAL_CTL2		0x45
+#define ADC_TM5_MEAS_INTERVAL_CTL2_SHIFT		0x4
+#define ADC_TM5_MEAS_INTERVAL_CTL2_MASK		0xf0
+#define ADC_TM5_MEAS_INTERVAL_CTL3_MASK		0xf
+
+#define ADC_TM5_Mn_EN(n)			((n * 8) + 0x67)
+#define ADC_TM5_Mn_MEAS_EN			BIT(7)
+#define ADC_TM5_Mn_HIGH_THR_INT_EN		BIT(1)
+#define ADC_TM5_Mn_LOW_THR_INT_EN		BIT(0)
+
+#define ADC_TM5_Mn_ADC_CH_SEL_CTL(n)		((n * 8) + 0x60)
+#define ADC_TM5_Mn_LOW_THR0(n)			((n * 8) + 0x61)
+#define ADC_TM5_Mn_LOW_THR1(n)			((n * 8) + 0x62)
+#define ADC_TM5_Mn_HIGH_THR0(n)			((n * 8) + 0x63)
+#define ADC_TM5_Mn_HIGH_THR1(n)			((n * 8) + 0x64)
+#define ADC_TM5_Mn_MEAS_INTERVAL_CTL(n)		((n * 8) + 0x65)
+#define ADC_TM5_Mn_CTL(n)			((n * 8) + 0x66)
+#define ADC_TM5_CTL_HW_SETTLE_DELAY_MASK		0xf
+#define ADC_TM5_CTL_CAL_SEL			0x30
+#define ADC_TM5_CTL_CAL_SEL_MASK_SHIFT		4
+#define ADC_TM5_CTL_CAL_VAL			0x40
+
+enum adc5_timer_select {
+	ADC5_TIMER_SEL_1 = 0,
+	ADC5_TIMER_SEL_2,
+	ADC5_TIMER_SEL_3,
+	ADC5_TIMER_SEL_NONE,
+};
+
+struct adc_tm5_data {
+	const u32	full_scale_code_volt;
+	unsigned int	*decimation;
+	unsigned int	*hw_settle;
+};
+
+enum adc_tm5_cal_method {
+	ADC_TM5_NO_CAL = 0,
+	ADC_TM5_RATIOMETRIC_CAL,
+	ADC_TM5_ABSOLUTE_CAL
+};
+
+struct adc_tm5_chip;
+
+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;
+	struct iio_channel	*iio;
+	struct adc_tm5_chip	*chip;
+	struct thermal_zone_device *tzd;
+};
+
+struct adc_tm5_chip {
+	struct regmap		*regmap;
+	struct device		*dev;
+	struct adc_tm5_channel	*channels;
+	const struct adc_tm5_data	*data;
+	spinlock_t		reg_lock;
+	unsigned int		decimation;
+	unsigned int		avg_samples;
+	unsigned int		timer1;
+	unsigned int		timer2;
+	unsigned int		timer3;
+	unsigned int		nchannels;
+	u16			base;
+};
+
+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,
+					1, 2, 4, 8, 16, 32, 64, 128},
+};
+
+static const struct of_device_id adc_tm5_match_table[] = {
+	{
+		.compatible = "qcom,spmi-adc-tm5",
+		.data = &adc_tm5_data_pmic,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, adc_tm5_match_table);
+
+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 = 0, i = 0;
+	unsigned long flags;
+
+	ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, 1);
+	if (ret < 0) {
+		dev_err(chip->dev, "read status low failed with %d\n", ret);
+		return IRQ_HANDLED;
+	}
+
+	ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, 1);
+	if (ret < 0) {
+		dev_err(chip->dev, "read 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;
+
+		if (!chip->channels[i].tzd) {
+			dev_err(chip->dev, "thermal device not found\n");
+			continue;
+		}
+
+		spin_lock_irqsave(&chip->reg_lock, flags);
+		ret = adc_tm5_read(chip, ADC_TM5_Mn_EN(ch), &ctl, 1);
+		spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+		if (ret) {
+			dev_err(chip->dev, "ctl read failed with %d\n", ret);
+			goto fail;
+		}
+
+		if ((status_low & BIT(ch)) && (ctl & ADC_TM5_Mn_MEAS_EN)
+				&& (ctl & ADC_TM5_Mn_LOW_THR_INT_EN))
+			lower_set = true;
+
+		if ((status_high & BIT(ch)) && (ctl & ADC_TM5_Mn_MEAS_EN) &&
+					(ctl & ADC_TM5_Mn_HIGH_THR_INT_EN))
+			upper_set = true;
+fail:
+
+		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(void *data, int *temp)
+{
+	struct adc_tm5_channel *channel = data;
+	int ret, milli_celsius;
+
+	if (!channel || !channel->iio)
+		return -EINVAL;
+
+	ret = iio_read_channel_processed(channel->iio, &milli_celsius);
+	if (ret < 0)
+		return ret;
+
+	*temp = milli_celsius;
+
+	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_Mn_EN(channel->channel);
+
+	return adc_tm5_reg_update(chip, reg,
+			ADC_TM5_Mn_MEAS_EN | ADC_TM5_Mn_HIGH_THR_INT_EN | ADC_TM5_Mn_LOW_THR_INT_EN,
+			0);
+}
+
+static int adc_tm5_configure(struct adc_tm5_channel *channel, u8 *low_thr, u8 *high_thr)
+{
+	struct adc_tm5_chip *chip = channel->chip;
+	u8 buf[8], cal_method;
+	u16 reg = ADC_TM5_Mn_ADC_CH_SEL_CTL(channel->channel);
+	int ret = 0;
+
+	ret = adc_tm5_read(chip, reg, buf, 8);
+	if (ret < 0) {
+		dev_err(chip->dev, "block read failed with %d\n", ret);
+		return ret;
+	}
+
+	/* Update ADC channel select */
+	buf[0] = channel->adc_channel;
+
+	if (low_thr) {
+		buf[1] = low_thr[0];
+		buf[2] = low_thr[1];
+		buf[7] |= ADC_TM5_Mn_LOW_THR_INT_EN;
+	}
+
+	if (high_thr) {
+		buf[3] = high_thr[0];
+		buf[4] = high_thr[1];
+		buf[7] |= ADC_TM5_Mn_HIGH_THR_INT_EN;
+	}
+
+	/* Update timer select */
+	buf[5] = ADC5_TIMER_SEL_2;
+
+	/* Set calibration select, hw_settle delay */
+	cal_method = (u8) (channel->cal_method << ADC_TM5_CTL_CAL_SEL_MASK_SHIFT);
+	buf[6] &= (u8) ~ADC_TM5_CTL_HW_SETTLE_DELAY_MASK;
+	buf[6] |= (u8) channel->hw_settle_time;
+	buf[6] &= (u8) ~ADC_TM5_CTL_CAL_SEL;
+	buf[6] |= (u8) cal_method;
+
+	buf[7] |= ADC_TM5_Mn_MEAS_EN;
+
+	ret = adc_tm5_write(chip, reg, buf, 8);
+	if (ret < 0) {
+		dev_err(chip->dev, "buf write failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int adc_tm5_set_trip_temp(void *data,
+		int low_temp, int high_temp)
+{
+	struct adc_tm5_channel *channel = data;
+	struct adc_tm5_chip *chip;
+	u8 trip_high_thr[2], trip_low_thr[2];
+	u8 *trip_high_ptr = NULL, *trip_low_ptr = NULL;
+	int ret;
+	unsigned long flags;
+
+	if (!channel)
+		return -EINVAL;
+
+	dev_info(channel->chip->dev, "%d:low_temp(mdegC):%d, high_temp(mdegC):%d\n",
+			channel->channel, low_temp, high_temp);
+	chip = channel->chip;
+
+	/* Warm temperature corresponds to low voltage threshold */
+	if (high_temp != INT_MAX) {
+		u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+				chip->data->full_scale_code_volt, high_temp);
+
+		trip_low_thr[0] = adc_code & 0xff;
+		trip_low_thr[1] = adc_code >> 8;
+		trip_low_ptr = trip_low_thr;
+	}
+
+	/* Cool temperature corresponds to high voltage threshold */
+	if (low_temp != -INT_MAX) {
+		u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
+				chip->data->full_scale_code_volt, low_temp);
+
+		trip_high_thr[0] = adc_code & 0xff;
+		trip_high_thr[1] = adc_code >> 8;
+		trip_high_ptr = trip_high_thr;
+	}
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (high_temp == INT_MAX && low_temp == INT_MIN)
+		ret = adc_tm5_disable_channel(channel);
+	else
+		ret = adc_tm5_configure(channel, trip_low_ptr, trip_high_ptr);
+
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+	return ret;
+}
+
+
+static struct thermal_zone_of_device_ops adc_tm5_ops = {
+	.get_temp = adc_tm5_get_temp,
+	.set_trips = adc_tm5_set_trip_temp,
+};
+
+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_zone_of_sensor_register(
+				adc_tm->dev, adc_tm->channels[i].channel,
+				&adc_tm->channels[i], &adc_tm5_ops);
+		if (IS_ERR(tzd)) {
+			dev_err(adc_tm->dev, "Error registering TZ zone:%ld for channel:%d\n",
+				PTR_ERR(tzd), adc_tm->channels[i].channel);
+			continue;
+		}
+		adc_tm->channels[i].tzd = tzd;
+	}
+
+	return 0;
+}
+
+static int adc_tm5_init(struct adc_tm5_chip *chip)
+{
+	u8 buf[4], channels_available, meas_int_timer_2_3 = 0;
+	int ret;
+	unsigned int i;
+
+	ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM, &channels_available, 1);
+	if (ret < 0) {
+		dev_err(chip->dev, "read failed for BTM channels\n");
+		return ret;
+	}
+
+	ret = adc_tm5_read(chip, ADC_TM5_ADC_DIG_PARAM, buf, 4);
+	if (ret < 0) {
+		dev_err(chip->dev, "block read failed with %d\n", ret);
+		return ret;
+	}
+
+	/* Select decimation */
+	buf[0] = chip->decimation;
+
+	/* Select number of samples in fast average mode */
+	buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
+
+	/* Select timer1 */
+	buf[2] = chip->timer1;
+
+	/* Select timer2 and timer3 */
+	meas_int_timer_2_3 |= chip->timer2 <<
+				ADC_TM5_MEAS_INTERVAL_CTL2_SHIFT;
+	meas_int_timer_2_3 |= chip->timer3;
+	buf[3] = meas_int_timer_2_3;
+
+	ret = adc_tm5_write(chip,
+			ADC_TM5_ADC_DIG_PARAM, buf, 4);
+	if (ret < 0)
+		dev_err(chip->dev, "block write failed with %d\n", 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;
+		}
+	}
+
+	return ret;
+}
+
+static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
+				       struct adc_tm5_channel *prop,
+				       struct device_node *node,
+				       const struct adc_tm5_data *data)
+{
+	const char *name = node->name;
+	u32 chan, value, varr[2];
+	int ret;
+	struct device *dev = adc_tm->dev;
+
+	ret = of_property_read_u32(node, "reg", &chan);
+	if (ret) {
+		dev_err(dev, "invalid channel number %s\n", name);
+		return ret;
+	}
+
+	if (chan >= ADC_TM5_NUM_CHANNELS) {
+		dev_err(dev, "%s invalid channel number %d\n", name, chan);
+		return -EINVAL;
+	}
+
+	/* the channel has DT description */
+	prop->channel = chan;
+
+	ret = of_property_read_u32(node, "qcom,adc-channel", &chan);
+	if (ret) {
+		dev_err(dev, "invalid channel number %s\n", name);
+		return ret;
+	}
+	if (chan >= ADC5_MAX_CHANNEL) {
+		dev_err(dev, "%s invalid ADC channel number %d\n", name, chan);
+		return ret;
+	}
+	prop->adc_channel = chan;
+
+	prop->iio = devm_iio_channel_get(adc_tm->dev, name);
+	if (IS_ERR(prop->iio)) {
+		ret = PTR_ERR(prop->iio);
+		prop->iio = NULL;
+		dev_err(dev, "error getting channel %s: %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, "%02x invalid pre-scaling <%d %d>\n",
+				chan, varr[0], varr[1]);
+			return ret;
+		}
+		prop->prescale = ret;
+	} else {
+		prop->prescale = 0; /*1:1 is index 0 */
+	}
+
+	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
+	if (!ret) {
+		ret = qcom_adc5_hw_settle_time_from_dt(value,
+							data->hw_settle);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
+				chan, value);
+			return ret;
+		}
+		prop->hw_settle_time = ret;
+	} else {
+		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
+	}
+
+	if (of_property_read_bool(node, "qcom,ratiometric"))
+		prop->cal_method = ADC_TM5_RATIOMETRIC_CAL;
+	else
+		prop->cal_method = ADC_TM5_ABSOLUTE_CAL;
+
+	dev_dbg(dev, "%02x name %s\n", chan, name);
+
+	return 0;
+}
+
+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;
+	unsigned int index = 0;
+	const struct of_device_id *id;
+	const struct adc_tm5_data *data;
+	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(adc_tm->dev, adc_tm->nchannels,
+					sizeof(*adc_tm->channels), GFP_KERNEL);
+	if (!adc_tm->channels)
+		return -ENOMEM;
+
+	channels = adc_tm->channels;
+
+	id = of_match_node(adc_tm5_match_table, node);
+	if (id)
+		data = id->data;
+	else
+		data = &adc_tm5_data_pmic;
+	adc_tm->data = data;
+
+	ret = of_property_read_u32(node, "qcom,decimation", &value);
+	if (!ret) {
+		ret = qcom_adc5_decimation_from_dt(value, 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;
+	}
+
+	adc_tm->timer1 = ADC_TM5_TIMER1;
+	adc_tm->timer2 = ADC_TM5_TIMER2;
+	adc_tm->timer3 = ADC_TM5_TIMER3;
+
+	for_each_available_child_of_node(node, child) {
+		ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child, data);
+		if (ret) {
+			of_node_put(child);
+			return ret;
+		}
+
+		channels++;
+		index++;
+	}
+
+	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 < 0)
+		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;
+	spin_lock_init(&adc_tm->reg_lock);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "get_irq failed: %d\n", irq);
+		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_tm5_init(adc_tm);
+	if (ret) {
+		dev_err(dev, "adc-tm init failed\n");
+		return ret;
+	}
+
+	ret = adc_tm5_register_tzd(adc_tm);
+
+	ret = devm_request_irq(dev, irq, adc_tm5_isr, 0,
+			       "pm-adc-tm5", adc_tm);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, adc_tm);
+
+	return 0;
+}
+
+static int adc_tm5_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct platform_driver adc_tm5_driver = {
+	.driver = {
+		.name = "spmi-adc-tm5",
+		.of_match_table = adc_tm5_match_table,
+	},
+	.probe = adc_tm5_probe,
+	.remove = adc_tm5_remove,
+};
+module_platform_driver(adc_tm5_driver);
+
+MODULE_ALIAS("platform:spmi-adc-tm5");
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL v2");
-- 
2.27.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 4/5] arm64: dts: qcom: pm8150x: add definitions for adc-tm5 part
  2020-06-21 19:35 [PATCH 0/5] qcom: pm8150: add support for thermal monitoring Dmitry Baryshkov
                   ` (2 preceding siblings ...)
  2020-06-21 19:35 ` [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor Dmitry Baryshkov
@ 2020-06-21 19:35 ` Dmitry Baryshkov
  2020-06-21 19:35 ` [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5 Dmitry Baryshkov
  4 siblings, 0 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-21 19:35 UTC (permalink / raw)
  To: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-arm-msm, devicetree, Vinod Koul, linux-iio

Define adc-tm5 thermal monitoring part. Individual channes and thermal
zones are to be configured in per-device dts files.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
 arch/arm64/boot/dts/qcom/pm8150.dtsi  | 10 ++++++++++
 arch/arm64/boot/dts/qcom/pm8150b.dtsi | 10 ++++++++++
 arch/arm64/boot/dts/qcom/pm8150l.dtsi | 10 ++++++++++
 3 files changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/pm8150.dtsi b/arch/arm64/boot/dts/qcom/pm8150.dtsi
index 027f55d53584..acb2cb02a73b 100644
--- a/arch/arm64/boot/dts/qcom/pm8150.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8150.dtsi
@@ -97,6 +97,16 @@ die-temp@6 {
 			};
 		};
 
+		pm8150_adc_tm: adc-tm@3500 {
+			compatible = "qcom,spmi-adc-tm5";
+			reg = <0x3500>;
+			interrupts = <0x0 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
+			#thermal-sensor-cells = <1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		rtc@6000 {
 			compatible = "qcom,pm8941-rtc";
 			reg = <0x6000>;
diff --git a/arch/arm64/boot/dts/qcom/pm8150b.dtsi b/arch/arm64/boot/dts/qcom/pm8150b.dtsi
index e112e8876db6..8e2f3250c914 100644
--- a/arch/arm64/boot/dts/qcom/pm8150b.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8150b.dtsi
@@ -95,6 +95,16 @@ chg-temp@9 {
 			};
 		};
 
+		pm8150b_adc_tm: adc-tm@3500 {
+			compatible = "qcom,spmi-adc-tm5";
+			reg = <0x3500>;
+			interrupts = <0x2 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
+			#thermal-sensor-cells = <1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		pm8150b_gpios: gpio@c000 {
 			compatible = "qcom,pm8150b-gpio";
 			reg = <0xc000>;
diff --git a/arch/arm64/boot/dts/qcom/pm8150l.dtsi b/arch/arm64/boot/dts/qcom/pm8150l.dtsi
index 62139538b7d9..9f214ceec2b7 100644
--- a/arch/arm64/boot/dts/qcom/pm8150l.dtsi
+++ b/arch/arm64/boot/dts/qcom/pm8150l.dtsi
@@ -89,6 +89,16 @@ die-temp@6 {
 			};
 		};
 
+		pm8150l_adc_tm: adc-tm@3500 {
+			compatible = "qcom,spmi-adc-tm5";
+			reg = <0x3500>;
+			interrupts = <0x4 0x35 0x0 IRQ_TYPE_EDGE_RISING>;
+			#thermal-sensor-cells = <1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		pm8150l_gpios: gpio@c000 {
 			compatible = "qcom,pm8150l-gpio";
 			reg = <0xc000>;
-- 
2.27.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5
  2020-06-21 19:35 [PATCH 0/5] qcom: pm8150: add support for thermal monitoring Dmitry Baryshkov
                   ` (3 preceding siblings ...)
  2020-06-21 19:35 ` [PATCH 4/5] arm64: dts: qcom: pm8150x: add definitions for adc-tm5 part Dmitry Baryshkov
@ 2020-06-21 19:35 ` Dmitry Baryshkov
  2020-06-30  5:06   ` Amit Kucheria
  4 siblings, 1 reply; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-21 19:35 UTC (permalink / raw)
  To: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler
  Cc: linux-arm-msm, devicetree, Vinod Koul, linux-iio

Port thermal zones definitions from msm-4.19 tree. Enable and add
channel configuration to PMIC's ADC-TM definitions. Declare thermal
zones and respective trip points.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
---
 arch/arm64/boot/dts/qcom/sm8250-mtp.dts | 237 ++++++++++++++++++++++++
 1 file changed, 237 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
index aa37eb112d85..78f0cf582a9a 100644
--- a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
+++ b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
@@ -24,6 +24,104 @@ chosen {
 		stdout-path = "serial0:115200n8";
 	};
 
+	thermal-zones {
+		xo-therm {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150_adc_tm 0>;
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+
+		skin-therm {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150_adc_tm 1>;
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+
+		mmw-pa1 {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150_adc_tm 2>;
+
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+
+		conn-therm {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150b_adc_tm 0>;
+
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+
+		camera-therm {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150l_adc_tm 0>;
+
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+
+		skin-msm-therm {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150l_adc_tm 1>;
+
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+
+		mmw-pa2 {
+			polling-delay-passive = <0>;
+			polling-delay = <0>;
+			thermal-sensors = <&pm8150l_adc_tm 2>;
+
+			trips {
+				active-config0 {
+					temperature = <125000>;
+					hysteresis = <1000>;
+					type = "passive";
+				};
+			};
+		};
+	};
+
 	vph_pwr: vph-pwr-regulator {
 		compatible = "regulator-fixed";
 		regulator-name = "vph_pwr";
@@ -355,6 +453,145 @@ vreg_l7f_1p8: ldo7 {
 	};
 };
 
+&pm8150_adc {
+	xo-therm@4c {
+		reg = <ADC5_XO_THERM_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	skin_therm@4d {
+		reg = <ADC5_AMUX_THM1_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	pa_therm1@4e {
+		reg = <ADC5_AMUX_THM2_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
+&pm8150_adc {
+	xo-therm@4c {
+		reg = <ADC5_XO_THERM_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	skin-therm@4d {
+		reg = <ADC5_AMUX_THM1_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	pa-therm1@4e {
+		reg = <ADC5_AMUX_THM2_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
+&pm8150b_adc {
+	conn-therm@4f {
+		reg = <ADC5_AMUX_THM3_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
+&pm8150l_adc {
+	camera_flash_therm@4d {
+		reg = <ADC5_AMUX_THM1_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	skin_msm_therm@4e {
+		reg = <ADC5_AMUX_THM2_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	pa_therm2@4f {
+		reg = <ADC5_AMUX_THM3_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
+&pm8150_adc_tm {
+	status = "okay";
+	io-channels = <&pm8150_adc ADC5_XO_THERM_100K_PU>,
+			<&pm8150_adc ADC5_AMUX_THM1_100K_PU>,
+			<&pm8150_adc ADC5_AMUX_THM2_100K_PU>;
+	io-channel-names = "xo-therm", "skin-therm", "pa-therm1";
+
+	xo-therm@0 {
+		reg = <0>;
+		qcom,adc-channel = <ADC5_XO_THERM_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	skin-therm@1 {
+		reg = <1>;
+		qcom,adc-channel = <ADC5_AMUX_THM1_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	pa-therm1@2 {
+		reg = <2>;
+		qcom,adc-channel = <ADC5_AMUX_THM2_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
+&pm8150b_adc_tm {
+	status = "okay";
+	io-channels = <&pm8150b_adc ADC5_AMUX_THM3_100K_PU>;
+	io-channel-names = "conn-therm";
+
+	conn-therm@0 {
+		reg = <0>;
+		qcom,adc-channel = <ADC5_AMUX_THM3_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
+&pm8150l_adc_tm {
+	status = "okay";
+	io-channels = <&pm8150l_adc ADC5_AMUX_THM1_100K_PU>,
+			<&pm8150l_adc ADC5_AMUX_THM2_100K_PU>,
+			<&pm8150l_adc ADC5_AMUX_THM3_100K_PU>;
+	io-channel-names = "camera-flash-therm", "skin-msm-therm", "pa-therm2";
+
+	camera-flash-therm@0 {
+		reg = <0>;
+		qcom,adc-channel = <ADC5_AMUX_THM1_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	skin-msm-therm@1 {
+		reg = <1>;
+		qcom,adc-channel = <ADC5_AMUX_THM2_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+
+	pa-therm2@2 {
+		reg = <2>;
+		qcom,adc-channel = <ADC5_AMUX_THM3_100K_PU>;
+		qcom,ratiometric;
+		qcom,hw-settle-time = <200>;
+	};
+};
+
 &qupv3_id_1 {
 	status = "okay";
 };
-- 
2.27.0


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 2/5] iio: adc: qcom-vadc: move several adc5 functions to common file
  2020-06-21 19:35 ` [PATCH 2/5] iio: adc: qcom-vadc: move several adc5 functions to common file Dmitry Baryshkov
@ 2020-06-21 19:43   ` Bjorn Andersson
  0 siblings, 0 replies; 14+ messages in thread
From: Bjorn Andersson @ 2020-06-21 19:43 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Andy Gross, Rob Herring, Zhang Rui, Daniel Lezcano,
	Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	devicetree, Vinod Koul, linux-iio

On Sun 21 Jun 12:35 PDT 2020, Dmitry Baryshkov wrote:

> ADC-TM5 driver will make use of several functions from ADC5 driver. Move
> them to qcom-vadc-common driver.
> 
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>

Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>

Regards,
Bjorn

> ---
>  drivers/iio/adc/qcom-spmi-adc5.c   | 73 +++---------------------------
>  drivers/iio/adc/qcom-vadc-common.c | 69 +++++++++++++++++++++++++++-
>  drivers/iio/adc/qcom-vadc-common.h | 12 ++++-
>  3 files changed, 85 insertions(+), 69 deletions(-)
> 
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> index 21fdcde77883..10ca0bf77160 100644
> --- a/drivers/iio/adc/qcom-spmi-adc5.c
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -143,18 +143,6 @@ struct adc5_chip {
>  	const struct adc5_data	*data;
>  };
>  
> -static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
> -	{.num =  1, .den =  1},
> -	{.num =  1, .den =  3},
> -	{.num =  1, .den =  4},
> -	{.num =  1, .den =  6},
> -	{.num =  1, .den = 20},
> -	{.num =  1, .den =  8},
> -	{.num = 10, .den = 81},
> -	{.num =  1, .den = 10},
> -	{.num =  1, .den = 16}
> -};
> -
>  static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len)
>  {
>  	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
> @@ -165,55 +153,6 @@ static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
>  	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
>  }
>  
> -static int adc5_prescaling_from_dt(u32 num, u32 den)
> -{
> -	unsigned int pre;
> -
> -	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
> -		if (adc5_prescale_ratios[pre].num == num &&
> -		    adc5_prescale_ratios[pre].den == den)
> -			break;
> -
> -	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
> -		return -EINVAL;
> -
> -	return pre;
> -}
> -
> -static int adc5_hw_settle_time_from_dt(u32 value,
> -					const unsigned int *hw_settle)
> -{
> -	unsigned int i;
> -
> -	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
> -		if (value == hw_settle[i])
> -			return i;
> -	}
> -
> -	return -EINVAL;
> -}
> -
> -static int adc5_avg_samples_from_dt(u32 value)
> -{
> -	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
> -		return -EINVAL;
> -
> -	return __ffs(value);
> -}
> -
> -static int adc5_decimation_from_dt(u32 value,
> -					const unsigned int *decimation)
> -{
> -	unsigned int i;
> -
> -	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
> -		if (value == decimation[i])
> -			return i;
> -	}
> -
> -	return -EINVAL;
> -}
> -
>  static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
>  {
>  	int ret;
> @@ -396,7 +335,7 @@ static int adc5_read_raw(struct iio_dev *indio_dev,
>  			return ret;
>  
>  		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
> -			&adc5_prescale_ratios[prop->prescale],
> +			prop->prescale,
>  			adc->data,
>  			adc_code_volt, val);
>  		if (ret)
> @@ -539,7 +478,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  
>  	ret = of_property_read_u32(node, "qcom,decimation", &value);
>  	if (!ret) {
> -		ret = adc5_decimation_from_dt(value, data->decimation);
> +		ret = qcom_adc5_decimation_from_dt(value, data->decimation);
>  		if (ret < 0) {
>  			dev_err(dev, "%02x invalid decimation %d\n",
>  				chan, value);
> @@ -552,7 +491,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  
>  	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
>  	if (!ret) {
> -		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
> +		ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
>  		if (ret < 0) {
>  			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
>  				chan, varr[0], varr[1]);
> @@ -580,10 +519,10 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  		/* Digital controller >= 5.3 have hw_settle_2 option */
>  		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
>  			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
> -			ret = adc5_hw_settle_time_from_dt(value,
> +			ret = qcom_adc5_hw_settle_time_from_dt(value,
>  							data->hw_settle_2);
>  		else
> -			ret = adc5_hw_settle_time_from_dt(value,
> +			ret = qcom_adc5_hw_settle_time_from_dt(value,
>  							data->hw_settle_1);
>  
>  		if (ret < 0) {
> @@ -598,7 +537,7 @@ static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>  
>  	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
>  	if (!ret) {
> -		ret = adc5_avg_samples_from_dt(value);
> +		ret = qcom_adc5_avg_samples_from_dt(value);
>  		if (ret < 0) {
>  			dev_err(dev, "%02x invalid avg-samples %d\n",
>  				chan, value);
> diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
> index 2bb78d1c4daa..ffa578ce76db 100644
> --- a/drivers/iio/adc/qcom-vadc-common.c
> +++ b/drivers/iio/adc/qcom-vadc-common.c
> @@ -89,6 +89,18 @@ static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = {
>  	{ 46,	125000 },
>  };
>  
> +static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
> +	{.num =  1, .den =  1},
> +	{.num =  1, .den =  3},
> +	{.num =  1, .den =  4},
> +	{.num =  1, .den =  6},
> +	{.num =  1, .den = 20},
> +	{.num =  1, .den =  8},
> +	{.num = 10, .den = 81},
> +	{.num =  1, .den = 10},
> +	{.num =  1, .den = 16}
> +};
> +
>  static int qcom_vadc_scale_hw_calib_volt(
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
> @@ -385,10 +397,12 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>  EXPORT_SYMBOL(qcom_vadc_scale);
>  
>  int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
> -		    const struct vadc_prescale_ratio *prescale,
> +		    unsigned int prescale_ratio,
>  		    const struct adc5_data *data,
>  		    u16 adc_code, int *result)
>  {
> +	const struct vadc_prescale_ratio *prescale = &adc5_prescale_ratios[prescale_ratio];
> +
>  	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
>  		scaletype < SCALE_HW_CALIB_INVALID)) {
>  		pr_err("Invalid scale type %d\n", scaletype);
> @@ -400,6 +414,59 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
>  }
>  EXPORT_SYMBOL(qcom_adc5_hw_scale);
>  
> +int qcom_adc5_prescaling_from_dt(u32 num, u32 den)
> +{
> +	unsigned int pre;
> +
> +	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
> +		if (adc5_prescale_ratios[pre].num == num &&
> +		    adc5_prescale_ratios[pre].den == den)
> +			break;
> +
> +	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
> +		return -EINVAL;
> +
> +	return pre;
> +}
> +EXPORT_SYMBOL(qcom_adc5_prescaling_from_dt);
> +
> +int qcom_adc5_hw_settle_time_from_dt(u32 value,
> +				     const unsigned int *hw_settle)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
> +		if (value == hw_settle[i])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +EXPORT_SYMBOL(qcom_adc5_hw_settle_time_from_dt);
> +
> +int qcom_adc5_avg_samples_from_dt(u32 value)
> +{
> +	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
> +		return -EINVAL;
> +
> +	return __ffs(value);
> +}
> +EXPORT_SYMBOL(qcom_adc5_avg_samples_from_dt);
> +
> +int qcom_adc5_decimation_from_dt(u32 value,
> +			    const unsigned int *decimation)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
> +		if (value == decimation[i])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +EXPORT_SYMBOL(qcom_adc5_decimation_from_dt);
> +
>  int qcom_vadc_decimation_from_dt(u32 value)
>  {
>  	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
> diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
> index e074902a24cc..2c65ddc98696 100644
> --- a/drivers/iio/adc/qcom-vadc-common.h
> +++ b/drivers/iio/adc/qcom-vadc-common.h
> @@ -153,10 +153,20 @@ struct qcom_adc5_scale_type {
>  };
>  
>  int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
> -		    const struct vadc_prescale_ratio *prescale,
> +		    unsigned int prescale_ratio,
>  		    const struct adc5_data *data,
>  		    u16 adc_code, int *result_mdec);
>  
> +int qcom_adc5_prescaling_from_dt(u32 num, u32 den);
> +
> +int qcom_adc5_hw_settle_time_from_dt(u32 value,
> +		const unsigned int *hw_settle);
> +
> +int qcom_adc5_avg_samples_from_dt(u32 value);
> +
> +int qcom_adc5_decimation_from_dt(u32 value,
> +			    const unsigned int *decimation);
> +
>  int qcom_vadc_decimation_from_dt(u32 value);
>  
>  #endif /* QCOM_VADC_COMMON_H */
> -- 
> 2.27.0
> 

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor
  2020-06-21 19:35 ` [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor Dmitry Baryshkov
@ 2020-06-22  5:39   ` Bjorn Andersson
  2020-06-27 14:42   ` Jonathan Cameron
  1 sibling, 0 replies; 14+ messages in thread
From: Bjorn Andersson @ 2020-06-22  5:39 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Andy Gross, Rob Herring, Zhang Rui, Daniel Lezcano,
	Amit Kucheria, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	devicetree, Vinod Koul, linux-iio

On Sun 21 Jun 12:35 PDT 2020, Dmitry Baryshkov wrote:
> diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
> index ffa578ce76db..e470beb8c6f9 100644
> --- a/drivers/iio/adc/qcom-vadc-common.c
> +++ b/drivers/iio/adc/qcom-vadc-common.c
> @@ -176,6 +176,47 @@ static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
>  	return 0;
>  }
>  
> +static s32 qcom_vadc_map_temp_voltage(const struct vadc_map_pt *pts,
> +				      u32 tablesize, int input)
> +{
> +	bool descending = 1;
> +	u32 i = 0;
> +
> +	/* Check if table is descending or ascending */
> +	if (tablesize > 1) {
> +		if (pts[0].y < pts[1].y)
> +			descending = 0;
> +	}
> +
> +	while (i < tablesize) {
> +		if ((descending) && (pts[i].y < input)) {
> +			/* table entry is less than measured*/
> +			 /* value and table is descending, stop */
> +			break;
> +		} else if ((!descending) &&
> +				(pts[i].y > input)) {
> +			/* table entry is greater than measured*/
> +			/*value and table is ascending, stop */
> +			break;
> +		}
> +		i++;
> +	}
> +
> +	if (i == 0)
> +		return pts[0].x;
> +	if (i == tablesize)
> +		return pts[tablesize - 1].x;
> +
> +	/* result is between search_index and search_index-1 */
> +	/* interpolate linearly */
> +	return (((s32)((pts[i].x - pts[i - 1].x) *
> +		(input - pts[i - 1].y)) /
> +		(pts[i].y - pts[i - 1].y)) +
> +		pts[i - 1].x);

I was convinced that we had a function to do this, but it seems like it
was never merged. So how about doing the community a favor by adding and
using Craig's patch from:

https://lore.kernel.org/linux-arm-msm/20180607181306.9766-1-ctatlor97@gmail.com/

> +
> +	return 0;
> +}
[..]
> diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
> index aa9c1d80fae4..c61df55760e9 100644
> --- a/drivers/thermal/qcom/Kconfig
> +++ b/drivers/thermal/qcom/Kconfig
> @@ -20,3 +20,14 @@ config QCOM_SPMI_TEMP_ALARM
>  	  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_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.
> diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
> index ec86eef7f6a6..5b9445a3fd26 100644
> --- a/drivers/thermal/qcom/Makefile
> +++ b/drivers/thermal/qcom/Makefile
> @@ -4,3 +4,4 @@ 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_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
> +obj-$(CONFIG_QCOM_SPMI_ADC_TM5)	+= qcom-spmi-adc-tm5.o

Please sort Kconfig and Makefile entries alphabetically whenever
possible.

> diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
[..]
> +static const struct of_device_id adc_tm5_match_table[] = {
> +	{
> +		.compatible = "qcom,spmi-adc-tm5",
> +		.data = &adc_tm5_data_pmic,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, adc_tm5_match_table);

Please move the match table next to the platform_driver definition and
use of_device_get_match_data() to get the data without having to
reference this array directly.

> +
> +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 = 0, i = 0;
> +	unsigned long flags;
> +
> +	ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, 1);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "read status low failed with %d\n", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, 1);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "read 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;
> +
> +		if (!chip->channels[i].tzd) {
> +			dev_err(chip->dev, "thermal device not found\n");

Afaict this would happen only if devm_thermal_zone_of_sensor_register()
failed for the particular channel, in which case you already have an
error print and probably don't want a dev_err() for every isr.

So I think you should drop this print.

> +			continue;
> +		}
> +
> +		spin_lock_irqsave(&chip->reg_lock, flags);

Why is this read done under a spinlock?

> +		ret = adc_tm5_read(chip, ADC_TM5_Mn_EN(ch), &ctl, 1);
> +		spin_unlock_irqrestore(&chip->reg_lock, flags);
> +
> +		if (ret) {
> +			dev_err(chip->dev, "ctl read failed with %d\n", ret);
> +			goto fail;

Your goto fail will arrive with both upper_set and lower_set false, and
hence this is simply a "continue".

> +		}
> +
> +		if ((status_low & BIT(ch)) && (ctl & ADC_TM5_Mn_MEAS_EN)
> +				&& (ctl & ADC_TM5_Mn_LOW_THR_INT_EN))
> +			lower_set = true;
> +
> +		if ((status_high & BIT(ch)) && (ctl & ADC_TM5_Mn_MEAS_EN) &&
> +					(ctl & ADC_TM5_Mn_HIGH_THR_INT_EN))
> +			upper_set = true;
> +fail:
> +
> +		if (upper_set || lower_set)
> +			thermal_zone_device_update(chip->channels[i].tzd,
> +					THERMAL_EVENT_UNSPECIFIED);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
[..]
> +static int adc_tm5_set_trip_temp(void *data,
> +		int low_temp, int high_temp)
> +{
> +	struct adc_tm5_channel *channel = data;
> +	struct adc_tm5_chip *chip;
> +	u8 trip_high_thr[2], trip_low_thr[2];
> +	u8 *trip_high_ptr = NULL, *trip_low_ptr = NULL;
> +	int ret;
> +	unsigned long flags;
> +
> +	if (!channel)
> +		return -EINVAL;
> +
> +	dev_info(channel->chip->dev, "%d:low_temp(mdegC):%d, high_temp(mdegC):%d\n",
> +			channel->channel, low_temp, high_temp);
> +	chip = channel->chip;
> +
> +	/* Warm temperature corresponds to low voltage threshold */
> +	if (high_temp != INT_MAX) {
> +		u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
> +				chip->data->full_scale_code_volt, high_temp);
> +
> +		trip_low_thr[0] = adc_code & 0xff;
> +		trip_low_thr[1] = adc_code >> 8;
> +		trip_low_ptr = trip_low_thr;
> +	}
> +
> +	/* Cool temperature corresponds to high voltage threshold */
> +	if (low_temp != -INT_MAX) {
> +		u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
> +				chip->data->full_scale_code_volt, low_temp);
> +
> +		trip_high_thr[0] = adc_code & 0xff;
> +		trip_high_thr[1] = adc_code >> 8;
> +		trip_high_ptr = trip_high_thr;
> +	}
> +
> +	spin_lock_irqsave(&chip->reg_lock, flags);

Afaict set_trips() is called only under the tz's mutex and as such
there's no race between multiple calls to this function. And the
operations below both serialized in the regmap.

So do you really need this lock?

> +	if (high_temp == INT_MAX && low_temp == INT_MIN)
> +		ret = adc_tm5_disable_channel(channel);
> +	else
> +		ret = adc_tm5_configure(channel, trip_low_ptr, trip_high_ptr);
> +
> +	spin_unlock_irqrestore(&chip->reg_lock, flags);
> +
> +	return ret;
> +}
> +
> +
> +static struct thermal_zone_of_device_ops adc_tm5_ops = {
> +	.get_temp = adc_tm5_get_temp,
> +	.set_trips = adc_tm5_set_trip_temp,
> +};
[..]
> +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;
> +	unsigned int index = 0;
> +	const struct of_device_id *id;
> +	const struct adc_tm5_data *data;
> +	u32 value;
> +	int ret;
> +	struct device *dev = adc_tm->dev;

Pass the platform_device instead of the node to this function and you
can acquire this from it's obvious origin (&pdev->dev) instead and you
can use of_device_get_match_data() below.

> +
> +	adc_tm->nchannels = of_get_available_child_count(node);
> +	if (!adc_tm->nchannels)
> +		return -EINVAL;
> +
> +	adc_tm->channels = devm_kcalloc(adc_tm->dev, adc_tm->nchannels,
> +					sizeof(*adc_tm->channels), GFP_KERNEL);
> +	if (!adc_tm->channels)
> +		return -ENOMEM;
> +
> +	channels = adc_tm->channels;
> +
> +	id = of_match_node(adc_tm5_match_table, node);
> +	if (id)
> +		data = id->data;
> +	else
> +		data = &adc_tm5_data_pmic;

Although this seems to fall back to the same value as the single entry
in the of_match_table, so perhaps you should just omit this part for
now.

> +	adc_tm->data = data;
> +
> +	ret = of_property_read_u32(node, "qcom,decimation", &value);
> +	if (!ret) {
> +		ret = qcom_adc5_decimation_from_dt(value, data->decimation);
> +		if (ret < 0) {
> +			dev_err(dev, "invalid decimation %d\n",
> +				value);

Afaict you don't need to line break this.

> +			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);

Ditto

> +			return ret;
> +		}
> +		adc_tm->avg_samples = ret;
> +	} else {
> +		adc_tm->avg_samples = VADC_DEF_AVG_SAMPLES;
> +	}
> +
> +	adc_tm->timer1 = ADC_TM5_TIMER1;
> +	adc_tm->timer2 = ADC_TM5_TIMER2;
> +	adc_tm->timer3 = ADC_TM5_TIMER3;
> +
> +	for_each_available_child_of_node(node, child) {
> +		ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child, data);
> +		if (ret) {
> +			of_node_put(child);
> +			return ret;
> +		}
> +
> +		channels++;
> +		index++;
> +	}
> +
> +	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 < 0)
> +		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;
> +	spin_lock_init(&adc_tm->reg_lock);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(dev, "get_irq failed: %d\n", irq);
> +		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_tm5_init(adc_tm);
> +	if (ret) {
> +		dev_err(dev, "adc-tm init failed\n");
> +		return ret;
> +	}
> +
> +	ret = adc_tm5_register_tzd(adc_tm);
> +
> +	ret = devm_request_irq(dev, irq, adc_tm5_isr, 0,
> +			       "pm-adc-tm5", adc_tm);
> +	if (ret)
> +		return ret;
> +
> +	platform_set_drvdata(pdev, adc_tm);

I don't see a get of the drvdata, you should be able to omit this.

> +
> +	return 0;
> +}
> +
> +static int adc_tm5_remove(struct platform_device *pdev)

Seems like you can omit this function.

> +{
> +	return 0;
> +}
> +
> +static struct platform_driver adc_tm5_driver = {
> +	.driver = {
> +		.name = "spmi-adc-tm5",
> +		.of_match_table = adc_tm5_match_table,
> +	},
> +	.probe = adc_tm5_probe,
> +	.remove = adc_tm5_remove,
> +};
> +module_platform_driver(adc_tm5_driver);
> +
> +MODULE_ALIAS("platform:spmi-adc-tm5");

No code will attempt to load the module by this alias, so please drop
it.

Regards,
Bjorn

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor
  2020-06-21 19:35 ` [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor Dmitry Baryshkov
  2020-06-22  5:39   ` Bjorn Andersson
@ 2020-06-27 14:42   ` Jonathan Cameron
  1 sibling, 0 replies; 14+ messages in thread
From: Jonathan Cameron @ 2020-06-27 14:42 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Amit Kucheria, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	devicetree, Vinod Koul, linux-iio

On Sun, 21 Jun 2020 22:35:47 +0300
Dmitry Baryshkov <dmitry.baryshkov@linaro.org> wrote:

> Add support for Thermal Monitoring part of PMIC5. This part is closely
> coupled with ADC, using it's channels directly. ADC-TM support
> generating interrupts on ADC value crossing low or high voltage bounds,
> which is used to support thermal trip points.
> 
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>

This may well overlap with Bjorn's comments though I'll avoid those
I can remember from quickly scanning through his review.

Anyhow various random comments inline.

Jonathan

> ---
>  drivers/iio/adc/qcom-vadc-common.c       |  67 +++
>  drivers/iio/adc/qcom-vadc-common.h       |   3 +
>  drivers/thermal/qcom/Kconfig             |  11 +
>  drivers/thermal/qcom/Makefile            |   1 +
>  drivers/thermal/qcom/qcom-spmi-adc-tm5.c | 617 +++++++++++++++++++++++
>  5 files changed, 699 insertions(+)
>  create mode 100644 drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> 
> diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
> index ffa578ce76db..e470beb8c6f9 100644
> --- a/drivers/iio/adc/qcom-vadc-common.c
> +++ b/drivers/iio/adc/qcom-vadc-common.c
> @@ -176,6 +176,47 @@ static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
>  	return 0;
>  }
>  
> +static s32 qcom_vadc_map_temp_voltage(const struct vadc_map_pt *pts,
> +				      u32 tablesize, int input)
> +{
> +	bool descending = 1;
> +	u32 i = 0;
> +
> +	/* Check if table is descending or ascending */
> +	if (tablesize > 1) {
> +		if (pts[0].y < pts[1].y)
> +			descending = 0;
> +	}
> +
> +	while (i < tablesize) {
> +		if ((descending) && (pts[i].y < input)) {
> +			/* table entry is less than measured*/
> +			 /* value and table is descending, stop */
> +			break;
> +		} else if ((!descending) &&
> +				(pts[i].y > input)) {
> +			/* table entry is greater than measured*/
> +			/*value and table is ascending, stop */
> +			break;
> +		}
> +		i++;
> +	}
> +
> +	if (i == 0)
> +		return pts[0].x;
> +	if (i == tablesize)
> +		return pts[tablesize - 1].x;
> +
> +	/* result is between search_index and search_index-1 */
> +	/* interpolate linearly */
> +	return (((s32)((pts[i].x - pts[i - 1].x) *
> +		(input - pts[i - 1].y)) /
> +		(pts[i].y - pts[i - 1].y)) +
> +		pts[i - 1].x);
> +
> +	return 0;
> +}
> +
>  static void qcom_vadc_scale_calib(const struct vadc_linear_graph *calib_graph,
>  				  u16 adc_code,
>  				  bool absolute,
> @@ -273,6 +314,19 @@ static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph,
>  	return 0;
>  }
>  
> +static u16 qcom_vadc_scale_voltage_code(int voltage,
> +				const struct vadc_prescale_ratio *prescale,
> +				const u32 full_scale_code_volt,
> +				unsigned int factor)
> +{
> +	s64 volt = voltage, adc_vdd_ref_mv = 1875;
> +
> +	volt *= prescale->num * factor * full_scale_code_volt;
> +	volt = div64_s64(volt, (s64)prescale->den * adc_vdd_ref_mv * 1000);
> +
> +	return volt;
> +}
> +
>  static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
>  				const struct vadc_prescale_ratio *prescale,
>  				const struct adc5_data *data,
> @@ -396,6 +450,19 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>  }
>  EXPORT_SYMBOL(qcom_vadc_scale);
>  
> +u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
> +		u32 full_scale_code_volt, int temp)
> +{
> +	const struct vadc_prescale_ratio *prescale = &adc5_prescale_ratios[prescale_ratio];
> +	s32 voltage;
> +
> +	voltage = qcom_vadc_map_temp_voltage(adcmap_100k_104ef_104fb_1875_vref,
> +				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
> +				 temp);
> +	return qcom_vadc_scale_voltage_code(voltage, prescale, full_scale_code_volt, 1000);
> +}
> +EXPORT_SYMBOL(qcom_adc_tm5_temp_volt_scale);
> +
>  int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
>  		    unsigned int prescale_ratio,
>  		    const struct adc5_data *data,
> diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
> index 2c65ddc98696..978820443d8a 100644
> --- a/drivers/iio/adc/qcom-vadc-common.h
> +++ b/drivers/iio/adc/qcom-vadc-common.h
> @@ -157,6 +157,9 @@ int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
>  		    const struct adc5_data *data,
>  		    u16 adc_code, int *result_mdec);
>  
> +u16 qcom_adc_tm5_temp_volt_scale(unsigned int prescale_ratio,
> +		u32 full_scale_code_volt, int temp);
> +
>  int qcom_adc5_prescaling_from_dt(u32 num, u32 den);
>  
>  int qcom_adc5_hw_settle_time_from_dt(u32 value,
> diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
> index aa9c1d80fae4..c61df55760e9 100644
> --- a/drivers/thermal/qcom/Kconfig
> +++ b/drivers/thermal/qcom/Kconfig
> @@ -20,3 +20,14 @@ config QCOM_SPMI_TEMP_ALARM
>  	  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_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.
> diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
> index ec86eef7f6a6..5b9445a3fd26 100644
> --- a/drivers/thermal/qcom/Makefile
> +++ b/drivers/thermal/qcom/Makefile
> @@ -4,3 +4,4 @@ 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_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
> +obj-$(CONFIG_QCOM_SPMI_ADC_TM5)	+= qcom-spmi-adc-tm5.o
> 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 000000000000..ebfe0c9fbf96
> --- /dev/null
> +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5.c
> @@ -0,0 +1,617 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
> + * Copyright (c) 2020 Linaro Limited
> + */
> +
> +#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 "../../iio/adc/qcom-vadc-common.h"
> +
> +#define ADC5_MAX_CHANNEL                        0xc0
> +#define ADC_TM5_NUM_CHANNELS		8
> +#define ADC_TM5_TIMER1			3 /* 3.9ms */
> +#define ADC_TM5_TIMER2			10 /* 1 second */
> +#define ADC_TM5_TIMER3			4 /* 4 second */
> +
> +#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			0x43
> +#define ADC_TM5_FAST_AVG_EN			BIT(7)
> +
> +#define ADC_TM5_MEAS_INTERVAL_CTL		0x44
> +
> +#define ADC_TM5_MEAS_INTERVAL_CTL2		0x45

Perhaps naming can make it clear what is a register address and
what is a field location etc?  GENMASK preferred for masks.
FIELD_PREP and friends might also make this more readable.

> +#define ADC_TM5_MEAS_INTERVAL_CTL2_SHIFT		0x4
> +#define ADC_TM5_MEAS_INTERVAL_CTL2_MASK		0xf0
> +#define ADC_TM5_MEAS_INTERVAL_CTL3_MASK		0xf
> +
> +#define ADC_TM5_Mn_EN(n)			((n * 8) + 0x67)

Perhaps move this so it's in the obvious place in the sequence below?
Also not convinced on the lower case n in the middle of the defines.


> +#define ADC_TM5_Mn_MEAS_EN			BIT(7)
> +#define ADC_TM5_Mn_HIGH_THR_INT_EN		BIT(1)
> +#define ADC_TM5_Mn_LOW_THR_INT_EN		BIT(0)
> +
> +#define ADC_TM5_Mn_ADC_CH_SEL_CTL(n)		((n * 8) + 0x60)
While is more verbose, perhaps it's worth making it more apparent that
this is blocks of 8 registers per channel all offset by 0x60

#define ADC_TM5_Mn_CHAN_BASE 0x60
#define ADC_TM5_Mn_ADC_CH_SE_CTL(b)		(ADC_TM5_Mn_CHAN_BASE + (n * 8) + 0)
#define ADC_TM5_Mn_LOW_THR0(n)			(ADC_TM5_Mn_CHAN_BASE + (n * 8) + 1)
etc?

> +#define ADC_TM5_Mn_LOW_THR0(n)			((n * 8) + 0x61)
> +#define ADC_TM5_Mn_LOW_THR1(n)			((n * 8) + 0x62)
> +#define ADC_TM5_Mn_HIGH_THR0(n)			((n * 8) + 0x63)
> +#define ADC_TM5_Mn_HIGH_THR1(n)			((n * 8) + 0x64)
> +#define ADC_TM5_Mn_MEAS_INTERVAL_CTL(n)		((n * 8) + 0x65)
> +#define ADC_TM5_Mn_CTL(n)			((n * 8) + 0x66)

Here for ADC_TM5_Mn_EN(n) perhaps?

> +#define ADC_TM5_CTL_HW_SETTLE_DELAY_MASK		0xf
> +#define ADC_TM5_CTL_CAL_SEL			0x30
> +#define ADC_TM5_CTL_CAL_SEL_MASK_SHIFT		4
> +#define ADC_TM5_CTL_CAL_VAL			0x40
> +
> +enum adc5_timer_select {
> +	ADC5_TIMER_SEL_1 = 0,
> +	ADC5_TIMER_SEL_2,
> +	ADC5_TIMER_SEL_3,
> +	ADC5_TIMER_SEL_NONE,
> +};
> +
> +struct adc_tm5_data {
> +	const u32	full_scale_code_volt;
> +	unsigned int	*decimation;
> +	unsigned int	*hw_settle;
> +};
> +
> +enum adc_tm5_cal_method {
> +	ADC_TM5_NO_CAL = 0,
> +	ADC_TM5_RATIOMETRIC_CAL,
> +	ADC_TM5_ABSOLUTE_CAL
> +};
> +
> +struct adc_tm5_chip;
> +
> +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;
> +	struct iio_channel	*iio;
> +	struct adc_tm5_chip	*chip;
> +	struct thermal_zone_device *tzd;
> +};
> +
> +struct adc_tm5_chip {
> +	struct regmap		*regmap;
> +	struct device		*dev;
> +	struct adc_tm5_channel	*channels;
> +	const struct adc_tm5_data	*data;
> +	spinlock_t		reg_lock;

Even with the naming making it fairly clear, a lock should have explicit
docs on what its scope is.

> +	unsigned int		decimation;
> +	unsigned int		avg_samples;
> +	unsigned int		timer1;

Some elements in here are non obvious enough in function that some docs
might be good.

> +	unsigned int		timer2;
> +	unsigned int		timer3;
> +	unsigned int		nchannels;
> +	u16			base;
> +};
> +
> +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,
> +					1, 2, 4, 8, 16, 32, 64, 128},
> +};
> +
> +static const struct of_device_id adc_tm5_match_table[] = {
> +	{
> +		.compatible = "qcom,spmi-adc-tm5",
> +		.data = &adc_tm5_data_pmic,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, adc_tm5_match_table);
> +
> +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 = 0, i = 0;
> +	unsigned long flags;
> +
> +	ret = adc_tm5_read(chip, ADC_TM5_STATUS_LOW, &status_low, 1);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "read status low failed with %d\n", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	ret = adc_tm5_read(chip, ADC_TM5_STATUS_HIGH, &status_high, 1);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "read 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;
> +
> +		if (!chip->channels[i].tzd) {
> +			dev_err(chip->dev, "thermal device not found\n");
> +			continue;
> +		}
> +
> +		spin_lock_irqsave(&chip->reg_lock, flags);
> +		ret = adc_tm5_read(chip, ADC_TM5_Mn_EN(ch), &ctl, 1);
> +		spin_unlock_irqrestore(&chip->reg_lock, flags);
> +
> +		if (ret) {
> +			dev_err(chip->dev, "ctl read failed with %d\n", ret);
> +			goto fail;
> +		}
> +
> +		if ((status_low & BIT(ch)) && (ctl & ADC_TM5_Mn_MEAS_EN)
> +				&& (ctl & ADC_TM5_Mn_LOW_THR_INT_EN))
Alignment of these is making them a little hard to read.

		if ((status_low & BIT(ch) &&
		    (ctl & ADC_TM5_Mn_MEAS_EN) &&
                    (ctl & ADC_TM5_Mn_LOW_THR_INT_EN)) 
perhaps?

Or maybe just treat them as a boolean assignment

		lower_set = (status_low & BIT(ch)) ...


> +			lower_set = true;
> +
> +		if ((status_high & BIT(ch)) && (ctl & ADC_TM5_Mn_MEAS_EN) &&
> +					(ctl & ADC_TM5_Mn_HIGH_THR_INT_EN))
> +			upper_set = true;
> +fail:

Why is fail here?  upper_set == false, lower_set == false if we get
here so the next bit doesn't run anyway. So goto fail: seems to be the
same as continue;

> +
> +		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(void *data, int *temp)
> +{
> +	struct adc_tm5_channel *channel = data;
> +	int ret, milli_celsius;
> +
> +	if (!channel || !channel->iio)
> +		return -EINVAL;
> +
> +	ret = iio_read_channel_processed(channel->iio, &milli_celsius);
> +	if (ret < 0)
> +		return ret;

Personally slight preference for if (ret) to make it clear good == 0.

> +
> +	*temp = milli_celsius;
> +
> +	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_Mn_EN(channel->channel);
> +
> +	return adc_tm5_reg_update(chip, reg,
> +			ADC_TM5_Mn_MEAS_EN | ADC_TM5_Mn_HIGH_THR_INT_EN | ADC_TM5_Mn_LOW_THR_INT_EN,
> +			0);
> +}
> +
> +static int adc_tm5_configure(struct adc_tm5_channel *channel, u8 *low_thr, u8 *high_thr)
> +{
> +	struct adc_tm5_chip *chip = channel->chip;
> +	u8 buf[8], cal_method;
> +	u16 reg = ADC_TM5_Mn_ADC_CH_SEL_CTL(channel->channel);
> +	int ret = 0;
> +
> +	ret = adc_tm5_read(chip, reg, buf, 8);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "block read failed with %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Update ADC channel select */
> +	buf[0] = channel->adc_channel;
> +
> +	if (low_thr) {
> +		buf[1] = low_thr[0];
> +		buf[2] = low_thr[1];

As mentioned below, this would be nicer as an explicit put_unaligned*

> +		buf[7] |= ADC_TM5_Mn_LOW_THR_INT_EN;
> +	}
> +
> +	if (high_thr) {
> +		buf[3] = high_thr[0];
> +		buf[4] = high_thr[1];
> +		buf[7] |= ADC_TM5_Mn_HIGH_THR_INT_EN;
> +	}
> +
> +	/* Update timer select */
> +	buf[5] = ADC5_TIMER_SEL_2;
> +
> +	/* Set calibration select, hw_settle delay */
> +	cal_method = (u8) (channel->cal_method << ADC_TM5_CTL_CAL_SEL_MASK_SHIFT);
> +	buf[6] &= (u8) ~ADC_TM5_CTL_HW_SETTLE_DELAY_MASK;
> +	buf[6] |= (u8) channel->hw_settle_time;
> +	buf[6] &= (u8) ~ADC_TM5_CTL_CAL_SEL;
> +	buf[6] |= (u8) cal_method;
> +
> +	buf[7] |= ADC_TM5_Mn_MEAS_EN;
> +
> +	ret = adc_tm5_write(chip, reg, buf, 8);
> +	if (ret < 0) {

Personally I prefer to do if (ret) for regmap error checks as
they can never be positive.

> +		dev_err(chip->dev, "buf write failed\n");
> +		return ret;

Drop the return ret out of brackets given it will be 0 if we aren't
in here anyway.

> +	}
> +
> +	return 0;
> +}
> +
> +static int adc_tm5_set_trip_temp(void *data,
> +		int low_temp, int high_temp)
> +{
> +	struct adc_tm5_channel *channel = data;
> +	struct adc_tm5_chip *chip;
> +	u8 trip_high_thr[2], trip_low_thr[2];

These look awfully like 16 bit value shoved into byte pairs.
Can we not just do what ever is need to keep them as appropriate
__?e16 values?  Looks like an unaligned put will be needed but
it would still be more readable I think.

> +	u8 *trip_high_ptr = NULL, *trip_low_ptr = NULL;
> +	int ret;
> +	unsigned long flags;
> +
> +	if (!channel)
> +		return -EINVAL;
> +
> +	dev_info(channel->chip->dev, "%d:low_temp(mdegC):%d, high_temp(mdegC):%d\n",
> +			channel->channel, low_temp, high_temp);

I'm going to guess that info is all available from elsewhere? Looks like log
spamming at first glance. Still I don't know thermal that well so maybe
this is standard?

> +	chip = channel->chip;
> +
> +	/* Warm temperature corresponds to low voltage threshold */
> +	if (high_temp != INT_MAX) {
> +		u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
> +				chip->data->full_scale_code_volt, high_temp);
> +
> +		trip_low_thr[0] = adc_code & 0xff;
> +		trip_low_thr[1] = adc_code >> 8;
> +		trip_low_ptr = trip_low_thr;
> +	}
> +
> +	/* Cool temperature corresponds to high voltage threshold */
> +	if (low_temp != -INT_MAX) {
> +		u16 adc_code = qcom_adc_tm5_temp_volt_scale(channel->prescale,
> +				chip->data->full_scale_code_volt, low_temp);
> +
> +		trip_high_thr[0] = adc_code & 0xff;
> +		trip_high_thr[1] = adc_code >> 8;
> +		trip_high_ptr = trip_high_thr;
> +	}
> +
> +	spin_lock_irqsave(&chip->reg_lock, flags);
> +	if (high_temp == INT_MAX && low_temp == INT_MIN)
> +		ret = adc_tm5_disable_channel(channel);
> +	else
> +		ret = adc_tm5_configure(channel, trip_low_ptr, trip_high_ptr);
> +
> +	spin_unlock_irqrestore(&chip->reg_lock, flags);
> +
> +	return ret;
> +}
> +
> +
> +static struct thermal_zone_of_device_ops adc_tm5_ops = {
> +	.get_temp = adc_tm5_get_temp,
> +	.set_trips = adc_tm5_set_trip_temp,
> +};
> +
> +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_zone_of_sensor_register(
> +				adc_tm->dev, adc_tm->channels[i].channel,
> +				&adc_tm->channels[i], &adc_tm5_ops);
> +		if (IS_ERR(tzd)) {
> +			dev_err(adc_tm->dev, "Error registering TZ zone:%ld for channel:%d\n",
> +				PTR_ERR(tzd), adc_tm->channels[i].channel);
> +			continue;
> +		}
> +		adc_tm->channels[i].tzd = tzd;
> +	}
> +
> +	return 0;
> +}
> +
> +static int adc_tm5_init(struct adc_tm5_chip *chip)
> +{
> +	u8 buf[4], channels_available, meas_int_timer_2_3 = 0;
> +	int ret;
> +	unsigned int i;
> +
> +	ret = adc_tm5_read(chip, ADC_TM5_NUM_BTM, &channels_available, 1);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "read failed for BTM channels\n");
> +		return ret;
> +	}
> +
> +	ret = adc_tm5_read(chip, ADC_TM5_ADC_DIG_PARAM, buf, 4);
> +	if (ret < 0) {
> +		dev_err(chip->dev, "block read failed with %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* Select decimation */
> +	buf[0] = chip->decimation;
> +
> +	/* Select number of samples in fast average mode */
> +	buf[1] = chip->avg_samples | ADC_TM5_FAST_AVG_EN;
> +
> +	/* Select timer1 */
> +	buf[2] = chip->timer1;
> +
> +	/* Select timer2 and timer3 */
> +	meas_int_timer_2_3 |= chip->timer2 <<
> +				ADC_TM5_MEAS_INTERVAL_CTL2_SHIFT;

Take advantage of new slightly more relaxed line length rules and just
put the above on one line for readability.

There are other places in the driver that would benefit from that as well.

> +	meas_int_timer_2_3 |= chip->timer3;
> +	buf[3] = meas_int_timer_2_3;
> +
> +	ret = adc_tm5_write(chip,
> +			ADC_TM5_ADC_DIG_PARAM, buf, 4);
> +	if (ret < 0)
> +		dev_err(chip->dev, "block write failed with %d\n", 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;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static int adc_tm5_get_dt_channel_data(struct adc_tm5_chip *adc_tm,
> +				       struct adc_tm5_channel *prop,
> +				       struct device_node *node,
> +				       const struct adc_tm5_data *data)
> +{
> +	const char *name = node->name;
> +	u32 chan, value, varr[2];
> +	int ret;
> +	struct device *dev = adc_tm->dev;
> +
> +	ret = of_property_read_u32(node, "reg", &chan);
> +	if (ret) {
> +		dev_err(dev, "invalid channel number %s\n", name);
> +		return ret;
> +	}
> +
> +	if (chan >= ADC_TM5_NUM_CHANNELS) {
> +		dev_err(dev, "%s invalid channel number %d\n", name, chan);
> +		return -EINVAL;
> +	}
> +
> +	/* the channel has DT description */
> +	prop->channel = chan;
> +
> +	ret = of_property_read_u32(node, "qcom,adc-channel", &chan);
> +	if (ret) {
> +		dev_err(dev, "invalid channel number %s\n", name);
> +		return ret;
> +	}
> +	if (chan >= ADC5_MAX_CHANNEL) {
> +		dev_err(dev, "%s invalid ADC channel number %d\n", name, chan);
> +		return ret;
> +	}
> +	prop->adc_channel = chan;
> +
> +	prop->iio = devm_iio_channel_get(adc_tm->dev, name);
> +	if (IS_ERR(prop->iio)) {
> +		ret = PTR_ERR(prop->iio);
> +		prop->iio = NULL;
> +		dev_err(dev, "error getting channel %s: %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, "%02x invalid pre-scaling <%d %d>\n",
> +				chan, varr[0], varr[1]);
> +			return ret;
> +		}
> +		prop->prescale = ret;
> +	} else {
> +		prop->prescale = 0; /*1:1 is index 0 */

/* 1:1...

> +	}
> +
> +	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
> +	if (!ret) {
> +		ret = qcom_adc5_hw_settle_time_from_dt(value,
> +							data->hw_settle);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->hw_settle_time = ret;
> +	} else {
> +		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
> +	}
> +
> +	if (of_property_read_bool(node, "qcom,ratiometric"))
> +		prop->cal_method = ADC_TM5_RATIOMETRIC_CAL;
> +	else
> +		prop->cal_method = ADC_TM5_ABSOLUTE_CAL;
> +
> +	dev_dbg(dev, "%02x name %s\n", chan, name);
> +
> +	return 0;
> +}
> +
> +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;
> +	unsigned int index = 0;
> +	const struct of_device_id *id;
> +	const struct adc_tm5_data *data;
> +	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(adc_tm->dev, adc_tm->nchannels,
> +					sizeof(*adc_tm->channels), GFP_KERNEL);
> +	if (!adc_tm->channels)
> +		return -ENOMEM;
> +
> +	channels = adc_tm->channels;
> +
> +	id = of_match_node(adc_tm5_match_table, node);
> +	if (id)
> +		data = id->data;
> +	else
> +		data = &adc_tm5_data_pmic;
> +	adc_tm->data = data;
> +
> +	ret = of_property_read_u32(node, "qcom,decimation", &value);
> +	if (!ret) {
> +		ret = qcom_adc5_decimation_from_dt(value, 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;
> +	}
> +
> +	adc_tm->timer1 = ADC_TM5_TIMER1;
> +	adc_tm->timer2 = ADC_TM5_TIMER2;
> +	adc_tm->timer3 = ADC_TM5_TIMER3;
> +
> +	for_each_available_child_of_node(node, child) {
> +		ret = adc_tm5_get_dt_channel_data(adc_tm, channels, child, data);
> +		if (ret) {
> +			of_node_put(child);
> +			return ret;
> +		}
> +
> +		channels++;
> +		index++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int adc_tm5_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;

Personally I slightly prefer the generic firmware property stuff now we have
it but I guess this driver is never going to be probed from anything other
than DT.

> +	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 < 0)
> +		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;
> +	spin_lock_init(&adc_tm->reg_lock);
> +
> +	irq = platform_get_irq(pdev, 0);
> +	if (irq < 0) {
> +		dev_err(dev, "get_irq failed: %d\n", irq);
> +		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_tm5_init(adc_tm);
> +	if (ret) {
> +		dev_err(dev, "adc-tm init failed\n");
> +		return ret;
> +	}
> +
> +	ret = adc_tm5_register_tzd(adc_tm);

Handle that ret;

> +
> +	ret = devm_request_irq(dev, irq, adc_tm5_isr, 0,
> +			       "pm-adc-tm5", adc_tm);
> +	if (ret)
> +		return ret;

As Bjorn said, drop the platform_set_drvdata which then
lets you do simply

return devm_request_irq


> +
> +	platform_set_drvdata(pdev, adc_tm);
> +
> +	return 0;
> +}
> +
> +static int adc_tm5_remove(struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static struct platform_driver adc_tm5_driver = {
> +	.driver = {
> +		.name = "spmi-adc-tm5",
> +		.of_match_table = adc_tm5_match_table,
> +	},
> +	.probe = adc_tm5_probe,
> +	.remove = adc_tm5_remove,
> +};
> +module_platform_driver(adc_tm5_driver);
> +
> +MODULE_ALIAS("platform:spmi-adc-tm5");
> +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
> +MODULE_LICENSE("GPL v2");


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings
  2020-06-21 19:35 ` [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings Dmitry Baryshkov
@ 2020-06-29 21:57   ` Rob Herring
  0 siblings, 0 replies; 14+ messages in thread
From: Rob Herring @ 2020-06-29 21:57 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Jonathan Cameron, Peter Meerwald-Stadler, Andy Gross,
	Amit Kucheria, Vinod Koul, Bjorn Andersson, Hartmut Knaack,
	devicetree, linux-iio, Lars-Peter Clausen, Rob Herring,
	linux-arm-msm, Zhang Rui, Daniel Lezcano

On Sun, 21 Jun 2020 22:35:45 +0300, Dmitry Baryshkov wrote:
> Add bindings for thermal monitor, part of Qualcomm PMIC5 chips. It is a
> close counterpart of VADC part of those PMICs.
> 
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> ---
>  .../bindings/thermal/qcom-spmi-adc-tm5.yaml   | 143 ++++++++++++++++++
>  1 file changed, 143 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml
> 


My bot found errors running 'make dt_binding_check' on your patch:

Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.yaml:  while scanning a block scalar
  in "<unicode string>", line 114, column 5
found a tab character where an indentation space is expected
  in "<unicode string>", line 115, column 1
Documentation/devicetree/bindings/Makefile:20: recipe for target 'Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.example.dts' failed
make[1]: *** [Documentation/devicetree/bindings/thermal/qcom-spmi-adc-tm5.example.dts] Error 1
make[1]: *** Waiting for unfinished jobs....
Makefile:1347: recipe for target 'dt_binding_check' failed
make: *** [dt_binding_check] Error 2


See https://patchwork.ozlabs.org/patch/1313977

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure dt-schema is up to date:

pip3 install git+https://github.com/devicetree-org/dt-schema.git@master --upgrade

Please check and re-submit.


^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5
  2020-06-21 19:35 ` [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5 Dmitry Baryshkov
@ 2020-06-30  5:06   ` Amit Kucheria
  2020-06-30 12:10     ` Dmitry Baryshkov
  0 siblings, 1 reply; 14+ messages in thread
From: Amit Kucheria @ 2020-06-30  5:06 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	Vinod Koul, linux-iio

Hi Dmitry,

On Mon, Jun 22, 2020 at 1:06 AM Dmitry Baryshkov
<dmitry.baryshkov@linaro.org> wrote:
>
> Port thermal zones definitions from msm-4.19 tree. Enable and add
> channel configuration to PMIC's ADC-TM definitions. Declare thermal
> zones and respective trip points.
>
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> ---
>  arch/arm64/boot/dts/qcom/sm8250-mtp.dts | 237 ++++++++++++++++++++++++
>  1 file changed, 237 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts

IMO, this should be separated in the pmic dts file like we do for
other QC platforms since the PMICs tend to be used in multiple
platforms.

> index aa37eb112d85..78f0cf582a9a 100644
> --- a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> +++ b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> @@ -24,6 +24,104 @@ chosen {
>                 stdout-path = "serial0:115200n8";
>         };
>
> +       thermal-zones {
> +               xo-therm {
> +                       polling-delay-passive = <0>;
> +                       polling-delay = <0>;
> +                       thermal-sensors = <&pm8150_adc_tm 0>;
> +                       trips {
> +                               active-config0 {
> +                                       temperature = <125000>;
> +                                       hysteresis = <1000>;
> +                                       type = "passive";
> +                               };
> +                       };
> +               };
> +

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5
  2020-06-30  5:06   ` Amit Kucheria
@ 2020-06-30 12:10     ` Dmitry Baryshkov
  2020-07-01  6:05       ` Amit Kucheria
  0 siblings, 1 reply; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-06-30 12:10 UTC (permalink / raw)
  To: Amit Kucheria
  Cc: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	Vinod Koul, linux-iio

Hi,

On Tue, 30 Jun 2020 at 08:06, Amit Kucheria <amit.kucheria@verdurent.com> wrote:
> On Mon, Jun 22, 2020 at 1:06 AM Dmitry Baryshkov
> <dmitry.baryshkov@linaro.org> wrote:
> >
> > Port thermal zones definitions from msm-4.19 tree. Enable and add
> > channel configuration to PMIC's ADC-TM definitions. Declare thermal
> > zones and respective trip points.
> >
> > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> > ---
> >  arch/arm64/boot/dts/qcom/sm8250-mtp.dts | 237 ++++++++++++++++++++++++
> >  1 file changed, 237 insertions(+)
> >
> > diff --git a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
>
> IMO, this should be separated in the pmic dts file like we do for
> other QC platforms since the PMICs tend to be used in multiple
> platforms.

Unlike other PMIC/tsens thermal zones, these definitions are quite
specific to the board from my point of view.

> > index aa37eb112d85..78f0cf582a9a 100644
> > --- a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > +++ b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > @@ -24,6 +24,104 @@ chosen {
> >                 stdout-path = "serial0:115200n8";
> >         };
> >
> > +       thermal-zones {
> > +               xo-therm {
> > +                       polling-delay-passive = <0>;
> > +                       polling-delay = <0>;
> > +                       thermal-sensors = <&pm8150_adc_tm 0>;
> > +                       trips {
> > +                               active-config0 {
> > +                                       temperature = <125000>;
> > +                                       hysteresis = <1000>;
> > +                                       type = "passive";
> > +                               };
> > +                       };
> > +               };
> > +



-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5
  2020-06-30 12:10     ` Dmitry Baryshkov
@ 2020-07-01  6:05       ` Amit Kucheria
  2020-07-01 10:56         ` Dmitry Baryshkov
  0 siblings, 1 reply; 14+ messages in thread
From: Amit Kucheria @ 2020-07-01  6:05 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	Vinod Koul, linux-iio

On Tue, Jun 30, 2020 at 5:40 PM Dmitry Baryshkov
<dmitry.baryshkov@linaro.org> wrote:
>
> Hi,
>
> On Tue, 30 Jun 2020 at 08:06, Amit Kucheria <amit.kucheria@verdurent.com> wrote:
> > On Mon, Jun 22, 2020 at 1:06 AM Dmitry Baryshkov
> > <dmitry.baryshkov@linaro.org> wrote:
> > >
> > > Port thermal zones definitions from msm-4.19 tree. Enable and add
> > > channel configuration to PMIC's ADC-TM definitions. Declare thermal
> > > zones and respective trip points.
> > >
> > > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> > > ---
> > >  arch/arm64/boot/dts/qcom/sm8250-mtp.dts | 237 ++++++++++++++++++++++++
> > >  1 file changed, 237 insertions(+)
> > >
> > > diff --git a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> >
> > IMO, this should be separated in the pmic dts file like we do for
> > other QC platforms since the PMICs tend to be used in multiple
> > platforms.
>
> Unlike other PMIC/tsens thermal zones, these definitions are quite
> specific to the board from my point of view.

How so? Can you describe what is different about this PMIC?

> > > index aa37eb112d85..78f0cf582a9a 100644
> > > --- a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > > +++ b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > > @@ -24,6 +24,104 @@ chosen {
> > >                 stdout-path = "serial0:115200n8";
> > >         };
> > >
> > > +       thermal-zones {
> > > +               xo-therm {
> > > +                       polling-delay-passive = <0>;
> > > +                       polling-delay = <0>;
> > > +                       thermal-sensors = <&pm8150_adc_tm 0>;
> > > +                       trips {
> > > +                               active-config0 {
> > > +                                       temperature = <125000>;
> > > +                                       hysteresis = <1000>;
> > > +                                       type = "passive";
> > > +                               };
> > > +                       };
> > > +               };
> > > +

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5
  2020-07-01  6:05       ` Amit Kucheria
@ 2020-07-01 10:56         ` Dmitry Baryshkov
  0 siblings, 0 replies; 14+ messages in thread
From: Dmitry Baryshkov @ 2020-07-01 10:56 UTC (permalink / raw)
  To: Amit Kucheria
  Cc: Andy Gross, Bjorn Andersson, Rob Herring, Zhang Rui,
	Daniel Lezcano, Jonathan Cameron, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, linux-arm-msm,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	Vinod Koul, linux-iio

On Wed, 1 Jul 2020 at 09:06, Amit Kucheria <amit.kucheria@verdurent.com> wrote:
>
> On Tue, Jun 30, 2020 at 5:40 PM Dmitry Baryshkov
> <dmitry.baryshkov@linaro.org> wrote:
> >
> > Hi,
> >
> > On Tue, 30 Jun 2020 at 08:06, Amit Kucheria <amit.kucheria@verdurent.com> wrote:
> > > On Mon, Jun 22, 2020 at 1:06 AM Dmitry Baryshkov
> > > <dmitry.baryshkov@linaro.org> wrote:
> > > >
> > > > Port thermal zones definitions from msm-4.19 tree. Enable and add
> > > > channel configuration to PMIC's ADC-TM definitions. Declare thermal
> > > > zones and respective trip points.
> > > >
> > > > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> > > > ---
> > > >  arch/arm64/boot/dts/qcom/sm8250-mtp.dts | 237 ++++++++++++++++++++++++
> > > >  1 file changed, 237 insertions(+)
> > > >
> > > > diff --git a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > >
> > > IMO, this should be separated in the pmic dts file like we do for
> > > other QC platforms since the PMICs tend to be used in multiple
> > > platforms.
> >
> > Unlike other PMIC/tsens thermal zones, these definitions are quite
> > specific to the board from my point of view.
>
> How so? Can you describe what is different about this PMIC?

It is not about this PMIC, but rather about particular thermistors
being placed up in different places on the board itself.

> > > > index aa37eb112d85..78f0cf582a9a 100644
> > > > --- a/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > > > +++ b/arch/arm64/boot/dts/qcom/sm8250-mtp.dts
> > > > @@ -24,6 +24,104 @@ chosen {
> > > >                 stdout-path = "serial0:115200n8";
> > > >         };
> > > >
> > > > +       thermal-zones {
> > > > +               xo-therm {
> > > > +                       polling-delay-passive = <0>;
> > > > +                       polling-delay = <0>;
> > > > +                       thermal-sensors = <&pm8150_adc_tm 0>;
> > > > +                       trips {
> > > > +                               active-config0 {
> > > > +                                       temperature = <125000>;
> > > > +                                       hysteresis = <1000>;
> > > > +                                       type = "passive";
> > > > +                               };
> > > > +                       };
> > > > +               };
> > > > +



-- 
With best wishes
Dmitry

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, back to index

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-06-21 19:35 [PATCH 0/5] qcom: pm8150: add support for thermal monitoring Dmitry Baryshkov
2020-06-21 19:35 ` [PATCH 1/5] dt-bindings: thermal: qcom: add adc-thermal monitor bindings Dmitry Baryshkov
2020-06-29 21:57   ` Rob Herring
2020-06-21 19:35 ` [PATCH 2/5] iio: adc: qcom-vadc: move several adc5 functions to common file Dmitry Baryshkov
2020-06-21 19:43   ` Bjorn Andersson
2020-06-21 19:35 ` [PATCH 3/5] thermal: qcom: add support for adc-tm5 PMIC thermal monitor Dmitry Baryshkov
2020-06-22  5:39   ` Bjorn Andersson
2020-06-27 14:42   ` Jonathan Cameron
2020-06-21 19:35 ` [PATCH 4/5] arm64: dts: qcom: pm8150x: add definitions for adc-tm5 part Dmitry Baryshkov
2020-06-21 19:35 ` [PATCH 5/5] arm64: dts: sm8250-dts: add thermal zones using pmic's adc-tm5 Dmitry Baryshkov
2020-06-30  5:06   ` Amit Kucheria
2020-06-30 12:10     ` Dmitry Baryshkov
2020-07-01  6:05       ` Amit Kucheria
2020-07-01 10:56         ` Dmitry Baryshkov

Linux-IIO Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-iio/0 linux-iio/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-iio linux-iio/ https://lore.kernel.org/linux-iio \
		linux-iio@vger.kernel.org
	public-inbox-index linux-iio

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-iio


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git