All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
@ 2018-07-26  0:09 Siddartha Mohanadoss
  2018-07-28 11:08 ` Jonathan Cameron
  0 siblings, 1 reply; 8+ messages in thread
From: Siddartha Mohanadoss @ 2018-07-26  0:09 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Siddartha Mohanadoss, linux-iio, devicetree, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring

This patch adds support for QCOM SPMI PMIC5 family
of ADC driver that supports hardware based offset and
gain compensation. The ADC peripheral can measure both
voltage and current channels whose input signal is
connected to the PMIC ADC AMUX.

The register set and configuration has been refreshed
compared to the prior QCOM PMIC ADC family. Register
ADC5 as part of the IIO framework.

Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
---
 drivers/iio/adc/Kconfig            |  20 +
 drivers/iio/adc/Makefile           |   1 +
 drivers/iio/adc/qcom-spmi-adc5.c   | 798 +++++++++++++++++++++++++++++++++++++
 drivers/iio/adc/qcom-vadc-common.c | 189 ++++++++-
 drivers/iio/adc/qcom-vadc-common.h |  54 +++
 5 files changed, 1057 insertions(+), 5 deletions(-)
 create mode 100644 drivers/iio/adc/qcom-spmi-adc5.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 9da7907..93ac929 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -597,6 +597,26 @@ config QCOM_SPMI_VADC
 	  To compile this driver as a module, choose M here: the module will
 	  be called qcom-spmi-vadc.
 
+config QCOM_SPMI_ADC5
+	tristate "Qualcomm Technologies Inc. SPMI PMIC5 ADC"
+	depends on SPMI
+	select REGMAP_SPMI
+	select QCOM_VADC_COMMON
+	help
+	  This is the IIO Voltage PMIC5 ADC driver for Qualcomm Technologies Inc.
+
+	  The driver supports multiple channels read. The ADC is a 16-bit
+	  sigma-delta ADC. The hardware supports calibrated results for
+	  conversion requests and clients include reading voltage phone
+	  power, on board system thermistors connected to the PMIC ADC,
+	  PMIC die temperature, charger temperature, battery current, USB voltage
+	  input, voltage signals connected to supported PMIC GPIO inputs. The
+	  hardware supports internal pull-up for thermistors and can choose between
+	  a 100k, 30k and 400k pull up using the ADC channels.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called qcom-spmi-adc5.
+
 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 28a9423..3c1a109 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
 obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
+obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
 obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
 obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o
 obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
new file mode 100644
index 0000000..9e3e2ae
--- /dev/null
+++ b/drivers/iio/adc/qcom-spmi-adc5.c
@@ -0,0 +1,798 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.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/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/iio/qcom,spmi-vadc.h>
+#include "qcom-vadc-common.h"
+
+#define ADC5_USR_REVISION1			0x0
+#define ADC5_USR_STATUS1			0x8
+#define ADC5_USR_STATUS1_REQ_STS		BIT(1)
+#define ADC5_USR_STATUS1_EOC			BIT(0)
+#define ADC5_USR_STATUS1_REQ_STS_EOC_MASK	0x3
+
+#define ADC5_USR_STATUS2			0x9
+#define ADC5_USR_STATUS2_CONV_SEQ_MASK		0x70
+#define ADC5_USR_STATUS2_CONV_SEQ_MASK_SHIFT	0x5
+
+#define ADC5_USR_IBAT_MEAS			0xf
+#define ADC5_USR_IBAT_MEAS_SUPPORTED		BIT(0)
+
+#define ADC5_USR_DIG_PARAM			0x42
+#define ADC5_USR_DIG_PARAM_CAL_VAL		BIT(6)
+#define ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT	6
+#define ADC5_USR_DIG_PARAM_CAL_SEL		0x30
+#define ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT	4
+#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL	0xc
+#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT	2
+
+#define ADC5_USR_FAST_AVG_CTL			0x43
+#define ADC5_USR_FAST_AVG_CTL_EN		BIT(7)
+#define ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK	0x7
+
+#define ADC5_USR_CH_SEL_CTL			0x44
+
+#define ADC5_USR_DELAY_CTL			0x45
+#define ADC5_USR_HW_SETTLE_DELAY_MASK		0xf
+
+#define ADC5_USR_EN_CTL1			0x46
+#define ADC5_USR_EN_CTL1_ADC_EN			BIT(7)
+
+#define ADC5_USR_CONV_REQ			0x47
+#define ADC5_USR_CONV_REQ_REQ			BIT(7)
+
+#define ADC5_USR_DATA0				0x50
+
+#define ADC5_USR_DATA1				0x51
+
+#define ADC5_USR_IBAT_DATA0			0x52
+
+#define ADC5_USR_IBAT_DATA1			0x53
+
+/*
+ * Conversion time varies based on the decimation, clock rate, fast average
+ * samples and measurements queued across different VADC peripherals.
+ * Set the timeout to a max of 100ms.
+ */
+#define ADC5_CONV_TIME_MIN_US			263
+#define ADC5_CONV_TIME_MAX_US			264
+#define ADC5_CONV_TIME_RETRY			400
+#define ADC5_CONV_TIMEOUT			msecs_to_jiffies(100)
+
+/* Digital version >= 5.3 supports hw_settle_2 */
+#define ADC5_HW_SETTLE_DIFF_MINOR		3
+#define ADC5_HW_SETTLE_DIFF_MAJOR		5
+
+enum adc5_cal_method {
+	ADC5_NO_CAL = 0,
+	ADC5_RATIOMETRIC_CAL,
+	ADC5_ABSOLUTE_CAL
+};
+
+enum adc5_cal_val {
+	ADC5_TIMER_CAL = 0,
+	ADC5_NEW_CAL
+};
+
+/**
+ * struct adc5_channel_prop - ADC channel property.
+ * @channel: channel number, refer to the channel list.
+ * @cal_method: calibration method.
+ * @cal_val: calibration value
+ * @decimation: sampling rate supported for the channel.
+ * @prescale: channel scaling performed on the input signal.
+ * @hw_settle_time: the time between AMUX being configured and the
+ *	start of conversion.
+ * @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.
+ * @datasheet_name: Channel name used in device tree.
+ */
+struct adc5_channel_prop {
+	unsigned int		channel;
+	enum adc5_cal_method	cal_method;
+	enum adc5_cal_val	cal_val;
+	unsigned int		decimation;
+	unsigned int		prescale;
+	unsigned int		hw_settle_time;
+	unsigned int		avg_samples;
+	enum vadc_scale_fn_type	scale_fn_type;
+	const char		*datasheet_name;
+};
+
+/**
+ * struct adc5_chip - ADC private structure.
+ * @regmap: SPMI ADC5 peripheral register map field.
+ * @dev: SPMI ADC5 device.
+ * @base: base address for the ADC peripheral.
+ * @nchannels: number of ADC channels.
+ * @chan_props: array of ADC channel properties.
+ * @iio_chans: array of IIO channels specification.
+ * @poll_eoc: use polling instead of interrupt.
+ * @complete: ADC result notification after interrupt is received.
+ * @lock: ADC lock for access to the peripheral.
+ * @data: software configuration data.
+ */
+struct adc5_chip {
+	struct regmap		*regmap;
+	struct device		*dev;
+	u16			base;
+	unsigned int		nchannels;
+	struct adc5_channel_prop	*chan_props;
+	struct iio_chan_spec	*iio_chans;
+	bool			poll_eoc;
+	struct completion	complete;
+	struct mutex		lock;
+	const struct adc_data	*data;
+};
+
+static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
+	{.num =  1, .den =  1},
+	{.num =  1, .den =  3},
+	{.num =  1, .den =  4},
+	{.num =  1, .den =  6},
+	{.num =  1, .den = 20},
+	{.num =  1, .den =  8},
+	{.num = 10, .den = 81},
+	{.num =  1, .den = 10},
+	{.num =  1, .den = 16}
+};
+
+static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len)
+{
+	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
+}
+
+static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
+{
+	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
+}
+
+static int adc5_prescaling_from_dt(u32 num, u32 den)
+{
+	unsigned int pre;
+
+	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
+		if (adc5_prescale_ratios[pre].num == num &&
+		    adc5_prescale_ratios[pre].den == den)
+			break;
+
+	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
+		return -EINVAL;
+
+	return pre;
+}
+
+static int adc5_get_dig_version(struct adc5_chip *adc,
+					u8 *dig_version)
+{
+	return adc5_read(adc, ADC5_USR_REVISION1, dig_version, 2);
+}
+
+static int adc5_hw_settle_time_from_dt(u32 value,
+					const unsigned int *hw_settle)
+{
+	uint32_t i;
+
+	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
+		if (value == hw_settle[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int adc5_avg_samples_from_dt(u32 value)
+{
+	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
+		return -EINVAL;
+
+	return __ffs64(value);
+}
+
+static int adc5_decimation_from_dt(u32 value,
+					const unsigned int *decimation)
+{
+	uint32_t i;
+
+	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
+		if (value == decimation[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
+{
+	int ret;
+	u8 rslt_lsb, rslt_msb;
+
+	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1);
+	if (ret)
+		return ret;
+
+	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1);
+	if (ret)
+		return ret;
+
+	*data = (rslt_msb << 8) | rslt_lsb;
+
+	if (*data == ADC5_USR_DATA_CHECK) {
+		pr_err("Invalid data:0x%x\n", *data);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int adc5_poll_wait_eoc(struct adc5_chip *adc)
+{
+	unsigned int count, retry = ADC5_CONV_TIME_RETRY;
+	u8 status1;
+	int ret;
+
+	for (count = 0; count < retry; count++) {
+		ret = adc5_read(adc, ADC5_USR_STATUS1, &status1, 1);
+		if (ret)
+			return ret;
+
+		status1 &= ADC5_USR_STATUS1_REQ_STS_EOC_MASK;
+		if (status1 == ADC5_USR_STATUS1_EOC)
+			return 0;
+		usleep_range(ADC5_CONV_TIME_MIN_US, ADC5_CONV_TIME_MAX_US);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static void adc5_update_dig_param(struct adc5_chip *adc,
+			struct adc5_channel_prop *prop, u8 *data)
+{
+	/* Update calibration value */
+	*data &= ~ADC5_USR_DIG_PARAM_CAL_VAL;
+	*data |= (prop->cal_val << ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT);
+
+	/* Update calibration select */
+	*data &= ~ADC5_USR_DIG_PARAM_CAL_SEL;
+	*data |= (prop->cal_method << ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT);
+
+	/* Update decimation ratio select */
+	*data &= ~ADC5_USR_DIG_PARAM_DEC_RATIO_SEL;
+	*data |= (prop->decimation << ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT);
+}
+
+static int adc5_configure(struct adc5_chip *adc,
+			struct adc5_channel_prop *prop)
+{
+	int ret;
+	u8 buf[6];
+
+	/* Read registers 0x42 through 0x46 */
+	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, 6);
+	if (ret < 0)
+		return ret;
+
+	/* Digital param selection */
+	adc5_update_dig_param(adc, prop, &buf[0]);
+
+	/* Update fast average sample value */
+	buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
+	buf[1] |= prop->avg_samples;
+
+	/* Select ADC channel */
+	buf[2] = prop->channel;
+
+	/* Select HW settle delay for channel */
+	buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK;
+	buf[3] |= prop->hw_settle_time;
+
+	/* Select ADC enable */
+	buf[4] |= ADC5_USR_EN_CTL1_ADC_EN;
+
+	/* Select CONV request */
+	buf[5] |= ADC5_USR_CONV_REQ_REQ;
+
+	if (!adc->poll_eoc)
+		reinit_completion(&adc->complete);
+
+	return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, 6);
+}
+
+static int adc5_do_conversion(struct adc5_chip *adc,
+			struct adc5_channel_prop *prop,
+			struct iio_chan_spec const *chan,
+			u16 *data_volt, u16 *data_cur)
+{
+	int ret;
+
+	mutex_lock(&adc->lock);
+
+	ret = adc5_configure(adc, prop);
+	if (ret) {
+		pr_err("ADC configure failed with %d\n", ret);
+		goto unlock;
+	}
+
+	if (adc->poll_eoc) {
+		ret = adc5_poll_wait_eoc(adc);
+		if (ret < 0) {
+			pr_err("EOC bit not set\n");
+			goto unlock;
+		}
+	} else {
+		ret = wait_for_completion_timeout(&adc->complete,
+							ADC5_CONV_TIMEOUT);
+		if (!ret) {
+			pr_debug("Did not get completion timeout.\n");
+			ret = adc5_poll_wait_eoc(adc);
+			if (ret < 0) {
+				pr_err("EOC bit not set\n");
+				goto unlock;
+			}
+		}
+	}
+
+	ret = adc5_read_voltage_data(adc, data_volt);
+unlock:
+	mutex_unlock(&adc->lock);
+
+	return ret;
+}
+
+static irqreturn_t adc5_isr(int irq, void *dev_id)
+{
+	struct adc5_chip *adc = dev_id;
+
+	complete(&adc->complete);
+
+	return IRQ_HANDLED;
+}
+
+static int adc5_of_xlate(struct iio_dev *indio_dev,
+				const struct of_phandle_args *iiospec)
+{
+	struct adc5_chip *adc = iio_priv(indio_dev);
+	int i;
+
+	for (i = 0; i < adc->nchannels; i++)
+		if (adc->chan_props[i].channel == iiospec->args[0])
+			return i;
+
+	return -EINVAL;
+}
+
+static int adc5_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_prop *prop;
+	u16 adc_code_volt, adc_code_cur;
+	int ret;
+
+	prop = &adc->chan_props[chan->address];
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = adc5_do_conversion(adc, prop, chan,
+				&adc_code_volt, &adc_code_cur);
+		if (ret)
+			break;
+
+		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
+			&adc5_prescale_ratios[prop->prescale],
+			adc->data,
+			adc_code_volt, val);
+		if (ret)
+			break;
+
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_RAW:
+		ret = adc5_do_conversion(adc, prop, chan,
+				&adc_code_volt, &adc_code_cur);
+		if (ret)
+			break;
+
+		*val = (int)adc_code_volt;
+		*val2 = (int)adc_code_cur;
+		return IIO_VAL_INT;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct iio_info adc5_info = {
+	.read_raw = adc5_read_raw,
+	.of_xlate = adc5_of_xlate,
+};
+
+struct adc_channels {
+	const char *datasheet_name;
+	unsigned int prescale_index;
+	enum iio_chan_type type;
+	long info_mask;
+	enum vadc_scale_fn_type scale_fn_type;
+};
+
+#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
+	{								\
+		.datasheet_name = (_dname),				\
+		.prescale_index = _pre,					\
+		.type = _type,						\
+		.info_mask = _mask,					\
+		.scale_fn_type = _scale,				\
+	},								\
+
+#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
+	ADC5_CHAN(_dname, IIO_TEMP,					\
+		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\
+		_pre, _scale)						\
+
+#define ADC5_CHAN_VOLT(_dname, _pre, _scale)				\
+	ADC5_CHAN(_dname, IIO_VOLTAGE,					\
+		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
+		  _pre, _scale)						\
+
+static const struct adc_channels adc_chans_pmic5[ADC5_MAX_CHANNEL] = {
+	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
+					SCALE_HW_CALIB_PMIC_THERM)
+	[ADC5_USB_IN_I]		= ADC5_CHAN_VOLT("usb_in_i_uv", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_USB_IN_V_16]	= ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_CHG_TEMP]		= ADC5_CHAN_TEMP("chg_temp", 1,
+					SCALE_HW_CALIB_PM5_CHG_TEMP)
+	/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit */
+	[ADC5_SBUx]		= ADC5_CHAN_VOLT("chg_sbux", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_MID_CHG_DIV6]	= ADC5_CHAN_VOLT("chg_mid_chg", 6,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm", 1,
+					SCALE_HW_CALIB_XOTHERM)
+	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM2]	= ADC5_CHAN_TEMP("amux_thm2", 1,
+					SCALE_HW_CALIB_PM5_SMB_TEMP)
+};
+
+static const struct adc_channels adc_chans_rev2[ADC5_MAX_CHANNEL] = {
+	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VCOIN]		= ADC5_CHAN_VOLT("vcoin", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
+					SCALE_HW_CALIB_PMIC_THERM)
+	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+};
+
+static int adc5_get_dt_channel_data(struct adc5_chip *adc,
+				    struct adc5_channel_prop *prop,
+				    struct device_node *node,
+				    const struct adc_data *data)
+{
+	const char *name = node->name, *channel_name;
+	u32 chan, value, varr[2];
+	int ret;
+	struct device *dev = adc->dev;
+
+	ret = of_property_read_u32(node, "reg", &chan);
+	if (ret) {
+		dev_err(dev, "invalid channel number %s\n", name);
+		return ret;
+	}
+
+	if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA) {
+		dev_err(dev, "%s invalid channel number %d\n", name, chan);
+		return -EINVAL;
+	}
+
+	/* the channel has DT description */
+	prop->channel = chan;
+
+	channel_name = of_get_property(node,
+				"label", NULL) ? : node->name;
+	if (!channel_name) {
+		pr_err("Invalid channel name\n");
+		return -EINVAL;
+	}
+	prop->datasheet_name = channel_name;
+
+	ret = of_property_read_u32(node, "qcom,decimation", &value);
+	if (!ret) {
+		ret = adc5_decimation_from_dt(value, data->decimation);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid decimation %d\n",
+				chan, value);
+			return ret;
+		}
+		prop->decimation = ret;
+	} else {
+		prop->decimation = ADC5_DECIMATION_DEFAULT;
+	}
+
+	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
+	if (!ret) {
+		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
+				chan, varr[0], varr[1]);
+			return ret;
+		}
+		prop->prescale = ret;
+	}
+
+	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
+	if (!ret) {
+		u8 dig_version[2];
+
+		ret = adc5_get_dig_version(adc, dig_version);
+		if (ret < 0) {
+			dev_err(dev, "Invalid dig version read %d\n", ret);
+			return ret;
+		}
+
+		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
+						dig_version[1]);
+		/* Digital controller >= 5.3 have hw_settle_2 option */
+		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
+			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
+			ret = adc5_hw_settle_time_from_dt(value,
+							data->hw_settle_2);
+		else
+			ret = adc5_hw_settle_time_from_dt(value,
+							data->hw_settle_1);
+
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
+				chan, value);
+			return ret;
+		}
+		prop->hw_settle_time = ret;
+	} else {
+		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
+	}
+
+	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+	if (!ret) {
+		ret = adc5_avg_samples_from_dt(value);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid avg-samples %d\n",
+				chan, value);
+			return ret;
+		}
+		prop->avg_samples = ret;
+	} else {
+		prop->avg_samples = VADC_DEF_AVG_SAMPLES;
+	}
+
+	if (of_property_read_bool(node, "qcom,ratiometric"))
+		prop->cal_method = ADC5_RATIOMETRIC_CAL;
+	else
+		prop->cal_method = ADC5_ABSOLUTE_CAL;
+
+	/*
+	 * Default to using timer calibration. Using a fresh calibration value
+	 * for every conversion will increase the overall time for a request.
+	 */
+	prop->cal_val = ADC5_TIMER_CAL;
+
+	dev_dbg(dev, "%02x name %s\n", chan, name);
+
+	return 0;
+}
+
+const struct adc_data data_pmic5 = {
+	.full_scale_code_volt = 0x70e4,
+	.full_scale_code_cur = 0x2710,
+	.adc_chans = adc_chans_pmic5,
+	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
+				{250, 420, 840},
+	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{15, 100, 200, 300, 400, 500, 600, 700,
+				800, 900, 1, 2, 4, 6, 8, 10},
+	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{15, 100, 200, 300, 400, 500, 600, 700,
+				1, 2, 4, 8, 16, 32, 64, 128},
+};
+
+const struct adc_data data_pmic_rev2 = {
+	.full_scale_code_volt = 0x4000,
+	.full_scale_code_cur = 0x1800,
+	.adc_chans = adc_chans_rev2,
+	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
+				{256, 512, 1024},
+	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{0, 100, 200, 300, 400, 500, 600, 700,
+				800, 900, 1, 2, 4, 6, 8, 10},
+	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{15, 100, 200, 300, 400, 500, 600, 700,
+				1, 2, 4, 8, 16, 32, 64, 128},
+};
+
+static const struct of_device_id adc5_match_table[] = {
+	{
+		.compatible = "qcom,spmi-adc5",
+		.data = &data_pmic5,
+	},
+	{
+		.compatible = "qcom,spmi-adc-rev2",
+		.data = &data_pmic_rev2,
+	},
+	{ }
+};
+
+static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node *node)
+{
+	const struct adc_channels *adc_chan;
+	struct iio_chan_spec *iio_chan;
+	struct adc5_channel_prop prop;
+	struct device_node *child;
+	unsigned int index = 0;
+	const struct of_device_id *id;
+	const struct adc_data *data;
+	int ret;
+
+	adc->nchannels = of_get_available_child_count(node);
+	if (!adc->nchannels)
+		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;
+
+	iio_chan = adc->iio_chans;
+	id = of_match_node(adc5_match_table, node);
+	if (id)
+		data = id->data;
+	else
+		data = &data_pmic5;
+	adc->data = data;
+
+	for_each_available_child_of_node(node, child) {
+		ret = adc5_get_dt_channel_data(adc, &prop, child, data);
+		if (ret) {
+			of_node_put(child);
+			return ret;
+		}
+
+		prop.scale_fn_type =
+			data->adc_chans[prop.channel].scale_fn_type;
+		*chan_props = prop;
+		adc_chan = &data->adc_chans[prop.channel];
+
+		iio_chan->channel = prop.channel;
+		iio_chan->datasheet_name = prop.datasheet_name;
+		iio_chan->extend_name = prop.datasheet_name;
+		iio_chan->info_mask_separate = adc_chan->info_mask;
+		iio_chan->type = adc_chan->type;
+		iio_chan->address = index;
+		iio_chan++;
+		chan_props++;
+		index++;
+	}
+
+	return 0;
+}
+
+static int adc5_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	struct iio_dev *indio_dev;
+	struct adc5_chip *adc;
+	struct regmap *regmap;
+	int ret, irq_eoc;
+	u32 reg;
+
+	regmap = dev_get_regmap(dev->parent, NULL);
+	if (!regmap)
+		return -ENODEV;
+
+	ret = of_property_read_u32(node, "reg", &reg);
+	if (ret < 0)
+		return ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	adc = iio_priv(indio_dev);
+	adc->regmap = regmap;
+	adc->dev = dev;
+	adc->base = reg;
+	init_completion(&adc->complete);
+	mutex_init(&adc->lock);
+
+	ret = adc5_get_dt_data(adc, node);
+	if (ret) {
+		pr_err("adc get dt data failed\n");
+		return ret;
+	}
+
+	irq_eoc = platform_get_irq(pdev, 0);
+	if (irq_eoc < 0) {
+		if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL)
+			return irq_eoc;
+		adc->poll_eoc = true;
+	} else {
+		ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0,
+				       "pm-adc5", adc);
+		if (ret)
+			return ret;
+	}
+
+	indio_dev->dev.parent = dev;
+	indio_dev->dev.of_node = node;
+	indio_dev->name = pdev->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &adc5_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_driver = {
+	.driver = {
+		.name = "qcom-spmi-adc5.c",
+		.of_match_table = adc5_match_table,
+	},
+	.probe = adc5_probe,
+};
+module_platform_driver(adc5_driver);
+
+MODULE_ALIAS("platform:qcom-spmi-adc5");
+MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
index fe3d782..2a9fecb 100644
--- a/drivers/iio/adc/qcom-vadc-common.c
+++ b/drivers/iio/adc/qcom-vadc-common.c
@@ -47,8 +47,79 @@
 	{44,	125}
 };
 
+/*
+ * Voltage to temperature table for 100k pull up for NTCG104EF104 with
+ * 1.875V reference.
+ */
+static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = {
+	{ 1831,	-40000 },
+	{ 1814,	-35000 },
+	{ 1791,	-30000 },
+	{ 1761,	-25000 },
+	{ 1723,	-20000 },
+	{ 1675,	-15000 },
+	{ 1616,	-10000 },
+	{ 1545,	-5000 },
+	{ 1463,	0 },
+	{ 1370,	5000 },
+	{ 1268,	10000 },
+	{ 1160,	15000 },
+	{ 1049,	20000 },
+	{ 937,	25000 },
+	{ 828,	30000 },
+	{ 726,	35000 },
+	{ 630,	40000 },
+	{ 544,	45000 },
+	{ 467,	50000 },
+	{ 399,	55000 },
+	{ 340,	60000 },
+	{ 290,	65000 },
+	{ 247,	70000 },
+	{ 209,	75000 },
+	{ 179,	80000 },
+	{ 153,	85000 },
+	{ 130,	90000 },
+	{ 112,	95000 },
+	{ 96,	100000 },
+	{ 82,	105000 },
+	{ 71,	110000 },
+	{ 62,	115000 },
+	{ 53,	120000 },
+	{ 46,	125000 },
+};
+
+static int qcom_vadc_scale_hw_calib_volt(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_uv);
+static int qcom_vadc_scale_hw_calib_therm(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+static int qcom_vadc_scale_hw_smb_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+static int qcom_vadc_scale_hw_chg5_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+static int qcom_vadc_scale_hw_calib_die_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+
+static struct qcom_adc5_scale_type scale_adc5_fn[] = {
+	[SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt},
+	[SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm},
+	[SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm},
+	[SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp},
+	[SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp},
+	[SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp},
+};
+
 static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
-				      u32 tablesize, s32 input, s64 *output)
+				      u32 tablesize, s32 input, int *output)
 {
 	bool descending = 1;
 	u32 i = 0;
@@ -128,7 +199,7 @@ static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
 				 bool absolute, u16 adc_code,
 				 int *result_mdec)
 {
-	s64 voltage = 0, result = 0;
+	s64 voltage = 0;
 	int ret;
 
 	qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
@@ -138,12 +209,11 @@ static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
 
 	ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
 					 ARRAY_SIZE(adcmap_100k_104ef_104fb),
-					 voltage, &result);
+					 voltage, result_mdec);
 	if (ret)
 		return ret;
 
-	result *= 1000;
-	*result_mdec = result;
+	*result_mdec *= 1000;
 
 	return 0;
 }
@@ -191,6 +261,99 @@ static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph,
 	return 0;
 }
 
+static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				unsigned int factor)
+{
+	s64 voltage, temp, adc_vdd_ref_mv = 1875;
+
+	/*
+	 * The normal data range is between 0V to 1.875V. On cases where
+	 * we read low voltage values, the ADC code can go beyond the
+	 * range and the scale result is incorrect so we clamp the values
+	 * for the cases where the code represents a value below 0V
+	 */
+	if (adc_code > VADC5_MAX_CODE)
+		adc_code = 0;
+
+	/* (ADC code * vref_vadc (1.875V)) / full_scale_code */
+	voltage = (s64) adc_code * adc_vdd_ref_mv * 1000;
+	voltage = div64_s64(voltage, data->full_scale_code_volt);
+	if (voltage > 0) {
+		voltage *= prescale->den;
+		temp = prescale->num * factor;
+		voltage = div64_s64(voltage, temp);
+	} else {
+		voltage = 0;
+	}
+
+	return (int) voltage;
+}
+
+static int qcom_vadc_scale_hw_calib_volt(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_uv)
+{
+	*result_uv = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 1);
+
+	return 0;
+}
+
+static int qcom_vadc_scale_hw_calib_therm(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	int ret, voltage;
+
+	voltage = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 1000);
+
+	/* Map voltage to temperature from look-up table */
+	return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref,
+				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
+				 voltage, result_mdec);
+}
+
+static int qcom_vadc_scale_hw_calib_die_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 2);
+	*result_mdec -= KELVINMIL_CELSIUSMIL;
+
+	return 0;
+}
+
+static int qcom_vadc_scale_hw_smb_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100,
+				prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR);
+	*result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec;
+
+	return 0;
+}
+
+static int qcom_vadc_scale_hw_chg5_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 4);
+	*result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec;
+
+	return 0;
+}
+
 int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 		    const struct vadc_linear_graph *calib_graph,
 		    const struct vadc_prescale_ratio *prescale,
@@ -221,6 +384,22 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 }
 EXPORT_SYMBOL(qcom_vadc_scale);
 
+int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
+		    const struct vadc_prescale_ratio *prescale,
+		    const struct adc_data *data,
+		    u16 adc_code, int *result)
+{
+	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
+		scaletype < SCALE_HW_CALIB_INVALID)) {
+		pr_err("Invalid scale type %d\n", scaletype);
+		return -EINVAL;
+	}
+
+	return scale_adc5_fn[scaletype].scale_fn(prescale, data,
+					adc_code, result);
+}
+EXPORT_SYMBOL(qcom_adc5_hw_scale);
+
 int qcom_vadc_decimation_from_dt(u32 value)
 {
 	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
index 1d5354f..bcd6959 100644
--- a/drivers/iio/adc/qcom-vadc-common.h
+++ b/drivers/iio/adc/qcom-vadc-common.h
@@ -25,15 +25,31 @@
 
 #define VADC_DECIMATION_MIN			512
 #define VADC_DECIMATION_MAX			4096
+#define ADC5_DEF_VBAT_PRESCALING		1 /* 1:3 */
+#define ADC5_DECIMATION_SHORT			250
+#define ADC5_DECIMATION_MEDIUM			420
+#define ADC5_DECIMATION_LONG			840
+/* Default decimation - 1024 for rev2, 840 for pmic5 */
+#define ADC5_DECIMATION_DEFAULT			2
+#define ADC5_DECIMATION_SAMPLES_MAX		3
 
 #define VADC_HW_SETTLE_DELAY_MAX		10000
+#define VADC_HW_SETTLE_SAMPLES_MAX		16
 #define VADC_AVG_SAMPLES_MAX			512
+#define ADC5_AVG_SAMPLES_MAX			16
 
 #define KELVINMIL_CELSIUSMIL			273150
+#define PMIC5_CHG_TEMP_SCALE_FACTOR		377500
+#define PMIC5_SMB_TEMP_CONSTANT			419400
+#define PMIC5_SMB_TEMP_SCALE_FACTOR		356
 
 #define PMI_CHG_SCALE_1				-138890
 #define PMI_CHG_SCALE_2				391750000000LL
 
+#define VADC5_MAX_CODE				0x7fff
+#define ADC5_FULL_SCALE_CODE			0x70e4
+#define ADC5_USR_DATA_CHECK			0x8000
+
 /**
  * struct vadc_map_pt - Map the graph representation for ADC channel
  * @x: Represent the ADC digitized code.
@@ -89,6 +105,18 @@ struct vadc_prescale_ratio {
  * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
  * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
  * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
+ * SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to
+ *	voltage (uV) with hardware applied offset/slope values to adc code.
+ * SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC using
+ *	lookup table. The hardware applies offset/slope to adc code.
+ * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC using
+ *	100k pullup. The hardware applies offset/slope to adc code.
+ * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade.
+ *	The hardware applies offset/slope to adc code.
+ * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for PMIC5
+ *	charger temperature.
+ * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5
+ *	SMB1390 temperature.
  */
 enum vadc_scale_fn_type {
 	SCALE_DEFAULT = 0,
@@ -96,6 +124,22 @@ enum vadc_scale_fn_type {
 	SCALE_PMIC_THERM,
 	SCALE_XOTHERM,
 	SCALE_PMI_CHG_TEMP,
+	SCALE_HW_CALIB_DEFAULT,
+	SCALE_HW_CALIB_THERM_100K_PULLUP,
+	SCALE_HW_CALIB_XOTHERM,
+	SCALE_HW_CALIB_PMIC_THERM,
+	SCALE_HW_CALIB_PM5_CHG_TEMP,
+	SCALE_HW_CALIB_PM5_SMB_TEMP,
+	SCALE_HW_CALIB_INVALID,
+};
+
+struct adc_data {
+	const u32	full_scale_code_volt;
+	const u32	full_scale_code_cur;
+	const struct adc_channels *adc_chans;
+	unsigned int	*decimation;
+	unsigned int	*hw_settle_1;
+	unsigned int	*hw_settle_2;
 };
 
 int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
@@ -104,6 +148,16 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 		    bool absolute,
 		    u16 adc_code, int *result_mdec);
 
+struct qcom_adc5_scale_type {
+	int (*scale_fn)(const struct vadc_prescale_ratio *prescale,
+		const struct adc_data *data, u16 adc_code, int *result);
+};
+
+int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
+		    const struct vadc_prescale_ratio *prescale,
+		    const struct adc_data *data,
+		    u16 adc_code, int *result_mdec);
+
 int qcom_vadc_decimation_from_dt(u32 value);
 
 #endif /* QCOM_VADC_COMMON_H */
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

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

* Re: [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
  2018-07-26  0:09 [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver Siddartha Mohanadoss
@ 2018-07-28 11:08 ` Jonathan Cameron
  2018-07-31 18:08   ` smohanad
  0 siblings, 1 reply; 8+ messages in thread
From: Jonathan Cameron @ 2018-07-28 11:08 UTC (permalink / raw)
  To: Siddartha Mohanadoss
  Cc: linux-iio, devicetree, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Rob Herring

On Wed, 25 Jul 2018 17:09:29 -0700
Siddartha Mohanadoss <smohanad@codeaurora.org> wrote:

> This patch adds support for QCOM SPMI PMIC5 family
> of ADC driver that supports hardware based offset and
> gain compensation. The ADC peripheral can measure both
> voltage and current channels whose input signal is
> connected to the PMIC ADC AMUX.
> 
> The register set and configuration has been refreshed
> compared to the prior QCOM PMIC ADC family. Register
> ADC5 as part of the IIO framework.
> 
> Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>

Hi Siddartha,

My main questions inline are around providing both PROCESSED and
RAW readouts for the channels.   Generally this is something we
don't ever do (as there is little point and it increases the exposed
ABI).  Now the oddity here is you've copied from the
qcom-spmi-vadc driver which does this and IIRC that was because
the initial submission didn't do any of the complex maths to get
to the PROCESSED values.  That was introduced later, leaving us with
a mess as we couldn't remove the existing ABI in case someone was
using it.

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ba71704af4a0aae0d9e5812dbdd7bca95e181b14

So... I'm not convinced either way yet on whether we should let
this one through as a continuation of the exception we made there
or not.  Does this matter to you, or can you drop the RAW interface?

There other bits inline are trivial enough I would just have
ignored or fixed them when applying if it weren't for this question.

(apologies if we have been round this before - I may have forgotten
or I may have been dozing during previous review.  This question has
come up for a few drivers recently so I'm more aware of it now)

> ---
>  drivers/iio/adc/Kconfig            |  20 +
>  drivers/iio/adc/Makefile           |   1 +
>  drivers/iio/adc/qcom-spmi-adc5.c   | 798 +++++++++++++++++++++++++++++++++++++
>  drivers/iio/adc/qcom-vadc-common.c | 189 ++++++++-
>  drivers/iio/adc/qcom-vadc-common.h |  54 +++
>  5 files changed, 1057 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/iio/adc/qcom-spmi-adc5.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 9da7907..93ac929 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -597,6 +597,26 @@ config QCOM_SPMI_VADC
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called qcom-spmi-vadc.
>  
> +config QCOM_SPMI_ADC5
> +	tristate "Qualcomm Technologies Inc. SPMI PMIC5 ADC"
> +	depends on SPMI
> +	select REGMAP_SPMI
> +	select QCOM_VADC_COMMON
> +	help
> +	  This is the IIO Voltage PMIC5 ADC driver for Qualcomm Technologies Inc.
> +
> +	  The driver supports multiple channels read. The ADC is a 16-bit
> +	  sigma-delta ADC. The hardware supports calibrated results for
> +	  conversion requests and clients include reading voltage phone
> +	  power, on board system thermistors connected to the PMIC ADC,
> +	  PMIC die temperature, charger temperature, battery current, USB voltage
> +	  input, voltage signals connected to supported PMIC GPIO inputs. The
> +	  hardware supports internal pull-up for thermistors and can choose between
> +	  a 100k, 30k and 400k pull up using the ADC channels.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called qcom-spmi-adc5.
> +
>  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 28a9423..3c1a109 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
>  obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
>  obj-$(CONFIG_NAU7802) += nau7802.o
>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> +obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>  obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o
>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
> new file mode 100644
> index 0000000..9e3e2ae
> --- /dev/null
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -0,0 +1,798 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/err.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/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <dt-bindings/iio/qcom,spmi-vadc.h>
> +#include "qcom-vadc-common.h"
> +
> +#define ADC5_USR_REVISION1			0x0
> +#define ADC5_USR_STATUS1			0x8
> +#define ADC5_USR_STATUS1_REQ_STS		BIT(1)
> +#define ADC5_USR_STATUS1_EOC			BIT(0)
> +#define ADC5_USR_STATUS1_REQ_STS_EOC_MASK	0x3
> +
> +#define ADC5_USR_STATUS2			0x9
> +#define ADC5_USR_STATUS2_CONV_SEQ_MASK		0x70
> +#define ADC5_USR_STATUS2_CONV_SEQ_MASK_SHIFT	0x5
> +
> +#define ADC5_USR_IBAT_MEAS			0xf
> +#define ADC5_USR_IBAT_MEAS_SUPPORTED		BIT(0)
> +
> +#define ADC5_USR_DIG_PARAM			0x42
> +#define ADC5_USR_DIG_PARAM_CAL_VAL		BIT(6)
> +#define ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT	6
> +#define ADC5_USR_DIG_PARAM_CAL_SEL		0x30
> +#define ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT	4
> +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL	0xc
> +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT	2
> +
> +#define ADC5_USR_FAST_AVG_CTL			0x43
> +#define ADC5_USR_FAST_AVG_CTL_EN		BIT(7)
> +#define ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK	0x7
> +
> +#define ADC5_USR_CH_SEL_CTL			0x44
> +
> +#define ADC5_USR_DELAY_CTL			0x45
> +#define ADC5_USR_HW_SETTLE_DELAY_MASK		0xf
> +
> +#define ADC5_USR_EN_CTL1			0x46
> +#define ADC5_USR_EN_CTL1_ADC_EN			BIT(7)
> +
> +#define ADC5_USR_CONV_REQ			0x47
> +#define ADC5_USR_CONV_REQ_REQ			BIT(7)
> +
> +#define ADC5_USR_DATA0				0x50
> +
> +#define ADC5_USR_DATA1				0x51
> +
> +#define ADC5_USR_IBAT_DATA0			0x52
> +
> +#define ADC5_USR_IBAT_DATA1			0x53
> +
> +/*
> + * Conversion time varies based on the decimation, clock rate, fast average
> + * samples and measurements queued across different VADC peripherals.
> + * Set the timeout to a max of 100ms.
> + */
> +#define ADC5_CONV_TIME_MIN_US			263
> +#define ADC5_CONV_TIME_MAX_US			264
> +#define ADC5_CONV_TIME_RETRY			400
> +#define ADC5_CONV_TIMEOUT			msecs_to_jiffies(100)
> +
> +/* Digital version >= 5.3 supports hw_settle_2 */
> +#define ADC5_HW_SETTLE_DIFF_MINOR		3
> +#define ADC5_HW_SETTLE_DIFF_MAJOR		5
> +
> +enum adc5_cal_method {
> +	ADC5_NO_CAL = 0,
> +	ADC5_RATIOMETRIC_CAL,
> +	ADC5_ABSOLUTE_CAL
> +};
> +
> +enum adc5_cal_val {
> +	ADC5_TIMER_CAL = 0,
> +	ADC5_NEW_CAL
> +};
> +
> +/**
> + * struct adc5_channel_prop - ADC channel property.
> + * @channel: channel number, refer to the channel list.
> + * @cal_method: calibration method.
> + * @cal_val: calibration value
> + * @decimation: sampling rate supported for the channel.
> + * @prescale: channel scaling performed on the input signal.
> + * @hw_settle_time: the time between AMUX being configured and the
> + *	start of conversion.
> + * @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.
> + * @datasheet_name: Channel name used in device tree.
> + */
> +struct adc5_channel_prop {
> +	unsigned int		channel;
> +	enum adc5_cal_method	cal_method;
> +	enum adc5_cal_val	cal_val;
> +	unsigned int		decimation;
> +	unsigned int		prescale;
> +	unsigned int		hw_settle_time;
> +	unsigned int		avg_samples;
> +	enum vadc_scale_fn_type	scale_fn_type;
> +	const char		*datasheet_name;
> +};
> +
> +/**
> + * struct adc5_chip - ADC private structure.
> + * @regmap: SPMI ADC5 peripheral register map field.
> + * @dev: SPMI ADC5 device.
> + * @base: base address for the ADC peripheral.
> + * @nchannels: number of ADC channels.
> + * @chan_props: array of ADC channel properties.
> + * @iio_chans: array of IIO channels specification.
> + * @poll_eoc: use polling instead of interrupt.
> + * @complete: ADC result notification after interrupt is received.
> + * @lock: ADC lock for access to the peripheral.
> + * @data: software configuration data.
> + */
> +struct adc5_chip {
> +	struct regmap		*regmap;
> +	struct device		*dev;
> +	u16			base;
> +	unsigned int		nchannels;
> +	struct adc5_channel_prop	*chan_props;
> +	struct iio_chan_spec	*iio_chans;
> +	bool			poll_eoc;
> +	struct completion	complete;
> +	struct mutex		lock;
> +	const struct adc_data	*data;
> +};
> +
> +static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
> +	{.num =  1, .den =  1},
> +	{.num =  1, .den =  3},
> +	{.num =  1, .den =  4},
> +	{.num =  1, .den =  6},
> +	{.num =  1, .den = 20},
> +	{.num =  1, .den =  8},
> +	{.num = 10, .den = 81},
> +	{.num =  1, .den = 10},
> +	{.num =  1, .den = 16}
> +};
> +
> +static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len)
> +{
> +	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
> +}
> +
> +static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
> +{
> +	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
> +}
> +
> +static int adc5_prescaling_from_dt(u32 num, u32 den)
> +{
> +	unsigned int pre;
> +
> +	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
> +		if (adc5_prescale_ratios[pre].num == num &&
> +		    adc5_prescale_ratios[pre].den == den)
> +			break;
> +
> +	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
> +		return -EINVAL;
> +
> +	return pre;
> +}
> +
> +static int adc5_get_dig_version(struct adc5_chip *adc,
> +					u8 *dig_version)
> +{
> +	return adc5_read(adc, ADC5_USR_REVISION1, dig_version, 2);
> +}
> +
> +static int adc5_hw_settle_time_from_dt(u32 value,
> +					const unsigned int *hw_settle)
> +{
> +	uint32_t i;
> +
> +	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
> +		if (value == hw_settle[i])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int adc5_avg_samples_from_dt(u32 value)
> +{
> +	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
> +		return -EINVAL;
> +
> +	return __ffs64(value);
> +}
> +
> +static int adc5_decimation_from_dt(u32 value,
> +					const unsigned int *decimation)
> +{
> +	uint32_t i;
> +
> +	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
> +		if (value == decimation[i])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
> +{
> +	int ret;
> +	u8 rslt_lsb, rslt_msb;
> +
> +	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1);
> +	if (ret)
> +		return ret;
> +
> +	*data = (rslt_msb << 8) | rslt_lsb;
> +
> +	if (*data == ADC5_USR_DATA_CHECK) {
> +		pr_err("Invalid data:0x%x\n", *data);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
return 0; Might as well make it clear it can't be anything else.

> +}
> +
> +static int adc5_poll_wait_eoc(struct adc5_chip *adc)
> +{
> +	unsigned int count, retry = ADC5_CONV_TIME_RETRY;
> +	u8 status1;
> +	int ret;
> +
> +	for (count = 0; count < retry; count++) {
> +		ret = adc5_read(adc, ADC5_USR_STATUS1, &status1, 1);
> +		if (ret)
> +			return ret;
> +
> +		status1 &= ADC5_USR_STATUS1_REQ_STS_EOC_MASK;
> +		if (status1 == ADC5_USR_STATUS1_EOC)
> +			return 0;

I would put a blank line here for readability.

> +		usleep_range(ADC5_CONV_TIME_MIN_US, ADC5_CONV_TIME_MAX_US);
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static void adc5_update_dig_param(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop, u8 *data)
> +{
> +	/* Update calibration value */
> +	*data &= ~ADC5_USR_DIG_PARAM_CAL_VAL;
> +	*data |= (prop->cal_val << ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT);
> +
> +	/* Update calibration select */
> +	*data &= ~ADC5_USR_DIG_PARAM_CAL_SEL;
> +	*data |= (prop->cal_method << ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT);
> +
> +	/* Update decimation ratio select */
> +	*data &= ~ADC5_USR_DIG_PARAM_DEC_RATIO_SEL;
> +	*data |= (prop->decimation << ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT);
> +}
> +
> +static int adc5_configure(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop)
> +{
> +	int ret;
> +	u8 buf[6];
> +
> +	/* Read registers 0x42 through 0x46 */
> +	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, 6);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Digital param selection */
> +	adc5_update_dig_param(adc, prop, &buf[0]);
> +
> +	/* Update fast average sample value */
> +	buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
> +	buf[1] |= prop->avg_samples;
> +
> +	/* Select ADC channel */
> +	buf[2] = prop->channel;
> +
> +	/* Select HW settle delay for channel */
> +	buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK;
> +	buf[3] |= prop->hw_settle_time;
> +
> +	/* Select ADC enable */
> +	buf[4] |= ADC5_USR_EN_CTL1_ADC_EN;
> +
> +	/* Select CONV request */
> +	buf[5] |= ADC5_USR_CONV_REQ_REQ;
> +
> +	if (!adc->poll_eoc)
> +		reinit_completion(&adc->complete);
> +
> +	return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, 6);
> +}
> +
> +static int adc5_do_conversion(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop,
> +			struct iio_chan_spec const *chan,
> +			u16 *data_volt, u16 *data_cur)
> +{
> +	int ret;
> +
> +	mutex_lock(&adc->lock);
> +
> +	ret = adc5_configure(adc, prop);
> +	if (ret) {
> +		pr_err("ADC configure failed with %d\n", ret);
> +		goto unlock;
> +	}
> +
> +	if (adc->poll_eoc) {
> +		ret = adc5_poll_wait_eoc(adc);
> +		if (ret < 0) {
> +			pr_err("EOC bit not set\n");
> +			goto unlock;
> +		}
> +	} else {
> +		ret = wait_for_completion_timeout(&adc->complete,
> +							ADC5_CONV_TIMEOUT);
> +		if (!ret) {
> +			pr_debug("Did not get completion timeout.\n");
> +			ret = adc5_poll_wait_eoc(adc);
> +			if (ret < 0) {
> +				pr_err("EOC bit not set\n");
> +				goto unlock;
> +			}
> +		}
> +	}
> +
> +	ret = adc5_read_voltage_data(adc, data_volt);
> +unlock:
> +	mutex_unlock(&adc->lock);
> +
> +	return ret;
> +}
> +
> +static irqreturn_t adc5_isr(int irq, void *dev_id)
> +{
> +	struct adc5_chip *adc = dev_id;
> +
> +	complete(&adc->complete);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int adc5_of_xlate(struct iio_dev *indio_dev,
> +				const struct of_phandle_args *iiospec)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	int i;
> +
> +	for (i = 0; i < adc->nchannels; i++)
> +		if (adc->chan_props[i].channel == iiospec->args[0])
> +			return i;
> +
> +	return -EINVAL;
> +}
> +
> +static int adc5_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_prop *prop;
> +	u16 adc_code_volt, adc_code_cur;
> +	int ret;
> +
> +	prop = &adc->chan_props[chan->address];
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = adc5_do_conversion(adc, prop, chan,
> +				&adc_code_volt, &adc_code_cur);
> +		if (ret)
return ret;
> +			break;
> +
> +		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
> +			&adc5_prescale_ratios[prop->prescale],
> +			adc->data,
> +			adc_code_volt, val);
> +		if (ret)
return ret;
> +			break;
> +
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_RAW:
> +		ret = adc5_do_conversion(adc, prop, chan,
> +				&adc_code_volt, &adc_code_cur);
> +		if (ret)
return ret;
> +			break;
> +
> +		*val = (int)adc_code_volt;
> +		*val2 = (int)adc_code_cur;
> +		return IIO_VAL_INT;
> +	default:
> +		ret = -EINVAL;
return -EINVAL and drop the retunr ret as we won't be able to get there.
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct iio_info adc5_info = {
> +	.read_raw = adc5_read_raw,
> +	.of_xlate = adc5_of_xlate,
> +};
> +
> +struct adc_channels {
> +	const char *datasheet_name;
> +	unsigned int prescale_index;
> +	enum iio_chan_type type;
> +	long info_mask;
> +	enum vadc_scale_fn_type scale_fn_type;
> +};
> +
> +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
> +	{								\
> +		.datasheet_name = (_dname),				\
> +		.prescale_index = _pre,					\
> +		.type = _type,						\
> +		.info_mask = _mask,					\
> +		.scale_fn_type = _scale,				\
> +	},								\
> +
> +#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
> +	ADC5_CHAN(_dname, IIO_TEMP,					\
> +		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\
I'm fairly sure we've been round this before.  A device should not supply
both raw and processed values without very very good reasons.
So far the only reasons we have had that I consider valid are:

1. We got it wrong initially and output raw values, only to have processed
   added later to support something non linear.  We are stuck with supporting
   raw by existing ABI.

2. We have a non linear channel that needs processed values and has events.
   For some reason we can't map the event controls from processed back
   to raw (weird case but who knows) so we have to support both.


Now this case 'kind' of falls into case 1 as that is what I think happened
with the qcom-spmi-vadc driver and lead to both being there.

Hmm. This is awkward as in theory we are adding another 'broken' interface
here, but it is reasonable to assume that there might be code that requires
this interface on such a similar chip.

Do you definitely need to support both for some applications?  Technically
we would not be causing a regression if we don't support _raw as it
never worked for this particular device, but I can sympathise (and be persuaded)
to let it go here if there is a strong usecase.

> +		_pre, _scale)						\
> +
> +#define ADC5_CHAN_VOLT(_dname, _pre, _scale)				\
> +	ADC5_CHAN(_dname, IIO_VOLTAGE,					\
> +		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
> +		  _pre, _scale)						\
> +
> +static const struct adc_channels adc_chans_pmic5[ADC5_MAX_CHANNEL] = {
> +	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
> +					SCALE_HW_CALIB_PMIC_THERM)
> +	[ADC5_USB_IN_I]		= ADC5_CHAN_VOLT("usb_in_i_uv", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_USB_IN_V_16]	= ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_CHG_TEMP]		= ADC5_CHAN_TEMP("chg_temp", 1,
> +					SCALE_HW_CALIB_PM5_CHG_TEMP)
> +	/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit */
> +	[ADC5_SBUx]		= ADC5_CHAN_VOLT("chg_sbux", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_MID_CHG_DIV6]	= ADC5_CHAN_VOLT("chg_mid_chg", 6,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm", 1,
> +					SCALE_HW_CALIB_XOTHERM)
> +	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM2]	= ADC5_CHAN_TEMP("amux_thm2", 1,
> +					SCALE_HW_CALIB_PM5_SMB_TEMP)
> +};
> +
> +static const struct adc_channels adc_chans_rev2[ADC5_MAX_CHANNEL] = {
> +	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VCOIN]		= ADC5_CHAN_VOLT("vcoin", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
> +					SCALE_HW_CALIB_PMIC_THERM)
> +	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +};
> +
> +static int adc5_get_dt_channel_data(struct adc5_chip *adc,
> +				    struct adc5_channel_prop *prop,
> +				    struct device_node *node,
> +				    const struct adc_data *data)
> +{
> +	const char *name = node->name, *channel_name;
> +	u32 chan, value, varr[2];
> +	int ret;
> +	struct device *dev = adc->dev;
> +
> +	ret = of_property_read_u32(node, "reg", &chan);
> +	if (ret) {
> +		dev_err(dev, "invalid channel number %s\n", name);
> +		return ret;
> +	}
> +
> +	if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA) {
> +		dev_err(dev, "%s invalid channel number %d\n", name, chan);
> +		return -EINVAL;
> +	}
> +
> +	/* the channel has DT description */
> +	prop->channel = chan;
> +
> +	channel_name = of_get_property(node,
> +				"label", NULL) ? : node->name;
> +	if (!channel_name) {
> +		pr_err("Invalid channel name\n");
> +		return -EINVAL;
> +	}
> +	prop->datasheet_name = channel_name;
> +
> +	ret = of_property_read_u32(node, "qcom,decimation", &value);
> +	if (!ret) {
> +		ret = adc5_decimation_from_dt(value, data->decimation);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid decimation %d\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->decimation = ret;
> +	} else {
> +		prop->decimation = ADC5_DECIMATION_DEFAULT;
> +	}
> +
> +	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
> +	if (!ret) {
> +		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
> +				chan, varr[0], varr[1]);
> +			return ret;
> +		}
> +		prop->prescale = ret;
> +	}
> +
> +	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
> +	if (!ret) {
> +		u8 dig_version[2];
> +
> +		ret = adc5_get_dig_version(adc, dig_version);
> +		if (ret < 0) {
> +			dev_err(dev, "Invalid dig version read %d\n", ret);
> +			return ret;
> +		}
> +
> +		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
> +						dig_version[1]);
> +		/* Digital controller >= 5.3 have hw_settle_2 option */
> +		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
> +			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
> +			ret = adc5_hw_settle_time_from_dt(value,
> +							data->hw_settle_2);
> +		else
> +			ret = adc5_hw_settle_time_from_dt(value,
> +							data->hw_settle_1);
> +
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->hw_settle_time = ret;
> +	} else {
> +		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
> +	}
> +
> +	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
> +	if (!ret) {
> +		ret = adc5_avg_samples_from_dt(value);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid avg-samples %d\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->avg_samples = ret;
> +	} else {
> +		prop->avg_samples = VADC_DEF_AVG_SAMPLES;
> +	}
> +
> +	if (of_property_read_bool(node, "qcom,ratiometric"))
> +		prop->cal_method = ADC5_RATIOMETRIC_CAL;
> +	else
> +		prop->cal_method = ADC5_ABSOLUTE_CAL;
> +
> +	/*
> +	 * Default to using timer calibration. Using a fresh calibration value
> +	 * for every conversion will increase the overall time for a request.
> +	 */
> +	prop->cal_val = ADC5_TIMER_CAL;
> +
> +	dev_dbg(dev, "%02x name %s\n", chan, name);
> +
> +	return 0;
> +}
> +
> +const struct adc_data data_pmic5 = {
> +	.full_scale_code_volt = 0x70e4,
> +	.full_scale_code_cur = 0x2710,
> +	.adc_chans = adc_chans_pmic5,
> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
> +				{250, 420, 840},
> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				800, 900, 1, 2, 4, 6, 8, 10},
> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				1, 2, 4, 8, 16, 32, 64, 128},
> +};
> +
> +const struct adc_data data_pmic_rev2 = {
> +	.full_scale_code_volt = 0x4000,
> +	.full_scale_code_cur = 0x1800,
> +	.adc_chans = adc_chans_rev2,
> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
> +				{256, 512, 1024},
> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{0, 100, 200, 300, 400, 500, 600, 700,
> +				800, 900, 1, 2, 4, 6, 8, 10},
> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				1, 2, 4, 8, 16, 32, 64, 128},
> +};
> +
> +static const struct of_device_id adc5_match_table[] = {
> +	{
> +		.compatible = "qcom,spmi-adc5",
> +		.data = &data_pmic5,
> +	},
> +	{
> +		.compatible = "qcom,spmi-adc-rev2",
> +		.data = &data_pmic_rev2,
> +	},
> +	{ }
> +};
> +
> +static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node *node)
> +{
> +	const struct adc_channels *adc_chan;
> +	struct iio_chan_spec *iio_chan;
> +	struct adc5_channel_prop prop;
> +	struct device_node *child;
> +	unsigned int index = 0;
> +	const struct of_device_id *id;
> +	const struct adc_data *data;
> +	int ret;
> +
> +	adc->nchannels = of_get_available_child_count(node);
> +	if (!adc->nchannels)
> +		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;
> +
> +	iio_chan = adc->iio_chans;
> +	id = of_match_node(adc5_match_table, node);
> +	if (id)
> +		data = id->data;
> +	else
> +		data = &data_pmic5;
> +	adc->data = data;
> +
> +	for_each_available_child_of_node(node, child) {
> +		ret = adc5_get_dt_channel_data(adc, &prop, child, data);
> +		if (ret) {
> +			of_node_put(child);
> +			return ret;
> +		}
> +
> +		prop.scale_fn_type =
> +			data->adc_chans[prop.channel].scale_fn_type;
> +		*chan_props = prop;
> +		adc_chan = &data->adc_chans[prop.channel];
> +
> +		iio_chan->channel = prop.channel;
> +		iio_chan->datasheet_name = prop.datasheet_name;
> +		iio_chan->extend_name = prop.datasheet_name;
> +		iio_chan->info_mask_separate = adc_chan->info_mask;
> +		iio_chan->type = adc_chan->type;
> +		iio_chan->address = index;
> +		iio_chan++;
> +		chan_props++;
> +		index++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int adc5_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	struct device *dev = &pdev->dev;
> +	struct iio_dev *indio_dev;
> +	struct adc5_chip *adc;
> +	struct regmap *regmap;
> +	int ret, irq_eoc;
> +	u32 reg;
> +
> +	regmap = dev_get_regmap(dev->parent, NULL);
> +	if (!regmap)
> +		return -ENODEV;
> +
> +	ret = of_property_read_u32(node, "reg", &reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	adc = iio_priv(indio_dev);
> +	adc->regmap = regmap;
> +	adc->dev = dev;
> +	adc->base = reg;
> +	init_completion(&adc->complete);
> +	mutex_init(&adc->lock);
> +
> +	ret = adc5_get_dt_data(adc, node);
> +	if (ret) {
> +		pr_err("adc get dt data failed\n");
> +		return ret;
> +	}
> +
> +	irq_eoc = platform_get_irq(pdev, 0);
> +	if (irq_eoc < 0) {
> +		if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL)
> +			return irq_eoc;
> +		adc->poll_eoc = true;
> +	} else {
> +		ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0,
> +				       "pm-adc5", adc);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	indio_dev->dev.parent = dev;
> +	indio_dev->dev.of_node = node;
> +	indio_dev->name = pdev->name;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->info = &adc5_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_driver = {
> +	.driver = {
> +		.name = "qcom-spmi-adc5.c",
> +		.of_match_table = adc5_match_table,
> +	},
> +	.probe = adc5_probe,
> +};
> +module_platform_driver(adc5_driver);
> +
> +MODULE_ALIAS("platform:qcom-spmi-adc5");
> +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 ADC driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
> index fe3d782..2a9fecb 100644
> --- a/drivers/iio/adc/qcom-vadc-common.c
> +++ b/drivers/iio/adc/qcom-vadc-common.c
> @@ -47,8 +47,79 @@
>  	{44,	125}
>  };
>  
> +/*
> + * Voltage to temperature table for 100k pull up for NTCG104EF104 with
> + * 1.875V reference.
> + */
> +static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = {
> +	{ 1831,	-40000 },
> +	{ 1814,	-35000 },
> +	{ 1791,	-30000 },
> +	{ 1761,	-25000 },
> +	{ 1723,	-20000 },
> +	{ 1675,	-15000 },
> +	{ 1616,	-10000 },
> +	{ 1545,	-5000 },
> +	{ 1463,	0 },
> +	{ 1370,	5000 },
> +	{ 1268,	10000 },
> +	{ 1160,	15000 },
> +	{ 1049,	20000 },
> +	{ 937,	25000 },
> +	{ 828,	30000 },
> +	{ 726,	35000 },
> +	{ 630,	40000 },
> +	{ 544,	45000 },
> +	{ 467,	50000 },
> +	{ 399,	55000 },
> +	{ 340,	60000 },
> +	{ 290,	65000 },
> +	{ 247,	70000 },
> +	{ 209,	75000 },
> +	{ 179,	80000 },
> +	{ 153,	85000 },
> +	{ 130,	90000 },
> +	{ 112,	95000 },
> +	{ 96,	100000 },
> +	{ 82,	105000 },
> +	{ 71,	110000 },
> +	{ 62,	115000 },
> +	{ 53,	120000 },
> +	{ 46,	125000 },
> +};
> +
> +static int qcom_vadc_scale_hw_calib_volt(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_uv);
> +static int qcom_vadc_scale_hw_calib_therm(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +static int qcom_vadc_scale_hw_smb_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +static int qcom_vadc_scale_hw_chg5_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +static int qcom_vadc_scale_hw_calib_die_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +
> +static struct qcom_adc5_scale_type scale_adc5_fn[] = {
> +	[SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt},
> +	[SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm},
> +	[SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm},
> +	[SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp},
> +	[SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp},
> +	[SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp},
> +};
> +
>  static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
> -				      u32 tablesize, s32 input, s64 *output)
> +				      u32 tablesize, s32 input, int *output)
>  {
>  	bool descending = 1;
>  	u32 i = 0;
> @@ -128,7 +199,7 @@ static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
>  				 bool absolute, u16 adc_code,
>  				 int *result_mdec)
>  {
> -	s64 voltage = 0, result = 0;
> +	s64 voltage = 0;
>  	int ret;
>  
>  	qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
> @@ -138,12 +209,11 @@ static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
>  
>  	ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
>  					 ARRAY_SIZE(adcmap_100k_104ef_104fb),
> -					 voltage, &result);
> +					 voltage, result_mdec);
>  	if (ret)
>  		return ret;
>  
> -	result *= 1000;
> -	*result_mdec = result;
> +	*result_mdec *= 1000;
>  
>  	return 0;
>  }
> @@ -191,6 +261,99 @@ static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph,
>  	return 0;
>  }
>  
> +static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				unsigned int factor)
> +{
> +	s64 voltage, temp, adc_vdd_ref_mv = 1875;
> +
> +	/*
> +	 * The normal data range is between 0V to 1.875V. On cases where
> +	 * we read low voltage values, the ADC code can go beyond the
> +	 * range and the scale result is incorrect so we clamp the values
> +	 * for the cases where the code represents a value below 0V
> +	 */
> +	if (adc_code > VADC5_MAX_CODE)
> +		adc_code = 0;
> +
> +	/* (ADC code * vref_vadc (1.875V)) / full_scale_code */
> +	voltage = (s64) adc_code * adc_vdd_ref_mv * 1000;
> +	voltage = div64_s64(voltage, data->full_scale_code_volt);
> +	if (voltage > 0) {
> +		voltage *= prescale->den;
> +		temp = prescale->num * factor;
> +		voltage = div64_s64(voltage, temp);
> +	} else {
> +		voltage = 0;
> +	}
> +
> +	return (int) voltage;
> +}
> +
> +static int qcom_vadc_scale_hw_calib_volt(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_uv)
> +{
> +	*result_uv = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 1);
> +
> +	return 0;
> +}
> +
> +static int qcom_vadc_scale_hw_calib_therm(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	int ret, voltage;
> +
> +	voltage = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 1000);
> +
> +	/* Map voltage to temperature from look-up table */
> +	return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref,
> +				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
> +				 voltage, result_mdec);
> +}
> +
> +static int qcom_vadc_scale_hw_calib_die_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 2);
> +	*result_mdec -= KELVINMIL_CELSIUSMIL;
> +
> +	return 0;
> +}
> +
> +static int qcom_vadc_scale_hw_smb_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100,
> +				prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR);
> +	*result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec;
> +
> +	return 0;
> +}
> +
> +static int qcom_vadc_scale_hw_chg5_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 4);
> +	*result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec;
> +
> +	return 0;
> +}
> +
>  int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>  		    const struct vadc_linear_graph *calib_graph,
>  		    const struct vadc_prescale_ratio *prescale,
> @@ -221,6 +384,22 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>  }
>  EXPORT_SYMBOL(qcom_vadc_scale);
>  
> +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
> +		    const struct vadc_prescale_ratio *prescale,
> +		    const struct adc_data *data,
> +		    u16 adc_code, int *result)
> +{
> +	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
> +		scaletype < SCALE_HW_CALIB_INVALID)) {
> +		pr_err("Invalid scale type %d\n", scaletype);
> +		return -EINVAL;
> +	}
> +
> +	return scale_adc5_fn[scaletype].scale_fn(prescale, data,
> +					adc_code, result);
> +}
> +EXPORT_SYMBOL(qcom_adc5_hw_scale);
> +
>  int qcom_vadc_decimation_from_dt(u32 value)
>  {
>  	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
> diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
> index 1d5354f..bcd6959 100644
> --- a/drivers/iio/adc/qcom-vadc-common.h
> +++ b/drivers/iio/adc/qcom-vadc-common.h
> @@ -25,15 +25,31 @@
>  
>  #define VADC_DECIMATION_MIN			512
>  #define VADC_DECIMATION_MAX			4096
> +#define ADC5_DEF_VBAT_PRESCALING		1 /* 1:3 */
> +#define ADC5_DECIMATION_SHORT			250
> +#define ADC5_DECIMATION_MEDIUM			420
> +#define ADC5_DECIMATION_LONG			840
> +/* Default decimation - 1024 for rev2, 840 for pmic5 */
> +#define ADC5_DECIMATION_DEFAULT			2
> +#define ADC5_DECIMATION_SAMPLES_MAX		3
>  
>  #define VADC_HW_SETTLE_DELAY_MAX		10000
> +#define VADC_HW_SETTLE_SAMPLES_MAX		16
>  #define VADC_AVG_SAMPLES_MAX			512
> +#define ADC5_AVG_SAMPLES_MAX			16
>  
>  #define KELVINMIL_CELSIUSMIL			273150
> +#define PMIC5_CHG_TEMP_SCALE_FACTOR		377500
> +#define PMIC5_SMB_TEMP_CONSTANT			419400
> +#define PMIC5_SMB_TEMP_SCALE_FACTOR		356
>  
>  #define PMI_CHG_SCALE_1				-138890
>  #define PMI_CHG_SCALE_2				391750000000LL
>  
> +#define VADC5_MAX_CODE				0x7fff
> +#define ADC5_FULL_SCALE_CODE			0x70e4
> +#define ADC5_USR_DATA_CHECK			0x8000
> +
>  /**
>   * struct vadc_map_pt - Map the graph representation for ADC channel
>   * @x: Represent the ADC digitized code.
> @@ -89,6 +105,18 @@ struct vadc_prescale_ratio {
>   * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
>   * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
>   * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
> + * SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to
> + *	voltage (uV) with hardware applied offset/slope values to adc code.
> + * SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC using
> + *	lookup table. The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC using
> + *	100k pullup. The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade.
> + *	The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for PMIC5
> + *	charger temperature.
> + * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5
> + *	SMB1390 temperature.
>   */
>  enum vadc_scale_fn_type {
>  	SCALE_DEFAULT = 0,
> @@ -96,6 +124,22 @@ enum vadc_scale_fn_type {
>  	SCALE_PMIC_THERM,
>  	SCALE_XOTHERM,
>  	SCALE_PMI_CHG_TEMP,
> +	SCALE_HW_CALIB_DEFAULT,
> +	SCALE_HW_CALIB_THERM_100K_PULLUP,
> +	SCALE_HW_CALIB_XOTHERM,
> +	SCALE_HW_CALIB_PMIC_THERM,
> +	SCALE_HW_CALIB_PM5_CHG_TEMP,
> +	SCALE_HW_CALIB_PM5_SMB_TEMP,
> +	SCALE_HW_CALIB_INVALID,
> +};
> +
> +struct adc_data {

I would have preferred this to be prefixed to make it clear
it is a local data type rather than a general one.

> +	const u32	full_scale_code_volt;
> +	const u32	full_scale_code_cur;
> +	const struct adc_channels *adc_chans;
> +	unsigned int	*decimation;
> +	unsigned int	*hw_settle_1;
> +	unsigned int	*hw_settle_2;
>  };
>  
>  int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
> @@ -104,6 +148,16 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>  		    bool absolute,
>  		    u16 adc_code, int *result_mdec);
>  
> +struct qcom_adc5_scale_type {
> +	int (*scale_fn)(const struct vadc_prescale_ratio *prescale,
> +		const struct adc_data *data, u16 adc_code, int *result);
> +};
> +
> +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
> +		    const struct vadc_prescale_ratio *prescale,
> +		    const struct adc_data *data,
> +		    u16 adc_code, int *result_mdec);
> +
>  int qcom_vadc_decimation_from_dt(u32 value);
>  
>  #endif /* QCOM_VADC_COMMON_H */

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

* Re: [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
  2018-07-28 11:08 ` Jonathan Cameron
@ 2018-07-31 18:08   ` smohanad
  2018-08-02  9:21       ` Jonathan Cameron
  0 siblings, 1 reply; 8+ messages in thread
From: smohanad @ 2018-07-31 18:08 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: linux-iio, devicetree, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Rob Herring, linux-iio-owner

On 2018-07-28 04:08, Jonathan Cameron wrote:
> On Wed, 25 Jul 2018 17:09:29 -0700
> Siddartha Mohanadoss <smohanad@codeaurora.org> wrote:
> 
>> This patch adds support for QCOM SPMI PMIC5 family
>> of ADC driver that supports hardware based offset and
>> gain compensation. The ADC peripheral can measure both
>> voltage and current channels whose input signal is
>> connected to the PMIC ADC AMUX.
>> 
>> The register set and configuration has been refreshed
>> compared to the prior QCOM PMIC ADC family. Register
>> ADC5 as part of the IIO framework.
>> 
>> Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
> 
> Hi Siddartha,
> 
> My main questions inline are around providing both PROCESSED and
> RAW readouts for the channels.   Generally this is something we
> don't ever do (as there is little point and it increases the exposed
> ABI).  Now the oddity here is you've copied from the
> qcom-spmi-vadc driver which does this and IIRC that was because
> the initial submission didn't do any of the complex maths to get
> to the PROCESSED values.  That was introduced later, leaving us with
> a mess as we couldn't remove the existing ABI in case someone was
> using it.
> 
> https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ba71704af4a0aae0d9e5812dbdd7bca95e181b14
> 
> So... I'm not convinced either way yet on whether we should let
> this one through as a continuation of the exception we made there
> or not.  Does this matter to you, or can you drop the RAW interface?

Hi Jonathan,

There could be few use cases that would be useful for the client to
have an option to read only the ADC code.

a) Few clients can request the ADC conversion and directly program
the raw code in the hardware.

b) Few clients can program the thresholds in hardware with ADC code to
receive notification on a threshold crossing.

c) Some clients may want to know when small movements occur,
so it would be useful for the client to measure the ADC code and they
could add a delta for the next threshold crossing.

d) If a client wants to do their own math and apply their own scaling i 
can see
them requesting only the ADC code. They could add the scaling in the ADC 
driver
but if they choose add offset to the raw code and program the hardware 
then
providing only the code would be useful.

e) The raw ADC code is useful for debugging purpose. This point is 
optional
as it can also be done by logging the ADC code with a pr_debug.

> 
> There other bits inline are trivial enough I would just have
> ignored or fixed them when applying if it weren't for this question.
> 
> (apologies if we have been round this before - I may have forgotten
> or I may have been dozing during previous review.  This question has
> come up for a few drivers recently so I'm more aware of it now)
> 
>> ---
>>  drivers/iio/adc/Kconfig            |  20 +
>>  drivers/iio/adc/Makefile           |   1 +
>>  drivers/iio/adc/qcom-spmi-adc5.c   | 798 
>> +++++++++++++++++++++++++++++++++++++
>>  drivers/iio/adc/qcom-vadc-common.c | 189 ++++++++-
>>  drivers/iio/adc/qcom-vadc-common.h |  54 +++
>>  5 files changed, 1057 insertions(+), 5 deletions(-)
>>  create mode 100644 drivers/iio/adc/qcom-spmi-adc5.c
>> 
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 9da7907..93ac929 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -597,6 +597,26 @@ config QCOM_SPMI_VADC
>>  	  To compile this driver as a module, choose M here: the module will
>>  	  be called qcom-spmi-vadc.
>> 
>> +config QCOM_SPMI_ADC5
>> +	tristate "Qualcomm Technologies Inc. SPMI PMIC5 ADC"
>> +	depends on SPMI
>> +	select REGMAP_SPMI
>> +	select QCOM_VADC_COMMON
>> +	help
>> +	  This is the IIO Voltage PMIC5 ADC driver for Qualcomm Technologies 
>> Inc.
>> +
>> +	  The driver supports multiple channels read. The ADC is a 16-bit
>> +	  sigma-delta ADC. The hardware supports calibrated results for
>> +	  conversion requests and clients include reading voltage phone
>> +	  power, on board system thermistors connected to the PMIC ADC,
>> +	  PMIC die temperature, charger temperature, battery current, USB 
>> voltage
>> +	  input, voltage signals connected to supported PMIC GPIO inputs. 
>> The
>> +	  hardware supports internal pull-up for thermistors and can choose 
>> between
>> +	  a 100k, 30k and 400k pull up using the ADC channels.
>> +
>> +	  To compile this driver as a module, choose M here: the module will
>> +	  be called qcom-spmi-adc5.
>> +
>>  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 28a9423..3c1a109 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -53,6 +53,7 @@ obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
>>  obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
>>  obj-$(CONFIG_NAU7802) += nau7802.o
>>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>> +obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
>>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>  obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o
>>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c 
>> b/drivers/iio/adc/qcom-spmi-adc5.c
>> new file mode 100644
>> index 0000000..9e3e2ae
>> --- /dev/null
>> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
>> @@ -0,0 +1,798 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/bitops.h>
>> +#include <linux/completion.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.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/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/slab.h>
>> +
>> +#include <dt-bindings/iio/qcom,spmi-vadc.h>
>> +#include "qcom-vadc-common.h"
>> +
>> +#define ADC5_USR_REVISION1			0x0
>> +#define ADC5_USR_STATUS1			0x8
>> +#define ADC5_USR_STATUS1_REQ_STS		BIT(1)
>> +#define ADC5_USR_STATUS1_EOC			BIT(0)
>> +#define ADC5_USR_STATUS1_REQ_STS_EOC_MASK	0x3
>> +
>> +#define ADC5_USR_STATUS2			0x9
>> +#define ADC5_USR_STATUS2_CONV_SEQ_MASK		0x70
>> +#define ADC5_USR_STATUS2_CONV_SEQ_MASK_SHIFT	0x5
>> +
>> +#define ADC5_USR_IBAT_MEAS			0xf
>> +#define ADC5_USR_IBAT_MEAS_SUPPORTED		BIT(0)
>> +
>> +#define ADC5_USR_DIG_PARAM			0x42
>> +#define ADC5_USR_DIG_PARAM_CAL_VAL		BIT(6)
>> +#define ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT	6
>> +#define ADC5_USR_DIG_PARAM_CAL_SEL		0x30
>> +#define ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT	4
>> +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL	0xc
>> +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT	2
>> +
>> +#define ADC5_USR_FAST_AVG_CTL			0x43
>> +#define ADC5_USR_FAST_AVG_CTL_EN		BIT(7)
>> +#define ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK	0x7
>> +
>> +#define ADC5_USR_CH_SEL_CTL			0x44
>> +
>> +#define ADC5_USR_DELAY_CTL			0x45
>> +#define ADC5_USR_HW_SETTLE_DELAY_MASK		0xf
>> +
>> +#define ADC5_USR_EN_CTL1			0x46
>> +#define ADC5_USR_EN_CTL1_ADC_EN			BIT(7)
>> +
>> +#define ADC5_USR_CONV_REQ			0x47
>> +#define ADC5_USR_CONV_REQ_REQ			BIT(7)
>> +
>> +#define ADC5_USR_DATA0				0x50
>> +
>> +#define ADC5_USR_DATA1				0x51
>> +
>> +#define ADC5_USR_IBAT_DATA0			0x52
>> +
>> +#define ADC5_USR_IBAT_DATA1			0x53
>> +
>> +/*
>> + * Conversion time varies based on the decimation, clock rate, fast 
>> average
>> + * samples and measurements queued across different VADC peripherals.
>> + * Set the timeout to a max of 100ms.
>> + */
>> +#define ADC5_CONV_TIME_MIN_US			263
>> +#define ADC5_CONV_TIME_MAX_US			264
>> +#define ADC5_CONV_TIME_RETRY			400
>> +#define ADC5_CONV_TIMEOUT			msecs_to_jiffies(100)
>> +
>> +/* Digital version >= 5.3 supports hw_settle_2 */
>> +#define ADC5_HW_SETTLE_DIFF_MINOR		3
>> +#define ADC5_HW_SETTLE_DIFF_MAJOR		5
>> +
>> +enum adc5_cal_method {
>> +	ADC5_NO_CAL = 0,
>> +	ADC5_RATIOMETRIC_CAL,
>> +	ADC5_ABSOLUTE_CAL
>> +};
>> +
>> +enum adc5_cal_val {
>> +	ADC5_TIMER_CAL = 0,
>> +	ADC5_NEW_CAL
>> +};
>> +
>> +/**
>> + * struct adc5_channel_prop - ADC channel property.
>> + * @channel: channel number, refer to the channel list.
>> + * @cal_method: calibration method.
>> + * @cal_val: calibration value
>> + * @decimation: sampling rate supported for the channel.
>> + * @prescale: channel scaling performed on the input signal.
>> + * @hw_settle_time: the time between AMUX being configured and the
>> + *	start of conversion.
>> + * @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.
>> + * @datasheet_name: Channel name used in device tree.
>> + */
>> +struct adc5_channel_prop {
>> +	unsigned int		channel;
>> +	enum adc5_cal_method	cal_method;
>> +	enum adc5_cal_val	cal_val;
>> +	unsigned int		decimation;
>> +	unsigned int		prescale;
>> +	unsigned int		hw_settle_time;
>> +	unsigned int		avg_samples;
>> +	enum vadc_scale_fn_type	scale_fn_type;
>> +	const char		*datasheet_name;
>> +};
>> +
>> +/**
>> + * struct adc5_chip - ADC private structure.
>> + * @regmap: SPMI ADC5 peripheral register map field.
>> + * @dev: SPMI ADC5 device.
>> + * @base: base address for the ADC peripheral.
>> + * @nchannels: number of ADC channels.
>> + * @chan_props: array of ADC channel properties.
>> + * @iio_chans: array of IIO channels specification.
>> + * @poll_eoc: use polling instead of interrupt.
>> + * @complete: ADC result notification after interrupt is received.
>> + * @lock: ADC lock for access to the peripheral.
>> + * @data: software configuration data.
>> + */
>> +struct adc5_chip {
>> +	struct regmap		*regmap;
>> +	struct device		*dev;
>> +	u16			base;
>> +	unsigned int		nchannels;
>> +	struct adc5_channel_prop	*chan_props;
>> +	struct iio_chan_spec	*iio_chans;
>> +	bool			poll_eoc;
>> +	struct completion	complete;
>> +	struct mutex		lock;
>> +	const struct adc_data	*data;
>> +};
>> +
>> +static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
>> +	{.num =  1, .den =  1},
>> +	{.num =  1, .den =  3},
>> +	{.num =  1, .den =  4},
>> +	{.num =  1, .den =  6},
>> +	{.num =  1, .den = 20},
>> +	{.num =  1, .den =  8},
>> +	{.num = 10, .den = 81},
>> +	{.num =  1, .den = 10},
>> +	{.num =  1, .den = 16}
>> +};
>> +
>> +static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int 
>> len)
>> +{
>> +	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
>> +}
>> +
>> +static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, 
>> int len)
>> +{
>> +	return regmap_bulk_write(adc->regmap, adc->base + offset, data, 
>> len);
>> +}
>> +
>> +static int adc5_prescaling_from_dt(u32 num, u32 den)
>> +{
>> +	unsigned int pre;
>> +
>> +	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
>> +		if (adc5_prescale_ratios[pre].num == num &&
>> +		    adc5_prescale_ratios[pre].den == den)
>> +			break;
>> +
>> +	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
>> +		return -EINVAL;
>> +
>> +	return pre;
>> +}
>> +
>> +static int adc5_get_dig_version(struct adc5_chip *adc,
>> +					u8 *dig_version)
>> +{
>> +	return adc5_read(adc, ADC5_USR_REVISION1, dig_version, 2);
>> +}
>> +
>> +static int adc5_hw_settle_time_from_dt(u32 value,
>> +					const unsigned int *hw_settle)
>> +{
>> +	uint32_t i;
>> +
>> +	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
>> +		if (value == hw_settle[i])
>> +			return i;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int adc5_avg_samples_from_dt(u32 value)
>> +{
>> +	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
>> +		return -EINVAL;
>> +
>> +	return __ffs64(value);
>> +}
>> +
>> +static int adc5_decimation_from_dt(u32 value,
>> +					const unsigned int *decimation)
>> +{
>> +	uint32_t i;
>> +
>> +	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
>> +		if (value == decimation[i])
>> +			return i;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
>> +{
>> +	int ret;
>> +	u8 rslt_lsb, rslt_msb;
>> +
>> +	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	*data = (rslt_msb << 8) | rslt_lsb;
>> +
>> +	if (*data == ADC5_USR_DATA_CHECK) {
>> +		pr_err("Invalid data:0x%x\n", *data);
>> +		return -EINVAL;
>> +	}
>> +
>> +	return ret;
> return 0; Might as well make it clear it can't be anything else.

Ok.

> 
>> +}
>> +
>> +static int adc5_poll_wait_eoc(struct adc5_chip *adc)
>> +{
>> +	unsigned int count, retry = ADC5_CONV_TIME_RETRY;
>> +	u8 status1;
>> +	int ret;
>> +
>> +	for (count = 0; count < retry; count++) {
>> +		ret = adc5_read(adc, ADC5_USR_STATUS1, &status1, 1);
>> +		if (ret)
>> +			return ret;
>> +
>> +		status1 &= ADC5_USR_STATUS1_REQ_STS_EOC_MASK;
>> +		if (status1 == ADC5_USR_STATUS1_EOC)
>> +			return 0;
> 
> I would put a blank line here for readability.

Ok.

> 
>> +		usleep_range(ADC5_CONV_TIME_MIN_US, ADC5_CONV_TIME_MAX_US);
>> +	}
>> +
>> +	return -ETIMEDOUT;
>> +}
>> +
>> +static void adc5_update_dig_param(struct adc5_chip *adc,
>> +			struct adc5_channel_prop *prop, u8 *data)
>> +{
>> +	/* Update calibration value */
>> +	*data &= ~ADC5_USR_DIG_PARAM_CAL_VAL;
>> +	*data |= (prop->cal_val << ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT);
>> +
>> +	/* Update calibration select */
>> +	*data &= ~ADC5_USR_DIG_PARAM_CAL_SEL;
>> +	*data |= (prop->cal_method << ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT);
>> +
>> +	/* Update decimation ratio select */
>> +	*data &= ~ADC5_USR_DIG_PARAM_DEC_RATIO_SEL;
>> +	*data |= (prop->decimation << 
>> ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT);
>> +}
>> +
>> +static int adc5_configure(struct adc5_chip *adc,
>> +			struct adc5_channel_prop *prop)
>> +{
>> +	int ret;
>> +	u8 buf[6];
>> +
>> +	/* Read registers 0x42 through 0x46 */
>> +	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, 6);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	/* Digital param selection */
>> +	adc5_update_dig_param(adc, prop, &buf[0]);
>> +
>> +	/* Update fast average sample value */
>> +	buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
>> +	buf[1] |= prop->avg_samples;
>> +
>> +	/* Select ADC channel */
>> +	buf[2] = prop->channel;
>> +
>> +	/* Select HW settle delay for channel */
>> +	buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK;
>> +	buf[3] |= prop->hw_settle_time;
>> +
>> +	/* Select ADC enable */
>> +	buf[4] |= ADC5_USR_EN_CTL1_ADC_EN;
>> +
>> +	/* Select CONV request */
>> +	buf[5] |= ADC5_USR_CONV_REQ_REQ;
>> +
>> +	if (!adc->poll_eoc)
>> +		reinit_completion(&adc->complete);
>> +
>> +	return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, 6);
>> +}
>> +
>> +static int adc5_do_conversion(struct adc5_chip *adc,
>> +			struct adc5_channel_prop *prop,
>> +			struct iio_chan_spec const *chan,
>> +			u16 *data_volt, u16 *data_cur)
>> +{
>> +	int ret;
>> +
>> +	mutex_lock(&adc->lock);
>> +
>> +	ret = adc5_configure(adc, prop);
>> +	if (ret) {
>> +		pr_err("ADC configure failed with %d\n", ret);
>> +		goto unlock;
>> +	}
>> +
>> +	if (adc->poll_eoc) {
>> +		ret = adc5_poll_wait_eoc(adc);
>> +		if (ret < 0) {
>> +			pr_err("EOC bit not set\n");
>> +			goto unlock;
>> +		}
>> +	} else {
>> +		ret = wait_for_completion_timeout(&adc->complete,
>> +							ADC5_CONV_TIMEOUT);
>> +		if (!ret) {
>> +			pr_debug("Did not get completion timeout.\n");
>> +			ret = adc5_poll_wait_eoc(adc);
>> +			if (ret < 0) {
>> +				pr_err("EOC bit not set\n");
>> +				goto unlock;
>> +			}
>> +		}
>> +	}
>> +
>> +	ret = adc5_read_voltage_data(adc, data_volt);
>> +unlock:
>> +	mutex_unlock(&adc->lock);
>> +
>> +	return ret;
>> +}
>> +
>> +static irqreturn_t adc5_isr(int irq, void *dev_id)
>> +{
>> +	struct adc5_chip *adc = dev_id;
>> +
>> +	complete(&adc->complete);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int adc5_of_xlate(struct iio_dev *indio_dev,
>> +				const struct of_phandle_args *iiospec)
>> +{
>> +	struct adc5_chip *adc = iio_priv(indio_dev);
>> +	int i;
>> +
>> +	for (i = 0; i < adc->nchannels; i++)
>> +		if (adc->chan_props[i].channel == iiospec->args[0])
>> +			return i;
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int adc5_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_prop *prop;
>> +	u16 adc_code_volt, adc_code_cur;
>> +	int ret;
>> +
>> +	prop = &adc->chan_props[chan->address];
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_PROCESSED:
>> +		ret = adc5_do_conversion(adc, prop, chan,
>> +				&adc_code_volt, &adc_code_cur);
>> +		if (ret)
> return ret;
>> +			break;
>> +
>> +		ret = qcom_adc5_hw_scale(prop->scale_fn_type,
>> +			&adc5_prescale_ratios[prop->prescale],
>> +			adc->data,
>> +			adc_code_volt, val);
>> +		if (ret)
> return ret;
>> +			break;
>> +
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_RAW:
>> +		ret = adc5_do_conversion(adc, prop, chan,
>> +				&adc_code_volt, &adc_code_cur);
>> +		if (ret)
> return ret;
>> +			break;
>> +
>> +		*val = (int)adc_code_volt;
>> +		*val2 = (int)adc_code_cur;
>> +		return IIO_VAL_INT;
>> +	default:
>> +		ret = -EINVAL;
> return -EINVAL and drop the retunr ret as we won't be able to get 
> there.
>> +		break;
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static const struct iio_info adc5_info = {
>> +	.read_raw = adc5_read_raw,
>> +	.of_xlate = adc5_of_xlate,
>> +};
>> +
>> +struct adc_channels {
>> +	const char *datasheet_name;
>> +	unsigned int prescale_index;
>> +	enum iio_chan_type type;
>> +	long info_mask;
>> +	enum vadc_scale_fn_type scale_fn_type;
>> +};
>> +
>> +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
>> +	{								\
>> +		.datasheet_name = (_dname),				\
>> +		.prescale_index = _pre,					\
>> +		.type = _type,						\
>> +		.info_mask = _mask,					\
>> +		.scale_fn_type = _scale,				\
>> +	},								\
>> +
>> +#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
>> +	ADC5_CHAN(_dname, IIO_TEMP,					\
>> +		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\
> I'm fairly sure we've been round this before.  A device should not 
> supply
> both raw and processed values without very very good reasons.
> So far the only reasons we have had that I consider valid are:
> 
> 1. We got it wrong initially and output raw values, only to have 
> processed
>    added later to support something non linear.  We are stuck with 
> supporting
>    raw by existing ABI.
> 
> 2. We have a non linear channel that needs processed values and has 
> events.
>    For some reason we can't map the event controls from processed back
>    to raw (weird case but who knows) so we have to support both.
> 
> 
> Now this case 'kind' of falls into case 1 as that is what I think 
> happened
> with the qcom-spmi-vadc driver and lead to both being there.

Clients could also request the reference channels ADC code and do their 
own
math and scaling. If there are any offsets that may be added to the ADC 
code
then this can be done within the client driver that programs its 
hardware
for any threshold crossing notification.

> 
> Hmm. This is awkward as in theory we are adding another 'broken' 
> interface
> here, but it is reasonable to assume that there might be code that 
> requires
> this interface on such a similar chip.
> 
> Do you definitely need to support both for some applications?  
> Technically
> we would not be causing a regression if we don't support _raw as it
> never worked for this particular device, but I can sympathise (and be 
> persuaded)
> to let it go here if there is a strong usecase.
> 
>> +		_pre, _scale)						\
>> +
>> +#define ADC5_CHAN_VOLT(_dname, _pre, _scale)				\
>> +	ADC5_CHAN(_dname, IIO_VOLTAGE,					\
>> +		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
>> +		  _pre, _scale)						\
>> +
>> +static const struct adc_channels adc_chans_pmic5[ADC5_MAX_CHANNEL] = 
>> {
>> +	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
>> +					SCALE_HW_CALIB_PMIC_THERM)
>> +	[ADC5_USB_IN_I]		= ADC5_CHAN_VOLT("usb_in_i_uv", 1,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_USB_IN_V_16]	= ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_CHG_TEMP]		= ADC5_CHAN_TEMP("chg_temp", 1,
>> +					SCALE_HW_CALIB_PM5_CHG_TEMP)
>> +	/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit 
>> */
>> +	[ADC5_SBUx]		= ADC5_CHAN_VOLT("chg_sbux", 3,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_MID_CHG_DIV6]	= ADC5_CHAN_VOLT("chg_mid_chg", 6,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm", 1,
>> +					SCALE_HW_CALIB_XOTHERM)
>> +	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +	[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +	[ADC5_AMUX_THM2]	= ADC5_CHAN_TEMP("amux_thm2", 1,
>> +					SCALE_HW_CALIB_PM5_SMB_TEMP)
>> +};
>> +
>> +static const struct adc_channels adc_chans_rev2[ADC5_MAX_CHANNEL] = {
>> +	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_VCOIN]		= ADC5_CHAN_VOLT("vcoin", 3,
>> +					SCALE_HW_CALIB_DEFAULT)
>> +	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
>> +					SCALE_HW_CALIB_PMIC_THERM)
>> +	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +	[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
>> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
>> +};
>> +
>> +static int adc5_get_dt_channel_data(struct adc5_chip *adc,
>> +				    struct adc5_channel_prop *prop,
>> +				    struct device_node *node,
>> +				    const struct adc_data *data)
>> +{
>> +	const char *name = node->name, *channel_name;
>> +	u32 chan, value, varr[2];
>> +	int ret;
>> +	struct device *dev = adc->dev;
>> +
>> +	ret = of_property_read_u32(node, "reg", &chan);
>> +	if (ret) {
>> +		dev_err(dev, "invalid channel number %s\n", name);
>> +		return ret;
>> +	}
>> +
>> +	if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA) {
>> +		dev_err(dev, "%s invalid channel number %d\n", name, chan);
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* the channel has DT description */
>> +	prop->channel = chan;
>> +
>> +	channel_name = of_get_property(node,
>> +				"label", NULL) ? : node->name;
>> +	if (!channel_name) {
>> +		pr_err("Invalid channel name\n");
>> +		return -EINVAL;
>> +	}
>> +	prop->datasheet_name = channel_name;
>> +
>> +	ret = of_property_read_u32(node, "qcom,decimation", &value);
>> +	if (!ret) {
>> +		ret = adc5_decimation_from_dt(value, data->decimation);
>> +		if (ret < 0) {
>> +			dev_err(dev, "%02x invalid decimation %d\n",
>> +				chan, value);
>> +			return ret;
>> +		}
>> +		prop->decimation = ret;
>> +	} else {
>> +		prop->decimation = ADC5_DECIMATION_DEFAULT;
>> +	}
>> +
>> +	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
>> +	if (!ret) {
>> +		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
>> +		if (ret < 0) {
>> +			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
>> +				chan, varr[0], varr[1]);
>> +			return ret;
>> +		}
>> +		prop->prescale = ret;
>> +	}
>> +
>> +	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
>> +	if (!ret) {
>> +		u8 dig_version[2];
>> +
>> +		ret = adc5_get_dig_version(adc, dig_version);
>> +		if (ret < 0) {
>> +			dev_err(dev, "Invalid dig version read %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
>> +						dig_version[1]);
>> +		/* Digital controller >= 5.3 have hw_settle_2 option */
>> +		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
>> +			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
>> +			ret = adc5_hw_settle_time_from_dt(value,
>> +							data->hw_settle_2);
>> +		else
>> +			ret = adc5_hw_settle_time_from_dt(value,
>> +							data->hw_settle_1);
>> +
>> +		if (ret < 0) {
>> +			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
>> +				chan, value);
>> +			return ret;
>> +		}
>> +		prop->hw_settle_time = ret;
>> +	} else {
>> +		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
>> +	}
>> +
>> +	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
>> +	if (!ret) {
>> +		ret = adc5_avg_samples_from_dt(value);
>> +		if (ret < 0) {
>> +			dev_err(dev, "%02x invalid avg-samples %d\n",
>> +				chan, value);
>> +			return ret;
>> +		}
>> +		prop->avg_samples = ret;
>> +	} else {
>> +		prop->avg_samples = VADC_DEF_AVG_SAMPLES;
>> +	}
>> +
>> +	if (of_property_read_bool(node, "qcom,ratiometric"))
>> +		prop->cal_method = ADC5_RATIOMETRIC_CAL;
>> +	else
>> +		prop->cal_method = ADC5_ABSOLUTE_CAL;
>> +
>> +	/*
>> +	 * Default to using timer calibration. Using a fresh calibration 
>> value
>> +	 * for every conversion will increase the overall time for a 
>> request.
>> +	 */
>> +	prop->cal_val = ADC5_TIMER_CAL;
>> +
>> +	dev_dbg(dev, "%02x name %s\n", chan, name);
>> +
>> +	return 0;
>> +}
>> +
>> +const struct adc_data data_pmic5 = {
>> +	.full_scale_code_volt = 0x70e4,
>> +	.full_scale_code_cur = 0x2710,
>> +	.adc_chans = adc_chans_pmic5,
>> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
>> +				{250, 420, 840},
>> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
>> +				{15, 100, 200, 300, 400, 500, 600, 700,
>> +				800, 900, 1, 2, 4, 6, 8, 10},
>> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
>> +				{15, 100, 200, 300, 400, 500, 600, 700,
>> +				1, 2, 4, 8, 16, 32, 64, 128},
>> +};
>> +
>> +const struct adc_data data_pmic_rev2 = {
>> +	.full_scale_code_volt = 0x4000,
>> +	.full_scale_code_cur = 0x1800,
>> +	.adc_chans = adc_chans_rev2,
>> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
>> +				{256, 512, 1024},
>> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
>> +				{0, 100, 200, 300, 400, 500, 600, 700,
>> +				800, 900, 1, 2, 4, 6, 8, 10},
>> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
>> +				{15, 100, 200, 300, 400, 500, 600, 700,
>> +				1, 2, 4, 8, 16, 32, 64, 128},
>> +};
>> +
>> +static const struct of_device_id adc5_match_table[] = {
>> +	{
>> +		.compatible = "qcom,spmi-adc5",
>> +		.data = &data_pmic5,
>> +	},
>> +	{
>> +		.compatible = "qcom,spmi-adc-rev2",
>> +		.data = &data_pmic_rev2,
>> +	},
>> +	{ }
>> +};
>> +
>> +static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node 
>> *node)
>> +{
>> +	const struct adc_channels *adc_chan;
>> +	struct iio_chan_spec *iio_chan;
>> +	struct adc5_channel_prop prop;
>> +	struct device_node *child;
>> +	unsigned int index = 0;
>> +	const struct of_device_id *id;
>> +	const struct adc_data *data;
>> +	int ret;
>> +
>> +	adc->nchannels = of_get_available_child_count(node);
>> +	if (!adc->nchannels)
>> +		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;
>> +
>> +	iio_chan = adc->iio_chans;
>> +	id = of_match_node(adc5_match_table, node);
>> +	if (id)
>> +		data = id->data;
>> +	else
>> +		data = &data_pmic5;
>> +	adc->data = data;
>> +
>> +	for_each_available_child_of_node(node, child) {
>> +		ret = adc5_get_dt_channel_data(adc, &prop, child, data);
>> +		if (ret) {
>> +			of_node_put(child);
>> +			return ret;
>> +		}
>> +
>> +		prop.scale_fn_type =
>> +			data->adc_chans[prop.channel].scale_fn_type;
>> +		*chan_props = prop;
>> +		adc_chan = &data->adc_chans[prop.channel];
>> +
>> +		iio_chan->channel = prop.channel;
>> +		iio_chan->datasheet_name = prop.datasheet_name;
>> +		iio_chan->extend_name = prop.datasheet_name;
>> +		iio_chan->info_mask_separate = adc_chan->info_mask;
>> +		iio_chan->type = adc_chan->type;
>> +		iio_chan->address = index;
>> +		iio_chan++;
>> +		chan_props++;
>> +		index++;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int adc5_probe(struct platform_device *pdev)
>> +{
>> +	struct device_node *node = pdev->dev.of_node;
>> +	struct device *dev = &pdev->dev;
>> +	struct iio_dev *indio_dev;
>> +	struct adc5_chip *adc;
>> +	struct regmap *regmap;
>> +	int ret, irq_eoc;
>> +	u32 reg;
>> +
>> +	regmap = dev_get_regmap(dev->parent, NULL);
>> +	if (!regmap)
>> +		return -ENODEV;
>> +
>> +	ret = of_property_read_u32(node, "reg", &reg);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
>> +	if (!indio_dev)
>> +		return -ENOMEM;
>> +
>> +	adc = iio_priv(indio_dev);
>> +	adc->regmap = regmap;
>> +	adc->dev = dev;
>> +	adc->base = reg;
>> +	init_completion(&adc->complete);
>> +	mutex_init(&adc->lock);
>> +
>> +	ret = adc5_get_dt_data(adc, node);
>> +	if (ret) {
>> +		pr_err("adc get dt data failed\n");
>> +		return ret;
>> +	}
>> +
>> +	irq_eoc = platform_get_irq(pdev, 0);
>> +	if (irq_eoc < 0) {
>> +		if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL)
>> +			return irq_eoc;
>> +		adc->poll_eoc = true;
>> +	} else {
>> +		ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0,
>> +				       "pm-adc5", adc);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	indio_dev->dev.parent = dev;
>> +	indio_dev->dev.of_node = node;
>> +	indio_dev->name = pdev->name;
>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>> +	indio_dev->info = &adc5_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_driver = {
>> +	.driver = {
>> +		.name = "qcom-spmi-adc5.c",
>> +		.of_match_table = adc5_match_table,
>> +	},
>> +	.probe = adc5_probe,
>> +};
>> +module_platform_driver(adc5_driver);
>> +
>> +MODULE_ALIAS("platform:qcom-spmi-adc5");
>> +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 ADC driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/drivers/iio/adc/qcom-vadc-common.c 
>> b/drivers/iio/adc/qcom-vadc-common.c
>> index fe3d782..2a9fecb 100644
>> --- a/drivers/iio/adc/qcom-vadc-common.c
>> +++ b/drivers/iio/adc/qcom-vadc-common.c
>> @@ -47,8 +47,79 @@
>>  	{44,	125}
>>  };
>> 
>> +/*
>> + * Voltage to temperature table for 100k pull up for NTCG104EF104 
>> with
>> + * 1.875V reference.
>> + */
>> +static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = 
>> {
>> +	{ 1831,	-40000 },
>> +	{ 1814,	-35000 },
>> +	{ 1791,	-30000 },
>> +	{ 1761,	-25000 },
>> +	{ 1723,	-20000 },
>> +	{ 1675,	-15000 },
>> +	{ 1616,	-10000 },
>> +	{ 1545,	-5000 },
>> +	{ 1463,	0 },
>> +	{ 1370,	5000 },
>> +	{ 1268,	10000 },
>> +	{ 1160,	15000 },
>> +	{ 1049,	20000 },
>> +	{ 937,	25000 },
>> +	{ 828,	30000 },
>> +	{ 726,	35000 },
>> +	{ 630,	40000 },
>> +	{ 544,	45000 },
>> +	{ 467,	50000 },
>> +	{ 399,	55000 },
>> +	{ 340,	60000 },
>> +	{ 290,	65000 },
>> +	{ 247,	70000 },
>> +	{ 209,	75000 },
>> +	{ 179,	80000 },
>> +	{ 153,	85000 },
>> +	{ 130,	90000 },
>> +	{ 112,	95000 },
>> +	{ 96,	100000 },
>> +	{ 82,	105000 },
>> +	{ 71,	110000 },
>> +	{ 62,	115000 },
>> +	{ 53,	120000 },
>> +	{ 46,	125000 },
>> +};
>> +
>> +static int qcom_vadc_scale_hw_calib_volt(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_uv);
>> +static int qcom_vadc_scale_hw_calib_therm(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec);
>> +static int qcom_vadc_scale_hw_smb_temp(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec);
>> +static int qcom_vadc_scale_hw_chg5_temp(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec);
>> +static int qcom_vadc_scale_hw_calib_die_temp(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec);
>> +
>> +static struct qcom_adc5_scale_type scale_adc5_fn[] = {
>> +	[SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt},
>> +	[SCALE_HW_CALIB_THERM_100K_PULLUP] = 
>> {qcom_vadc_scale_hw_calib_therm},
>> +	[SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm},
>> +	[SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp},
>> +	[SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp},
>> +	[SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp},
>> +};
>> +
>>  static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
>> -				      u32 tablesize, s32 input, s64 *output)
>> +				      u32 tablesize, s32 input, int *output)
>>  {
>>  	bool descending = 1;
>>  	u32 i = 0;
>> @@ -128,7 +199,7 @@ static int qcom_vadc_scale_therm(const struct 
>> vadc_linear_graph *calib_graph,
>>  				 bool absolute, u16 adc_code,
>>  				 int *result_mdec)
>>  {
>> -	s64 voltage = 0, result = 0;
>> +	s64 voltage = 0;
>>  	int ret;
>> 
>>  	qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
>> @@ -138,12 +209,11 @@ static int qcom_vadc_scale_therm(const struct 
>> vadc_linear_graph *calib_graph,
>> 
>>  	ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
>>  					 ARRAY_SIZE(adcmap_100k_104ef_104fb),
>> -					 voltage, &result);
>> +					 voltage, result_mdec);
>>  	if (ret)
>>  		return ret;
>> 
>> -	result *= 1000;
>> -	*result_mdec = result;
>> +	*result_mdec *= 1000;
>> 
>>  	return 0;
>>  }
>> @@ -191,6 +261,99 @@ static int qcom_vadc_scale_chg_temp(const struct 
>> vadc_linear_graph *calib_graph,
>>  	return 0;
>>  }
>> 
>> +static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				unsigned int factor)
>> +{
>> +	s64 voltage, temp, adc_vdd_ref_mv = 1875;
>> +
>> +	/*
>> +	 * The normal data range is between 0V to 1.875V. On cases where
>> +	 * we read low voltage values, the ADC code can go beyond the
>> +	 * range and the scale result is incorrect so we clamp the values
>> +	 * for the cases where the code represents a value below 0V
>> +	 */
>> +	if (adc_code > VADC5_MAX_CODE)
>> +		adc_code = 0;
>> +
>> +	/* (ADC code * vref_vadc (1.875V)) / full_scale_code */
>> +	voltage = (s64) adc_code * adc_vdd_ref_mv * 1000;
>> +	voltage = div64_s64(voltage, data->full_scale_code_volt);
>> +	if (voltage > 0) {
>> +		voltage *= prescale->den;
>> +		temp = prescale->num * factor;
>> +		voltage = div64_s64(voltage, temp);
>> +	} else {
>> +		voltage = 0;
>> +	}
>> +
>> +	return (int) voltage;
>> +}
>> +
>> +static int qcom_vadc_scale_hw_calib_volt(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_uv)
>> +{
>> +	*result_uv = qcom_vadc_scale_code_voltage_factor(adc_code,
>> +				prescale, data, 1);
>> +
>> +	return 0;
>> +}
>> +
>> +static int qcom_vadc_scale_hw_calib_therm(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec)
>> +{
>> +	int ret, voltage;
>> +
>> +	voltage = qcom_vadc_scale_code_voltage_factor(adc_code,
>> +				prescale, data, 1000);
>> +
>> +	/* Map voltage to temperature from look-up table */
>> +	return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref,
>> +				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
>> +				 voltage, result_mdec);
>> +}
>> +
>> +static int qcom_vadc_scale_hw_calib_die_temp(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec)
>> +{
>> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
>> +				prescale, data, 2);
>> +	*result_mdec -= KELVINMIL_CELSIUSMIL;
>> +
>> +	return 0;
>> +}
>> +
>> +static int qcom_vadc_scale_hw_smb_temp(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec)
>> +{
>> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100,
>> +				prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR);
>> +	*result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec;
>> +
>> +	return 0;
>> +}
>> +
>> +static int qcom_vadc_scale_hw_chg5_temp(
>> +				const struct vadc_prescale_ratio *prescale,
>> +				const struct adc_data *data,
>> +				u16 adc_code, int *result_mdec)
>> +{
>> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
>> +				prescale, data, 4);
>> +	*result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec;
>> +
>> +	return 0;
>> +}
>> +
>>  int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>>  		    const struct vadc_linear_graph *calib_graph,
>>  		    const struct vadc_prescale_ratio *prescale,
>> @@ -221,6 +384,22 @@ int qcom_vadc_scale(enum vadc_scale_fn_type 
>> scaletype,
>>  }
>>  EXPORT_SYMBOL(qcom_vadc_scale);
>> 
>> +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
>> +		    const struct vadc_prescale_ratio *prescale,
>> +		    const struct adc_data *data,
>> +		    u16 adc_code, int *result)
>> +{
>> +	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
>> +		scaletype < SCALE_HW_CALIB_INVALID)) {
>> +		pr_err("Invalid scale type %d\n", scaletype);
>> +		return -EINVAL;
>> +	}
>> +
>> +	return scale_adc5_fn[scaletype].scale_fn(prescale, data,
>> +					adc_code, result);
>> +}
>> +EXPORT_SYMBOL(qcom_adc5_hw_scale);
>> +
>>  int qcom_vadc_decimation_from_dt(u32 value)
>>  {
>>  	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
>> diff --git a/drivers/iio/adc/qcom-vadc-common.h 
>> b/drivers/iio/adc/qcom-vadc-common.h
>> index 1d5354f..bcd6959 100644
>> --- a/drivers/iio/adc/qcom-vadc-common.h
>> +++ b/drivers/iio/adc/qcom-vadc-common.h
>> @@ -25,15 +25,31 @@
>> 
>>  #define VADC_DECIMATION_MIN			512
>>  #define VADC_DECIMATION_MAX			4096
>> +#define ADC5_DEF_VBAT_PRESCALING		1 /* 1:3 */
>> +#define ADC5_DECIMATION_SHORT			250
>> +#define ADC5_DECIMATION_MEDIUM			420
>> +#define ADC5_DECIMATION_LONG			840
>> +/* Default decimation - 1024 for rev2, 840 for pmic5 */
>> +#define ADC5_DECIMATION_DEFAULT			2
>> +#define ADC5_DECIMATION_SAMPLES_MAX		3
>> 
>>  #define VADC_HW_SETTLE_DELAY_MAX		10000
>> +#define VADC_HW_SETTLE_SAMPLES_MAX		16
>>  #define VADC_AVG_SAMPLES_MAX			512
>> +#define ADC5_AVG_SAMPLES_MAX			16
>> 
>>  #define KELVINMIL_CELSIUSMIL			273150
>> +#define PMIC5_CHG_TEMP_SCALE_FACTOR		377500
>> +#define PMIC5_SMB_TEMP_CONSTANT			419400
>> +#define PMIC5_SMB_TEMP_SCALE_FACTOR		356
>> 
>>  #define PMI_CHG_SCALE_1				-138890
>>  #define PMI_CHG_SCALE_2				391750000000LL
>> 
>> +#define VADC5_MAX_CODE				0x7fff
>> +#define ADC5_FULL_SCALE_CODE			0x70e4
>> +#define ADC5_USR_DATA_CHECK			0x8000
>> +
>>  /**
>>   * struct vadc_map_pt - Map the graph representation for ADC channel
>>   * @x: Represent the ADC digitized code.
>> @@ -89,6 +105,18 @@ struct vadc_prescale_ratio {
>>   * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
>>   * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
>>   * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
>> + * SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to
>> + *	voltage (uV) with hardware applied offset/slope values to adc 
>> code.
>> + * SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC 
>> using
>> + *	lookup table. The hardware applies offset/slope to adc code.
>> + * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC 
>> using
>> + *	100k pullup. The hardware applies offset/slope to adc code.
>> + * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's 
>> Centigrade.
>> + *	The hardware applies offset/slope to adc code.
>> + * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for 
>> PMIC5
>> + *	charger temperature.
>> + * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for 
>> PMIC5
>> + *	SMB1390 temperature.
>>   */
>>  enum vadc_scale_fn_type {
>>  	SCALE_DEFAULT = 0,
>> @@ -96,6 +124,22 @@ enum vadc_scale_fn_type {
>>  	SCALE_PMIC_THERM,
>>  	SCALE_XOTHERM,
>>  	SCALE_PMI_CHG_TEMP,
>> +	SCALE_HW_CALIB_DEFAULT,
>> +	SCALE_HW_CALIB_THERM_100K_PULLUP,
>> +	SCALE_HW_CALIB_XOTHERM,
>> +	SCALE_HW_CALIB_PMIC_THERM,
>> +	SCALE_HW_CALIB_PM5_CHG_TEMP,
>> +	SCALE_HW_CALIB_PM5_SMB_TEMP,
>> +	SCALE_HW_CALIB_INVALID,
>> +};
>> +
>> +struct adc_data {
> 
> I would have preferred this to be prefixed to make it clear
> it is a local data type rather than a general one.

Will pre-fix with adc5_data.

> 
>> +	const u32	full_scale_code_volt;
>> +	const u32	full_scale_code_cur;
>> +	const struct adc_channels *adc_chans;
>> +	unsigned int	*decimation;
>> +	unsigned int	*hw_settle_1;
>> +	unsigned int	*hw_settle_2;
>>  };
>> 
>>  int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>> @@ -104,6 +148,16 @@ int qcom_vadc_scale(enum vadc_scale_fn_type 
>> scaletype,
>>  		    bool absolute,
>>  		    u16 adc_code, int *result_mdec);
>> 
>> +struct qcom_adc5_scale_type {
>> +	int (*scale_fn)(const struct vadc_prescale_ratio *prescale,
>> +		const struct adc_data *data, u16 adc_code, int *result);
>> +};
>> +
>> +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
>> +		    const struct vadc_prescale_ratio *prescale,
>> +		    const struct adc_data *data,
>> +		    u16 adc_code, int *result_mdec);
>> +
>>  int qcom_vadc_decimation_from_dt(u32 value);
>> 
>>  #endif /* QCOM_VADC_COMMON_H */
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
  2018-07-31 18:08   ` smohanad
@ 2018-08-02  9:21       ` Jonathan Cameron
  0 siblings, 0 replies; 8+ messages in thread
From: Jonathan Cameron @ 2018-08-02  9:21 UTC (permalink / raw)
  To: smohanad
  Cc: Jonathan Cameron, linux-iio, devicetree, Hartmut Knaack, Lars-Peter

On Tue, 31 Jul 2018 11:08:13 -0700
<smohanad@codeaurora.org> wrote:

> On 2018-07-28 04:08, Jonathan Cameron wrote:
> > On Wed, 25 Jul 2018 17:09:29 -0700
> > Siddartha Mohanadoss <smohanad@codeaurora.org> wrote:
> >   
> >> This patch adds support for QCOM SPMI PMIC5 family
> >> of ADC driver that supports hardware based offset and
> >> gain compensation. The ADC peripheral can measure both
> >> voltage and current channels whose input signal is
> >> connected to the PMIC ADC AMUX.
> >> 
> >> The register set and configuration has been refreshed
> >> compared to the prior QCOM PMIC ADC family. Register
> >> ADC5 as part of the IIO framework.
> >> 
> >> Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>  
> > 
> > Hi Siddartha,
> > 
> > My main questions inline are around providing both PROCESSED and
> > RAW readouts for the channels.   Generally this is something we
> > don't ever do (as there is little point and it increases the exposed
> > ABI).  Now the oddity here is you've copied from the
> > qcom-spmi-vadc driver which does this and IIRC that was because
> > the initial submission didn't do any of the complex maths to get
> > to the PROCESSED values.  That was introduced later, leaving us with
> > a mess as we couldn't remove the existing ABI in case someone was
> > using it.
> > 
> > https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ba71704af4a0aae0d9e5812dbdd7bca95e181b14
> > 
> > So... I'm not convinced either way yet on whether we should let
> > this one through as a continuation of the exception we made there
> > or not.  Does this matter to you, or can you drop the RAW interface?  
> 
> Hi Jonathan,
> 
> There could be few use cases that would be useful for the client to
> have an option to read only the ADC code.
> 
> a) Few clients can request the ADC conversion and directly program
> the raw code in the hardware.

Why?  Typically it's of no use to them unless they have 'magically' gotten
the conversion routines from somewhere.  So this never applies to general
purpose code, just to stuff tuned for the particular hardware platform.
Not a zero sized pool of applications, but I'm not convinced we need
to put much effort into support them (unless as I said earlier they are
already out there for this platform due to similarities to the ones where
we already provide this).

There is also a problem - how does software know if the threshold is in
raw units or processed ones?  Normal assumption (I hope we documented it
somewhere) is that thresholds are matched to channel read outs. So if
the channel is processed, the threshold is in the right units, if raw
then it's in raw units.

> 
> b) Few clients can program the thresholds in hardware with ADC code to
> receive notification on a threshold crossing.

This one is interesting.  Mostly the client needs the conversion information
to know how to set that threshold.  Hence it is better off setting it in
real world units and letting the driver match that to the actual ADC threshold.
There is one common exception to this.  Light sensors typically use multiple
photodiodes to figure out illuminance and thresholds tend to based on one
of these raw readings.

Normally we get around this one by exposing the 'intensity' channel for the
photodiode in question as _raw only so the threshold can be applied against it.

> 
> c) Some clients may want to know when small movements occur,
> so it would be useful for the client to measure the ADC code and they
> could add a delta for the next threshold crossing.

It can still do that, it's just not as 'cheap' as it can apply those thresholds
as 'processed' units.  Otherwise small is extremely poorly defined so how
would userspace set it?

> 
> d) If a client wants to do their own math and apply their own scaling i 
> can see
> them requesting only the ADC code. They could add the scaling in the ADC 
> driver
> but if they choose add offset to the raw code and program the hardware 
> then
> providing only the code would be useful.

Works for specific devices, but is unusable for generic software. If we go
this way there is little point in having a generic subsystem which is one
of the reasons I resist providing this.

> 
> e) The raw ADC code is useful for debugging purpose. This point is 
> optional
> as it can also be done by logging the ADC code with a pr_debug.
Sure, or debugfs.
> 
> > 
> > There other bits inline are trivial enough I would just have
> > ignored or fixed them when applying if it weren't for this question.
> > 
> > (apologies if we have been round this before - I may have forgotten
> > or I may have been dozing during previous review.  This question has
> > come up for a few drivers recently so I'm more aware of it now)
> >   
...
> >> +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
> >> +	{								\
> >> +		.datasheet_name = (_dname),				\
> >> +		.prescale_index = _pre,					\
> >> +		.type = _type,						\
> >> +		.info_mask = _mask,					\
> >> +		.scale_fn_type = _scale,				\
> >> +	},								\
> >> +
> >> +#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
> >> +	ADC5_CHAN(_dname, IIO_TEMP,					\
> >> +		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\  
> > I'm fairly sure we've been round this before.  A device should not 
> > supply
> > both raw and processed values without very very good reasons.
> > So far the only reasons we have had that I consider valid are:
> > 
> > 1. We got it wrong initially and output raw values, only to have 
> > processed
> >    added later to support something non linear.  We are stuck with 
> > supporting
> >    raw by existing ABI.
> > 
> > 2. We have a non linear channel that needs processed values and has 
> > events.
> >    For some reason we can't map the event controls from processed back
> >    to raw (weird case but who knows) so we have to support both.
> > 
> > 
> > Now this case 'kind' of falls into case 1 as that is what I think 
> > happened
> > with the qcom-spmi-vadc driver and lead to both being there.  
> 
> Clients could also request the reference channels ADC code and do their 
> own
> math and scaling. If there are any offsets that may be added to the ADC 
> code
> then this can be done within the client driver that programs its 
> hardware
> for any threshold crossing notification.

Hmm. I'm having trouble being convinced.  The whole point is that a client
should not be doing it's own scaling.  If it has an offset or similar
to apply it should be applied to the 'real world' value that is a standard
interface, not magically combined with it to generate the answer.

We go through this question every couple of months, and I try to remain
consistent in blocking drivers from providing both interfaces simply because
it makes it very hard for generic userspace.

Jonathan

> 
> > 
> > Hmm. This is awkward as in theory we are adding another 'broken' 
> > interface
> > here, but it is reasonable to assume that there might be code that 
> > requires
> > this interface on such a similar chip.
> > 
> > Do you definitely need to support both for some applications?  
> > Technically
> > we would not be causing a regression if we don't support _raw as it
> > never worked for this particular device, but I can sympathise (and be 
> > persuaded)
> > to let it go here if there is a strong usecase.
> >   
> >> +		_pre, _scale)						\
...

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

* Re: [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
@ 2018-08-02  9:21       ` Jonathan Cameron
  0 siblings, 0 replies; 8+ messages in thread
From: Jonathan Cameron @ 2018-08-02  9:21 UTC (permalink / raw)
  To: smohanad
  Cc: Jonathan Cameron, linux-iio, devicetree, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	linux-iio-owner

On Tue, 31 Jul 2018 11:08:13 -0700
<smohanad@codeaurora.org> wrote:

> On 2018-07-28 04:08, Jonathan Cameron wrote:
> > On Wed, 25 Jul 2018 17:09:29 -0700
> > Siddartha Mohanadoss <smohanad@codeaurora.org> wrote:
> >   
> >> This patch adds support for QCOM SPMI PMIC5 family
> >> of ADC driver that supports hardware based offset and
> >> gain compensation. The ADC peripheral can measure both
> >> voltage and current channels whose input signal is
> >> connected to the PMIC ADC AMUX.
> >> 
> >> The register set and configuration has been refreshed
> >> compared to the prior QCOM PMIC ADC family. Register
> >> ADC5 as part of the IIO framework.
> >> 
> >> Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>  
> > 
> > Hi Siddartha,
> > 
> > My main questions inline are around providing both PROCESSED and
> > RAW readouts for the channels.   Generally this is something we
> > don't ever do (as there is little point and it increases the exposed
> > ABI).  Now the oddity here is you've copied from the
> > qcom-spmi-vadc driver which does this and IIRC that was because
> > the initial submission didn't do any of the complex maths to get
> > to the PROCESSED values.  That was introduced later, leaving us with
> > a mess as we couldn't remove the existing ABI in case someone was
> > using it.
> > 
> > https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ba71704af4a0aae0d9e5812dbdd7bca95e181b14
> > 
> > So... I'm not convinced either way yet on whether we should let
> > this one through as a continuation of the exception we made there
> > or not.  Does this matter to you, or can you drop the RAW interface?  
> 
> Hi Jonathan,
> 
> There could be few use cases that would be useful for the client to
> have an option to read only the ADC code.
> 
> a) Few clients can request the ADC conversion and directly program
> the raw code in the hardware.

Why?  Typically it's of no use to them unless they have 'magically' gotten
the conversion routines from somewhere.  So this never applies to general
purpose code, just to stuff tuned for the particular hardware platform.
Not a zero sized pool of applications, but I'm not convinced we need
to put much effort into support them (unless as I said earlier they are
already out there for this platform due to similarities to the ones where
we already provide this).

There is also a problem - how does software know if the threshold is in
raw units or processed ones?  Normal assumption (I hope we documented it
somewhere) is that thresholds are matched to channel read outs. So if
the channel is processed, the threshold is in the right units, if raw
then it's in raw units.

> 
> b) Few clients can program the thresholds in hardware with ADC code to
> receive notification on a threshold crossing.

This one is interesting.  Mostly the client needs the conversion information
to know how to set that threshold.  Hence it is better off setting it in
real world units and letting the driver match that to the actual ADC threshold.
There is one common exception to this.  Light sensors typically use multiple
photodiodes to figure out illuminance and thresholds tend to based on one
of these raw readings.

Normally we get around this one by exposing the 'intensity' channel for the
photodiode in question as _raw only so the threshold can be applied against it.

> 
> c) Some clients may want to know when small movements occur,
> so it would be useful for the client to measure the ADC code and they
> could add a delta for the next threshold crossing.

It can still do that, it's just not as 'cheap' as it can apply those thresholds
as 'processed' units.  Otherwise small is extremely poorly defined so how
would userspace set it?

> 
> d) If a client wants to do their own math and apply their own scaling i 
> can see
> them requesting only the ADC code. They could add the scaling in the ADC 
> driver
> but if they choose add offset to the raw code and program the hardware 
> then
> providing only the code would be useful.

Works for specific devices, but is unusable for generic software. If we go
this way there is little point in having a generic subsystem which is one
of the reasons I resist providing this.

> 
> e) The raw ADC code is useful for debugging purpose. This point is 
> optional
> as it can also be done by logging the ADC code with a pr_debug.
Sure, or debugfs.
> 
> > 
> > There other bits inline are trivial enough I would just have
> > ignored or fixed them when applying if it weren't for this question.
> > 
> > (apologies if we have been round this before - I may have forgotten
> > or I may have been dozing during previous review.  This question has
> > come up for a few drivers recently so I'm more aware of it now)
> >   
...
> >> +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
> >> +	{								\
> >> +		.datasheet_name = (_dname),				\
> >> +		.prescale_index = _pre,					\
> >> +		.type = _type,						\
> >> +		.info_mask = _mask,					\
> >> +		.scale_fn_type = _scale,				\
> >> +	},								\
> >> +
> >> +#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
> >> +	ADC5_CHAN(_dname, IIO_TEMP,					\
> >> +		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\  
> > I'm fairly sure we've been round this before.  A device should not 
> > supply
> > both raw and processed values without very very good reasons.
> > So far the only reasons we have had that I consider valid are:
> > 
> > 1. We got it wrong initially and output raw values, only to have 
> > processed
> >    added later to support something non linear.  We are stuck with 
> > supporting
> >    raw by existing ABI.
> > 
> > 2. We have a non linear channel that needs processed values and has 
> > events.
> >    For some reason we can't map the event controls from processed back
> >    to raw (weird case but who knows) so we have to support both.
> > 
> > 
> > Now this case 'kind' of falls into case 1 as that is what I think 
> > happened
> > with the qcom-spmi-vadc driver and lead to both being there.  
> 
> Clients could also request the reference channels ADC code and do their 
> own
> math and scaling. If there are any offsets that may be added to the ADC 
> code
> then this can be done within the client driver that programs its 
> hardware
> for any threshold crossing notification.

Hmm. I'm having trouble being convinced.  The whole point is that a client
should not be doing it's own scaling.  If it has an offset or similar
to apply it should be applied to the 'real world' value that is a standard
interface, not magically combined with it to generate the answer.

We go through this question every couple of months, and I try to remain
consistent in blocking drivers from providing both interfaces simply because
it makes it very hard for generic userspace.

Jonathan

> 
> > 
> > Hmm. This is awkward as in theory we are adding another 'broken' 
> > interface
> > here, but it is reasonable to assume that there might be code that 
> > requires
> > this interface on such a similar chip.
> > 
> > Do you definitely need to support both for some applications?  
> > Technically
> > we would not be causing a regression if we don't support _raw as it
> > never worked for this particular device, but I can sympathise (and be 
> > persuaded)
> > to let it go here if there is a strong usecase.
> >   
> >> +		_pre, _scale)						\
...

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

* Re: [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
  2018-08-02  9:21       ` Jonathan Cameron
  (?)
@ 2018-08-03  0:57       ` smohanad
  -1 siblings, 0 replies; 8+ messages in thread
From: smohanad @ 2018-08-03  0:57 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Jonathan Cameron, linux-iio, devicetree, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring,
	linux-iio-owner

On 2018-08-02 02:21, Jonathan Cameron wrote:
> On Tue, 31 Jul 2018 11:08:13 -0700
> <smohanad@codeaurora.org> wrote:
> 
>> On 2018-07-28 04:08, Jonathan Cameron wrote:
>> > On Wed, 25 Jul 2018 17:09:29 -0700
>> > Siddartha Mohanadoss <smohanad@codeaurora.org> wrote:
>> >
>> >> This patch adds support for QCOM SPMI PMIC5 family
>> >> of ADC driver that supports hardware based offset and
>> >> gain compensation. The ADC peripheral can measure both
>> >> voltage and current channels whose input signal is
>> >> connected to the PMIC ADC AMUX.
>> >>
>> >> The register set and configuration has been refreshed
>> >> compared to the prior QCOM PMIC ADC family. Register
>> >> ADC5 as part of the IIO framework.
>> >>
>> >> Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
>> >
>> > Hi Siddartha,
>> >
>> > My main questions inline are around providing both PROCESSED and
>> > RAW readouts for the channels.   Generally this is something we
>> > don't ever do (as there is little point and it increases the exposed
>> > ABI).  Now the oddity here is you've copied from the
>> > qcom-spmi-vadc driver which does this and IIRC that was because
>> > the initial submission didn't do any of the complex maths to get
>> > to the PROCESSED values.  That was introduced later, leaving us with
>> > a mess as we couldn't remove the existing ABI in case someone was
>> > using it.
>> >
>> > https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ba71704af4a0aae0d9e5812dbdd7bca95e181b14
>> >
>> > So... I'm not convinced either way yet on whether we should let
>> > this one through as a continuation of the exception we made there
>> > or not.  Does this matter to you, or can you drop the RAW interface?
>> 
>> Hi Jonathan,
>> 
>> There could be few use cases that would be useful for the client to
>> have an option to read only the ADC code.
>> 
>> a) Few clients can request the ADC conversion and directly program
>> the raw code in the hardware.
> 
> Why?  Typically it's of no use to them unless they have 'magically' 
> gotten
> the conversion routines from somewhere.  So this never applies to 
> general
> purpose code, just to stuff tuned for the particular hardware platform.
> Not a zero sized pool of applications, but I'm not convinced we need
> to put much effort into support them (unless as I said earlier they are
> already out there for this platform due to similarities to the ones 
> where
> we already provide this).

The use case would typically be for clients that want to be aware when
small changes in temperature occur so they could use a threshold
monitor to alarm them. If they have the raw code vs temperature it
would be easier to set the threshold monitor for notification.
It does require knowledge of scaling and mapping table.

> 
> There is also a problem - how does software know if the threshold is in
> raw units or processed ones?  Normal assumption (I hope we documented 
> it
> somewhere) is that thresholds are matched to channel read outs. So if
> the channel is processed, the threshold is in the right units, if raw
> then it's in raw units.

Yes.

> 
>> 
>> b) Few clients can program the thresholds in hardware with ADC code to
>> receive notification on a threshold crossing.
> 
> This one is interesting.  Mostly the client needs the conversion 
> information
> to know how to set that threshold.  Hence it is better off setting it 
> in
> real world units and letting the driver match that to the actual ADC 
> threshold.
> There is one common exception to this.  Light sensors typically use 
> multiple
> photodiodes to figure out illuminance and thresholds tend to based on 
> one
> of these raw readings.
> 
> Normally we get around this one by exposing the 'intensity' channel for 
> the
> photodiode in question as _raw only so the threshold can be applied 
> against it.
> 

Ok. We could use the same and expose just the _raw option for that 
channel.

>> 
>> c) Some clients may want to know when small movements occur,
>> so it would be useful for the client to measure the ADC code and they
>> could add a delta for the next threshold crossing.
> 
> It can still do that, it's just not as 'cheap' as it can apply those 
> thresholds
> as 'processed' units.  Otherwise small is extremely poorly defined so 
> how
> would userspace set it?

Agree, for userspace it would be better for have them set the thresholds 
in
processed units and have a generic driver handing the threshold monitor 
to scale
it to raw units and program the hardware.

> 
>> 
>> d) If a client wants to do their own math and apply their own scaling 
>> i
>> can see
>> them requesting only the ADC code. They could add the scaling in the 
>> ADC
>> driver
>> but if they choose add offset to the raw code and program the hardware
>> then
>> providing only the code would be useful.
> 
> Works for specific devices, but is unusable for generic software. If we 
> go
> this way there is little point in having a generic subsystem which is 
> one
> of the reasons I resist providing this.
> 
>> 
>> e) The raw ADC code is useful for debugging purpose. This point is
>> optional
>> as it can also be done by logging the ADC code with a pr_debug.
> Sure, or debugfs.
>> 
>> >
>> > There other bits inline are trivial enough I would just have
>> > ignored or fixed them when applying if it weren't for this question.
>> >
>> > (apologies if we have been round this before - I may have forgotten
>> > or I may have been dozing during previous review.  This question has
>> > come up for a few drivers recently so I'm more aware of it now)
>> >
> ...
>> >> +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
>> >> +	{								\
>> >> +		.datasheet_name = (_dname),				\
>> >> +		.prescale_index = _pre,					\
>> >> +		.type = _type,						\
>> >> +		.info_mask = _mask,					\
>> >> +		.scale_fn_type = _scale,				\
>> >> +	},								\
>> >> +
>> >> +#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
>> >> +	ADC5_CHAN(_dname, IIO_TEMP,					\
>> >> +		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\
>> > I'm fairly sure we've been round this before.  A device should not
>> > supply
>> > both raw and processed values without very very good reasons.
>> > So far the only reasons we have had that I consider valid are:
>> >
>> > 1. We got it wrong initially and output raw values, only to have
>> > processed
>> >    added later to support something non linear.  We are stuck with
>> > supporting
>> >    raw by existing ABI.
>> >
>> > 2. We have a non linear channel that needs processed values and has
>> > events.
>> >    For some reason we can't map the event controls from processed back
>> >    to raw (weird case but who knows) so we have to support both.
>> >
>> >
>> > Now this case 'kind' of falls into case 1 as that is what I think
>> > happened
>> > with the qcom-spmi-vadc driver and lead to both being there.
>> 
>> Clients could also request the reference channels ADC code and do 
>> their
>> own
>> math and scaling. If there are any offsets that may be added to the 
>> ADC
>> code
>> then this can be done within the client driver that programs its
>> hardware
>> for any threshold crossing notification.
> 
> Hmm. I'm having trouble being convinced.  The whole point is that a 
> client
> should not be doing it's own scaling.  If it has an offset or similar
> to apply it should be applied to the 'real world' value that is a 
> standard
> interface, not magically combined with it to generate the answer.

Ok. I will remove support for the raw code option and add only the
processed results for the subsequent patch. If there is a hard use case
we could use the _raw option for the specific channel like you had
listed earlier. If there are different clients and one wants only
the _raw option while the other client wants processed value then
we can revisit to check for any other alternatives.

> 
> We go through this question every couple of months, and I try to remain
> consistent in blocking drivers from providing both interfaces simply 
> because
> it makes it very hard for generic userspace.
> 
> Jonathan
> 
>> 
>> >
>> > Hmm. This is awkward as in theory we are adding another 'broken'
>> > interface
>> > here, but it is reasonable to assume that there might be code that
>> > requires
>> > this interface on such a similar chip.
>> >
>> > Do you definitely need to support both for some applications?
>> > Technically
>> > we would not be causing a regression if we don't support _raw as it
>> > never worked for this particular device, but I can sympathise (and be
>> > persuaded)
>> > to let it go here if there is a strong usecase.
>> >
>> >> +		_pre, _scale)						\
> ...
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
  2018-07-25 23:44 Siddartha Mohanadoss
@ 2018-07-26  0:09 ` smohanad
  0 siblings, 0 replies; 8+ messages in thread
From: smohanad @ 2018-07-26  0:09 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: linux-iio, devicetree, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald-Stadler, Rob Herring

Hi,

Please ignore this mail patch - [PATCH v3 2/3] iio: adc: Add QCOM SPMI 
PMIC5 ADC driver.
I will re-send this patch again to overwrite the earlier one.

Thanks,
Siddartha

On 2018-07-25 16:44, Siddartha Mohanadoss wrote:
> This patch adds support for QCOM SPMI PMIC5 family
> of ADC driver that supports hardware based offset and
> gain compensation. The ADC peripheral can measure both
> voltage and current channels whose input signal is
> connected to the PMIC ADC AMUX.
> 
> The register set and configuration has been refreshed
> compared to the prior QCOM PMIC ADC family. Register
> ADC5 as part of the IIO framework.
> 
> Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
> ---
>  drivers/iio/adc/Kconfig            |  20 +
>  drivers/iio/adc/Makefile           |   1 +
>  drivers/iio/adc/qcom-spmi-adc5.c   | 864 
> +++++++++++++++++++++++++++++++++++++
>  drivers/iio/adc/qcom-vadc-common.c | 189 +++++++-
>  drivers/iio/adc/qcom-vadc-common.h |  54 +++
>  5 files changed, 1123 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/iio/adc/qcom-spmi-adc5.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 9da7907..93ac929 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -597,6 +597,26 @@ config QCOM_SPMI_VADC
>  	  To compile this driver as a module, choose M here: the module will
>  	  be called qcom-spmi-vadc.
> 
> +config QCOM_SPMI_ADC5
> +	tristate "Qualcomm Technologies Inc. SPMI PMIC5 ADC"
> +	depends on SPMI
> +	select REGMAP_SPMI
> +	select QCOM_VADC_COMMON
> +	help
> +	  This is the IIO Voltage PMIC5 ADC driver for Qualcomm Technologies 
> Inc.
> +
> +	  The driver supports multiple channels read. The ADC is a 16-bit
> +	  sigma-delta ADC. The hardware supports calibrated results for
> +	  conversion requests and clients include reading voltage phone
> +	  power, on board system thermistors connected to the PMIC ADC,
> +	  PMIC die temperature, charger temperature, battery current, USB 
> voltage
> +	  input, voltage signals connected to supported PMIC GPIO inputs. The
> +	  hardware supports internal pull-up for thermistors and can choose 
> between
> +	  a 100k, 30k and 400k pull up using the ADC channels.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called qcom-spmi-adc5.
> +
>  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 28a9423..3c1a109 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -53,6 +53,7 @@ obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
>  obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
>  obj-$(CONFIG_NAU7802) += nau7802.o
>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> +obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>  obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o
>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> diff --git a/drivers/iio/adc/qcom-spmi-adc5.c 
> b/drivers/iio/adc/qcom-spmi-adc5.c
> new file mode 100644
> index 0000000..9393097
> --- /dev/null
> +++ b/drivers/iio/adc/qcom-spmi-adc5.c
> @@ -0,0 +1,864 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/completion.h>
> +#include <linux/delay.h>
> +#include <linux/err.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/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +#include <dt-bindings/iio/qcom,spmi-vadc.h>
> +#include "qcom-vadc-common.h"
> +
> +#define ADC5_USR_REVISION1			0x0
> +#define ADC5_USR_STATUS1			0x8
> +#define ADC5_USR_STATUS1_REQ_STS		BIT(1)
> +#define ADC5_USR_STATUS1_EOC			BIT(0)
> +#define ADC5_USR_STATUS1_REQ_STS_EOC_MASK	0x3
> +
> +#define ADC5_USR_STATUS2			0x9
> +#define ADC5_USR_STATUS2_CONV_SEQ_MASK		0x70
> +#define ADC5_USR_STATUS2_CONV_SEQ_MASK_SHIFT	0x5
> +
> +#define ADC5_USR_IBAT_MEAS			0xf
> +#define ADC5_USR_IBAT_MEAS_SUPPORTED		BIT(0)
> +
> +#define ADC5_USR_DIG_PARAM			0x42
> +#define ADC5_USR_DIG_PARAM_CAL_VAL		BIT(6)
> +#define ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT	6
> +#define ADC5_USR_DIG_PARAM_CAL_SEL		0x30
> +#define ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT	4
> +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL	0xc
> +#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT	2
> +
> +#define ADC5_USR_FAST_AVG_CTL			0x43
> +#define ADC5_USR_FAST_AVG_CTL_EN		BIT(7)
> +#define ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK	0x7
> +
> +#define ADC5_USR_CH_SEL_CTL			0x44
> +
> +#define ADC5_USR_DELAY_CTL			0x45
> +#define ADC5_USR_HW_SETTLE_DELAY_MASK		0xf
> +
> +#define ADC5_USR_EN_CTL1			0x46
> +#define ADC5_USR_EN_CTL1_ADC_EN			BIT(7)
> +
> +#define ADC5_USR_CONV_REQ			0x47
> +#define ADC5_USR_CONV_REQ_REQ			BIT(7)
> +
> +#define ADC5_USR_DATA0				0x50
> +
> +#define ADC5_USR_DATA1				0x51
> +
> +#define ADC5_USR_IBAT_DATA0			0x52
> +
> +#define ADC5_USR_IBAT_DATA1			0x53
> +
> +/*
> + * Conversion time varies based on the decimation, clock rate, fast 
> average
> + * samples and measurements queued across different VADC peripherals.
> + * Set the timeout to a max of 100ms.
> + */
> +#define ADC5_CONV_TIME_MIN_US			263
> +#define ADC5_CONV_TIME_MAX_US			264
> +#define ADC5_CONV_TIME_RETRY			400
> +#define ADC5_CONV_TIMEOUT			msecs_to_jiffies(100)
> +
> +/* Digital version >= 5.3 supports hw_settle_2 */
> +#define ADC5_HW_SETTLE_DIFF_MINOR		3
> +#define ADC5_HW_SETTLE_DIFF_MAJOR		5
> +
> +enum adc5_cal_method {
> +	ADC5_NO_CAL = 0,
> +	ADC5_RATIOMETRIC_CAL,
> +	ADC5_ABSOLUTE_CAL
> +};
> +
> +enum adc5_cal_val {
> +	ADC5_TIMER_CAL = 0,
> +	ADC5_NEW_CAL
> +};
> +
> +/**
> + * struct adc5_channel_prop - ADC channel property.
> + * @channel: channel number, refer to the channel list.
> + * @cal_method: calibration method.
> + * @cal_val: calibration value
> + * @decimation: sampling rate supported for the channel.
> + * @prescale: channel scaling performed on the input signal.
> + * @hw_settle_time: the time between AMUX being configured and the
> + *	start of conversion.
> + * @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.
> + * @datasheet_name: Channel name used in device tree.
> + */
> +struct adc5_channel_prop {
> +	unsigned int		channel;
> +	enum adc5_cal_method	cal_method;
> +	enum adc5_cal_val	cal_val;
> +	unsigned int		decimation;
> +	unsigned int		prescale;
> +	unsigned int		hw_settle_time;
> +	unsigned int		avg_samples;
> +	enum vadc_scale_fn_type	scale_fn_type;
> +	const char		*datasheet_name;
> +};
> +
> +/**
> + * struct adc5_chip - ADC private structure.
> + * @regmap: SPMI ADC5 peripheral register map field.
> + * @dev: SPMI ADC5 device.
> + * @base: base address for the ADC peripheral.
> + * @nchannels: number of ADC channels.
> + * @chan_props: array of ADC channel properties.
> + * @iio_chans: array of IIO channels specification.
> + * @poll_eoc: use polling instead of interrupt.
> + * @complete: ADC result notification after interrupt is received.
> + * @lock: ADC lock for access to the peripheral.
> + * @data: software configuration data.
> + */
> +struct adc5_chip {
> +	struct regmap		*regmap;
> +	struct device		*dev;
> +	u16			base;
> +	unsigned int		nchannels;
> +	struct adc5_channel_prop	*chan_props;
> +	struct iio_chan_spec	*iio_chans;
> +	bool			poll_eoc;
> +	struct completion	complete;
> +	struct mutex		lock;
> +	const struct adc_data	*data;
> +};
> +
> +static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
> +	{.num =  1, .den =  1},
> +	{.num =  1, .den =  3},
> +	{.num =  1, .den =  4},
> +	{.num =  1, .den =  6},
> +	{.num =  1, .den = 20},
> +	{.num =  1, .den =  8},
> +	{.num = 10, .den = 81},
> +	{.num =  1, .den = 10},
> +	{.num =  1, .den = 16}
> +};
> +
> +static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int 
> len)
> +{
> +	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
> +}
> +
> +static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int 
> len)
> +{
> +	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
> +}
> +
> +static int adc5_prescaling_from_dt(u32 num, u32 den)
> +{
> +	unsigned int pre;
> +
> +	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
> +		if (adc5_prescale_ratios[pre].num == num &&
> +		    adc5_prescale_ratios[pre].den == den)
> +			break;
> +
> +	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
> +		return -EINVAL;
> +
> +	return pre;
> +}
> +
> +static int adc5_get_dig_version(struct adc5_chip *adc,
> +					u8 *dig_version)
> +{
> +	return adc5_read(adc, ADC5_USR_REVISION1, dig_version, 2);
> +}
> +
> +static int adc5_hw_settle_time_from_dt(u32 value,
> +					const unsigned int *hw_settle)
> +{
> +	uint32_t i;
> +
> +	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
> +		if (value == hw_settle[i])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int adc5_avg_samples_from_dt(u32 value)
> +{
> +	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
> +		return -EINVAL;
> +
> +	return __ffs64(value);
> +}
> +
> +static int adc5_decimation_from_dt(u32 value,
> +					const unsigned int *decimation)
> +{
> +	uint32_t i;
> +
> +	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
> +		if (value == decimation[i])
> +			return i;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +static int adc5_read_current_data(struct adc5_chip *adc, u16 *data)
> +{
> +	int ret;
> +	u8 rslt_lsb = 0, rslt_msb = 0;
> +
> +	ret = adc5_read(adc, ADC5_USR_IBAT_DATA0, &rslt_lsb, 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = adc5_read(adc, ADC5_USR_IBAT_DATA1, &rslt_msb, 1);
> +	if (ret)
> +		return ret;
> +
> +	*data = (rslt_msb << 8) | rslt_lsb;
> +
> +	if (*data == ADC5_USR_DATA_CHECK) {
> +		pr_err("Invalid data:0x%x\n", *data);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
> +{
> +	int ret;
> +	u8 rslt_lsb, rslt_msb;
> +
> +	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1);
> +	if (ret)
> +		return ret;
> +
> +	*data = (rslt_msb << 8) | rslt_lsb;
> +
> +	if (*data == ADC5_USR_DATA_CHECK) {
> +		pr_err("Invalid data:0x%x\n", *data);
> +		return -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int adc5_poll_wait_eoc(struct adc5_chip *adc)
> +{
> +	unsigned int count, retry = ADC5_CONV_TIME_RETRY;
> +	u8 status1;
> +	int ret;
> +
> +	for (count = 0; count < retry; count++) {
> +		ret = adc5_read(adc, ADC5_USR_STATUS1, &status1, 1);
> +		if (ret)
> +			return ret;
> +
> +		status1 &= ADC5_USR_STATUS1_REQ_STS_EOC_MASK;
> +		if (status1 == ADC5_USR_STATUS1_EOC)
> +			return 0;
> +		usleep_range(ADC5_CONV_TIME_MIN_US, ADC5_CONV_TIME_MAX_US);
> +	}
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static void adc5_update_dig_param(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop, u8 *data)
> +{
> +	/* Update calibration value */
> +	*data &= ~ADC5_USR_DIG_PARAM_CAL_VAL;
> +	*data |= (prop->cal_val << ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT);
> +
> +	/* Update calibration select */
> +	*data &= ~ADC5_USR_DIG_PARAM_CAL_SEL;
> +	*data |= (prop->cal_method << ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT);
> +
> +	/* Update decimation ratio select */
> +	*data &= ~ADC5_USR_DIG_PARAM_DEC_RATIO_SEL;
> +	*data |= (prop->decimation << 
> ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT);
> +}
> +
> +static int adc5_configure(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop)
> +{
> +	int ret;
> +	u8 buf[6];
> +
> +	/* Read registers 0x42 through 0x46 */
> +	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, 6);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Digital param selection */
> +	adc5_update_dig_param(adc, prop, &buf[0]);
> +
> +	/* Update fast average sample value */
> +	buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
> +	buf[1] |= prop->avg_samples;
> +
> +	/* Select ADC channel */
> +	buf[2] = prop->channel;
> +
> +	/* Select HW settle delay for channel */
> +	buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK;
> +	buf[3] |= prop->hw_settle_time;
> +
> +	/* Select ADC enable */
> +	buf[4] |= ADC5_USR_EN_CTL1_ADC_EN;
> +
> +	/* Select CONV request */
> +	buf[5] |= ADC5_USR_CONV_REQ_REQ;
> +
> +	if (!adc->poll_eoc)
> +		reinit_completion(&adc->complete);
> +
> +	return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, 6);
> +}
> +
> +static int adc5_do_conversion(struct adc5_chip *adc,
> +			struct adc5_channel_prop *prop,
> +			struct iio_chan_spec const *chan,
> +			u16 *data_volt, u16 *data_cur)
> +{
> +	int ret;
> +
> +	mutex_lock(&adc->lock);
> +
> +	ret = adc5_configure(adc, prop);
> +	if (ret) {
> +		pr_err("ADC configure failed with %d\n", ret);
> +		goto unlock;
> +	}
> +
> +	if (adc->poll_eoc) {
> +		ret = adc5_poll_wait_eoc(adc);
> +		if (ret < 0) {
> +			pr_err("EOC bit not set\n");
> +			goto unlock;
> +		}
> +	} else {
> +		ret = wait_for_completion_timeout(&adc->complete,
> +							ADC5_CONV_TIMEOUT);
> +		if (!ret) {
> +			pr_debug("Did not get completion timeout.\n");
> +			ret = adc5_poll_wait_eoc(adc);
> +			if (ret < 0) {
> +				pr_err("EOC bit not set\n");
> +				goto unlock;
> +			}
> +		}
> +	}
> +
> +	if ((chan->type == IIO_VOLTAGE) || (chan->type == IIO_TEMP))
> +		ret = adc5_read_voltage_data(adc, data_volt);
> +	else if (chan->type == IIO_POWER) {
> +		ret = adc5_read_voltage_data(adc, data_volt);
> +		if (ret)
> +			goto unlock;
> +
> +		ret = adc5_read_current_data(adc, data_cur);
> +	}
> +unlock:
> +	mutex_unlock(&adc->lock);
> +
> +	return ret;
> +}
> +
> +static irqreturn_t adc5_isr(int irq, void *dev_id)
> +{
> +	struct adc5_chip *adc = dev_id;
> +
> +	complete(&adc->complete);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int adc5_of_xlate(struct iio_dev *indio_dev,
> +				const struct of_phandle_args *iiospec)
> +{
> +	struct adc5_chip *adc = iio_priv(indio_dev);
> +	int i;
> +
> +	for (i = 0; i < adc->nchannels; i++)
> +		if (adc->chan_props[i].channel == iiospec->args[0])
> +			return i;
> +
> +	return -EINVAL;
> +}
> +
> +static int adc5_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_prop *prop;
> +	u16 adc_code_volt, adc_code_cur;
> +	int ret;
> +
> +	prop = &adc->chan_props[chan->address];
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = adc5_do_conversion(adc, prop, chan,
> +				&adc_code_volt, &adc_code_cur);
> +		if (ret)
> +			break;
> +
> +		if ((chan->type == IIO_VOLTAGE) || (chan->type == IIO_TEMP))
> +			ret = qcom_adc5_hw_scale(prop->scale_fn_type,
> +				&adc5_prescale_ratios[prop->prescale],
> +				adc->data,
> +				adc_code_volt, val);
> +		if (ret)
> +			break;
> +
> +		if (chan->type == IIO_POWER) {
> +			ret = qcom_adc5_hw_scale(SCALE_HW_CALIB_DEFAULT,
> +				&adc5_prescale_ratios[ADC5_DEF_VBAT_PRESCALING],
> +				adc->data,
> +				adc_code_volt, val);
> +			if (ret)
> +				break;
> +
> +			ret = qcom_adc5_hw_scale(prop->scale_fn_type,
> +				&adc5_prescale_ratios[prop->prescale],
> +				adc->data,
> +				adc_code_cur, val2);
> +			if (ret)
> +				break;
> +		}
> +
> +		if (chan->type == IIO_POWER)
> +			return IIO_VAL_INT_MULTIPLE;
> +		else
> +			return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_RAW:
> +		ret = adc5_do_conversion(adc, prop, chan,
> +				&adc_code_volt, &adc_code_cur);
> +		if (ret)
> +			break;
> +
> +		*val = (int)adc_code_volt;
> +		*val2 = (int)adc_code_cur;
> +		if (chan->type == IIO_POWER)
> +			return IIO_VAL_INT_MULTIPLE;
> +		else
> +			return IIO_VAL_INT;
> +	default:
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static const struct iio_info adc5_info = {
> +	.read_raw = adc5_read_raw,
> +	.of_xlate = adc5_of_xlate,
> +};
> +
> +struct adc_channels {
> +	const char *datasheet_name;
> +	unsigned int prescale_index;
> +	enum iio_chan_type type;
> +	long info_mask;
> +	enum vadc_scale_fn_type scale_fn_type;
> +};
> +
> +#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
> +	{								\
> +		.datasheet_name = (_dname),				\
> +		.prescale_index = _pre,					\
> +		.type = _type,						\
> +		.info_mask = _mask,					\
> +		.scale_fn_type = _scale,				\
> +	},								\
> +
> +#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
> +	ADC5_CHAN(_dname, IIO_TEMP,					\
> +		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\
> +		_pre, _scale)						\
> +
> +#define ADC5_CHAN_VOLT(_dname, _pre, _scale)				\
> +	ADC5_CHAN(_dname, IIO_VOLTAGE,					\
> +		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
> +		  _pre, _scale)						\
> +
> +#define ADC5_CHAN_POWER(_dname, _pre, _scale)				\
> +	ADC5_CHAN(_dname, IIO_POWER,					\
> +		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
> +		  _pre, _scale)						\
> +
> +static const struct adc_channels adc_chans_pmic5[ADC5_MAX_CHANNEL] = {
> +	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
> +					SCALE_HW_CALIB_PMIC_THERM)
> +	[ADC5_USB_IN_I]		= ADC5_CHAN_VOLT("usb_in_i_uv", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_USB_IN_V_16]	= ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_CHG_TEMP]		= ADC5_CHAN_TEMP("chg_temp", 1,
> +					SCALE_HW_CALIB_PM5_CHG_TEMP)
> +	/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit 
> */
> +	[ADC5_SBUx]		= ADC5_CHAN_VOLT("chg_sbux", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_MID_CHG_DIV6]	= ADC5_CHAN_VOLT("chg_mid_chg", 6,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm", 1,
> +					SCALE_HW_CALIB_XOTHERM)
> +	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_INT_EXT_ISENSE_VBAT_VDATA] = ADC5_CHAN_POWER("int_ext_isense", 
> 1,
> +					SCALE_HW_CALIB_CUR)
> +	[ADC5_EXT_ISENSE_VBAT_VDATA] = ADC5_CHAN_POWER("ext_isense", 1,
> +					SCALE_HW_CALIB_CUR)
> +	[ADC5_PARALLEL_ISENSE_VBAT_VDATA] =
> +					ADC5_CHAN_POWER("parallel_isense", 1,
> +					SCALE_HW_CALIB_CUR)
> +	[ADC5_AMUX_THM2]	= ADC5_CHAN_TEMP("amux_thm2", 1,
> +					SCALE_HW_CALIB_PM5_SMB_TEMP)
> +};
> +
> +static const struct adc_channels adc_chans_rev2[ADC5_MAX_CHANNEL] = {
> +	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_VCOIN]		= ADC5_CHAN_VOLT("vcoin", 3,
> +					SCALE_HW_CALIB_DEFAULT)
> +	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
> +					SCALE_HW_CALIB_PMIC_THERM)
> +	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
> +					SCALE_HW_CALIB_THERM_100K_PULLUP)
> +};
> +
> +static int adc5_get_dt_channel_data(struct adc5_chip *adc,
> +				    struct adc5_channel_prop *prop,
> +				    struct device_node *node,
> +				    const struct adc_data *data)
> +{
> +	const char *name = node->name, *channel_name;
> +	u32 chan, value, varr[2];
> +	int ret;
> +	struct device *dev = adc->dev;
> +
> +	ret = of_property_read_u32(node, "reg", &chan);
> +	if (ret) {
> +		dev_err(dev, "invalid channel number %s\n", name);
> +		return ret;
> +	}
> +
> +	if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA) {
> +		dev_err(dev, "%s invalid channel number %d\n", name, chan);
> +		return -EINVAL;
> +	}
> +
> +	/* the channel has DT description */
> +	prop->channel = chan;
> +
> +	channel_name = of_get_property(node,
> +				"label", NULL) ? : node->name;
> +	if (!channel_name) {
> +		pr_err("Invalid channel name\n");
> +		return -EINVAL;
> +	}
> +	prop->datasheet_name = channel_name;
> +
> +	ret = of_property_read_u32(node, "qcom,decimation", &value);
> +	if (!ret) {
> +		ret = adc5_decimation_from_dt(value, data->decimation);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid decimation %d\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->decimation = ret;
> +	} else {
> +		prop->decimation = ADC5_DECIMATION_DEFAULT;
> +	}
> +
> +	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
> +	if (!ret) {
> +		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
> +				chan, varr[0], varr[1]);
> +			return ret;
> +		}
> +		prop->prescale = ret;
> +	}
> +
> +	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
> +	if (!ret) {
> +		u8 dig_version[2];
> +
> +		ret = adc5_get_dig_version(adc, dig_version);
> +		if (ret < 0) {
> +			dev_err(dev, "Invalid dig version read %d\n", ret);
> +			return ret;
> +		}
> +
> +		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
> +						dig_version[1]);
> +		/* Digital controller >= 5.3 have hw_settle_2 option */
> +		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
> +			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
> +			ret = adc5_hw_settle_time_from_dt(value,
> +							data->hw_settle_2);
> +		else
> +			ret = adc5_hw_settle_time_from_dt(value,
> +							data->hw_settle_1);
> +
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->hw_settle_time = ret;
> +	} else {
> +		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
> +	}
> +
> +	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
> +	if (!ret) {
> +		ret = adc5_avg_samples_from_dt(value);
> +		if (ret < 0) {
> +			dev_err(dev, "%02x invalid avg-samples %d\n",
> +				chan, value);
> +			return ret;
> +		}
> +		prop->avg_samples = ret;
> +	} else {
> +		prop->avg_samples = VADC_DEF_AVG_SAMPLES;
> +	}
> +
> +	if (of_property_read_bool(node, "qcom,ratiometric"))
> +		prop->cal_method = ADC5_RATIOMETRIC_CAL;
> +	else
> +		prop->cal_method = ADC5_ABSOLUTE_CAL;
> +
> +	/*
> +	 * Default to using timer calibration. Using a fresh calibration 
> value
> +	 * for every conversion will increase the overall time for a request.
> +	 */
> +	prop->cal_val = ADC5_TIMER_CAL;
> +
> +	dev_dbg(dev, "%02x name %s\n", chan, name);
> +
> +	return 0;
> +}
> +
> +const struct adc_data data_pmic5 = {
> +	.full_scale_code_volt = 0x70e4,
> +	.full_scale_code_cur = 0x2710,
> +	.adc_chans = adc_chans_pmic5,
> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
> +				{250, 420, 840},
> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				800, 900, 1, 2, 4, 6, 8, 10},
> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				1, 2, 4, 8, 16, 32, 64, 128},
> +};
> +
> +const struct adc_data data_pmic_rev2 = {
> +	.full_scale_code_volt = 0x4000,
> +	.full_scale_code_cur = 0x1800,
> +	.adc_chans = adc_chans_rev2,
> +	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
> +				{256, 512, 1024},
> +	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{0, 100, 200, 300, 400, 500, 600, 700,
> +				800, 900, 1, 2, 4, 6, 8, 10},
> +	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
> +				{15, 100, 200, 300, 400, 500, 600, 700,
> +				1, 2, 4, 8, 16, 32, 64, 128},
> +};
> +
> +static const struct of_device_id adc5_match_table[] = {
> +	{
> +		.compatible = "qcom,spmi-adc5",
> +		.data = &data_pmic5,
> +	},
> +	{
> +		.compatible = "qcom,spmi-adc-rev2",
> +		.data = &data_pmic_rev2,
> +	},
> +	{ }
> +};
> +
> +static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node 
> *node)
> +{
> +	const struct adc_channels *adc_chan;
> +	struct iio_chan_spec *iio_chan;
> +	struct adc5_channel_prop prop;
> +	struct device_node *child;
> +	unsigned int index = 0;
> +	const struct of_device_id *id;
> +	const struct adc_data *data;
> +	int ret;
> +
> +	adc->nchannels = of_get_available_child_count(node);
> +	if (!adc->nchannels)
> +		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;
> +
> +	iio_chan = adc->iio_chans;
> +	id = of_match_node(adc5_match_table, node);
> +	if (id)
> +		data = id->data;
> +	else
> +		data = &data_pmic5;
> +	adc->data = data;
> +
> +	for_each_available_child_of_node(node, child) {
> +		ret = adc5_get_dt_channel_data(adc, &prop, child, data);
> +		if (ret) {
> +			of_node_put(child);
> +			return ret;
> +		}
> +
> +		prop.scale_fn_type =
> +			data->adc_chans[prop.channel].scale_fn_type;
> +		*chan_props = prop;
> +		adc_chan = &data->adc_chans[prop.channel];
> +
> +		iio_chan->channel = prop.channel;
> +		iio_chan->datasheet_name = prop.datasheet_name;
> +		iio_chan->extend_name = prop.datasheet_name;
> +		iio_chan->info_mask_separate = adc_chan->info_mask;
> +		iio_chan->type = adc_chan->type;
> +		iio_chan->address = index;
> +		iio_chan++;
> +		chan_props++;
> +		index++;
> +	}
> +
> +	return 0;
> +}
> +
> +static int adc5_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	struct device *dev = &pdev->dev;
> +	struct iio_dev *indio_dev;
> +	struct adc5_chip *adc;
> +	struct regmap *regmap;
> +	int ret, irq_eoc;
> +	u32 reg;
> +
> +	regmap = dev_get_regmap(dev->parent, NULL);
> +	if (!regmap)
> +		return -ENODEV;
> +
> +	ret = of_property_read_u32(node, "reg", &reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	adc = iio_priv(indio_dev);
> +	adc->regmap = regmap;
> +	adc->dev = dev;
> +	adc->base = reg;
> +	init_completion(&adc->complete);
> +	mutex_init(&adc->lock);
> +
> +	ret = adc5_get_dt_data(adc, node);
> +	if (ret) {
> +		pr_err("adc get dt data failed\n");
> +		return ret;
> +	}
> +
> +	irq_eoc = platform_get_irq(pdev, 0);
> +	if (irq_eoc < 0) {
> +		if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL)
> +			return irq_eoc;
> +		adc->poll_eoc = true;
> +	} else {
> +		ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0,
> +				       "pm-adc5", adc);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	indio_dev->dev.parent = dev;
> +	indio_dev->dev.of_node = node;
> +	indio_dev->name = pdev->name;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->info = &adc5_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_driver = {
> +	.driver = {
> +		.name = "qcom-spmi-adc5.c",
> +		.of_match_table = adc5_match_table,
> +	},
> +	.probe = adc5_probe,
> +};
> +module_platform_driver(adc5_driver);
> +
> +MODULE_ALIAS("platform:qcom-spmi-adc5");
> +MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 ADC driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/iio/adc/qcom-vadc-common.c
> b/drivers/iio/adc/qcom-vadc-common.c
> index fe3d782..2a9fecb 100644
> --- a/drivers/iio/adc/qcom-vadc-common.c
> +++ b/drivers/iio/adc/qcom-vadc-common.c
> @@ -47,8 +47,79 @@
>  	{44,	125}
>  };
> 
> +/*
> + * Voltage to temperature table for 100k pull up for NTCG104EF104 with
> + * 1.875V reference.
> + */
> +static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = 
> {
> +	{ 1831,	-40000 },
> +	{ 1814,	-35000 },
> +	{ 1791,	-30000 },
> +	{ 1761,	-25000 },
> +	{ 1723,	-20000 },
> +	{ 1675,	-15000 },
> +	{ 1616,	-10000 },
> +	{ 1545,	-5000 },
> +	{ 1463,	0 },
> +	{ 1370,	5000 },
> +	{ 1268,	10000 },
> +	{ 1160,	15000 },
> +	{ 1049,	20000 },
> +	{ 937,	25000 },
> +	{ 828,	30000 },
> +	{ 726,	35000 },
> +	{ 630,	40000 },
> +	{ 544,	45000 },
> +	{ 467,	50000 },
> +	{ 399,	55000 },
> +	{ 340,	60000 },
> +	{ 290,	65000 },
> +	{ 247,	70000 },
> +	{ 209,	75000 },
> +	{ 179,	80000 },
> +	{ 153,	85000 },
> +	{ 130,	90000 },
> +	{ 112,	95000 },
> +	{ 96,	100000 },
> +	{ 82,	105000 },
> +	{ 71,	110000 },
> +	{ 62,	115000 },
> +	{ 53,	120000 },
> +	{ 46,	125000 },
> +};
> +
> +static int qcom_vadc_scale_hw_calib_volt(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_uv);
> +static int qcom_vadc_scale_hw_calib_therm(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +static int qcom_vadc_scale_hw_smb_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +static int qcom_vadc_scale_hw_chg5_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +static int qcom_vadc_scale_hw_calib_die_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec);
> +
> +static struct qcom_adc5_scale_type scale_adc5_fn[] = {
> +	[SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt},
> +	[SCALE_HW_CALIB_THERM_100K_PULLUP] = 
> {qcom_vadc_scale_hw_calib_therm},
> +	[SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm},
> +	[SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp},
> +	[SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp},
> +	[SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp},
> +};
> +
>  static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
> -				      u32 tablesize, s32 input, s64 *output)
> +				      u32 tablesize, s32 input, int *output)
>  {
>  	bool descending = 1;
>  	u32 i = 0;
> @@ -128,7 +199,7 @@ static int qcom_vadc_scale_therm(const struct
> vadc_linear_graph *calib_graph,
>  				 bool absolute, u16 adc_code,
>  				 int *result_mdec)
>  {
> -	s64 voltage = 0, result = 0;
> +	s64 voltage = 0;
>  	int ret;
> 
>  	qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
> @@ -138,12 +209,11 @@ static int qcom_vadc_scale_therm(const struct
> vadc_linear_graph *calib_graph,
> 
>  	ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
>  					 ARRAY_SIZE(adcmap_100k_104ef_104fb),
> -					 voltage, &result);
> +					 voltage, result_mdec);
>  	if (ret)
>  		return ret;
> 
> -	result *= 1000;
> -	*result_mdec = result;
> +	*result_mdec *= 1000;
> 
>  	return 0;
>  }
> @@ -191,6 +261,99 @@ static int qcom_vadc_scale_chg_temp(const struct
> vadc_linear_graph *calib_graph,
>  	return 0;
>  }
> 
> +static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				unsigned int factor)
> +{
> +	s64 voltage, temp, adc_vdd_ref_mv = 1875;
> +
> +	/*
> +	 * The normal data range is between 0V to 1.875V. On cases where
> +	 * we read low voltage values, the ADC code can go beyond the
> +	 * range and the scale result is incorrect so we clamp the values
> +	 * for the cases where the code represents a value below 0V
> +	 */
> +	if (adc_code > VADC5_MAX_CODE)
> +		adc_code = 0;
> +
> +	/* (ADC code * vref_vadc (1.875V)) / full_scale_code */
> +	voltage = (s64) adc_code * adc_vdd_ref_mv * 1000;
> +	voltage = div64_s64(voltage, data->full_scale_code_volt);
> +	if (voltage > 0) {
> +		voltage *= prescale->den;
> +		temp = prescale->num * factor;
> +		voltage = div64_s64(voltage, temp);
> +	} else {
> +		voltage = 0;
> +	}
> +
> +	return (int) voltage;
> +}
> +
> +static int qcom_vadc_scale_hw_calib_volt(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_uv)
> +{
> +	*result_uv = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 1);
> +
> +	return 0;
> +}
> +
> +static int qcom_vadc_scale_hw_calib_therm(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	int ret, voltage;
> +
> +	voltage = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 1000);
> +
> +	/* Map voltage to temperature from look-up table */
> +	return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref,
> +				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
> +				 voltage, result_mdec);
> +}
> +
> +static int qcom_vadc_scale_hw_calib_die_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 2);
> +	*result_mdec -= KELVINMIL_CELSIUSMIL;
> +
> +	return 0;
> +}
> +
> +static int qcom_vadc_scale_hw_smb_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100,
> +				prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR);
> +	*result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec;
> +
> +	return 0;
> +}
> +
> +static int qcom_vadc_scale_hw_chg5_temp(
> +				const struct vadc_prescale_ratio *prescale,
> +				const struct adc_data *data,
> +				u16 adc_code, int *result_mdec)
> +{
> +	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
> +				prescale, data, 4);
> +	*result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec;
> +
> +	return 0;
> +}
> +
>  int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
>  		    const struct vadc_linear_graph *calib_graph,
>  		    const struct vadc_prescale_ratio *prescale,
> @@ -221,6 +384,22 @@ int qcom_vadc_scale(enum vadc_scale_fn_type 
> scaletype,
>  }
>  EXPORT_SYMBOL(qcom_vadc_scale);
> 
> +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
> +		    const struct vadc_prescale_ratio *prescale,
> +		    const struct adc_data *data,
> +		    u16 adc_code, int *result)
> +{
> +	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
> +		scaletype < SCALE_HW_CALIB_INVALID)) {
> +		pr_err("Invalid scale type %d\n", scaletype);
> +		return -EINVAL;
> +	}
> +
> +	return scale_adc5_fn[scaletype].scale_fn(prescale, data,
> +					adc_code, result);
> +}
> +EXPORT_SYMBOL(qcom_adc5_hw_scale);
> +
>  int qcom_vadc_decimation_from_dt(u32 value)
>  {
>  	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
> diff --git a/drivers/iio/adc/qcom-vadc-common.h
> b/drivers/iio/adc/qcom-vadc-common.h
> index 1d5354f..bcd6959 100644
> --- a/drivers/iio/adc/qcom-vadc-common.h
> +++ b/drivers/iio/adc/qcom-vadc-common.h
> @@ -25,15 +25,31 @@
> 
>  #define VADC_DECIMATION_MIN			512
>  #define VADC_DECIMATION_MAX			4096
> +#define ADC5_DEF_VBAT_PRESCALING		1 /* 1:3 */
> +#define ADC5_DECIMATION_SHORT			250
> +#define ADC5_DECIMATION_MEDIUM			420
> +#define ADC5_DECIMATION_LONG			840
> +/* Default decimation - 1024 for rev2, 840 for pmic5 */
> +#define ADC5_DECIMATION_DEFAULT			2
> +#define ADC5_DECIMATION_SAMPLES_MAX		3
> 
>  #define VADC_HW_SETTLE_DELAY_MAX		10000
> +#define VADC_HW_SETTLE_SAMPLES_MAX		16
>  #define VADC_AVG_SAMPLES_MAX			512
> +#define ADC5_AVG_SAMPLES_MAX			16
> 
>  #define KELVINMIL_CELSIUSMIL			273150
> +#define PMIC5_CHG_TEMP_SCALE_FACTOR		377500
> +#define PMIC5_SMB_TEMP_CONSTANT			419400
> +#define PMIC5_SMB_TEMP_SCALE_FACTOR		356
> 
>  #define PMI_CHG_SCALE_1				-138890
>  #define PMI_CHG_SCALE_2				391750000000LL
> 
> +#define VADC5_MAX_CODE				0x7fff
> +#define ADC5_FULL_SCALE_CODE			0x70e4
> +#define ADC5_USR_DATA_CHECK			0x8000
> +
>  /**
>   * struct vadc_map_pt - Map the graph representation for ADC channel
>   * @x: Represent the ADC digitized code.
> @@ -89,6 +105,18 @@ struct vadc_prescale_ratio {
>   * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
>   * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
>   * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
> + * SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to
> + *	voltage (uV) with hardware applied offset/slope values to adc code.
> + * SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC 
> using
> + *	lookup table. The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC 
> using
> + *	100k pullup. The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's 
> Centigrade.
> + *	The hardware applies offset/slope to adc code.
> + * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for 
> PMIC5
> + *	charger temperature.
> + * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for 
> PMIC5
> + *	SMB1390 temperature.
>   */
>  enum vadc_scale_fn_type {
>  	SCALE_DEFAULT = 0,
> @@ -96,6 +124,22 @@ enum vadc_scale_fn_type {
>  	SCALE_PMIC_THERM,
>  	SCALE_XOTHERM,
>  	SCALE_PMI_CHG_TEMP,
> +	SCALE_HW_CALIB_DEFAULT,
> +	SCALE_HW_CALIB_THERM_100K_PULLUP,
> +	SCALE_HW_CALIB_XOTHERM,
> +	SCALE_HW_CALIB_PMIC_THERM,
> +	SCALE_HW_CALIB_PM5_CHG_TEMP,
> +	SCALE_HW_CALIB_PM5_SMB_TEMP,
> +	SCALE_HW_CALIB_INVALID,
> +};
> +
> +struct adc_data {
> +	const u32	full_scale_code_volt;
> +	const u32	full_scale_code_cur;
> +	const struct adc_channels *adc_chans;
> +	unsigned int	*decimation;
> +	unsigned int	*hw_settle_1;
> +	unsigned int	*hw_settle_2;
>  };
> 
>  int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
> @@ -104,6 +148,16 @@ int qcom_vadc_scale(enum vadc_scale_fn_type 
> scaletype,
>  		    bool absolute,
>  		    u16 adc_code, int *result_mdec);
> 
> +struct qcom_adc5_scale_type {
> +	int (*scale_fn)(const struct vadc_prescale_ratio *prescale,
> +		const struct adc_data *data, u16 adc_code, int *result);
> +};
> +
> +int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
> +		    const struct vadc_prescale_ratio *prescale,
> +		    const struct adc_data *data,
> +		    u16 adc_code, int *result_mdec);
> +
>  int qcom_vadc_decimation_from_dt(u32 value);
> 
>  #endif /* QCOM_VADC_COMMON_H */

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

* [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver
@ 2018-07-25 23:44 Siddartha Mohanadoss
  2018-07-26  0:09 ` smohanad
  0 siblings, 1 reply; 8+ messages in thread
From: Siddartha Mohanadoss @ 2018-07-25 23:44 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Siddartha Mohanadoss, linux-iio, devicetree, Hartmut Knaack,
	Lars-Peter Clausen, Peter Meerwald-Stadler, Rob Herring

This patch adds support for QCOM SPMI PMIC5 family
of ADC driver that supports hardware based offset and
gain compensation. The ADC peripheral can measure both
voltage and current channels whose input signal is
connected to the PMIC ADC AMUX.

The register set and configuration has been refreshed
compared to the prior QCOM PMIC ADC family. Register
ADC5 as part of the IIO framework.

Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
---
 drivers/iio/adc/Kconfig            |  20 +
 drivers/iio/adc/Makefile           |   1 +
 drivers/iio/adc/qcom-spmi-adc5.c   | 864 +++++++++++++++++++++++++++++++++++++
 drivers/iio/adc/qcom-vadc-common.c | 189 +++++++-
 drivers/iio/adc/qcom-vadc-common.h |  54 +++
 5 files changed, 1123 insertions(+), 5 deletions(-)
 create mode 100644 drivers/iio/adc/qcom-spmi-adc5.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 9da7907..93ac929 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -597,6 +597,26 @@ config QCOM_SPMI_VADC
 	  To compile this driver as a module, choose M here: the module will
 	  be called qcom-spmi-vadc.
 
+config QCOM_SPMI_ADC5
+	tristate "Qualcomm Technologies Inc. SPMI PMIC5 ADC"
+	depends on SPMI
+	select REGMAP_SPMI
+	select QCOM_VADC_COMMON
+	help
+	  This is the IIO Voltage PMIC5 ADC driver for Qualcomm Technologies Inc.
+
+	  The driver supports multiple channels read. The ADC is a 16-bit
+	  sigma-delta ADC. The hardware supports calibrated results for
+	  conversion requests and clients include reading voltage phone
+	  power, on board system thermistors connected to the PMIC ADC,
+	  PMIC die temperature, charger temperature, battery current, USB voltage
+	  input, voltage signals connected to supported PMIC GPIO inputs. The
+	  hardware supports internal pull-up for thermistors and can choose between
+	  a 100k, 30k and 400k pull up using the ADC channels.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called qcom-spmi-adc5.
+
 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 28a9423..3c1a109 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
 obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
+obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
 obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
 obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o
 obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c
new file mode 100644
index 0000000..9393097
--- /dev/null
+++ b/drivers/iio/adc/qcom-spmi-adc5.c
@@ -0,0 +1,864 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.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/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/iio/qcom,spmi-vadc.h>
+#include "qcom-vadc-common.h"
+
+#define ADC5_USR_REVISION1			0x0
+#define ADC5_USR_STATUS1			0x8
+#define ADC5_USR_STATUS1_REQ_STS		BIT(1)
+#define ADC5_USR_STATUS1_EOC			BIT(0)
+#define ADC5_USR_STATUS1_REQ_STS_EOC_MASK	0x3
+
+#define ADC5_USR_STATUS2			0x9
+#define ADC5_USR_STATUS2_CONV_SEQ_MASK		0x70
+#define ADC5_USR_STATUS2_CONV_SEQ_MASK_SHIFT	0x5
+
+#define ADC5_USR_IBAT_MEAS			0xf
+#define ADC5_USR_IBAT_MEAS_SUPPORTED		BIT(0)
+
+#define ADC5_USR_DIG_PARAM			0x42
+#define ADC5_USR_DIG_PARAM_CAL_VAL		BIT(6)
+#define ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT	6
+#define ADC5_USR_DIG_PARAM_CAL_SEL		0x30
+#define ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT	4
+#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL	0xc
+#define ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT	2
+
+#define ADC5_USR_FAST_AVG_CTL			0x43
+#define ADC5_USR_FAST_AVG_CTL_EN		BIT(7)
+#define ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK	0x7
+
+#define ADC5_USR_CH_SEL_CTL			0x44
+
+#define ADC5_USR_DELAY_CTL			0x45
+#define ADC5_USR_HW_SETTLE_DELAY_MASK		0xf
+
+#define ADC5_USR_EN_CTL1			0x46
+#define ADC5_USR_EN_CTL1_ADC_EN			BIT(7)
+
+#define ADC5_USR_CONV_REQ			0x47
+#define ADC5_USR_CONV_REQ_REQ			BIT(7)
+
+#define ADC5_USR_DATA0				0x50
+
+#define ADC5_USR_DATA1				0x51
+
+#define ADC5_USR_IBAT_DATA0			0x52
+
+#define ADC5_USR_IBAT_DATA1			0x53
+
+/*
+ * Conversion time varies based on the decimation, clock rate, fast average
+ * samples and measurements queued across different VADC peripherals.
+ * Set the timeout to a max of 100ms.
+ */
+#define ADC5_CONV_TIME_MIN_US			263
+#define ADC5_CONV_TIME_MAX_US			264
+#define ADC5_CONV_TIME_RETRY			400
+#define ADC5_CONV_TIMEOUT			msecs_to_jiffies(100)
+
+/* Digital version >= 5.3 supports hw_settle_2 */
+#define ADC5_HW_SETTLE_DIFF_MINOR		3
+#define ADC5_HW_SETTLE_DIFF_MAJOR		5
+
+enum adc5_cal_method {
+	ADC5_NO_CAL = 0,
+	ADC5_RATIOMETRIC_CAL,
+	ADC5_ABSOLUTE_CAL
+};
+
+enum adc5_cal_val {
+	ADC5_TIMER_CAL = 0,
+	ADC5_NEW_CAL
+};
+
+/**
+ * struct adc5_channel_prop - ADC channel property.
+ * @channel: channel number, refer to the channel list.
+ * @cal_method: calibration method.
+ * @cal_val: calibration value
+ * @decimation: sampling rate supported for the channel.
+ * @prescale: channel scaling performed on the input signal.
+ * @hw_settle_time: the time between AMUX being configured and the
+ *	start of conversion.
+ * @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.
+ * @datasheet_name: Channel name used in device tree.
+ */
+struct adc5_channel_prop {
+	unsigned int		channel;
+	enum adc5_cal_method	cal_method;
+	enum adc5_cal_val	cal_val;
+	unsigned int		decimation;
+	unsigned int		prescale;
+	unsigned int		hw_settle_time;
+	unsigned int		avg_samples;
+	enum vadc_scale_fn_type	scale_fn_type;
+	const char		*datasheet_name;
+};
+
+/**
+ * struct adc5_chip - ADC private structure.
+ * @regmap: SPMI ADC5 peripheral register map field.
+ * @dev: SPMI ADC5 device.
+ * @base: base address for the ADC peripheral.
+ * @nchannels: number of ADC channels.
+ * @chan_props: array of ADC channel properties.
+ * @iio_chans: array of IIO channels specification.
+ * @poll_eoc: use polling instead of interrupt.
+ * @complete: ADC result notification after interrupt is received.
+ * @lock: ADC lock for access to the peripheral.
+ * @data: software configuration data.
+ */
+struct adc5_chip {
+	struct regmap		*regmap;
+	struct device		*dev;
+	u16			base;
+	unsigned int		nchannels;
+	struct adc5_channel_prop	*chan_props;
+	struct iio_chan_spec	*iio_chans;
+	bool			poll_eoc;
+	struct completion	complete;
+	struct mutex		lock;
+	const struct adc_data	*data;
+};
+
+static const struct vadc_prescale_ratio adc5_prescale_ratios[] = {
+	{.num =  1, .den =  1},
+	{.num =  1, .den =  3},
+	{.num =  1, .den =  4},
+	{.num =  1, .den =  6},
+	{.num =  1, .den = 20},
+	{.num =  1, .den =  8},
+	{.num = 10, .den = 81},
+	{.num =  1, .den = 10},
+	{.num =  1, .den = 16}
+};
+
+static int adc5_read(struct adc5_chip *adc, u16 offset, u8 *data, int len)
+{
+	return regmap_bulk_read(adc->regmap, adc->base + offset, data, len);
+}
+
+static int adc5_write(struct adc5_chip *adc, u16 offset, u8 *data, int len)
+{
+	return regmap_bulk_write(adc->regmap, adc->base + offset, data, len);
+}
+
+static int adc5_prescaling_from_dt(u32 num, u32 den)
+{
+	unsigned int pre;
+
+	for (pre = 0; pre < ARRAY_SIZE(adc5_prescale_ratios); pre++)
+		if (adc5_prescale_ratios[pre].num == num &&
+		    adc5_prescale_ratios[pre].den == den)
+			break;
+
+	if (pre == ARRAY_SIZE(adc5_prescale_ratios))
+		return -EINVAL;
+
+	return pre;
+}
+
+static int adc5_get_dig_version(struct adc5_chip *adc,
+					u8 *dig_version)
+{
+	return adc5_read(adc, ADC5_USR_REVISION1, dig_version, 2);
+}
+
+static int adc5_hw_settle_time_from_dt(u32 value,
+					const unsigned int *hw_settle)
+{
+	uint32_t i;
+
+	for (i = 0; i < VADC_HW_SETTLE_SAMPLES_MAX; i++) {
+		if (value == hw_settle[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int adc5_avg_samples_from_dt(u32 value)
+{
+	if (!is_power_of_2(value) || value > ADC5_AVG_SAMPLES_MAX)
+		return -EINVAL;
+
+	return __ffs64(value);
+}
+
+static int adc5_decimation_from_dt(u32 value,
+					const unsigned int *decimation)
+{
+	uint32_t i;
+
+	for (i = 0; i < ADC5_DECIMATION_SAMPLES_MAX; i++) {
+		if (value == decimation[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static int adc5_read_current_data(struct adc5_chip *adc, u16 *data)
+{
+	int ret;
+	u8 rslt_lsb = 0, rslt_msb = 0;
+
+	ret = adc5_read(adc, ADC5_USR_IBAT_DATA0, &rslt_lsb, 1);
+	if (ret)
+		return ret;
+
+	ret = adc5_read(adc, ADC5_USR_IBAT_DATA1, &rslt_msb, 1);
+	if (ret)
+		return ret;
+
+	*data = (rslt_msb << 8) | rslt_lsb;
+
+	if (*data == ADC5_USR_DATA_CHECK) {
+		pr_err("Invalid data:0x%x\n", *data);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int adc5_read_voltage_data(struct adc5_chip *adc, u16 *data)
+{
+	int ret;
+	u8 rslt_lsb, rslt_msb;
+
+	ret = adc5_read(adc, ADC5_USR_DATA0, &rslt_lsb, 1);
+	if (ret)
+		return ret;
+
+	ret = adc5_read(adc, ADC5_USR_DATA1, &rslt_msb, 1);
+	if (ret)
+		return ret;
+
+	*data = (rslt_msb << 8) | rslt_lsb;
+
+	if (*data == ADC5_USR_DATA_CHECK) {
+		pr_err("Invalid data:0x%x\n", *data);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int adc5_poll_wait_eoc(struct adc5_chip *adc)
+{
+	unsigned int count, retry = ADC5_CONV_TIME_RETRY;
+	u8 status1;
+	int ret;
+
+	for (count = 0; count < retry; count++) {
+		ret = adc5_read(adc, ADC5_USR_STATUS1, &status1, 1);
+		if (ret)
+			return ret;
+
+		status1 &= ADC5_USR_STATUS1_REQ_STS_EOC_MASK;
+		if (status1 == ADC5_USR_STATUS1_EOC)
+			return 0;
+		usleep_range(ADC5_CONV_TIME_MIN_US, ADC5_CONV_TIME_MAX_US);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static void adc5_update_dig_param(struct adc5_chip *adc,
+			struct adc5_channel_prop *prop, u8 *data)
+{
+	/* Update calibration value */
+	*data &= ~ADC5_USR_DIG_PARAM_CAL_VAL;
+	*data |= (prop->cal_val << ADC5_USR_DIG_PARAM_CAL_VAL_SHIFT);
+
+	/* Update calibration select */
+	*data &= ~ADC5_USR_DIG_PARAM_CAL_SEL;
+	*data |= (prop->cal_method << ADC5_USR_DIG_PARAM_CAL_SEL_SHIFT);
+
+	/* Update decimation ratio select */
+	*data &= ~ADC5_USR_DIG_PARAM_DEC_RATIO_SEL;
+	*data |= (prop->decimation << ADC5_USR_DIG_PARAM_DEC_RATIO_SEL_SHIFT);
+}
+
+static int adc5_configure(struct adc5_chip *adc,
+			struct adc5_channel_prop *prop)
+{
+	int ret;
+	u8 buf[6];
+
+	/* Read registers 0x42 through 0x46 */
+	ret = adc5_read(adc, ADC5_USR_DIG_PARAM, buf, 6);
+	if (ret < 0)
+		return ret;
+
+	/* Digital param selection */
+	adc5_update_dig_param(adc, prop, &buf[0]);
+
+	/* Update fast average sample value */
+	buf[1] &= (u8) ~ADC5_USR_FAST_AVG_CTL_SAMPLES_MASK;
+	buf[1] |= prop->avg_samples;
+
+	/* Select ADC channel */
+	buf[2] = prop->channel;
+
+	/* Select HW settle delay for channel */
+	buf[3] &= (u8) ~ADC5_USR_HW_SETTLE_DELAY_MASK;
+	buf[3] |= prop->hw_settle_time;
+
+	/* Select ADC enable */
+	buf[4] |= ADC5_USR_EN_CTL1_ADC_EN;
+
+	/* Select CONV request */
+	buf[5] |= ADC5_USR_CONV_REQ_REQ;
+
+	if (!adc->poll_eoc)
+		reinit_completion(&adc->complete);
+
+	return adc5_write(adc, ADC5_USR_DIG_PARAM, buf, 6);
+}
+
+static int adc5_do_conversion(struct adc5_chip *adc,
+			struct adc5_channel_prop *prop,
+			struct iio_chan_spec const *chan,
+			u16 *data_volt, u16 *data_cur)
+{
+	int ret;
+
+	mutex_lock(&adc->lock);
+
+	ret = adc5_configure(adc, prop);
+	if (ret) {
+		pr_err("ADC configure failed with %d\n", ret);
+		goto unlock;
+	}
+
+	if (adc->poll_eoc) {
+		ret = adc5_poll_wait_eoc(adc);
+		if (ret < 0) {
+			pr_err("EOC bit not set\n");
+			goto unlock;
+		}
+	} else {
+		ret = wait_for_completion_timeout(&adc->complete,
+							ADC5_CONV_TIMEOUT);
+		if (!ret) {
+			pr_debug("Did not get completion timeout.\n");
+			ret = adc5_poll_wait_eoc(adc);
+			if (ret < 0) {
+				pr_err("EOC bit not set\n");
+				goto unlock;
+			}
+		}
+	}
+
+	if ((chan->type == IIO_VOLTAGE) || (chan->type == IIO_TEMP))
+		ret = adc5_read_voltage_data(adc, data_volt);
+	else if (chan->type == IIO_POWER) {
+		ret = adc5_read_voltage_data(adc, data_volt);
+		if (ret)
+			goto unlock;
+
+		ret = adc5_read_current_data(adc, data_cur);
+	}
+unlock:
+	mutex_unlock(&adc->lock);
+
+	return ret;
+}
+
+static irqreturn_t adc5_isr(int irq, void *dev_id)
+{
+	struct adc5_chip *adc = dev_id;
+
+	complete(&adc->complete);
+
+	return IRQ_HANDLED;
+}
+
+static int adc5_of_xlate(struct iio_dev *indio_dev,
+				const struct of_phandle_args *iiospec)
+{
+	struct adc5_chip *adc = iio_priv(indio_dev);
+	int i;
+
+	for (i = 0; i < adc->nchannels; i++)
+		if (adc->chan_props[i].channel == iiospec->args[0])
+			return i;
+
+	return -EINVAL;
+}
+
+static int adc5_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_prop *prop;
+	u16 adc_code_volt, adc_code_cur;
+	int ret;
+
+	prop = &adc->chan_props[chan->address];
+
+	switch (mask) {
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = adc5_do_conversion(adc, prop, chan,
+				&adc_code_volt, &adc_code_cur);
+		if (ret)
+			break;
+
+		if ((chan->type == IIO_VOLTAGE) || (chan->type == IIO_TEMP))
+			ret = qcom_adc5_hw_scale(prop->scale_fn_type,
+				&adc5_prescale_ratios[prop->prescale],
+				adc->data,
+				adc_code_volt, val);
+		if (ret)
+			break;
+
+		if (chan->type == IIO_POWER) {
+			ret = qcom_adc5_hw_scale(SCALE_HW_CALIB_DEFAULT,
+				&adc5_prescale_ratios[ADC5_DEF_VBAT_PRESCALING],
+				adc->data,
+				adc_code_volt, val);
+			if (ret)
+				break;
+
+			ret = qcom_adc5_hw_scale(prop->scale_fn_type,
+				&adc5_prescale_ratios[prop->prescale],
+				adc->data,
+				adc_code_cur, val2);
+			if (ret)
+				break;
+		}
+
+		if (chan->type == IIO_POWER)
+			return IIO_VAL_INT_MULTIPLE;
+		else
+			return IIO_VAL_INT;
+	case IIO_CHAN_INFO_RAW:
+		ret = adc5_do_conversion(adc, prop, chan,
+				&adc_code_volt, &adc_code_cur);
+		if (ret)
+			break;
+
+		*val = (int)adc_code_volt;
+		*val2 = (int)adc_code_cur;
+		if (chan->type == IIO_POWER)
+			return IIO_VAL_INT_MULTIPLE;
+		else
+			return IIO_VAL_INT;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct iio_info adc5_info = {
+	.read_raw = adc5_read_raw,
+	.of_xlate = adc5_of_xlate,
+};
+
+struct adc_channels {
+	const char *datasheet_name;
+	unsigned int prescale_index;
+	enum iio_chan_type type;
+	long info_mask;
+	enum vadc_scale_fn_type scale_fn_type;
+};
+
+#define ADC5_CHAN(_dname, _type, _mask, _pre, _scale)			\
+	{								\
+		.datasheet_name = (_dname),				\
+		.prescale_index = _pre,					\
+		.type = _type,						\
+		.info_mask = _mask,					\
+		.scale_fn_type = _scale,				\
+	},								\
+
+#define ADC5_CHAN_TEMP(_dname, _pre, _scale)				\
+	ADC5_CHAN(_dname, IIO_TEMP,					\
+		BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),	\
+		_pre, _scale)						\
+
+#define ADC5_CHAN_VOLT(_dname, _pre, _scale)				\
+	ADC5_CHAN(_dname, IIO_VOLTAGE,					\
+		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
+		  _pre, _scale)						\
+
+#define ADC5_CHAN_POWER(_dname, _pre, _scale)				\
+	ADC5_CHAN(_dname, IIO_POWER,					\
+		  BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\
+		  _pre, _scale)						\
+
+static const struct adc_channels adc_chans_pmic5[ADC5_MAX_CHANNEL] = {
+	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
+					SCALE_HW_CALIB_PMIC_THERM)
+	[ADC5_USB_IN_I]		= ADC5_CHAN_VOLT("usb_in_i_uv", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_USB_IN_V_16]	= ADC5_CHAN_VOLT("usb_in_v_div_16", 16,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_CHG_TEMP]		= ADC5_CHAN_TEMP("chg_temp", 1,
+					SCALE_HW_CALIB_PM5_CHG_TEMP)
+	/* Charger prescales SBUx and MID_CHG to fit within 1.8V upper unit */
+	[ADC5_SBUx]		= ADC5_CHAN_VOLT("chg_sbux", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_MID_CHG_DIV6]	= ADC5_CHAN_VOLT("chg_mid_chg", 6,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm", 1,
+					SCALE_HW_CALIB_XOTHERM)
+	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM2_100K_PU] = ADC5_CHAN_TEMP("amux_thm2_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_INT_EXT_ISENSE_VBAT_VDATA] = ADC5_CHAN_POWER("int_ext_isense", 1,
+					SCALE_HW_CALIB_CUR)
+	[ADC5_EXT_ISENSE_VBAT_VDATA] = ADC5_CHAN_POWER("ext_isense", 1,
+					SCALE_HW_CALIB_CUR)
+	[ADC5_PARALLEL_ISENSE_VBAT_VDATA] =
+					ADC5_CHAN_POWER("parallel_isense", 1,
+					SCALE_HW_CALIB_CUR)
+	[ADC5_AMUX_THM2]	= ADC5_CHAN_TEMP("amux_thm2", 1,
+					SCALE_HW_CALIB_PM5_SMB_TEMP)
+};
+
+static const struct adc_channels adc_chans_rev2[ADC5_MAX_CHANNEL] = {
+	[ADC5_REF_GND]		= ADC5_CHAN_VOLT("ref_gnd", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_1P25VREF]		= ADC5_CHAN_VOLT("vref_1p25", 1,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VPH_PWR]		= ADC5_CHAN_VOLT("vph_pwr", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VBAT_SNS]		= ADC5_CHAN_VOLT("vbat_sns", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_VCOIN]		= ADC5_CHAN_VOLT("vcoin", 3,
+					SCALE_HW_CALIB_DEFAULT)
+	[ADC5_DIE_TEMP]		= ADC5_CHAN_TEMP("die_temp", 1,
+					SCALE_HW_CALIB_PMIC_THERM)
+	[ADC5_AMUX_THM1_100K_PU] = ADC5_CHAN_TEMP("amux_thm1_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM3_100K_PU] = ADC5_CHAN_TEMP("amux_thm3_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_AMUX_THM5_100K_PU] = ADC5_CHAN_TEMP("amux_thm5_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+	[ADC5_XO_THERM_100K_PU]	= ADC5_CHAN_TEMP("xo_therm_100k_pu", 1,
+					SCALE_HW_CALIB_THERM_100K_PULLUP)
+};
+
+static int adc5_get_dt_channel_data(struct adc5_chip *adc,
+				    struct adc5_channel_prop *prop,
+				    struct device_node *node,
+				    const struct adc_data *data)
+{
+	const char *name = node->name, *channel_name;
+	u32 chan, value, varr[2];
+	int ret;
+	struct device *dev = adc->dev;
+
+	ret = of_property_read_u32(node, "reg", &chan);
+	if (ret) {
+		dev_err(dev, "invalid channel number %s\n", name);
+		return ret;
+	}
+
+	if (chan > ADC5_PARALLEL_ISENSE_VBAT_IDATA) {
+		dev_err(dev, "%s invalid channel number %d\n", name, chan);
+		return -EINVAL;
+	}
+
+	/* the channel has DT description */
+	prop->channel = chan;
+
+	channel_name = of_get_property(node,
+				"label", NULL) ? : node->name;
+	if (!channel_name) {
+		pr_err("Invalid channel name\n");
+		return -EINVAL;
+	}
+	prop->datasheet_name = channel_name;
+
+	ret = of_property_read_u32(node, "qcom,decimation", &value);
+	if (!ret) {
+		ret = adc5_decimation_from_dt(value, data->decimation);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid decimation %d\n",
+				chan, value);
+			return ret;
+		}
+		prop->decimation = ret;
+	} else {
+		prop->decimation = ADC5_DECIMATION_DEFAULT;
+	}
+
+	ret = of_property_read_u32_array(node, "qcom,pre-scaling", varr, 2);
+	if (!ret) {
+		ret = adc5_prescaling_from_dt(varr[0], varr[1]);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid pre-scaling <%d %d>\n",
+				chan, varr[0], varr[1]);
+			return ret;
+		}
+		prop->prescale = ret;
+	}
+
+	ret = of_property_read_u32(node, "qcom,hw-settle-time", &value);
+	if (!ret) {
+		u8 dig_version[2];
+
+		ret = adc5_get_dig_version(adc, dig_version);
+		if (ret < 0) {
+			dev_err(dev, "Invalid dig version read %d\n", ret);
+			return ret;
+		}
+
+		pr_debug("dig_ver:minor:%d, major:%d\n", dig_version[0],
+						dig_version[1]);
+		/* Digital controller >= 5.3 have hw_settle_2 option */
+		if (dig_version[0] >= ADC5_HW_SETTLE_DIFF_MINOR &&
+			dig_version[1] >= ADC5_HW_SETTLE_DIFF_MAJOR)
+			ret = adc5_hw_settle_time_from_dt(value,
+							data->hw_settle_2);
+		else
+			ret = adc5_hw_settle_time_from_dt(value,
+							data->hw_settle_1);
+
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid hw-settle-time %d us\n",
+				chan, value);
+			return ret;
+		}
+		prop->hw_settle_time = ret;
+	} else {
+		prop->hw_settle_time = VADC_DEF_HW_SETTLE_TIME;
+	}
+
+	ret = of_property_read_u32(node, "qcom,avg-samples", &value);
+	if (!ret) {
+		ret = adc5_avg_samples_from_dt(value);
+		if (ret < 0) {
+			dev_err(dev, "%02x invalid avg-samples %d\n",
+				chan, value);
+			return ret;
+		}
+		prop->avg_samples = ret;
+	} else {
+		prop->avg_samples = VADC_DEF_AVG_SAMPLES;
+	}
+
+	if (of_property_read_bool(node, "qcom,ratiometric"))
+		prop->cal_method = ADC5_RATIOMETRIC_CAL;
+	else
+		prop->cal_method = ADC5_ABSOLUTE_CAL;
+
+	/*
+	 * Default to using timer calibration. Using a fresh calibration value
+	 * for every conversion will increase the overall time for a request.
+	 */
+	prop->cal_val = ADC5_TIMER_CAL;
+
+	dev_dbg(dev, "%02x name %s\n", chan, name);
+
+	return 0;
+}
+
+const struct adc_data data_pmic5 = {
+	.full_scale_code_volt = 0x70e4,
+	.full_scale_code_cur = 0x2710,
+	.adc_chans = adc_chans_pmic5,
+	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
+				{250, 420, 840},
+	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{15, 100, 200, 300, 400, 500, 600, 700,
+				800, 900, 1, 2, 4, 6, 8, 10},
+	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{15, 100, 200, 300, 400, 500, 600, 700,
+				1, 2, 4, 8, 16, 32, 64, 128},
+};
+
+const struct adc_data data_pmic_rev2 = {
+	.full_scale_code_volt = 0x4000,
+	.full_scale_code_cur = 0x1800,
+	.adc_chans = adc_chans_rev2,
+	.decimation = (unsigned int [ADC5_DECIMATION_SAMPLES_MAX])
+				{256, 512, 1024},
+	.hw_settle_1 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{0, 100, 200, 300, 400, 500, 600, 700,
+				800, 900, 1, 2, 4, 6, 8, 10},
+	.hw_settle_2 = (unsigned int [VADC_HW_SETTLE_SAMPLES_MAX])
+				{15, 100, 200, 300, 400, 500, 600, 700,
+				1, 2, 4, 8, 16, 32, 64, 128},
+};
+
+static const struct of_device_id adc5_match_table[] = {
+	{
+		.compatible = "qcom,spmi-adc5",
+		.data = &data_pmic5,
+	},
+	{
+		.compatible = "qcom,spmi-adc-rev2",
+		.data = &data_pmic_rev2,
+	},
+	{ }
+};
+
+static int adc5_get_dt_data(struct adc5_chip *adc, struct device_node *node)
+{
+	const struct adc_channels *adc_chan;
+	struct iio_chan_spec *iio_chan;
+	struct adc5_channel_prop prop;
+	struct device_node *child;
+	unsigned int index = 0;
+	const struct of_device_id *id;
+	const struct adc_data *data;
+	int ret;
+
+	adc->nchannels = of_get_available_child_count(node);
+	if (!adc->nchannels)
+		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;
+
+	iio_chan = adc->iio_chans;
+	id = of_match_node(adc5_match_table, node);
+	if (id)
+		data = id->data;
+	else
+		data = &data_pmic5;
+	adc->data = data;
+
+	for_each_available_child_of_node(node, child) {
+		ret = adc5_get_dt_channel_data(adc, &prop, child, data);
+		if (ret) {
+			of_node_put(child);
+			return ret;
+		}
+
+		prop.scale_fn_type =
+			data->adc_chans[prop.channel].scale_fn_type;
+		*chan_props = prop;
+		adc_chan = &data->adc_chans[prop.channel];
+
+		iio_chan->channel = prop.channel;
+		iio_chan->datasheet_name = prop.datasheet_name;
+		iio_chan->extend_name = prop.datasheet_name;
+		iio_chan->info_mask_separate = adc_chan->info_mask;
+		iio_chan->type = adc_chan->type;
+		iio_chan->address = index;
+		iio_chan++;
+		chan_props++;
+		index++;
+	}
+
+	return 0;
+}
+
+static int adc5_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	struct iio_dev *indio_dev;
+	struct adc5_chip *adc;
+	struct regmap *regmap;
+	int ret, irq_eoc;
+	u32 reg;
+
+	regmap = dev_get_regmap(dev->parent, NULL);
+	if (!regmap)
+		return -ENODEV;
+
+	ret = of_property_read_u32(node, "reg", &reg);
+	if (ret < 0)
+		return ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*adc));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	adc = iio_priv(indio_dev);
+	adc->regmap = regmap;
+	adc->dev = dev;
+	adc->base = reg;
+	init_completion(&adc->complete);
+	mutex_init(&adc->lock);
+
+	ret = adc5_get_dt_data(adc, node);
+	if (ret) {
+		pr_err("adc get dt data failed\n");
+		return ret;
+	}
+
+	irq_eoc = platform_get_irq(pdev, 0);
+	if (irq_eoc < 0) {
+		if (irq_eoc == -EPROBE_DEFER || irq_eoc == -EINVAL)
+			return irq_eoc;
+		adc->poll_eoc = true;
+	} else {
+		ret = devm_request_irq(dev, irq_eoc, adc5_isr, 0,
+				       "pm-adc5", adc);
+		if (ret)
+			return ret;
+	}
+
+	indio_dev->dev.parent = dev;
+	indio_dev->dev.of_node = node;
+	indio_dev->name = pdev->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &adc5_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_driver = {
+	.driver = {
+		.name = "qcom-spmi-adc5.c",
+		.of_match_table = adc5_match_table,
+	},
+	.probe = adc5_probe,
+};
+module_platform_driver(adc5_driver);
+
+MODULE_ALIAS("platform:qcom-spmi-adc5");
+MODULE_DESCRIPTION("Qualcomm Technologies Inc. PMIC5 ADC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/adc/qcom-vadc-common.c b/drivers/iio/adc/qcom-vadc-common.c
index fe3d782..2a9fecb 100644
--- a/drivers/iio/adc/qcom-vadc-common.c
+++ b/drivers/iio/adc/qcom-vadc-common.c
@@ -47,8 +47,79 @@
 	{44,	125}
 };
 
+/*
+ * Voltage to temperature table for 100k pull up for NTCG104EF104 with
+ * 1.875V reference.
+ */
+static const struct vadc_map_pt adcmap_100k_104ef_104fb_1875_vref[] = {
+	{ 1831,	-40000 },
+	{ 1814,	-35000 },
+	{ 1791,	-30000 },
+	{ 1761,	-25000 },
+	{ 1723,	-20000 },
+	{ 1675,	-15000 },
+	{ 1616,	-10000 },
+	{ 1545,	-5000 },
+	{ 1463,	0 },
+	{ 1370,	5000 },
+	{ 1268,	10000 },
+	{ 1160,	15000 },
+	{ 1049,	20000 },
+	{ 937,	25000 },
+	{ 828,	30000 },
+	{ 726,	35000 },
+	{ 630,	40000 },
+	{ 544,	45000 },
+	{ 467,	50000 },
+	{ 399,	55000 },
+	{ 340,	60000 },
+	{ 290,	65000 },
+	{ 247,	70000 },
+	{ 209,	75000 },
+	{ 179,	80000 },
+	{ 153,	85000 },
+	{ 130,	90000 },
+	{ 112,	95000 },
+	{ 96,	100000 },
+	{ 82,	105000 },
+	{ 71,	110000 },
+	{ 62,	115000 },
+	{ 53,	120000 },
+	{ 46,	125000 },
+};
+
+static int qcom_vadc_scale_hw_calib_volt(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_uv);
+static int qcom_vadc_scale_hw_calib_therm(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+static int qcom_vadc_scale_hw_smb_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+static int qcom_vadc_scale_hw_chg5_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+static int qcom_vadc_scale_hw_calib_die_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec);
+
+static struct qcom_adc5_scale_type scale_adc5_fn[] = {
+	[SCALE_HW_CALIB_DEFAULT] = {qcom_vadc_scale_hw_calib_volt},
+	[SCALE_HW_CALIB_THERM_100K_PULLUP] = {qcom_vadc_scale_hw_calib_therm},
+	[SCALE_HW_CALIB_XOTHERM] = {qcom_vadc_scale_hw_calib_therm},
+	[SCALE_HW_CALIB_PMIC_THERM] = {qcom_vadc_scale_hw_calib_die_temp},
+	[SCALE_HW_CALIB_PM5_CHG_TEMP] = {qcom_vadc_scale_hw_chg5_temp},
+	[SCALE_HW_CALIB_PM5_SMB_TEMP] = {qcom_vadc_scale_hw_smb_temp},
+};
+
 static int qcom_vadc_map_voltage_temp(const struct vadc_map_pt *pts,
-				      u32 tablesize, s32 input, s64 *output)
+				      u32 tablesize, s32 input, int *output)
 {
 	bool descending = 1;
 	u32 i = 0;
@@ -128,7 +199,7 @@ static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
 				 bool absolute, u16 adc_code,
 				 int *result_mdec)
 {
-	s64 voltage = 0, result = 0;
+	s64 voltage = 0;
 	int ret;
 
 	qcom_vadc_scale_calib(calib_graph, adc_code, absolute, &voltage);
@@ -138,12 +209,11 @@ static int qcom_vadc_scale_therm(const struct vadc_linear_graph *calib_graph,
 
 	ret = qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb,
 					 ARRAY_SIZE(adcmap_100k_104ef_104fb),
-					 voltage, &result);
+					 voltage, result_mdec);
 	if (ret)
 		return ret;
 
-	result *= 1000;
-	*result_mdec = result;
+	*result_mdec *= 1000;
 
 	return 0;
 }
@@ -191,6 +261,99 @@ static int qcom_vadc_scale_chg_temp(const struct vadc_linear_graph *calib_graph,
 	return 0;
 }
 
+static int qcom_vadc_scale_code_voltage_factor(u16 adc_code,
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				unsigned int factor)
+{
+	s64 voltage, temp, adc_vdd_ref_mv = 1875;
+
+	/*
+	 * The normal data range is between 0V to 1.875V. On cases where
+	 * we read low voltage values, the ADC code can go beyond the
+	 * range and the scale result is incorrect so we clamp the values
+	 * for the cases where the code represents a value below 0V
+	 */
+	if (adc_code > VADC5_MAX_CODE)
+		adc_code = 0;
+
+	/* (ADC code * vref_vadc (1.875V)) / full_scale_code */
+	voltage = (s64) adc_code * adc_vdd_ref_mv * 1000;
+	voltage = div64_s64(voltage, data->full_scale_code_volt);
+	if (voltage > 0) {
+		voltage *= prescale->den;
+		temp = prescale->num * factor;
+		voltage = div64_s64(voltage, temp);
+	} else {
+		voltage = 0;
+	}
+
+	return (int) voltage;
+}
+
+static int qcom_vadc_scale_hw_calib_volt(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_uv)
+{
+	*result_uv = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 1);
+
+	return 0;
+}
+
+static int qcom_vadc_scale_hw_calib_therm(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	int ret, voltage;
+
+	voltage = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 1000);
+
+	/* Map voltage to temperature from look-up table */
+	return qcom_vadc_map_voltage_temp(adcmap_100k_104ef_104fb_1875_vref,
+				 ARRAY_SIZE(adcmap_100k_104ef_104fb_1875_vref),
+				 voltage, result_mdec);
+}
+
+static int qcom_vadc_scale_hw_calib_die_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 2);
+	*result_mdec -= KELVINMIL_CELSIUSMIL;
+
+	return 0;
+}
+
+static int qcom_vadc_scale_hw_smb_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code * 100,
+				prescale, data, PMIC5_SMB_TEMP_SCALE_FACTOR);
+	*result_mdec = PMIC5_SMB_TEMP_CONSTANT - *result_mdec;
+
+	return 0;
+}
+
+static int qcom_vadc_scale_hw_chg5_temp(
+				const struct vadc_prescale_ratio *prescale,
+				const struct adc_data *data,
+				u16 adc_code, int *result_mdec)
+{
+	*result_mdec = qcom_vadc_scale_code_voltage_factor(adc_code,
+				prescale, data, 4);
+	*result_mdec = PMIC5_CHG_TEMP_SCALE_FACTOR - *result_mdec;
+
+	return 0;
+}
+
 int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 		    const struct vadc_linear_graph *calib_graph,
 		    const struct vadc_prescale_ratio *prescale,
@@ -221,6 +384,22 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 }
 EXPORT_SYMBOL(qcom_vadc_scale);
 
+int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
+		    const struct vadc_prescale_ratio *prescale,
+		    const struct adc_data *data,
+		    u16 adc_code, int *result)
+{
+	if (!(scaletype >= SCALE_HW_CALIB_DEFAULT &&
+		scaletype < SCALE_HW_CALIB_INVALID)) {
+		pr_err("Invalid scale type %d\n", scaletype);
+		return -EINVAL;
+	}
+
+	return scale_adc5_fn[scaletype].scale_fn(prescale, data,
+					adc_code, result);
+}
+EXPORT_SYMBOL(qcom_adc5_hw_scale);
+
 int qcom_vadc_decimation_from_dt(u32 value)
 {
 	if (!is_power_of_2(value) || value < VADC_DECIMATION_MIN ||
diff --git a/drivers/iio/adc/qcom-vadc-common.h b/drivers/iio/adc/qcom-vadc-common.h
index 1d5354f..bcd6959 100644
--- a/drivers/iio/adc/qcom-vadc-common.h
+++ b/drivers/iio/adc/qcom-vadc-common.h
@@ -25,15 +25,31 @@
 
 #define VADC_DECIMATION_MIN			512
 #define VADC_DECIMATION_MAX			4096
+#define ADC5_DEF_VBAT_PRESCALING		1 /* 1:3 */
+#define ADC5_DECIMATION_SHORT			250
+#define ADC5_DECIMATION_MEDIUM			420
+#define ADC5_DECIMATION_LONG			840
+/* Default decimation - 1024 for rev2, 840 for pmic5 */
+#define ADC5_DECIMATION_DEFAULT			2
+#define ADC5_DECIMATION_SAMPLES_MAX		3
 
 #define VADC_HW_SETTLE_DELAY_MAX		10000
+#define VADC_HW_SETTLE_SAMPLES_MAX		16
 #define VADC_AVG_SAMPLES_MAX			512
+#define ADC5_AVG_SAMPLES_MAX			16
 
 #define KELVINMIL_CELSIUSMIL			273150
+#define PMIC5_CHG_TEMP_SCALE_FACTOR		377500
+#define PMIC5_SMB_TEMP_CONSTANT			419400
+#define PMIC5_SMB_TEMP_SCALE_FACTOR		356
 
 #define PMI_CHG_SCALE_1				-138890
 #define PMI_CHG_SCALE_2				391750000000LL
 
+#define VADC5_MAX_CODE				0x7fff
+#define ADC5_FULL_SCALE_CODE			0x70e4
+#define ADC5_USR_DATA_CHECK			0x8000
+
 /**
  * struct vadc_map_pt - Map the graph representation for ADC channel
  * @x: Represent the ADC digitized code.
@@ -89,6 +105,18 @@ struct vadc_prescale_ratio {
  * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade.
  * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC.
  * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp
+ * SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to
+ *	voltage (uV) with hardware applied offset/slope values to adc code.
+ * SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC using
+ *	lookup table. The hardware applies offset/slope to adc code.
+ * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC using
+ *	100k pullup. The hardware applies offset/slope to adc code.
+ * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade.
+ *	The hardware applies offset/slope to adc code.
+ * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for PMIC5
+ *	charger temperature.
+ * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5
+ *	SMB1390 temperature.
  */
 enum vadc_scale_fn_type {
 	SCALE_DEFAULT = 0,
@@ -96,6 +124,22 @@ enum vadc_scale_fn_type {
 	SCALE_PMIC_THERM,
 	SCALE_XOTHERM,
 	SCALE_PMI_CHG_TEMP,
+	SCALE_HW_CALIB_DEFAULT,
+	SCALE_HW_CALIB_THERM_100K_PULLUP,
+	SCALE_HW_CALIB_XOTHERM,
+	SCALE_HW_CALIB_PMIC_THERM,
+	SCALE_HW_CALIB_PM5_CHG_TEMP,
+	SCALE_HW_CALIB_PM5_SMB_TEMP,
+	SCALE_HW_CALIB_INVALID,
+};
+
+struct adc_data {
+	const u32	full_scale_code_volt;
+	const u32	full_scale_code_cur;
+	const struct adc_channels *adc_chans;
+	unsigned int	*decimation;
+	unsigned int	*hw_settle_1;
+	unsigned int	*hw_settle_2;
 };
 
 int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
@@ -104,6 +148,16 @@ int qcom_vadc_scale(enum vadc_scale_fn_type scaletype,
 		    bool absolute,
 		    u16 adc_code, int *result_mdec);
 
+struct qcom_adc5_scale_type {
+	int (*scale_fn)(const struct vadc_prescale_ratio *prescale,
+		const struct adc_data *data, u16 adc_code, int *result);
+};
+
+int qcom_adc5_hw_scale(enum vadc_scale_fn_type scaletype,
+		    const struct vadc_prescale_ratio *prescale,
+		    const struct adc_data *data,
+		    u16 adc_code, int *result_mdec);
+
 int qcom_vadc_decimation_from_dt(u32 value);
 
 #endif /* QCOM_VADC_COMMON_H */
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
a Linux Foundation Collaborative Project

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

end of thread, other threads:[~2018-08-03  0:57 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-07-26  0:09 [PATCH v3 2/3] iio: adc: Add QCOM SPMI PMIC5 ADC driver Siddartha Mohanadoss
2018-07-28 11:08 ` Jonathan Cameron
2018-07-31 18:08   ` smohanad
2018-08-02  9:21     ` Jonathan Cameron
2018-08-02  9:21       ` Jonathan Cameron
2018-08-03  0:57       ` smohanad
  -- strict thread matches above, loose matches on Subject: below --
2018-07-25 23:44 Siddartha Mohanadoss
2018-07-26  0:09 ` smohanad

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.