public inbox for linux-pm@vger.kernel.org
 help / color / mirror / Atom feed
From: Jonathan Cameron <jic23@kernel.org>
To: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
	agross@kernel.org, andersson@kernel.org, lumag@kernel.org,
	dmitry.baryshkov@oss.qualcomm.com, konradybcio@kernel.org,
	daniel.lezcano@linaro.org, sboyd@kernel.org, amitk@kernel.org,
	thara.gopinath@gmail.com, lee@kernel.org, rafael@kernel.org,
	subbaraman.narayanamurthy@oss.qualcomm.com,
	david.collins@oss.qualcomm.com,
	anjelique.melendez@oss.qualcomm.com,
	kamal.wadhwa@oss.qualcomm.com, rui.zhang@intel.com,
	lukasz.luba@arm.com, devicetree@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, linux-iio@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org,
	cros-qcom-dts-watchers@chromium.org, quic_kotarake@quicinc.com,
	neil.armstrong@linaro.org, stephan.gerhold@linaro.org
Subject: Re: [PATCH V11 3/4] iio: adc: Add support for QCOM PMIC5 Gen3 ADC
Date: Sat, 28 Feb 2026 19:39:46 +0000	[thread overview]
Message-ID: <20260228193946.2e60ea5e@jic23-huawei> (raw)
In-Reply-To: <20260209105438.596339-4-jishnu.prakash@oss.qualcomm.com>

On Mon,  9 Feb 2026 16:24:37 +0530
Jishnu Prakash <jishnu.prakash@oss.qualcomm.com> wrote:

