lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <1c7a3c58-8e78-4615-82d8-c76f1d4838a6@linaro.org>
Date: Fri, 9 May 2025 17:40:10 +0200
From: neil.armstrong@...aro.org
To: Jishnu Prakash <jishnu.prakash@....qualcomm.com>, jic23@...nel.org,
 robh@...nel.org, krzysztof.kozlowski@...aro.org, krzk+dt@...nel.org,
 conor+dt@...nel.org, agross@...nel.org, andersson@...nel.org,
 lumag@...nel.org, dmitry.baryshkov@....qualcomm.com, konradybcio@...nel.org,
 daniel.lezcano@...aro.org, sboyd@...nel.org, amitk@...nel.org,
 thara.gopinath@...il.com, lee@...nel.org, rafael@...nel.org,
 subbaraman.narayanamurthy@....qualcomm.com, david.collins@....qualcomm.com,
 anjelique.melendez@....qualcomm.com, quic_kamalw@...cinc.com
Cc: rui.zhang@...el.com, lukasz.luba@....com, devicetree@...r.kernel.org,
 linux-arm-msm@...r.kernel.org, linux-iio@...r.kernel.org,
 linux-kernel@...r.kernel.org, linux-pm@...r.kernel.org,
 cros-qcom-dts-watchers@...omium.org, quic_skakitap@...cinc.com,
 stephan.gerhold@...aro.org
Subject: Re: [PATCH V6 4/5] iio: adc: Add support for QCOM PMIC5 Gen3 ADC

On 09/05/2025 13:09, Jishnu Prakash 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@....qualcomm.com>
> ---
> Changes since v5:
> - Split mutex function exported to ADC_TM driver into separate functions
>    for acquiring and releasing mutex.
> - Updated logic for acquiring IRQ numbers to account for removing
>    "interrupt-names" DT property.
> - Addressed other reviewer comments related to kernel-doc formatting and
>    other changes.
> 
> Changes since v4:
> - Moved out common funtions from newly added .h file into a separate .c
>    file to avoid duplicating them. Updated interrupt name as suggested
>    by reviewer. 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.
> - 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                       |  30 +
>   drivers/iio/adc/Makefile                      |   2 +
>   drivers/iio/adc/qcom-adc5-gen3-common.c       | 104 +++
>   drivers/iio/adc/qcom-spmi-adc5-gen3.c         | 763 ++++++++++++++++++
>   include/linux/iio/adc/qcom-adc5-gen3-common.h | 193 +++++
>   5 files changed, 1092 insertions(+)
>   create mode 100644 drivers/iio/adc/qcom-adc5-gen3-common.c
>   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 ad06cf556785..f2f1ee15b3da 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1220,6 +1220,36 @@ config QCOM_SPMI_ADC5
>   	  To compile this driver as a module, choose M here: the module will
>   	  be called qcom-spmi-adc5.
>   
> +config QCOM_ADC5_GEN3_COMMON
> +	tristate
> +
> +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 QCOM_ADC5_GEN3_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 07d4b832c42e..8adbb5f42285 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -103,8 +103,10 @@ obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
>   obj-$(CONFIG_PAC1921) += pac1921.o
>   obj-$(CONFIG_PAC1934) += pac1934.o
>   obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> +obj-$(CONFIG_QCOM_ADC5_GEN3_COMMON) += qcom-adc5-gen3-common.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-adc5-gen3-common.c b/drivers/iio/adc/qcom-adc5-gen3-common.c
> new file mode 100644
> index 000000000000..edd618b0d39f
> --- /dev/null
> +++ b/drivers/iio/adc/qcom-adc5-gen3-common.c
> @@ -0,0 +1,104 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
> + *
> + * Code shared between the main and auxiliary Qualcomm PMIC voltage ADCs
> + * of type ADC5 Gen3.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/iio/adc/qcom-adc5-gen3-common.h>
> +
> +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(adc5_gen3_read);
> +
> +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(adc5_gen3_write);
> +
> +/*
> + * 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, count;
> +	u8 status = 0;
> +
> +	for (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(adc5_gen3_poll_wait_hs);
> +
> +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(adc5_gen3_update_dig_param);
> +
> +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(adc5_gen3_status_clear);
> +
> +MODULE_DESCRIPTION("Qualcomm ADC5 Gen3 common functionality");
> +MODULE_LICENSE("GPL");
> 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..25b58b9be3b7
> --- /dev/null
> +++ b/drivers/iio/adc/qcom-spmi-adc5-gen3.c
> @@ -0,0 +1,763 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/completion.h>
> +#include <linux/err.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/log2.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/slab.h>
> +#include <linux/thermal.h>
> +#include <linux/unaligned.h>
> +
> +#include <dt-bindings/iio/adc/qcom,spmi-vadc.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.
> + * @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;
> +	/*
> +	 * lock for access to the peripheral, to prevent concurrent
> +	 * requests from multiple clients.
> +	 */
> +	struct mutex lock;
> +	const struct adc5_data *data;
> +	unsigned int n_tm_channels;
> +	struct auxiliary_device *tm_aux;
> +};
> +
> +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;
> +}
> +
> +#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 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)
> +{
> +	u8 status, tm_status[2], eoc_status, val;
> +	struct adc_tm5_auxiliary_drv *adrv_tm;
> +	struct adc5_chip *adc = dev_id;
> +	struct auxiliary_device *adev;
> +	int ret;
> +
> +	ret = adc5_gen3_read(&adc->dev_data, ADC5_GEN3_VADC_SDAM,
> +			     ADC5_GEN3_STATUS1, &status, sizeof(status));
> +	if (ret) {
> +		dev_err(adc->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(adc->dev, "adc read eoc status failed with %d\n", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (status & ADC5_GEN3_STATUS1_CONV_FAULT) {
> +		dev_err_ratelimited(adc->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(adc->dev, "adc read TM status failed with %d\n", ret);
> +		return IRQ_HANDLED;
> +	}
> +
> +	if (tm_status[0] || tm_status[1]) {
> +		adev = adc->tm_aux;
> +		if (!adev || !adev->dev.driver) {
> +			dev_err(adc->dev,
> +				"adc_tm auxiliary device not initialized\n");
> +			return IRQ_HANDLED;
> +		}
> +
> +		adrv_tm = container_of(adev->dev.driver,
> +				       struct adc_tm5_auxiliary_drv,
> +				       adrv.driver);
> +
> +		if (adrv_tm && adrv_tm->tm_event_notify)
> +			adrv_tm->tm_event_notify(adev);
> +		else
> +			dev_err(adc->dev,
> +				"adc_tm auxiliary driver not initialized\n");
> +	}
> +
> +	dev_dbg(adc->dev,
> +		"Interrupt status:%#x, EOC status:%#x, high:%#x, low:%#x\n",
> +		status, eoc_status, tm_status[0], tm_status[1]);
> +
> +	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 i, v_channel;
> +
> +	for (i = 0; i < adc->nchannels; i++) {
> +		v_channel = 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_TEMP_ALARM_LITE]	= 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;
> +	u32 chan, value, varr[2], sid = 0;
> +	struct device *dev = adc->dev;
> +	const char *channel_name;
> +	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_GEN3_OFFSET_EXT2)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "%s invalid channel number %d\n",
> +				     name, chan);
> +
> +	prop->common_props.channel = chan;
> +	prop->common_props.sid = sid;
> +
> +	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 fwnode_handle *child = NULL;
> +	struct iio_chan_spec *iio_chan;
> +	unsigned int index = 0;
> +	int ret;
> +
> +	adc->nchannels = device_get_child_node_count(adc->dev);
> +	if (!adc->nchannels) {
> +		dev_err(adc->dev, "No ADC channels found\n");
> +		return -EINVAL;
> +	}
> +
> +	adc->iio_chans = devm_kcalloc(adc->dev, adc->nchannels,
> +				      sizeof(*adc->iio_chans), GFP_KERNEL);
> +	if (!adc->iio_chans)
> +		return -ENOMEM;
> +
> +	adc->chan_props = devm_kcalloc(adc->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(adc->dev);
> +
> +	device_for_each_child_node(adc->dev, child) {
> +		ret = adc5_gen3_get_fw_channel_data(adc, chan_props, child);
> +		if (ret < 0)
> +			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 = 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)
> +{
> +	struct auxiliary_device *aux = container_of(dev, struct auxiliary_device,
> +						    dev);
> +
> +	kfree(aux);
> +}
> +
> +static int adc5_gen3_add_aux_tm_device(struct adc5_chip *adc)
> +{
> +	struct tm5_aux_dev_wrapper *aux_device;
> +	int i, 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 (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) {
> +		kfree(&aux_device->aux_dev);
> +		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)
> +{
> +	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)
> +{
> +	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");
> +
> +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, i;
> +	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;
> +
> +	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;
> +
> +	platform_set_drvdata(pdev, indio_dev);
> +	init_completion(&adc->complete);
> +	mutex_init(&adc->lock);
> +
> +	for (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",
> +					     adc->dev_data.base[i].irq);
> +
> +		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 < 0)
> +		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 < 0)
> +		return ret;
> +
> +	if (adc->n_tm_channels > 0)
> +		adc5_gen3_add_aux_tm_device(adc);
> +
> +	indio_dev->name = pdev->name;
> +	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");
> 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..4f476cd77b37
> --- /dev/null
> +++ b/include/linux/iio/adc/qcom-adc5-gen3-common.h
> @@ -0,0 +1,193 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
> + *
> + * Code shared between 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/regmap.h>
> +#include <linux/iio/adc/qcom-vadc-common.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 V_CHAN(x)		\
> +	(FIELD_PREP(ADC5_GEN3_VIRTUAL_SID_MASK, (x).sid) | (x).channel)
> +
> +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;
> +};
> +
> +/**
> + * struct tm5_aux_dev_wrapper - wrapper structure around TM auxiliary driver
> + * @adrv: TM auxiliary driver structure.
> + * @tm_event_notify: TM callback to be called by parent driver.
> + */
> +struct adc_tm5_auxiliary_drv {
> +	struct auxiliary_driver adrv;
> +	void (*tm_event_notify)(struct auxiliary_device *adev);
> +};
> +
> +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);
> +
> +#endif /* QCOM_ADC5_GEN3_COMMON_H */

Tested-by: Neil Armstrong <neil.armstrong@...aro.org> # on SM8550-QRD
Tested-by: Neil Armstrong <neil.armstrong@...aro.org> # on SM8550-HDK
Tested-by: Neil Armstrong <neil.armstrong@...aro.org> # on SM8650-QRD
Tested-by: Neil Armstrong <neil.armstrong@...aro.org> # on SM8650-HDK

With the following DT change:
https://git.codelinaro.org/neil.armstrong/linux/-/commit/0776fd6eeaf08c568674bacfe075f3a26c840356

Thanks,
Neil

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