* [PATCH v4 1/3] iio: adc: qcom-spmi-adc5-gen3: Remove an unnecessary print
2026-07-22 11:26 [PATCH v4 0/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
@ 2026-07-22 11:26 ` Jishnu Prakash
2026-07-22 11:26 ` [PATCH v4 2/3] iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver Jishnu Prakash
` (2 subsequent siblings)
3 siblings, 0 replies; 9+ messages in thread
From: Jishnu Prakash @ 2026-07-22 11:26 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold, Jishnu Prakash, Andy Shevchenko, Maxwell Doose,
Joshua Crofts
devm_request_threaded_irq() internally prints an error message using
dev_err_probe() in case of any errors. Remove the error print in the
devm_request_threaded_irq() failure path as it is not needed.
Suggested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
Reviewed-by: Maxwell Doose <m32285159@gmail.com>
Reviewed-by: Joshua Crofts <joshua.crofts1@gmail.com>
Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
---
drivers/iio/adc/qcom-spmi-adc5-gen3.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
index c56b650fd8c0..2eaebf6b34e8 100644
--- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c
+++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
@@ -821,9 +821,7 @@ static int adc5_gen3_probe(struct platform_device *pdev)
adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name,
adc);
if (ret)
- return dev_err_probe(dev, ret,
- "Failed to request SDAM%d irq\n",
- ADC5_GEN3_VADC_SDAM);
+ return ret;
ret = adc5_get_fw_data(adc);
if (ret)
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v4 2/3] iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver
2026-07-22 11:26 [PATCH v4 0/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
2026-07-22 11:26 ` [PATCH v4 1/3] iio: adc: qcom-spmi-adc5-gen3: Remove an unnecessary print Jishnu Prakash
@ 2026-07-22 11:26 ` Jishnu Prakash
2026-07-22 11:26 ` [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
2026-08-07 12:06 ` [PATCH v4 0/3] " Daniel Lezcano
3 siblings, 0 replies; 9+ messages in thread
From: Jishnu Prakash @ 2026-07-22 11:26 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold, Jishnu Prakash
The SDAM0 IRQ can be triggered for both EOC (end of conversion) events for
immediate ADC reads done in this driver and for threshold violation events,
based on ADC_TM thresholds configured from the auxiliary ADC_TM driver on
TM channels on the first SDAM.
At present, this interrupt is handled only in the ISR in the main ADC driver.
When the ISR is triggered for an ADC_TM event, this driver notifies the ADC_TM
driver by calling a notifier callback exposed from it for this purpose.
To simplify the interrupt handling in both drivers, share the interrupt between
the drivers. With this, ADC_TM interrupts on SDAM0 will be handled directly in
the ADC_TM driver, so remove the notifier callback and all TM interrupt
handling in the main ADC ISR.
Reviewed-by: Jonathan Cameron <jic23@kernel.org>
Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
---
drivers/iio/adc/qcom-spmi-adc5-gen3.c | 65 ++++++++-------------------
include/linux/iio/adc/qcom-adc5-gen3-common.h | 2 -
2 files changed, 18 insertions(+), 49 deletions(-)
diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
index 2eaebf6b34e8..c68c6c5f6aca 100644
--- a/drivers/iio/adc/qcom-spmi-adc5-gen3.c
+++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
@@ -55,9 +55,6 @@ struct adc5_channel_prop {
* requests from multiple clients.
* @data: software configuration data.
* @n_tm_channels: number of ADC channels used for TM measurements.
- * @handler: TM callback to be called for threshold violation interrupt
- * on first SDAM.
- * @tm_aux: pointer to auxiliary TM device.
*/
struct adc5_chip {
struct device *dev;
@@ -69,8 +66,6 @@ struct adc5_chip {
struct mutex lock;
const struct adc5_data *data;
unsigned int n_tm_channels;
- void (*handler)(struct auxiliary_device *tm_aux);
- struct auxiliary_device *tm_aux;
};
int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index,
@@ -286,23 +281,21 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id)
{
struct adc5_chip *adc = dev_id;
struct device *dev = adc->dev;
- struct auxiliary_device *adev;
u8 status, eoc_status, val;
- u8 tm_status[2];
int ret;
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
ADC5_GEN3_STATUS1, &status, sizeof(status));
if (ret) {
dev_err(dev, "adc read status1 failed with %d\n", ret);
- return IRQ_HANDLED;
+ return IRQ_NONE;
}
ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
ADC5_GEN3_EOC_STS, &eoc_status, sizeof(eoc_status));
if (ret) {
dev_err(dev, "adc read eoc status failed with %d\n", ret);
- return IRQ_HANDLED;
+ return IRQ_NONE;
}
if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
@@ -315,30 +308,13 @@ static irqreturn_t adc5_gen3_isr(int irq, void *dev_id)
return IRQ_HANDLED;
}
- /* CHAN0 is the preconfigured channel for immediate conversion */
- if (eoc_status & ADC5_GEN3_EOC_CHAN_0)
- complete(&adc->complete);
-
- ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
- ADC5_GEN3_TM_HIGH_STS, tm_status, sizeof(tm_status));
- if (ret) {
- dev_err(dev, "adc read TM status failed with %d\n", ret);
- return IRQ_HANDLED;
- }
-
- dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n",
- status, eoc_status, tm_status[0], tm_status[1]);
-
- if (tm_status[0] || tm_status[1]) {
- adev = adc->tm_aux;
- if (!adev || !adev->dev.driver) {
- dev_err(dev, "adc_tm auxiliary device not initialized\n");
- return IRQ_HANDLED;
- }
+ dev_dbg(dev, "Interrupt status:%#x, EOC status:%#x\n", status, eoc_status);
- adc->handler(adev);
- }
+ /* CHAN0 is the preconfigured channel for immediate conversion */
+ if (!(eoc_status & ADC5_GEN3_EOC_CHAN_0))
+ return IRQ_NONE;
+ complete(&adc->complete);
return IRQ_HANDLED;
}
@@ -683,8 +659,6 @@ static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc)
if (ret)
return ret;
- adc->tm_aux = &aux_device->aux_dev;
-
return 0;
}
@@ -740,16 +714,6 @@ int adc5_gen3_therm_code_to_temp(struct device *dev,
}
EXPORT_SYMBOL_NS_GPL(adc5_gen3_therm_code_to_temp, "QCOM_SPMI_ADC5_GEN3");
-void adc5_gen3_register_tm_event_notifier(struct device *dev,
- void (*handler)(struct auxiliary_device *))
-{
- struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
- struct adc5_chip *adc = iio_priv(indio_dev);
-
- adc->handler = handler;
-}
-EXPORT_SYMBOL_NS_GPL(adc5_gen3_register_tm_event_notifier, "QCOM_SPMI_ADC5_GEN3");
-
static int adc5_gen3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -816,10 +780,17 @@ static int adc5_gen3_probe(struct platform_device *pdev)
return -ENOMEM;
}
- ret = devm_request_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq,
- adc5_gen3_isr, 0,
- adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name,
- adc);
+ /*
+ * This interrupt is shared with the ADC_TM auxiliary driver, which
+ * is threaded and uses IRQF_ONESHOT. Since shared interrupts need
+ * to agree on IRQF_ONESHOT configuration and there is a kernel
+ * warning for using IRQF_ONESHOT with non-threaded interrupts,
+ * make this also a threaded IRQ.
+ */
+ ret = devm_request_threaded_irq(dev, adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq,
+ NULL, adc5_gen3_isr, IRQF_ONESHOT | IRQF_SHARED,
+ adc->dev_data.base[ADC5_GEN3_VADC_SDAM].irq_name,
+ adc);
if (ret)
return ret;
diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h
index 6303eaa6640b..39cbfcbdb101 100644
--- a/include/linux/iio/adc/qcom-adc5-gen3-common.h
+++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h
@@ -205,7 +205,5 @@ int adc5_gen3_get_scaled_reading(struct device *dev,
int adc5_gen3_therm_code_to_temp(struct device *dev,
struct adc5_channel_common_prop *common_props,
u16 code, int *val);
-void adc5_gen3_register_tm_event_notifier(struct device *dev,
- void (*handler)(struct auxiliary_device *));
#endif /* QCOM_ADC5_GEN3_COMMON_H */
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
2026-07-22 11:26 [PATCH v4 0/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
2026-07-22 11:26 ` [PATCH v4 1/3] iio: adc: qcom-spmi-adc5-gen3: Remove an unnecessary print Jishnu Prakash
2026-07-22 11:26 ` [PATCH v4 2/3] iio: adc: qcom-spmi-adc5-gen3: Share SDAM0 IRQ with ADC_TM auxiliary driver Jishnu Prakash
@ 2026-07-22 11:26 ` Jishnu Prakash
2026-07-27 15:43 ` Daniel Lezcano
2026-08-07 12:06 ` [PATCH v4 0/3] " Daniel Lezcano
3 siblings, 1 reply; 9+ messages in thread
From: Jishnu Prakash @ 2026-07-22 11:26 UTC (permalink / raw)
To: Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold, Jishnu Prakash
Add support for ADC_TM part of PMIC5 Gen3 in an auxiliary driver
under the Gen3 ADC driver. Its functionality is similar to that of
PMIC5 Gen2 ADC_TM, which implements the threshold setting and
interrupt generating functions, used to support thermal trip points.
In Gen3 ADC, the register interface is implemented on one or more SDAM
(Shared Direct Access Memory) peripherals instead of dedicated ADC
peripherals. Each ADC SDAM has eight channels which can be configured
for either immediate reads (main ADC driver's functionality) or ADC_TM
reads. By convention, the first channel of the first ADC SDAM is reserved
for all immediate reads and remaining channels across all SDAMs are used
for ADC_TM functionality.
On the first SDAM, the interrupt line and configuration registers are
shared between the main ADC and auxiliary ADC_TM drivers. Access to the
registers is protected through a mutex shared between the drivers. The
ADC_TM driver accesses this mutex and some other functions shared from
the main driver (like adc5_gen3_get_scaled_reading() for immediate channel
reads in the .get_temp() callback) through APIs exported into a shared
namespace.
Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
---
drivers/thermal/qcom/Kconfig | 9 +
drivers/thermal/qcom/Makefile | 1 +
drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 434 ++++++++++++++++++++++++++
3 files changed, 444 insertions(+)
diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index a6bb01082ec6..1acb11e4ac80 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -21,6 +21,15 @@ config QCOM_SPMI_ADC_TM5
Thermal client sets threshold temperature for both warm and cool and
gets updated when a threshold is reached.
+config QCOM_SPMI_ADC_TM5_GEN3
+ tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3"
+ depends on QCOM_SPMI_ADC5_GEN3
+ help
+ This enables the auxiliary thermal driver for the ADC5 Gen3 thermal
+ monitoring device. It shows up as a thermal zone with multiple trip points.
+ Thermal client sets threshold temperature for both warm and cool and
+ gets updated when a threshold is reached.
+
config QCOM_SPMI_TEMP_ALARM
tristate "Qualcomm SPMI PMIC Temperature Alarm"
depends on OF && SPMI && IIO
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index 0fa2512042e7..828d9e7bc797 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
tsens-8960.o
obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
+obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o
obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
obj-$(CONFIG_QCOM_LMH) += lmh.o
diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
new file mode 100644
index 000000000000..9cf552d36868
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/device/devres.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/iio/adc/qcom-adc5-gen3-common.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/thermal.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include "../thermal_hwmon.h"
+
+#define ADC_TM5_GEN3_CONFIG_REGS 12
+
+struct device;
+struct adc_tm5_gen3_chip;
+
+/**
+ * struct adc_tm5_gen3_channel_props - ADC_TM channel structure
+ * @common_props: structure with common ADC channel properties.
+ * @chip: ADC TM device.
+ * @tzd: pointer to thermal device corresponding to TM channel.
+ * @sdam_index: SDAM on which this TM channel lies.
+ * @timer: time period of recurring TM measurement.
+ * @tm_chan_index: TM channel number used.
+ * @high_thr_en: TM high threshold crossing detection enabled.
+ * @low_thr_en: TM low threshold crossing detection enabled.
+ */
+struct adc_tm5_gen3_channel_props {
+ struct adc5_channel_common_prop common_props;
+ struct adc_tm5_gen3_chip *chip;
+ struct thermal_zone_device *tzd;
+ unsigned int sdam_index;
+ unsigned int timer;
+ unsigned int tm_chan_index;
+ bool high_thr_en;
+ bool low_thr_en;
+};
+
+/**
+ * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure
+ * @dev_data: Top-level ADC device data.
+ * @chan_props: Array of ADC_TM channel structures.
+ * @dev: SPMI ADC5 Gen3 device.
+ * @nchannels: number of TM channels allocated
+ */
+struct adc_tm5_gen3_chip {
+ struct adc5_device_data *dev_data;
+ struct adc_tm5_gen3_channel_props *chan_props;
+ struct device *dev;
+ unsigned int nchannels;
+};
+
+DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *,
+ adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev))
+
+static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq)
+{
+ for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
+ if (adc_tm5->dev_data->base[i].irq == irq)
+ return i;
+ }
+ return -ENOENT;
+}
+
+static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
+ int ret, sdam_num;
+ u8 tm_status[2];
+ u8 status, val;
+
+ sdam_num = get_sdam_from_irq(adc_tm5, irq);
+ if (sdam_num < 0)
+ return IRQ_NONE;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1,
+ &status, sizeof(status));
+ if (ret)
+ return IRQ_NONE;
+
+ if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
+ val = ADC5_GEN3_CONV_ERR_CLR_REQ;
+ adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num,
+ ADC5_GEN3_CONV_ERR_CLR, &val, 1);
+ return IRQ_HANDLED;
+ }
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS,
+ tm_status, sizeof(tm_status));
+ if (ret)
+ return IRQ_NONE;
+
+ if (tm_status[0] || tm_status[1])
+ return IRQ_WAKE_THREAD;
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
+ u8 tm_status[2];
+ int sdam_index;
+
+ sdam_index = get_sdam_from_irq(adc_tm5, irq);
+ if (sdam_index < 0)
+ return IRQ_NONE;
+
+ scoped_guard(adc5_gen3, adc_tm5) {
+ int ret;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS,
+ tm_status, sizeof(tm_status));
+ if (ret)
+ return IRQ_NONE;
+
+ ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index,
+ ADC5_GEN3_TM_HIGH_STS_CLR, tm_status,
+ sizeof(tm_status));
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i];
+ int offset = chan_prop->tm_chan_index;
+ bool upper_set, lower_set;
+
+ if (chan_prop->sdam_index != sdam_index)
+ continue;
+
+ upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en);
+ lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en);
+
+ if (!(upper_set || lower_set))
+ continue;
+
+ thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+ struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
+ struct adc_tm5_gen3_chip *adc_tm5;
+
+ if (!prop || !prop->chip)
+ return -EINVAL;
+
+ adc_tm5 = prop->chip;
+
+ return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp);
+}
+
+static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
+ int ret;
+ u8 val;
+
+ prop->high_thr_en = false;
+ prop->low_thr_en = false;
+
+ ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
+ if (ret)
+ return ret;
+
+ val = BIT(prop->tm_chan_index);
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = MEAS_INT_DISABLE;
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_TIMER_SEL, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ /* To indicate there is an actual conversion request */
+ val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_PERPH_CH, &val, sizeof(val));
+ if (ret)
+ return ret;
+
+ val = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_CONV_REQ, &val, sizeof(val));
+}
+
+static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop,
+ int low_temp, int high_temp)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
+ u8 buf[ADC_TM5_GEN3_CONFIG_REGS];
+ u8 conv_req;
+ u16 adc_code;
+ int ret;
+
+ ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
+ if (ret < 0)
+ return ret;
+
+ ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_SID, buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ /* Write SID */
+ buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid);
+
+ /* Select TM channel and indicate there is an actual conversion request */
+ buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
+
+ buf[2] = prop->timer;
+
+ /* Digital param selection */
+ adc5_gen3_update_dig_param(&prop->common_props, &buf[3]);
+
+ /* Update fast average sample value */
+ buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK,
+ prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN;
+
+ /* Select ADC channel */
+ buf[5] = prop->common_props.channel;
+
+ /* Select HW settle delay for channel */
+ buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
+ prop->common_props.hw_settle_time_us);
+
+ buf[7] = 0;
+
+ /* High temperature corresponds to low voltage threshold */
+ prop->low_thr_en = (high_temp != INT_MAX);
+ if (prop->low_thr_en) {
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp);
+ put_unaligned_le16(adc_code, &buf[8]);
+ buf[7] |= ADC5_GEN3_LOW_THR_INT_EN;
+ }
+
+ /* Low temperature corresponds to high voltage threshold */
+ prop->high_thr_en = (low_temp != -INT_MAX);
+ if (prop->high_thr_en) {
+ adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp);
+ put_unaligned_le16(adc_code, &buf[10]);
+ buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN;
+ }
+
+ ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID,
+ buf, sizeof(buf));
+ if (ret < 0)
+ return ret;
+
+ conv_req = ADC5_GEN3_CONV_REQ_REQ;
+ return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
+ ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
+}
+
+static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz,
+ int low_temp, int high_temp)
+{
+ struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
+ struct adc_tm5_gen3_chip *adc_tm5;
+
+ if (!prop || !prop->chip)
+ return -EINVAL;
+
+ adc_tm5 = prop->chip;
+
+ dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n",
+ prop->common_props.label, low_temp, high_temp);
+
+ guard(adc5_gen3)(adc_tm5);
+
+ return adc_tm5_gen3_configure(prop, low_temp, high_temp);
+}
+
+static const struct thermal_zone_device_ops adc_tm_ops = {
+ .get_temp = adc_tm5_gen3_get_temp,
+ .set_trips = adc_tm5_gen3_set_trip_temp,
+};
+
+static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5)
+{
+ struct thermal_zone_device *tzd;
+ unsigned int channel;
+ int ret;
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props);
+ tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel,
+ &adc_tm5->chan_props[i],
+ &adc_tm_ops);
+ if (IS_ERR(tzd)) {
+ if (PTR_ERR(tzd) == -ENODEV) {
+ dev_dbg(adc_tm5->dev,
+ "thermal sensor on channel %d is not used\n",
+ channel);
+ continue;
+ }
+ return PTR_ERR(tzd);
+ }
+ adc_tm5->chan_props[i].tzd = tzd;
+ ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void adc5_gen3_disable(void *data)
+{
+ struct adc_tm5_gen3_chip *adc_tm5 = data;
+
+ guard(adc5_gen3)(adc_tm5);
+
+ /* Disable all available TM channels */
+ for (int i = 0; i < adc_tm5->nchannels; i++)
+ adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]);
+}
+
+static int adc_tm5_probe(struct auxiliary_device *aux_dev,
+ const struct auxiliary_device_id *id)
+{
+ struct adc_tm5_gen3_chip *adc_tm5;
+ struct tm5_aux_dev_wrapper *aux_dev_wrapper;
+ struct device *dev = &aux_dev->dev;
+ int ret;
+
+ adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL);
+ if (!adc_tm5)
+ return -ENOMEM;
+
+ aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev);
+
+ adc_tm5->dev = dev;
+ adc_tm5->dev_data = aux_dev_wrapper->dev_data;
+ adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels;
+ adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels,
+ sizeof(*adc_tm5->chan_props), GFP_KERNEL);
+ if (!adc_tm5->chan_props)
+ return -ENOMEM;
+
+ for (int i = 0; i < adc_tm5->nchannels; i++) {
+ /*
+ * Since the first channel of the first SDAM is reserved for
+ * immediate ADC conversions, TM channel count must start from
+ * the channel just after it. The variable tm_count is used to
+ * calculate SDAM and TM channel index on that SDAM correctly
+ * for each TM channel.
+ */
+ int tm_count = i + 1;
+
+ adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i];
+ adc_tm5->chan_props[i].timer = MEAS_INT_1S;
+ adc_tm5->chan_props[i].sdam_index = tm_count / 8;
+ adc_tm5->chan_props[i].tm_chan_index = tm_count % 8;
+ adc_tm5->chan_props[i].chip = adc_tm5;
+ }
+
+ /*
+ * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as
+ * part of the set_trips calls during thermal zone registration. This
+ * action is to disable them all in case of probe failure.
+ */
+ ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5);
+ if (ret)
+ return ret;
+
+ ret = adc_tm5_register_tzd(adc_tm5);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
+ u32 irq_flags = IRQF_ONESHOT;
+
+ /*
+ * First SDAM's interrupt is shared between main ADC driver and
+ * auxiliary TM driver, so its flags must include IRQF_SHARED.
+ * This is not needed for other SDAMs as they will be used only
+ * for TM functionality.
+ */
+ if (i == 0)
+ irq_flags |= IRQF_SHARED;
+
+ ret = devm_request_threaded_irq(dev,
+ adc_tm5->dev_data->base[i].irq,
+ adctm5_gen3_isr,
+ adctm5_gen3_isr_thread,
+ irq_flags,
+ adc_tm5->dev_data->base[i].irq_name,
+ adc_tm5);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = {
+ { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" },
+ { }
+};
+MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table);
+
+static struct auxiliary_driver adctm5gen3_auxiliary_driver = {
+ .id_table = adctm5_auxiliary_id_table,
+ .probe = adc_tm5_probe,
+};
+module_auxiliary_driver(adctm5gen3_auxiliary_driver);
+
+MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
--
2.43.0
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
2026-07-22 11:26 ` [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
@ 2026-07-27 15:43 ` Daniel Lezcano
2026-08-05 10:47 ` Jishnu Prakash
2026-08-06 20:37 ` Andy Shevchenko
0 siblings, 2 replies; 9+ messages in thread
From: Daniel Lezcano @ 2026-07-27 15:43 UTC (permalink / raw)
To: Jishnu Prakash, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold
Hi,
Andy ? Are fine with these changes ?
On 7/22/26 13:26, Jishnu Prakash wrote:
> Add support for ADC_TM part of PMIC5 Gen3 in an auxiliary driver
> under the Gen3 ADC driver. Its functionality is similar to that of
> PMIC5 Gen2 ADC_TM, which implements the threshold setting and
> interrupt generating functions, used to support thermal trip points.
>
> In Gen3 ADC, the register interface is implemented on one or more SDAM
> (Shared Direct Access Memory) peripherals instead of dedicated ADC
> peripherals. Each ADC SDAM has eight channels which can be configured
> for either immediate reads (main ADC driver's functionality) or ADC_TM
> reads. By convention, the first channel of the first ADC SDAM is reserved
> for all immediate reads and remaining channels across all SDAMs are used
> for ADC_TM functionality.
>
> On the first SDAM, the interrupt line and configuration registers are
> shared between the main ADC and auxiliary ADC_TM drivers. Access to the
> registers is protected through a mutex shared between the drivers. The
> ADC_TM driver accesses this mutex and some other functions shared from
> the main driver (like adc5_gen3_get_scaled_reading() for immediate channel
> reads in the .get_temp() callback) through APIs exported into a shared
> namespace.
>
> Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
> ---
> drivers/thermal/qcom/Kconfig | 9 +
> drivers/thermal/qcom/Makefile | 1 +
> drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 434 ++++++++++++++++++++++++++
> 3 files changed, 444 insertions(+)
>
> diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
> index a6bb01082ec6..1acb11e4ac80 100644
> --- a/drivers/thermal/qcom/Kconfig
> +++ b/drivers/thermal/qcom/Kconfig
> @@ -21,6 +21,15 @@ config QCOM_SPMI_ADC_TM5
> Thermal client sets threshold temperature for both warm and cool and
> gets updated when a threshold is reached.
>
> +config QCOM_SPMI_ADC_TM5_GEN3
> + tristate "Qualcomm SPMI PMIC Thermal Monitor ADC5 Gen3"
> + depends on QCOM_SPMI_ADC5_GEN3
> + help
> + This enables the auxiliary thermal driver for the ADC5 Gen3 thermal
> + monitoring device. It shows up as a thermal zone with multiple trip points.
> + Thermal client sets threshold temperature for both warm and cool and
> + gets updated when a threshold is reached.
> +
> config QCOM_SPMI_TEMP_ALARM
> tristate "Qualcomm SPMI PMIC Temperature Alarm"
> depends on OF && SPMI && IIO
> diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
> index 0fa2512042e7..828d9e7bc797 100644
> --- a/drivers/thermal/qcom/Makefile
> +++ b/drivers/thermal/qcom/Makefile
> @@ -4,5 +4,6 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o
> qcom_tsens-y += tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
> tsens-8960.o
> obj-$(CONFIG_QCOM_SPMI_ADC_TM5) += qcom-spmi-adc-tm5.o
> +obj-$(CONFIG_QCOM_SPMI_ADC_TM5_GEN3) += qcom-spmi-adc-tm5-gen3.o
> obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o
> obj-$(CONFIG_QCOM_LMH) += lmh.o
> diff --git a/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
> new file mode 100644
> index 000000000000..9cf552d36868
> --- /dev/null
> +++ b/drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c
> @@ -0,0 +1,434 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/cleanup.h>
> +#include <linux/container_of.h>
> +#include <linux/device/devres.h>
> +#include <linux/dev_printk.h>
> +#include <linux/err.h>
> +#include <linux/iio/adc/qcom-adc5-gen3-common.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/thermal.h>
> +#include <linux/types.h>
> +#include <linux/unaligned.h>
> +
> +#include "../thermal_hwmon.h"
> +
> +#define ADC_TM5_GEN3_CONFIG_REGS 12
> +
> +struct device;
> +struct adc_tm5_gen3_chip;
> +
> +/**
> + * struct adc_tm5_gen3_channel_props - ADC_TM channel structure
> + * @common_props: structure with common ADC channel properties.
> + * @chip: ADC TM device.
> + * @tzd: pointer to thermal device corresponding to TM channel.
> + * @sdam_index: SDAM on which this TM channel lies.
> + * @timer: time period of recurring TM measurement.
> + * @tm_chan_index: TM channel number used.
> + * @high_thr_en: TM high threshold crossing detection enabled.
> + * @low_thr_en: TM low threshold crossing detection enabled.
> + */
> +struct adc_tm5_gen3_channel_props {
> + struct adc5_channel_common_prop common_props;
> + struct adc_tm5_gen3_chip *chip;
> + struct thermal_zone_device *tzd;
> + unsigned int sdam_index;
> + unsigned int timer;
> + unsigned int tm_chan_index;
> + bool high_thr_en;
> + bool low_thr_en;
> +};
> +
> +/**
> + * struct adc_tm5_gen3_chip - ADC Thermal Monitoring device structure
> + * @dev_data: Top-level ADC device data.
> + * @chan_props: Array of ADC_TM channel structures.
> + * @dev: SPMI ADC5 Gen3 device.
> + * @nchannels: number of TM channels allocated
> + */
> +struct adc_tm5_gen3_chip {
> + struct adc5_device_data *dev_data;
> + struct adc_tm5_gen3_channel_props *chan_props;
> + struct device *dev;
> + unsigned int nchannels;
> +};
> +
> +DEFINE_GUARD(adc5_gen3, struct adc_tm5_gen3_chip *,
> + adc5_gen3_mutex_lock(_T->dev), adc5_gen3_mutex_unlock(_T->dev))
> +
> +static int get_sdam_from_irq(struct adc_tm5_gen3_chip *adc_tm5, int irq)
> +{
> + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
> + if (adc_tm5->dev_data->base[i].irq == irq)
> + return i;
> + }
> + return -ENOENT;
> +}
> +
> +static irqreturn_t adctm5_gen3_isr(int irq, void *dev_id)
> +{
> + struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
> + int ret, sdam_num;
> + u8 tm_status[2];
> + u8 status, val;
> +
> + sdam_num = get_sdam_from_irq(adc_tm5, irq);
> + if (sdam_num < 0)
> + return IRQ_NONE;
> +
> + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_STATUS1,
> + &status, sizeof(status));
> + if (ret)
> + return IRQ_NONE;
> +
> + if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
> + val = ADC5_GEN3_CONV_ERR_CLR_REQ;
> + adc5_gen3_status_clear(adc_tm5->dev_data, sdam_num,
> + ADC5_GEN3_CONV_ERR_CLR, &val, 1);
> + return IRQ_HANDLED;
> + }
> +
> + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_num, ADC5_GEN3_TM_HIGH_STS,
> + tm_status, sizeof(tm_status));
> + if (ret)
> + return IRQ_NONE;
> +
> + if (tm_status[0] || tm_status[1])
> + return IRQ_WAKE_THREAD;
> +
> + return IRQ_NONE;
> +}
> +
> +static irqreturn_t adctm5_gen3_isr_thread(int irq, void *dev_id)
> +{
> + struct adc_tm5_gen3_chip *adc_tm5 = dev_id;
> + u8 tm_status[2];
> + int sdam_index;
> +
> + sdam_index = get_sdam_from_irq(adc_tm5, irq);
> + if (sdam_index < 0)
> + return IRQ_NONE;
> +
> + scoped_guard(adc5_gen3, adc_tm5) {
> + int ret;
> +
> + ret = adc5_gen3_read(adc_tm5->dev_data, sdam_index, ADC5_GEN3_TM_HIGH_STS,
> + tm_status, sizeof(tm_status));
> + if (ret)
> + return IRQ_NONE;
> +
> + ret = adc5_gen3_status_clear(adc_tm5->dev_data, sdam_index,
> + ADC5_GEN3_TM_HIGH_STS_CLR, tm_status,
> + sizeof(tm_status));
> + if (ret)
> + return IRQ_NONE;
> + }
> +
> + for (int i = 0; i < adc_tm5->nchannels; i++) {
> + struct adc_tm5_gen3_channel_props *chan_prop = &adc_tm5->chan_props[i];
> + int offset = chan_prop->tm_chan_index;
> + bool upper_set, lower_set;
> +
> + if (chan_prop->sdam_index != sdam_index)
> + continue;
> +
> + upper_set = ((tm_status[0] & BIT(offset)) && chan_prop->high_thr_en);
> + lower_set = ((tm_status[1] & BIT(offset)) && chan_prop->low_thr_en);
> +
> + if (!(upper_set || lower_set))
> + continue;
> +
> + thermal_zone_device_update(chan_prop->tzd, THERMAL_TRIP_VIOLATED);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int adc_tm5_gen3_get_temp(struct thermal_zone_device *tz, int *temp)
> +{
> + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
> + struct adc_tm5_gen3_chip *adc_tm5;
> +
> + if (!prop || !prop->chip)
> + return -EINVAL;
> +
> + adc_tm5 = prop->chip;
> +
> + return adc5_gen3_get_scaled_reading(adc_tm5->dev, &prop->common_props, temp);
> +}
> +
> +static int adc_tm5_gen3_disable_channel(struct adc_tm5_gen3_channel_props *prop)
> +{
> + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
> + int ret;
> + u8 val;
> +
> + prop->high_thr_en = false;
> + prop->low_thr_en = false;
> +
> + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
> + if (ret)
> + return ret;
> +
> + val = BIT(prop->tm_chan_index);
> + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_TM_HIGH_STS_CLR, &val, sizeof(val));
> + if (ret)
> + return ret;
> +
> + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_TM_LOW_STS_CLR, &val, sizeof(val));
> + if (ret)
> + return ret;
> +
> + val = MEAS_INT_DISABLE;
> + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_TIMER_SEL, &val, sizeof(val));
> + if (ret)
> + return ret;
> +
> + /* To indicate there is an actual conversion request */
> + val = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
> + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_PERPH_CH, &val, sizeof(val));
> + if (ret)
> + return ret;
> +
> + val = ADC5_GEN3_CONV_REQ_REQ;
> + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_CONV_REQ, &val, sizeof(val));
> +}
> +
> +static int adc_tm5_gen3_configure(struct adc_tm5_gen3_channel_props *prop,
> + int low_temp, int high_temp)
> +{
> + struct adc_tm5_gen3_chip *adc_tm5 = prop->chip;
> + u8 buf[ADC_TM5_GEN3_CONFIG_REGS];
> + u8 conv_req;
> + u16 adc_code;
> + int ret;
> +
> + ret = adc5_gen3_poll_wait_hs(adc_tm5->dev_data, prop->sdam_index);
> + if (ret < 0)
> + return ret;
> +
> + ret = adc5_gen3_read(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_SID, buf, sizeof(buf));
> + if (ret < 0)
> + return ret;
> +
> + /* Write SID */
> + buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->common_props.sid);
> +
> + /* Select TM channel and indicate there is an actual conversion request */
> + buf[1] = ADC5_GEN3_CHAN_CONV_REQ | prop->tm_chan_index;
> +
> + buf[2] = prop->timer;
> +
> + /* Digital param selection */
> + adc5_gen3_update_dig_param(&prop->common_props, &buf[3]);
> +
> + /* Update fast average sample value */
> + buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK,
> + prop->common_props.avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN;
> +
> + /* Select ADC channel */
> + buf[5] = prop->common_props.channel;
> +
> + /* Select HW settle delay for channel */
> + buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
> + prop->common_props.hw_settle_time_us);
> +
> + buf[7] = 0;
> +
> + /* High temperature corresponds to low voltage threshold */
> + prop->low_thr_en = (high_temp != INT_MAX);
> + if (prop->low_thr_en) {
> + adc_code = qcom_adc_tm5_gen2_temp_res_scale(high_temp);
> + put_unaligned_le16(adc_code, &buf[8]);
> + buf[7] |= ADC5_GEN3_LOW_THR_INT_EN;
> + }
> +
> + /* Low temperature corresponds to high voltage threshold */
> + prop->high_thr_en = (low_temp != -INT_MAX);
> + if (prop->high_thr_en) {
> + adc_code = qcom_adc_tm5_gen2_temp_res_scale(low_temp);
> + put_unaligned_le16(adc_code, &buf[10]);
> + buf[7] |= ADC5_GEN3_HIGH_THR_INT_EN;
> + }
> +
> + ret = adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index, ADC5_GEN3_SID,
> + buf, sizeof(buf));
> + if (ret < 0)
> + return ret;
> +
> + conv_req = ADC5_GEN3_CONV_REQ_REQ;
> + return adc5_gen3_write(adc_tm5->dev_data, prop->sdam_index,
> + ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
> +}
> +
> +static int adc_tm5_gen3_set_trip_temp(struct thermal_zone_device *tz,
> + int low_temp, int high_temp)
> +{
> + struct adc_tm5_gen3_channel_props *prop = thermal_zone_device_priv(tz);
> + struct adc_tm5_gen3_chip *adc_tm5;
> +
> + if (!prop || !prop->chip)
> + return -EINVAL;
> +
> + adc_tm5 = prop->chip;
> +
> + dev_dbg(adc_tm5->dev, "channel:%s, low_temp(mdegC):%d, high_temp(mdegC):%d\n",
> + prop->common_props.label, low_temp, high_temp);
> +
> + guard(adc5_gen3)(adc_tm5);
> +
> + return adc_tm5_gen3_configure(prop, low_temp, high_temp);
> +}
> +
> +static const struct thermal_zone_device_ops adc_tm_ops = {
> + .get_temp = adc_tm5_gen3_get_temp,
> + .set_trips = adc_tm5_gen3_set_trip_temp,
> +};
> +
> +static int adc_tm5_register_tzd(struct adc_tm5_gen3_chip *adc_tm5)
> +{
> + struct thermal_zone_device *tzd;
> + unsigned int channel;
> + int ret;
> +
> + for (int i = 0; i < adc_tm5->nchannels; i++) {
> + channel = ADC5_GEN3_V_CHAN(adc_tm5->chan_props[i].common_props);
> + tzd = devm_thermal_of_zone_register(adc_tm5->dev, channel,
> + &adc_tm5->chan_props[i],
> + &adc_tm_ops);
> + if (IS_ERR(tzd)) {
> + if (PTR_ERR(tzd) == -ENODEV) {
> + dev_dbg(adc_tm5->dev,
> + "thermal sensor on channel %d is not used\n",
> + channel);
> + continue;
> + }
> + return PTR_ERR(tzd);
> + }
> + adc_tm5->chan_props[i].tzd = tzd;
> + ret = devm_thermal_add_hwmon_sysfs(adc_tm5->dev, tzd);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void adc5_gen3_disable(void *data)
> +{
> + struct adc_tm5_gen3_chip *adc_tm5 = data;
> +
> + guard(adc5_gen3)(adc_tm5);
> +
> + /* Disable all available TM channels */
> + for (int i = 0; i < adc_tm5->nchannels; i++)
> + adc_tm5_gen3_disable_channel(&adc_tm5->chan_props[i]);
> +}
> +
> +static int adc_tm5_probe(struct auxiliary_device *aux_dev,
> + const struct auxiliary_device_id *id)
> +{
> + struct adc_tm5_gen3_chip *adc_tm5;
> + struct tm5_aux_dev_wrapper *aux_dev_wrapper;
> + struct device *dev = &aux_dev->dev;
> + int ret;
> +
> + adc_tm5 = devm_kzalloc(dev, sizeof(*adc_tm5), GFP_KERNEL);
> + if (!adc_tm5)
> + return -ENOMEM;
> +
> + aux_dev_wrapper = container_of(aux_dev, struct tm5_aux_dev_wrapper, aux_dev);
> +
> + adc_tm5->dev = dev;
> + adc_tm5->dev_data = aux_dev_wrapper->dev_data;
> + adc_tm5->nchannels = aux_dev_wrapper->n_tm_channels;
> + adc_tm5->chan_props = devm_kcalloc(dev, aux_dev_wrapper->n_tm_channels,
> + sizeof(*adc_tm5->chan_props), GFP_KERNEL);
> + if (!adc_tm5->chan_props)
> + return -ENOMEM;
> +
> + for (int i = 0; i < adc_tm5->nchannels; i++) {
> + /*
> + * Since the first channel of the first SDAM is reserved for
> + * immediate ADC conversions, TM channel count must start from
> + * the channel just after it. The variable tm_count is used to
> + * calculate SDAM and TM channel index on that SDAM correctly
> + * for each TM channel.
> + */
> + int tm_count = i + 1;
> +
> + adc_tm5->chan_props[i].common_props = aux_dev_wrapper->tm_props[i];
> + adc_tm5->chan_props[i].timer = MEAS_INT_1S;
> + adc_tm5->chan_props[i].sdam_index = tm_count / 8;
> + adc_tm5->chan_props[i].tm_chan_index = tm_count % 8;
> + adc_tm5->chan_props[i].chip = adc_tm5;
> + }
> +
> + /*
> + * ADC_TM channels are enabled in the loop in adc_tm5_register_tzd() as
> + * part of the set_trips calls during thermal zone registration. This
> + * action is to disable them all in case of probe failure.
> + */
> + ret = devm_add_action(dev, adc5_gen3_disable, adc_tm5);
> + if (ret)
> + return ret;
> +
> + ret = adc_tm5_register_tzd(adc_tm5);
> + if (ret)
> + return ret;
> +
> + for (int i = 0; i < adc_tm5->dev_data->num_sdams; i++) {
> + u32 irq_flags = IRQF_ONESHOT;
> +
> + /*
> + * First SDAM's interrupt is shared between main ADC driver and
> + * auxiliary TM driver, so its flags must include IRQF_SHARED.
> + * This is not needed for other SDAMs as they will be used only
> + * for TM functionality.
> + */
> + if (i == 0)
> + irq_flags |= IRQF_SHARED;
> +
> + ret = devm_request_threaded_irq(dev,
> + adc_tm5->dev_data->base[i].irq,
> + adctm5_gen3_isr,
> + adctm5_gen3_isr_thread,
> + irq_flags,
> + adc_tm5->dev_data->base[i].irq_name,
> + adc_tm5);
> + if (ret < 0)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct auxiliary_device_id adctm5_auxiliary_id_table[] = {
> + { .name = "qcom_spmi_adc5_gen3.adc5_tm_gen3" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(auxiliary, adctm5_auxiliary_id_table);
> +
> +static struct auxiliary_driver adctm5gen3_auxiliary_driver = {
> + .id_table = adctm5_auxiliary_id_table,
> + .probe = adc_tm5_probe,
> +};
> +module_auxiliary_driver(adctm5gen3_auxiliary_driver);
> +
> +MODULE_DESCRIPTION("SPMI PMIC Thermal Monitor ADC driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
2026-07-27 15:43 ` Daniel Lezcano
@ 2026-08-05 10:47 ` Jishnu Prakash
2026-08-06 20:37 ` Andy Shevchenko
1 sibling, 0 replies; 9+ messages in thread
From: Jishnu Prakash @ 2026-08-05 10:47 UTC (permalink / raw)
To: Daniel Lezcano, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Andy Shevchenko
Cc: linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold, Andy Shevchenko
On 7/27/2026 9:13 PM, Daniel Lezcano wrote:
>
> Hi,
>
> Andy ? Are fine with these changes ?
>
Hi Andy,
I have addressed all your V3 comments in this V4 patch.
Can you please check and confirm if it looks fine to you ?
Thanks,
Jishnu
>
>
> On 7/22/26 13:26, Jishnu Prakash wrote:
>> Add support for ADC_TM part of PMIC5 Gen3 in an auxiliary driver
>> under the Gen3 ADC driver. Its functionality is similar to that of
>> PMIC5 Gen2 ADC_TM, which implements the threshold setting and
>> interrupt generating functions, used to support thermal trip points.
>>
>> In Gen3 ADC, the register interface is implemented on one or more SDAM
>> (Shared Direct Access Memory) peripherals instead of dedicated ADC
>> peripherals. Each ADC SDAM has eight channels which can be configured
>> for either immediate reads (main ADC driver's functionality) or ADC_TM
>> reads. By convention, the first channel of the first ADC SDAM is reserved
>> for all immediate reads and remaining channels across all SDAMs are used
>> for ADC_TM functionality.
>>
>> On the first SDAM, the interrupt line and configuration registers are
>> shared between the main ADC and auxiliary ADC_TM drivers. Access to the
>> registers is protected through a mutex shared between the drivers. The
>> ADC_TM driver accesses this mutex and some other functions shared from
>> the main driver (like adc5_gen3_get_scaled_reading() for immediate channel
>> reads in the .get_temp() callback) through APIs exported into a shared
>> namespace.
>>
>> Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
>> ---
>> drivers/thermal/qcom/Kconfig | 9 +
>> drivers/thermal/qcom/Makefile | 1 +
>> drivers/thermal/qcom/qcom-spmi-adc-tm5-gen3.c | 434 ++++++++++++++++++++++++++
>> 3 files changed, 444 insertions(+)
>>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
2026-07-27 15:43 ` Daniel Lezcano
2026-08-05 10:47 ` Jishnu Prakash
@ 2026-08-06 20:37 ` Andy Shevchenko
2026-08-07 12:01 ` Daniel Lezcano
1 sibling, 1 reply; 9+ messages in thread
From: Andy Shevchenko @ 2026-08-06 20:37 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Jishnu Prakash, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold
On Mon, Jul 27, 2026 at 05:43:22PM +0200, Daniel Lezcano wrote:
>
> Andy ? Are fine with these changes ?
I think yes, sorry was on vacation. Will be at full speed somewhere next week.
> On 7/22/26 13:26, Jishnu Prakash wrote:
Formally you may use my
Acked-by: Andy Shevchenko <andriy.shevchenko@intel.com>
or wait for a better review (1400+ new msgs for 2 weeks of vacation in my mailbox).
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
2026-08-06 20:37 ` Andy Shevchenko
@ 2026-08-07 12:01 ` Daniel Lezcano
0 siblings, 0 replies; 9+ messages in thread
From: Daniel Lezcano @ 2026-08-07 12:01 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Jishnu Prakash, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold
Hi Andy,
On 8/6/26 22:37, Andy Shevchenko wrote:
> On Mon, Jul 27, 2026 at 05:43:22PM +0200, Daniel Lezcano wrote:
>>
>> Andy ? Are fine with these changes ?
>
> I think yes, sorry was on vacation. Will be at full speed somewhere next week.
>
>> On 7/22/26 13:26, Jishnu Prakash wrote:
>
> Formally you may use my
> Acked-by: Andy Shevchenko <andriy.shevchenko@intel.com>
>
> or wait for a better review (1400+ new msgs for 2 weeks of vacation in my mailbox).
I'll pick the patches, Rafael is expecting a PR soon
Thanks
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v4 0/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring
2026-07-22 11:26 [PATCH v4 0/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
` (2 preceding siblings ...)
2026-07-22 11:26 ` [PATCH v4 3/3] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash
@ 2026-08-07 12:06 ` Daniel Lezcano
3 siblings, 0 replies; 9+ messages in thread
From: Daniel Lezcano @ 2026-08-07 12:06 UTC (permalink / raw)
To: Jishnu Prakash, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Amit Kucheria, Thara Gopinath,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba
Cc: linux-arm-msm, linux-iio, linux-kernel, linux-pm, Kamal Wadhwa,
David Collins, Anjelique Melendez, Neil Armstrong,
Stephan Gerhold, Andy Shevchenko, Maxwell Doose, Joshua Crofts
On 7/22/26 13:26, Jishnu Prakash wrote:
> Support for the main PMIC5 Gen3 ADC driver has been merged now.
>
> In order to complete adding support for the Gen3 ADC_TM auxiliary
> driver, some more changes are needed in the main driver, mainly to
> improve its shared interrupt's handling mechanism.
>
> Patch 1 removes an unneeded print in the main ADC driver.
>
> Patch 2 simplifies the interrupt handling in the main ADC driver
> by declaring the interrupt as a shared one and dropping the logic
> used to call the ADC_TM notifier for handling ADC_TM interrupts on
> the first SDAM whose interrupt is shared.
>
> Patch 3 adds the auxiliary thermal driver which supports the ADC_TM
> functionality of ADC5 Gen3.
>
> Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
> ---
Applied, thanks
^ permalink raw reply [flat|nested] 9+ messages in thread