> The ADC architecture on PMIC5 Gen3 is similar to that on PMIC5 Gen2,
> with all SW communication to ADC going through PMK8550 which
> communicates with other PMICs through PBS.
> 
> One major difference is that the register interface used here is that
> of an SDAM (Shared Direct Access Memory) peripheral present on PMK8550.
> There may be more than one SDAM used for ADC5 Gen3 and each has eight
> channels, which may be used for either immediate reads (same functionality
> as previous PMIC5 and PMIC5 Gen2 ADC peripherals) or recurring measurements
> (same as ADC_TM functionality).
> 
> By convention, we reserve the first channel of the first SDAM for all
> immediate reads and use the remaining channels across all SDAMs for
> ADC_TM monitoring functionality.
> 
> Add support for PMIC5 Gen3 ADC driver for immediate read functionality.
> ADC_TM is implemented as an auxiliary thermal driver under this ADC
> driver.
> 
> Signed-off-by: Jishnu Prakash <jishnu.prakash@oss.qualcomm.com>
Ah. I just realized I built the immutable branch from v10 not this.
I wasn't seeing any review outstanding on that version and didn't read your
email closely enough :(

I'd rather not cause more confusion by ripping it down and creating a new
one.  From a quick look none of the changes since v10 look vital.
Perhaps we can spin a follow up patch to pull into IIO to make those
tweaks if you want them?  That way the immutable branch remains
the same.

Thanks,

Jonathan


> ---
> Changes since v10:
> - Made following changes to address Jonathan's comments:
>   - Replaced inclusion of device.h header file in 
>     include/linux/iio/adc/qcom-adc5-gen3-common.h with forwards declaration
>     of struct device.
>   - Updated all for() loops to declare and initialize the loop iterator
>     inside the for() loop statement.
>   - Moved initializations for drvdata, completion struct and mutex within
>     adc5_gen3_probe().
> 
> Changes since v8:
> - Dropped the common module (drivers/iio/adc/qcom-adc5-gen3-common.c) and moved
>   all of its contents to drivers/iio/adc/qcom-spmi-adc5-gen3.c as suggested by Dmitry.
> - Made following changes to address Dmitry's comment to use module_auxiliary_driver()
>   in auxiliary driver patch, by simplifying auxiliary device structures:
>   - Added function pointer for TM interrupt handler callback under struct adc5_chip
>     (to be called in case of TM interrupt on first SDAM), to replace the
>     tm_event_notify() callback.
>   - Add new exported function (adc5_gen3_register_tm_event_notifier()) to be called
>     by TM auxiliary driver in its probe to initialize the above callback function.
>   - Updated adc5_gen3_isr() to call this TM callback function instead of
>     tm_event_notify() callback from the wrapper struct adc_tm5_auxiliary_drv.
>   - Completely dropped the above wrapper struct definition.
> - Made following changes to address Jonathan's comments:
>   - Updated header files included in drivers/iio/adc/qcom-spmi-adc5-gen3.c and 
>     include/linux/iio/adc/qcom-adc5-gen3-common.h to follow IWYU (include-what-you-use)
>     principles.
>   - Dropped comment inside adc5_chip struct definition describing mutex lock.
> - Dropped ADC5_GEN3_TEMP_ALARM_LITE channel as it had some inaccuracy issue, which
>   is being debugged internally. Will add it in a separate patch along with channel user.
> - Replaced dev_err() with dev_err_probe() in adc5_get_fw_data.
> 
> Changes since v7:
> - Addressed following comments from Jonathan:
>   - Included regmap header file in drivers/iio/adc/qcom-adc5-gen3-common.c.
>   - Increased comment wrap length in adc5_gen3_configure() and 
>     struct adc5_chip definition.
>   - Updated error checks in adc5_gen3_isr() to remove NULL check for
>     adrv_tm and keep (!adrv_tm->tm_event_notify) error check alone
> 	  within if() condition.
>   - Removed sid initialization in adc5_gen3_get_fw_channel_data()
> - Added definitions for ADC channel macros used in adc5_gen3_chans_pmic[]
>   in include/linux/iio/adc/qcom-adc5-gen3-common.h instead of 
>   include/dt-bindings/iio/adc/qcom,spmi-vadc.h, as this latter file
>   will be moved out of bindings folder in a separate change. Also
>   removed its inclusion in drivers/iio/adc/qcom-spmi-adc5-gen3.c.
> - Cleaned up local variable declarations in adc5_gen3_isr() and
>   adc5_gen3_get_fw_channel_data() and added local variable for
>   adc->dev in adc5_get_fw_data().
> - Fixed error message after platform_get_irq() call in adc5_gen3_probe()
>   to print IRQ number correctly.
> - Added a check in adc5_gen3_get_fw_channel_data() to exit with error
>   if ADC channel value obtained from `reg` channel property is not
>   among the supported ones in the array adc5_gen3_chans_pmic[].
> - Corrected the value used in checking for max valid ADC channel value,
>   in adc5_gen3_get_fw_channel_data().
> 
> Changes since v6:
> - Addressed following comments from Jonathan:
>   - Moved functions exported in drivers/iio/adc/qcom-adc5-gen3-common.c
>     into namespace "QCOM_SPMI_ADC5_GEN3".
>   - Increased line wrap length for comments.
>   - Added local variable for adc->dev in adc5_gen3_isr().
>   - Shifted debug print showing IRQ status registers in adc5_gen3_isr()
>     to before tm_status[] check.
>   - Fixed indentation and brackets in adc5_gen3_get_fw_channel_data().
>   - Cleaned up array formatting in adc5_gen3_data_pmic struct.
>   - Used scoped variant of device_for_each_child_node() in adc5_get_fw_data().
>   - Updated auxiliary device cleanup handling to fix memory freeing
>     issues, by adding empty auxiliary device release function.
>   - Used devm_mutex_init() in adc5_gen3_probe().
>   - Updated virtual channel macro name from V_CHAN to ADC5_GEN3_V_CHAN.
>   - Set IIO device name to "spmi-adc5-gen3".
> - Added __acquires and __releases macros for exported mutex lock
>   and unlock functions in drivers/iio/adc/qcom-spmi-adc5-gen3.c.
> - Added error check to fail probe in case adding auxiliary TM device fails.
> - Replaced 2025 copyright in newly added files with yearless copyright,
>   following new internal guidelines.
> 
> Changes since v5:
> - Addressed following comments from Jonathan:
>   - Corrected line wrap length in Kconfig and driver files.
>   - Replaced usleep_range() with fsleep() in adc5_gen3_poll_wait_hs()
>   - Corrected all files to follow kernel-doc formatting fully.
>   - Removed IIO_CHAN_INFO_RAW case in adc5_gen3_read_raw()
>   - Cleaned up formatting in adc5_gen3_data_pmic struct and in other
>     struct definitions.
>   - Updated adc5_gen3_add_aux_tm_device() to keep errors alone out of line.
>   - Split mutex function exported to ADC_TM driver into separate functions
>     for acquiring and releasing mutex.
>   - Removed num_sdams member from struct adc5_chip.
>   - Fixed dev_err_probe() print in adc5_gen3_probe().
>   - Updated logic for acquiring IRQ numbers to account for removing
>     "interrupt-names" DT property.
> - Included bitfield.h header file in drivers/iio/adc/qcom-adc5-gen3-common.c
>   to fix kernel bot error.
> 
> Changes since v4:
> - Moved out common funtions from newly added .h file into a separate .c
>   file to avoid duplicating them and updated interrupt name, as suggested
>   by Krzysztof. Updated namespace export symbol statement to have a string
>   as second argument to follow framework change.
> 
> Changes since v3:
> - Split out TM functionality into auxiliary driver in separate patch and
>   added required changes in main driver, as suggested by Dmitry.
> - Addressed other reviewer comments in main driver patch. 
> 
> Changes since v1:
> - Removed datashet_name usage and implemented read_label() function
> - In probe, updated channel property in iio_chan_spec from individual
>   channel to virtual channel and set indexed property to 1, due to the
>   above change.
> - Updated order of checks in ISR
> - Removed the driver remove callback and replaced with callbacks in a
>   devm_add_action call in probe.
> - Addressed other comments from reviewers.
> 
>  drivers/iio/adc/Kconfig                       |  26 +
>  drivers/iio/adc/Makefile                      |   1 +
>  drivers/iio/adc/qcom-spmi-adc5-gen3.c         | 860 ++++++++++++++++++
>  include/linux/iio/adc/qcom-adc5-gen3-common.h | 212 +++++
>  4 files changed, 1099 insertions(+)
>  create mode 100644 drivers/iio/adc/qcom-spmi-adc5-gen3.c
>  create mode 100644 include/linux/iio/adc/qcom-adc5-gen3-common.h
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 58da8255525e..5300e9236ba8 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1329,6 +1329,32 @@ config QCOM_SPMI_ADC5
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called qcom-spmi-adc5.
>  
> +config QCOM_SPMI_ADC5_GEN3
> +	tristate "Qualcomm Technologies Inc. SPMI PMIC5 GEN3 ADC"
> +	depends on SPMI && THERMAL
> +	select REGMAP_SPMI
> +	select QCOM_VADC_COMMON
> +	select AUXILIARY_BUS
> +	help
> +	  IIO Voltage PMIC5 Gen3 ADC driver for Qualcomm Technologies Inc.
> +
> +	  The driver supports reading multiple channels. The ADC is a 16-bit
> +	  sigma-delta ADC. The hardware supports calibrated results for
> +	  conversion requests and clients include reading phone power supply
> +	  voltage, on board system thermistors connected to the PMIC ADC,
> +	  PMIC die temperature, charger temperature, battery current, USB
> +	  voltage input and voltage signals connected to supported PMIC GPIO
> +	  pins. The hardware supports internal pull-up for thermistors and can
> +	  choose between a 30k, 100k or 400k ohm pull up using the ADC channels.
> +
> +	  In addition, the same driver supports ADC thermal monitoring devices
> +	  too. They appear as thermal zones with multiple trip points. A thermal
> +	  client sets threshold temperature for both warm and cool trips and
> +	  gets updated when a threshold is reached.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called qcom-spmi-adc5-gen3.
> +
>  config RCAR_GYRO_ADC
>  	tristate "Renesas R-Car GyroADC driver"
>  	depends on ARCH_RCAR_GEN2 || COMPILE_TEST
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 7cc8f9a12f76..cb1874fd7912 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -113,6 +113,7 @@ obj-$(CONFIG_PAC1934) += pac1934.o
>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>  obj-$(CONFIG_QCOM_PM8XXX_XOADC) += qcom-pm8xxx-xoadc.o
>  obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
> +obj-$(CONFIG_QCOM_SPMI_ADC5_GEN3) += qcom-spmi-adc5-gen3.o
>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>  obj-$(CONFIG_QCOM_SPMI_RRADC) += qcom-spmi-rradc.o
>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> diff --git a/drivers/iio/adc/qcom-spmi-adc5-gen3.c b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
> new file mode 100644
> index 000000000000..76ea9bde6168
> --- /dev/null
> +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
> @@ -0,0 +1,860 @@
> +// 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/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/device/devres.h>
> +#include <linux/dev_printk.h>
> +#include <linux/err.h>
> +#include <linux/export.h>
> +#include <linux/iio/adc/qcom-adc5-gen3-common.h>
> +#include <linux/iio/iio.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +#include <linux/unaligned.h>
> +
> +#define ADC5_GEN3_VADC_SDAM			0x0
> +
> +struct adc5_chip;
> +
> +/**
> + * struct adc5_channel_prop - ADC channel structure
> + * @common_props: structure with ADC channel properties (common to TM usage).
> + * @adc_tm: indicates TM type if the channel is used for TM measurements.
> + * @chip: pointer to top-level ADC device structure.
> + */
> +struct adc5_channel_prop {
> +	struct adc5_channel_common_prop common_props;
> +	int adc_tm;
> +	struct adc5_chip *chip;
> +};
> +
> +/**
> + * struct adc5_chip - ADC private structure.
> + * @dev: SPMI ADC5 Gen3 device.
> + * @dev_data: Top-level ADC device data.
> + * @nchannels: number of ADC channels.
> + * @chan_props: array of ADC channel properties.
> + * @iio_chans: array of IIO channels specification.
> + * @complete: ADC result notification after interrupt is received.
> + * @lock: ADC lock for access to the peripheral, to prevent concurrent
> + *	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;
> +	struct adc5_device_data dev_data;
> +	unsigned int nchannels;
> +	struct adc5_channel_prop *chan_props;
> +	struct iio_chan_spec *iio_chans;
> +	struct completion complete;
> +	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,
> +		   u16 offset, u8 *data, int len)
> +{
> +	return regmap_bulk_read(adc->regmap,
> +				adc->base[sdam_index].base_addr + offset,
> +				data, len);
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_read, "QCOM_SPMI_ADC5_GEN3");
> +
> +int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index,
> +		    u16 offset, u8 *data, int len)
> +{
> +	return regmap_bulk_write(adc->regmap,
> +				 adc->base[sdam_index].base_addr + offset,
> +				 data, len);
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_write, "QCOM_SPMI_ADC5_GEN3");
> +
> +static int adc5_gen3_read_voltage_data(struct adc5_chip *adc, u16 *data)
> +{
> +	u8 rslt[2];
> +	int ret;
> +
> +	ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
> +			     ADC5_GEN3_CH_DATA0(0), rslt, sizeof(rslt));
> +	if (ret)
> +		return ret;
> +
> +	*data = get_unaligned_le16(rslt);
> +
> +	if (*data == ADC5_USR_DATA_CHECK) {
> +		dev_err(adc->dev, "Invalid data:%#x\n", *data);
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(adc->dev, "voltage raw code:%#x\n", *data);
> +
> +	return 0;
> +}
> +
> +void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop, u8 *data)
> +{
> +	/* Update calibration select and decimation ratio select */
> +	*data &= ~(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK | ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK);
> +	*data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK, prop->cal_method);
> +	*data |= FIELD_PREP(ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK, prop->decimation);
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_update_dig_param, "QCOM_SPMI_ADC5_GEN3");
> +
> +#define ADC5_GEN3_READ_CONFIG_REGS 7
> +
> +static int adc5_gen3_configure(struct adc5_chip *adc,
> +			       struct adc5_channel_common_prop *prop)
> +{
> +	u8 buf[ADC5_GEN3_READ_CONFIG_REGS];
> +	u8 conv_req = 0;
> +	int ret;
> +
> +	ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID,
> +			     buf, sizeof(buf));
> +	if (ret)
> +		return ret;
> +
> +	/* Write SID */
> +	buf[0] = FIELD_PREP(ADC5_GEN3_SID_MASK, prop->sid);
> +
> +	/*
> +	 * Use channel 0 by default for immediate conversion and to indicate
> +	 * there is an actual conversion request
> +	 */
> +	buf[1] = ADC5_GEN3_CHAN_CONV_REQ | 0;
> +
> +	buf[2] = ADC5_GEN3_TIME_IMMEDIATE;
> +
> +	/* Digital param selection */
> +	adc5_gen3_update_dig_param(prop, &buf[3]);
> +
> +	/* Update fast average sample value */
> +	buf[4] = FIELD_PREP(ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK,
> +			    prop->avg_samples) | ADC5_GEN3_FAST_AVG_CTL_EN;
> +
> +	/* Select ADC channel */
> +	buf[5] = prop->channel;
> +
> +	/* Select HW settle delay for channel */
> +	buf[6] = FIELD_PREP(ADC5_GEN3_HW_SETTLE_DELAY_MASK,
> +			    prop->hw_settle_time_us);
> +
> +	reinit_completion(&adc->complete);
> +
> +	ret = adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM, ADC5_GEN3_SID,
> +			      buf, sizeof(buf));
> +	if (ret)
> +		return ret;
> +
> +	conv_req = ADC5_GEN3_CONV_REQ_REQ;
> +	return adc5_gen3_write(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
> +			       ADC5_GEN3_CONV_REQ, &conv_req, sizeof(conv_req));
> +}
> +
> +/*
> + * Worst case delay from PBS in readying handshake bit  can be up to 15ms, when
> + * PBS is busy running other simultaneous transactions, while in the best case,
> + * it is already ready at this point. Assigning polling delay and retry count
> + * accordingly.
> + */
> +
> +#define ADC5_GEN3_HS_DELAY_US			100
> +#define ADC5_GEN3_HS_RETRY_COUNT		150
> +
> +int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc,
> +			   unsigned int sdam_index)
> +{
> +	u8 conv_req = ADC5_GEN3_CONV_REQ_REQ;
> +	int ret;
> +	u8 status = 0;
> +
> +	for (int count = 0; count < ADC5_GEN3_HS_RETRY_COUNT; count++) {
> +		ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_HS, &status, sizeof(status));
> +		if (ret)
> +			return ret;
> +
> +		if (status == ADC5_GEN3_HS_READY) {
> +			ret = adc5_gen3_read(adc, sdam_index, ADC5_GEN3_CONV_REQ,
> +					     &conv_req, sizeof(conv_req));
> +			if (ret)
> +				return ret;
> +
> +			if (!conv_req)
> +				return 0;
> +		}
> +
> +		fsleep(ADC5_GEN3_HS_DELAY_US);
> +	}
> +
> +	pr_err("Setting HS ready bit timed out, sdam_index:%d, status:%#x\n",
> +	       sdam_index, status);
> +	return -ETIMEDOUT;
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_poll_wait_hs, "QCOM_SPMI_ADC5_GEN3");
> +
> +int adc5_gen3_status_clear(struct adc5_device_data *adc,
> +			   int sdam_index, u16 offset, u8 *val, int len)
> +{
> +	u8 value;
> +	int ret;
> +
> +	ret = adc5_gen3_write(adc, sdam_index, offset, val, len);
> +	if (ret)
> +		return ret;
> +
> +	/* To indicate conversion request is only to clear a status */
> +	value = 0;
> +	ret = adc5_gen3_write(adc, sdam_index, ADC5_GEN3_PERPH_CH, &value,
> +			      sizeof(value));
> +	if (ret)
> +		return ret;
> +
> +	value = ADC5_GEN3_CONV_REQ_REQ;
> +	return adc5_gen3_write(adc, sdam_index, ADC5_GEN3_CONV_REQ, &value,
> +			      sizeof(value));
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_status_clear, "QCOM_SPMI_ADC5_GEN3");
> +
> +/*
> + * Worst case delay from PBS for conversion time can be up to 500ms, when PBS
> + * has timed out twice, once for the initial attempt and once for a retry of
> + * the same transaction.
> + */
> +
> +#define ADC5_GEN3_CONV_TIMEOUT_MS	501
> +
> +static int adc5_gen3_do_conversion(struct adc5_chip *adc,
> +				   struct adc5_channel_common_prop *prop,
> +				   u16 *data_volt)
> +{
> +	unsigned long rc;
> +	int ret;
> +	u8 val;
> +
> +	guard(mutex)(&adc->lock);
> +	ret = adc5_gen3_poll_wait_hs(&adc->dev_data, ADC5_GEN3_VADC_SDAM);
> +	if (ret)
> +		return ret;
> +
> +	ret = adc5_gen3_configure(adc, prop);
> +	if (ret) {
> +		dev_err(adc->dev, "ADC configure failed with %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* No support for polling mode at present */
> +	rc = wait_for_completion_timeout(&adc->complete,
> +					 msecs_to_jiffies(ADC5_GEN3_CONV_TIMEOUT_MS));
> +	if (!rc) {
> +		dev_err(adc->dev, "Reading ADC channel %s timed out\n",
> +			prop->label);
> +		return -ETIMEDOUT;
> +	}
> +
> +	ret = adc5_gen3_read_voltage_data(adc, data_volt);
> +	if (ret)
> +		return ret;
> +
> +	val = BIT(0);
> +	return adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
> +				      ADC5_GEN3_EOC_CLR, &val, 1);
> +}
> +
> +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;
> +	}
> +
> +	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;
> +	}
> +
> +	if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
> +		dev_err_ratelimited(dev,
> +				    "Unexpected conversion fault, status:%#x, eoc_status:%#x\n",
> +				    status, eoc_status);
> +		val = ADC5_GEN3_CONV_ERR_CLR_REQ;
> +		adc5_gen3_status_clear(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
> +				       ADC5_GEN3_CONV_ERR_CLR, &val, 1);
> +		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;
> +		}
> +
> +		adc->handler(adev);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int adc5_gen3_fwnode_xlate(struct iio_dev *indio_dev,
> +				  const struct fwnode_reference_args *iiospec)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	int v_channel;
> +
> +	for (int i = 0; i < adc->nchannels; i++) {
> +		v_channel = ADC5_GEN3_V_CHAN(adc->chan_props[i].common_props);
> +		if (v_channel == iiospec->args[0])
> +			return i;
> +	}
> +
> +	return -ENOENT;
> +}
> +
> +static int adc5_gen3_read_raw(struct iio_dev *indio_dev,
> +			      struct iio_chan_spec const *chan, int *val,
> +			      int *val2, long mask)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	struct adc5_channel_common_prop *prop;
> +	u16 adc_code_volt;
> +	int ret;
> +
> +	prop = &adc->chan_props[chan->address].common_props;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = adc5_gen3_do_conversion(adc, prop, &adc_code_volt);
> +		if (ret)
> +			return ret;
> +
> +		ret = qcom_adc5_hw_scale(prop->scale_fn_type, prop->prescale,
> +					 adc->data, adc_code_volt, val);
> +		if (ret)
> +			return ret;
> +
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int adc5_gen3_read_label(struct iio_dev *indio_dev,
> +				const struct iio_chan_spec *chan, char *label)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	struct adc5_channel_prop *prop;
> +
> +	prop = &adc->chan_props[chan->address];
> +	return sprintf(label, "%s\n", prop->common_props.label);
> +}
> +
> +static const struct iio_info adc5_gen3_info = {
> +	.read_raw = adc5_gen3_read_raw,
> +	.read_label = adc5_gen3_read_label,
> +	.fwnode_xlate = adc5_gen3_fwnode_xlate,
> +};
> +
> +struct adc5_channels {
> +	unsigned int prescale_index;
> +	enum iio_chan_type type;
> +	long info_mask;
> +	enum vadc_scale_fn_type scale_fn_type;
> +};
> +
> +/* In these definitions, _pre refers to an index into adc5_prescale_ratios. */
> +#define ADC5_CHAN(_type, _mask, _pre, _scale)	\
> +	{						\
> +		.prescale_index = _pre,			\
> +		.type = _type,				\
> +		.info_mask = _mask,			\
> +		.scale_fn_type = _scale,		\
> +	},						\
> +
> +#define ADC5_CHAN_TEMP(_pre, _scale)		\
> +	ADC5_CHAN(IIO_TEMP, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale)	\
> +
> +#define ADC5_CHAN_VOLT(_pre, _scale)		\
> +	ADC5_CHAN(IIO_VOLTAGE, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale)	\
> +
> +#define ADC5_CHAN_CUR(_pre, _scale)		\
> +	ADC5_CHAN(IIO_CURRENT, BIT(IIO_CHAN_INFO_PROCESSED), _pre, _scale)	\
> +
> +static const struct adc5_channels adc5_gen3_chans_pmic[ADC5_MAX_CHANNEL] = {
> +	[ADC5_GEN3_REF_GND]		= ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_GEN3_1P25VREF]		= ADC5_CHAN_VOLT(0, SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_GEN3_VPH_PWR]		= ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_GEN3_VBAT_SNS_QBG]	= ADC5_CHAN_VOLT(1, SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_GEN3_USB_SNS_V_16]	= ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_GEN3_VIN_DIV16_MUX]	= ADC5_CHAN_TEMP(8, SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_GEN3_DIE_TEMP]		= ADC5_CHAN_TEMP(0,
> +						SCALE_HW_CALIB_PMIC_THERM_PM7)
> +	[ADC5_GEN3_AMUX1_THM_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX2_THM_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX3_THM_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX4_THM_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX5_THM_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX6_THM_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX1_GPIO_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX2_GPIO_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX3_GPIO_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +	[ADC5_GEN3_AMUX4_GPIO_100K_PU]	= ADC5_CHAN_TEMP(0,
> +					SCALE_HW_CALIB_THERM_100K_PU_PM7)
> +};
> +
> +static int adc5_gen3_get_fw_channel_data(struct adc5_chip *adc,
> +					 struct adc5_channel_prop *prop,
> +					 struct fwnode_handle *fwnode)
> +{
> +	const char *name = fwnode_get_name(fwnode);
> +	const struct adc5_data *data = adc->data;
> +	struct device *dev = adc->dev;
> +	const char *channel_name;
> +	u32 chan, value, sid;
> +	u32 varr[2];
> +	int ret;
> +
> +	ret = fwnode_property_read_u32(fwnode, "reg", &chan);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "invalid channel number %s\n",
> +				     name);
> +
> +	/*
> +	 * Value read from "reg" is virtual channel number
> +	 * virtual channel number = sid << 8 | channel number
> +	 */
> +	sid = FIELD_GET(ADC5_GEN3_VIRTUAL_SID_MASK, chan);
> +	chan = FIELD_GET(ADC5_GEN3_CHANNEL_MASK, chan);
> +
> +	if (chan > ADC5_MAX_CHANNEL)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "%s invalid channel number %d\n",
> +				     name, chan);
> +
> +	prop->common_props.channel = chan;
> +	prop->common_props.sid = sid;
> +
> +	if (!adc->data->adc_chans[chan].info_mask)
> +		return dev_err_probe(dev, -EINVAL, "Channel %#x not supported\n", chan);
> +
> +	channel_name = name;
> +	fwnode_property_read_string(fwnode, "label", &channel_name);
> +	prop->common_props.label = channel_name;
> +
> +	value = data->decimation[ADC5_DECIMATION_DEFAULT];
> +	fwnode_property_read_u32(fwnode, "qcom,decimation", &value);
> +	ret = qcom_adc5_decimation_from_dt(value, data->decimation);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "%#x invalid decimation %d\n",
> +				     chan, value);
> +	prop->common_props.decimation = ret;
> +
> +	prop->common_props.prescale = adc->data->adc_chans[chan].prescale_index;
> +	ret = fwnode_property_read_u32_array(fwnode, "qcom,pre-scaling", varr, 2);
> +	if (!ret) {
> +		ret = qcom_adc5_prescaling_from_dt(varr[0], varr[1]);
> +		if (ret < 0)
> +			return dev_err_probe(dev, ret,
> +					     "%#x invalid pre-scaling <%d %d>\n",
> +					     chan, varr[0], varr[1]);
> +		prop->common_props.prescale = ret;
> +	}
> +
> +	value = data->hw_settle_1[VADC_DEF_HW_SETTLE_TIME];
> +	fwnode_property_read_u32(fwnode, "qcom,hw-settle-time", &value);
> +	ret = qcom_adc5_hw_settle_time_from_dt(value, data->hw_settle_1);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret,
> +				     "%#x invalid hw-settle-time %d us\n",
> +				     chan, value);
> +	prop->common_props.hw_settle_time_us = ret;
> +
> +	value = BIT(VADC_DEF_AVG_SAMPLES);
> +	fwnode_property_read_u32(fwnode, "qcom,avg-samples", &value);
> +	ret = qcom_adc5_avg_samples_from_dt(value);
> +	if (ret < 0)
> +		return dev_err_probe(dev, ret, "%#x invalid avg-samples %d\n",
> +				     chan, value);
> +	prop->common_props.avg_samples = ret;
> +
> +	if (fwnode_property_read_bool(fwnode, "qcom,ratiometric"))
> +		prop->common_props.cal_method = ADC5_RATIOMETRIC_CAL;
> +	else
> +		prop->common_props.cal_method = ADC5_ABSOLUTE_CAL;
> +
> +	prop->adc_tm = fwnode_property_read_bool(fwnode, "qcom,adc-tm");
> +	if (prop->adc_tm) {
> +		adc->n_tm_channels++;
> +		if (adc->n_tm_channels > (adc->dev_data.num_sdams * 8 - 1))
> +			return dev_err_probe(dev, -EINVAL,
> +					     "Number of TM nodes %u greater than channels supported:%u\n",
> +					     adc->n_tm_channels,
> +					     adc->dev_data.num_sdams * 8 - 1);
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct adc5_data adc5_gen3_data_pmic = {
> +	.full_scale_code_volt = 0x70e4,
> +	.adc_chans = adc5_gen3_chans_pmic,
> +	.info = &adc5_gen3_info,
> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
> +			   { 85, 340, 1360 },
> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +			   { 15, 100, 200, 300,
> +			     400, 500, 600, 700,
> +			     1000, 2000, 4000, 8000,
> +			     16000, 32000, 64000, 128000 },
> +};
> +
> +static const struct of_device_id adc5_match_table[] = {
> +	{
> +		.compatible = "qcom,spmi-adc5-gen3",
> +		.data = &adc5_gen3_data_pmic,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, adc5_match_table);
> +
> +static int adc5_get_fw_data(struct adc5_chip *adc)
> +{
> +	const struct adc5_channels *adc_chan;
> +	struct adc5_channel_prop *chan_props;
> +	struct iio_chan_spec *iio_chan;
> +	struct device *dev = adc->dev;
> +	unsigned int index = 0;
> +	int ret;
> +
> +	adc->nchannels = device_get_child_node_count(dev);
> +	if (!adc->nchannels)
> +		return dev_err_probe(dev, -EINVAL, "No ADC channels found\n");
> +
> +	adc->iio_chans = devm_kcalloc(dev, adc->nchannels,
> +				      sizeof(*adc->iio_chans), GFP_KERNEL);
> +	if (!adc->iio_chans)
> +		return -ENOMEM;
> +
> +	adc->chan_props = devm_kcalloc(dev, adc->nchannels,
> +				       sizeof(*adc->chan_props), GFP_KERNEL);
> +	if (!adc->chan_props)
> +		return -ENOMEM;
> +
> +	chan_props = adc->chan_props;
> +	adc->n_tm_channels = 0;
> +	iio_chan = adc->iio_chans;
> +	adc->data = device_get_match_data(dev);
> +
> +	device_for_each_child_node_scoped(dev, child) {
> +		ret = adc5_gen3_get_fw_channel_data(adc, chan_props, child);
> +		if (ret)
> +			return ret;
> +
> +		chan_props->chip = adc;
> +		adc_chan = &adc->data->adc_chans[chan_props->common_props.channel];
> +		chan_props->common_props.scale_fn_type = adc_chan->scale_fn_type;
> +
> +		iio_chan->channel = ADC5_GEN3_V_CHAN(chan_props->common_props);
> +		iio_chan->info_mask_separate = adc_chan->info_mask;
> +		iio_chan->type = adc_chan->type;
> +		iio_chan->address = index;
> +		iio_chan->indexed = 1;
> +		iio_chan++;
> +		chan_props++;
> +		index++;
> +	}
> +
> +	return 0;
> +}
> +
> +static void adc5_gen3_uninit_aux(void *data)
> +{
> +	auxiliary_device_uninit(data);
> +}
> +
> +static void adc5_gen3_delete_aux(void *data)
> +{
> +	auxiliary_device_delete(data);
> +}
> +
> +static void adc5_gen3_aux_device_release(struct device *dev) {}
> +
> +static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc)
> +{
> +	struct tm5_aux_dev_wrapper *aux_device;
> +	int ret, i_tm = 0;
> +
> +	aux_device = devm_kzalloc(adc->dev, sizeof(*aux_device), GFP_KERNEL);
> +	if (!aux_device)
> +		return -ENOMEM;
> +
> +	aux_device->aux_dev.name = "adc5_tm_gen3";
> +	aux_device->aux_dev.dev.parent = adc->dev;
> +	aux_device->aux_dev.dev.release = adc5_gen3_aux_device_release;
> +
> +	aux_device->tm_props = devm_kcalloc(adc->dev, adc->n_tm_channels,
> +					    sizeof(*aux_device->tm_props),
> +					    GFP_KERNEL);
> +	if (!aux_device->tm_props)
> +		return -ENOMEM;
> +
> +	aux_device->dev_data = &adc->dev_data;
> +
> +	for (int i = 0; i < adc->nchannels; i++) {
> +		if (!adc->chan_props[i].adc_tm)
> +			continue;
> +		aux_device->tm_props[i_tm] = adc->chan_props[i].common_props;
> +		i_tm++;
> +	}
> +
> +	device_set_of_node_from_dev(&aux_device->aux_dev.dev, adc->dev);
> +
> +	aux_device->n_tm_channels = adc->n_tm_channels;
> +
> +	ret = auxiliary_device_init(&aux_device->aux_dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(adc->dev, adc5_gen3_uninit_aux,
> +				       &aux_device->aux_dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = auxiliary_device_add(&aux_device->aux_dev);
> +	if (ret)
> +		return ret;
> +	ret = devm_add_action_or_reset(adc->dev, adc5_gen3_delete_aux,
> +				       &aux_device->aux_dev);
> +	if (ret)
> +		return ret;
> +
> +	adc->tm_aux = &aux_device->aux_dev;
> +
> +	return 0;
> +}
> +
> +void adc5_gen3_mutex_lock(struct device *dev)
> +	__acquires(&adc->lock)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +
> +	mutex_lock(&adc->lock);
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_lock, "QCOM_SPMI_ADC5_GEN3");
> +
> +void adc5_gen3_mutex_unlock(struct device *dev)
> +	__releases(&adc->lock)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +
> +	mutex_unlock(&adc->lock);
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_mutex_unlock, "QCOM_SPMI_ADC5_GEN3");
> +
> +int adc5_gen3_get_scaled_reading(struct device *dev,
> +				 struct adc5_channel_common_prop *common_props,
> +				 int *val)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	u16 adc_code_volt;
> +	int ret;
> +
> +	ret = adc5_gen3_do_conversion(adc, common_props, &adc_code_volt);
> +	if (ret)
> +		return ret;
> +
> +	return qcom_adc5_hw_scale(common_props->scale_fn_type,
> +				  common_props->prescale,
> +				  adc->data, adc_code_volt, val);
> +}
> +EXPORT_SYMBOL_NS_GPL(adc5_gen3_get_scaled_reading, "QCOM_SPMI_ADC5_GEN3");
> +
> +int adc5_gen3_therm_code_to_temp(struct device *dev,
> +				 struct adc5_channel_common_prop *common_props,
> +				 u16 code, int *val)
> +{
> +	struct iio_dev *indio_dev = dev_get_drvdata(dev->parent);
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +
> +	return qcom_adc5_hw_scale(common_props->scale_fn_type,
> +				  common_props->prescale,
> +				  adc->data, code, val);
> +}
> +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;
> +	struct iio_dev *indio_dev;
> +	struct adc5_chip *adc;
> +	struct regmap *regmap;
> +	int ret;
> +	u32 *reg;
> +
> +	regmap = dev_get_regmap(dev->parent, NULL);
> +	if (!regmap)
> +		return -ENODEV;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	adc = iio_priv(indio_dev);
> +	adc->dev_data.regmap = regmap;
> +	adc->dev = dev;
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +	init_completion(&adc->complete);
> +	ret = devm_mutex_init(dev, &adc->lock);
> +	if (ret)
> +		return ret;
> +
> +	ret = device_property_count_u32(dev, "reg");
> +	if (ret < 0)
> +		return ret;
> +
> +	adc->dev_data.num_sdams = ret;
> +
> +	reg = devm_kcalloc(dev, adc->dev_data.num_sdams, sizeof(u32),
> +			   GFP_KERNEL);
> +	if (!reg)
> +		return -ENOMEM;
> +
> +	ret = device_property_read_u32_array(dev, "reg", reg,
> +					     adc->dev_data.num_sdams);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to read reg property\n");
> +
> +	adc->dev_data.base = devm_kcalloc(dev, adc->dev_data.num_sdams,
> +					  sizeof(*adc->dev_data.base),
> +					  GFP_KERNEL);
> +	if (!adc->dev_data.base)
> +		return -ENOMEM;
> +
> +	for (int i = 0; i < adc->dev_data.num_sdams; i++) {
> +		adc->dev_data.base[i].base_addr = reg[i];
> +
> +		ret = platform_get_irq(pdev, i);
> +		if (ret < 0)
> +			return dev_err_probe(dev, ret,
> +					     "Getting IRQ %d failed\n", i);
> +
> +		adc->dev_data.base[i].irq = ret;
> +
> +		adc->dev_data.base[i].irq_name = devm_kasprintf(dev, GFP_KERNEL,
> +								"sdam%d", i);
> +		if (!adc->dev_data.base[i].irq_name)
> +			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);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Failed to request SDAM%d irq\n",
> +				     ADC5_GEN3_VADC_SDAM);
> +
> +	ret = adc5_get_fw_data(adc);
> +	if (ret)
> +		return ret;
> +
> +	if (adc->n_tm_channels > 0) {
> +		ret = adc5_gen3_add_aux_tm_device(adc);
> +		if (ret)
> +			dev_err_probe(dev, ret,
> +				      "Failed to add auxiliary TM device\n");
> +	}
> +
> +	indio_dev->name = "spmi-adc5-gen3";
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->info = &adc5_gen3_info;
> +	indio_dev->channels = adc->iio_chans;
> +	indio_dev->num_channels = adc->nchannels;
> +
> +	return devm_iio_device_register(dev, indio_dev);
> +}
> +
> +static struct platform_driver adc5_gen3_driver = {
> +	.driver = {
> +		.name = "qcom-spmi-adc5-gen3",
> +		.of_match_table = adc5_match_table,
> +	},
> +	.probe = adc5_gen3_probe,
> +};
> +module_platform_driver(adc5_gen3_driver);
> +
> +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 Gen3 ADC driver");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("QCOM_SPMI_ADC5_GEN3");
> diff --git a/include/linux/iio/adc/qcom-adc5-gen3-common.h b/include/linux/iio/adc/qcom-adc5-gen3-common.h
> new file mode 100644
> index 000000000000..e352733f4a70
> --- /dev/null
> +++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * Code used in the main and auxiliary Qualcomm PMIC voltage ADCs
> + * of type ADC5 Gen3.
> + */
> +
> +#ifndef QCOM_ADC5_GEN3_COMMON_H
> +#define QCOM_ADC5_GEN3_COMMON_H
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/iio/adc/qcom-vadc-common.h>
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +#define ADC5_GEN3_HS				0x45
> +#define ADC5_GEN3_HS_BUSY			BIT(7)
> +#define ADC5_GEN3_HS_READY			BIT(0)
> +
> +#define ADC5_GEN3_STATUS1			0x46
> +#define ADC5_GEN3_STATUS1_CONV_FAULT		BIT(7)
> +#define ADC5_GEN3_STATUS1_THR_CROSS		BIT(6)
> +#define ADC5_GEN3_STATUS1_EOC			BIT(0)
> +
> +#define ADC5_GEN3_TM_EN_STS			0x47
> +#define ADC5_GEN3_TM_HIGH_STS			0x48
> +#define ADC5_GEN3_TM_LOW_STS			0x49
> +
> +#define ADC5_GEN3_EOC_STS			0x4a
> +#define ADC5_GEN3_EOC_CHAN_0			BIT(0)
> +
> +#define ADC5_GEN3_EOC_CLR			0x4b
> +#define ADC5_GEN3_TM_HIGH_STS_CLR		0x4c
> +#define ADC5_GEN3_TM_LOW_STS_CLR		0x4d
> +#define ADC5_GEN3_CONV_ERR_CLR			0x4e
> +#define ADC5_GEN3_CONV_ERR_CLR_REQ		BIT(0)
> +
> +#define ADC5_GEN3_SID				0x4f
> +#define ADC5_GEN3_SID_MASK			GENMASK(3, 0)
> +
> +#define ADC5_GEN3_PERPH_CH			0x50
> +#define ADC5_GEN3_CHAN_CONV_REQ			BIT(7)
> +
> +#define ADC5_GEN3_TIMER_SEL			0x51
> +#define ADC5_GEN3_TIME_IMMEDIATE		0x1
> +
> +#define ADC5_GEN3_DIG_PARAM			0x52
> +#define ADC5_GEN3_DIG_PARAM_CAL_SEL_MASK	GENMASK(5, 4)
> +#define ADC5_GEN3_DIG_PARAM_DEC_RATIO_SEL_MASK	GENMASK(3, 2)
> +
> +#define ADC5_GEN3_FAST_AVG			0x53
> +#define ADC5_GEN3_FAST_AVG_CTL_EN		BIT(7)
> +#define ADC5_GEN3_FAST_AVG_CTL_SAMPLES_MASK	GENMASK(2, 0)
> +
> +#define ADC5_GEN3_ADC_CH_SEL_CTL		0x54
> +#define ADC5_GEN3_DELAY_CTL			0x55
> +#define ADC5_GEN3_HW_SETTLE_DELAY_MASK		GENMASK(3, 0)
> +
> +#define ADC5_GEN3_CH_EN				0x56
> +#define ADC5_GEN3_HIGH_THR_INT_EN		BIT(1)
> +#define ADC5_GEN3_LOW_THR_INT_EN		BIT(0)
> +
> +#define ADC5_GEN3_LOW_THR0			0x57
> +#define ADC5_GEN3_LOW_THR1			0x58
> +#define ADC5_GEN3_HIGH_THR0			0x59
> +#define ADC5_GEN3_HIGH_THR1			0x5a
> +
> +#define ADC5_GEN3_CH_DATA0(channel)	(0x5c + (channel) * 2)
> +#define ADC5_GEN3_CH_DATA1(channel)	(0x5d + (channel) * 2)
> +
> +#define ADC5_GEN3_CONV_REQ			0xe5
> +#define ADC5_GEN3_CONV_REQ_REQ			BIT(0)
> +
> +#define ADC5_GEN3_VIRTUAL_SID_MASK		GENMASK(15, 8)
> +#define ADC5_GEN3_CHANNEL_MASK			GENMASK(7, 0)
> +#define ADC5_GEN3_V_CHAN(x)		\
> +	(FIELD_PREP(ADC5_GEN3_VIRTUAL_SID_MASK, (x).sid) | (x).channel)
> +
> +/* ADC channels for PMIC5 Gen3 */
> +#define ADC5_GEN3_REF_GND			0x00
> +#define ADC5_GEN3_1P25VREF			0x01
> +#define ADC5_GEN3_DIE_TEMP			0x03
> +#define ADC5_GEN3_USB_SNS_V_16			0x11
> +#define ADC5_GEN3_VIN_DIV16_MUX			0x12
> +#define ADC5_GEN3_VPH_PWR			0x8e
> +#define ADC5_GEN3_VBAT_SNS_QBG			0x8f
> +/* 100k pull-up channels */
> +#define ADC5_GEN3_AMUX1_THM_100K_PU		0x44
> +#define ADC5_GEN3_AMUX2_THM_100K_PU		0x45
> +#define ADC5_GEN3_AMUX3_THM_100K_PU		0x46
> +#define ADC5_GEN3_AMUX4_THM_100K_PU		0x47
> +#define ADC5_GEN3_AMUX5_THM_100K_PU		0x48
> +#define ADC5_GEN3_AMUX6_THM_100K_PU		0x49
> +#define ADC5_GEN3_AMUX1_GPIO_100K_PU		0x4a
> +#define ADC5_GEN3_AMUX2_GPIO_100K_PU		0x4b
> +#define ADC5_GEN3_AMUX3_GPIO_100K_PU		0x4c
> +#define ADC5_GEN3_AMUX4_GPIO_100K_PU		0x4d
> +
> +#define ADC5_MAX_CHANNEL			0xc0
> +
> +struct device;
> +
> +enum adc5_cal_method {
> +	ADC5_NO_CAL = 0,
> +	ADC5_RATIOMETRIC_CAL,
> +	ADC5_ABSOLUTE_CAL,
> +};
> +
> +enum adc5_time_select {
> +	MEAS_INT_DISABLE = 0,
> +	MEAS_INT_IMMEDIATE,
> +	MEAS_INT_50MS,
> +	MEAS_INT_100MS,
> +	MEAS_INT_1S,
> +	MEAS_INT_NONE,
> +};
> +
> +/**
> + * struct adc5_sdam_data - data per SDAM allocated for adc usage
> + * @base_addr: base address for the ADC SDAM peripheral.
> + * @irq_name: ADC IRQ name.
> + * @irq: ADC IRQ number.
> + */
> +struct adc5_sdam_data {
> +	u16 base_addr;
> +	const char *irq_name;
> +	int irq;
> +};
> +
> +/**
> + * struct adc5_device_data - Top-level ADC device data
> + * @regmap: ADC peripheral register map field.
> + * @base: array of SDAM data.
> + * @num_sdams: number of ADC SDAM peripherals.
> + */
> +struct adc5_device_data {
> +	struct regmap *regmap;
> +	struct adc5_sdam_data *base;
> +	int num_sdams;
> +};
> +
> +/**
> + * struct adc5_channel_common_prop - ADC channel properties (common to ADC and TM).
> + * @channel: channel number, refer to the channel list.
> + * @cal_method: calibration method.
> + * @decimation: sampling rate supported for the channel.
> + * @sid: ID of PMIC owning the channel.
> + * @label: Channel name used in device tree.
> + * @prescale: channel scaling performed on the input signal.
> + * @hw_settle_time_us: the time between AMUX being configured and the
> + *	start of conversion in uS.
> + * @avg_samples: ability to provide single result from the ADC
> + *	that is an average of multiple measurements.
> + * @scale_fn_type: Represents the scaling function to convert voltage
> + *	physical units desired by the client for the channel.
> + */
> +struct adc5_channel_common_prop {
> +	unsigned int channel;
> +	enum adc5_cal_method cal_method;
> +	unsigned int decimation;
> +	unsigned int sid;
> +	const char *label;
> +	unsigned int prescale;
> +	unsigned int hw_settle_time_us;
> +	unsigned int avg_samples;
> +	enum vadc_scale_fn_type scale_fn_type;
> +};
> +
> +/**
> + * struct tm5_aux_dev_wrapper - wrapper structure around TM auxiliary device
> + * @aux_dev: TM auxiliary device structure.
> + * @dev_data: Top-level ADC device data.
> + * @tm_props: Array of common ADC channel properties for TM channels.
> + * @n_tm_channels: number of TM channels.
> + */
> +struct tm5_aux_dev_wrapper {
> +	struct auxiliary_device aux_dev;
> +	struct adc5_device_data *dev_data;
> +	struct adc5_channel_common_prop *tm_props;
> +	unsigned int n_tm_channels;
> +};
> +
> +int adc5_gen3_read(struct adc5_device_data *adc, unsigned int sdam_index,
> +		   u16 offset, u8 *data, int len);
> +
> +int adc5_gen3_write(struct adc5_device_data *adc, unsigned int sdam_index,
> +		    u16 offset, u8 *data, int len);
> +
> +int adc5_gen3_poll_wait_hs(struct adc5_device_data *adc,
> +			   unsigned int sdam_index);
> +
> +void adc5_gen3_update_dig_param(struct adc5_channel_common_prop *prop,
> +				u8 *data);
> +
> +int adc5_gen3_status_clear(struct adc5_device_data *adc,
> +			   int sdam_index, u16 offset, u8 *val, int len);
> +
> +void adc5_gen3_mutex_lock(struct device *dev);
> +void adc5_gen3_mutex_unlock(struct device *dev);
> +int adc5_gen3_get_scaled_reading(struct device *dev,
> +				 struct adc5_channel_common_prop *common_props,
> +				 int *val);
> +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 */


  reply	other threads:[~2026-02-28 19:40 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-09 10:54 [PATCH V11 0/4] Add support for QCOM SPMI PMIC5 Gen3 ADC Jishnu Prakash
2026-02-09 10:54 ` [PATCH V11 1/4] dt-bindings: iio: adc: Split out QCOM VADC channel properties Jishnu Prakash
2026-02-09 10:54 ` [PATCH V11 2/4] dt-bindings: iio: adc: Add support for QCOM PMIC5 Gen3 ADC Jishnu Prakash
2026-03-10  9:19   ` Lee Jones
2026-02-09 10:54 ` [PATCH V11 3/4] " Jishnu Prakash
2026-02-28 19:39   ` Jonathan Cameron [this message]
2026-03-06  5:57     ` Jishnu Prakash
2026-02-09 10:54 ` [PATCH V11 4/4] thermal: qcom: add support for PMIC5 Gen3 ADC thermal monitoring Jishnu Prakash

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260228193946.2e60ea5e@jic23-huawei \
    --to=jic23@kernel.org \
    --cc=agross@kernel.org \
    --cc=amitk@kernel.org \
    --cc=andersson@kernel.org \
    --cc=anjelique.melendez@oss.qualcomm.com \
    --cc=conor+dt@kernel.org \
    --cc=cros-qcom-dts-watchers@chromium.org \
    --cc=daniel.lezcano@linaro.org \
    --cc=david.collins@oss.qualcomm.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.baryshkov@oss.qualcomm.com \
    --cc=jishnu.prakash@oss.qualcomm.com \
    --cc=kamal.wadhwa@oss.qualcomm.com \
    --cc=konradybcio@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=lukasz.luba@arm.com \
    --cc=lumag@kernel.org \
    --cc=neil.armstrong@linaro.org \
    --cc=quic_kotarake@quicinc.com \
    --cc=rafael@kernel.org \
    --cc=robh@kernel.org \
    --cc=rui.zhang@intel.com \
    --cc=sboyd@kernel.org \
    --cc=stephan.gerhold@linaro.org \
    --cc=subbaraman.narayanamurthy@oss.qualcomm.com \
    --cc=thara.gopinath@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox