linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/3] Add Palmas iio gpadc
       [not found] <1437657169.3419965.331206849.51D66A2E@webmail.messagingengine.com>
@ 2015-09-23 12:48 ` H. Nikolaus Schaller
       [not found]   ` <cover.1443973837.git.hns@goldelico.com>
  2015-10-05  6:14   ` [PATCH v2 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
       [not found] ` <cover.1443012491.git.hns@goldelico.com>
  1 sibling, 2 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-23 12:48 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, notaz

Add iio driver for the TI Palmas (twl6035, 6037) including device tree bindings.
It enables the gpadc for the OMAP5 uevm.

This patch series is based on original code taken from Android Tegra kernels:
(https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc)

Tegra code was developed by:
Pradeep Goudagunta <pgoudagunta@nvidia.com>
Laxman Dewangan <ldewangan@nvidia.com>

Edited and extended for mainline by:
H. Nikolaus Schaller <hns@goldelico.com>
Marek Belisko <marek@goldelico.com>



H. Nikolaus Schaller (2):
 iio:adc: add iio driver for Palmas (twl6035/7) gpadc
 ARM: dts: omap5-uevm: enable iio gpadc for Palmas

Marek Belisko (1):
 iio:adc:palmas: add DT support

.../devicetree/bindings/iio/adc/palmas-gpadc.txt   |  67 ++
arch/arm/boot/dts/omap5-uevm.dts                   |  22 +
drivers/iio/adc/Kconfig                            |   9 +
drivers/iio/adc/Makefile                           |   1 +
drivers/iio/adc/palmas_gpadc.c                     | 883 +++++++++++++++++++++
include/linux/mfd/palmas.h                         |  59 +-
6 files changed, 1037 insertions(+), 4 deletions(-)
create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
create mode 100644 drivers/iio/adc/palmas_gpadc.c

-- 
2.5.1


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

* [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
       [not found] ` <cover.1443012491.git.hns@goldelico.com>
@ 2015-09-23 12:48   ` H. Nikolaus Schaller
  2015-09-23 13:26     ` Peter Meerwald
  2015-09-27 15:21     ` Jonathan Cameron
  2015-09-23 12:49   ` [PATCH 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
  2015-09-23 12:49   ` [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
  2 siblings, 2 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-23 12:48 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, notaz

This driver code was found as:

https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc

Fixed various compilation issues and test this driver on omap5 evm.

Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Signed-off-by: Marek Belisko <marek@goldelico.com>
---
drivers/iio/adc/Kconfig        |   9 +
drivers/iio/adc/Makefile       |   1 +
drivers/iio/adc/palmas_gpadc.c | 797 +++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/palmas.h     |  59 ++-
4 files changed, 862 insertions(+), 4 deletions(-)
create mode 100644 drivers/iio/adc/palmas_gpadc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index eb0cd89..f6df9db 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -242,6 +242,15 @@ config NAU7802
	  To compile this driver as a module, choose M here: the
	  module will be called nau7802.

+config PALMAS_GPADC
+	tristate "TI Palmas General Purpose ADC"
+	depends on MFD_PALMAS
+	help
+	  Palmas series pmic chip by texas Instruments (twl6035/6037)
+	  is used in smartphones and tablets and supports a 16 channel
+	  general purpose ADC. Add iio driver to read different channel
+	  of the GPADCs.
+
config QCOM_SPMI_IADC
	tristate "Qualcomm SPMI PMIC current ADC"
	depends on SPMI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index a096210..716f112 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
obj-$(CONFIG_MCP3422) += mcp3422.o
obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
obj-$(CONFIG_NAU7802) += nau7802.o
+obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
new file mode 100644
index 0000000..17abb28
--- /dev/null
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -0,0 +1,797 @@
+/*
+ * palmas-adc.c -- TI PALMAS GPADC.
+ *
+ * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
+ *
+ * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/pm.h>
+#include <linux/mfd/palmas.h>
+#include <linux/completion.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+#define MOD_NAME "palmas-gpadc"
+#define ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
+#define TO_BE_CALCULATED 0
+
+struct palmas_gpadc_info {
+/* calibration codes and regs */
+	int x1;
+	int x2;
+	int v1;
+	int v2;
+	u8 trim1_reg;
+	u8 trim2_reg;
+	int gain;
+	int offset;
+	int gain_error;
+	bool is_correct_code;
+};
+
+#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_correct_code)\
+[PALMAS_ADC_CH_##_chan] = {						\
+		.x1 = _x1,						\
+		.x2 = _x2,						\
+		.v1 = _v1,						\
+		.v2 = _v2,						\
+		.gain = TO_BE_CALCULATED,				\
+		.offset = TO_BE_CALCULATED,				\
+		.gain_error = TO_BE_CALCULATED,				\
+		.trim1_reg = PALMAS_GPADC_TRIM##_t1,			\
+		.trim2_reg = PALMAS_GPADC_TRIM##_t2,			\
+		.is_correct_code = _is_correct_code			\
+	}
+
+static struct palmas_gpadc_info palmas_gpadc_info[] = {
+	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
+	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
+	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
+	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
+	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
+	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
+	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
+	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
+};
+
+struct palmas_gpadc {
+	struct device			*dev;
+	struct palmas			*palmas;
+	u8				ch0_current;
+	u8				ch3_current;
+	bool				extended_delay;
+	int				irq;
+	int				irq_auto_0;
+	int				irq_auto_1;
+	struct palmas_gpadc_info	*adc_info;
+	struct completion		conv_completion;
+	struct palmas_adc_wakeup_property wakeup1_data;
+	struct palmas_adc_wakeup_property wakeup2_data;
+	bool				wakeup1_enable;
+	bool				wakeup2_enable;
+	int				auto_conversion_period;
+};
+
+/*
+ * GPADC lock issue in AUTO mode.
+ * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
+ *	   mode feature.
+ * Details:
+ *	When the AUTO mode is the only conversion mode enabled, if the AUTO
+ *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
+ *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
+ *	conversion mechanism can be seen as locked meaning that all following
+ *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
+ *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
+ *	the GPADC.
+ *
+ * Workaround(s):
+ *	To avoid the lock mechanism, the workaround to follow before any stop
+ *	conversion request is:
+ *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
+ *		GPADC_FORCE bit = 1
+ *	Shutdown the GPADC AUTO conversion using
+ *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
+ *	After 100us, force the GPADC state machine to be OFF by using the
+ *		GPADC_CTRL1.  GPADC_FORCE bit = 0
+ */
+static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
+{
+	int ret;
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_CTRL1,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE);
+	if (ret < 0) {
+		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
+			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
+			0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
+		return ret;
+	}
+
+	udelay(100);
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_CTRL1,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+	if (ret < 0) {
+		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static irqreturn_t palmas_gpadc_irq(int irq, void *data)
+{
+	struct palmas_gpadc *adc = data;
+
+	complete(&adc->conv_completion);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
+{
+	struct palmas_gpadc *adc = data;
+
+	dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
+	palmas_disable_auto_conversion(adc);
+	return IRQ_HANDLED;
+}
+
+static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)
+{
+	int ret;
+
+	if (!mask)
+		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
+					PALMAS_INT3_MASK,
+					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
+	else
+		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
+					PALMAS_INT3_MASK,
+					PALMAS_INT3_MASK_GPADC_EOC_SW,
+					PALMAS_INT3_MASK_GPADC_EOC_SW);
+	if (ret < 0)
+		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
+			       int enable)
+{
+	unsigned int mask, val;
+	int ret;
+
+	if (enable) {
+		val = (adc->extended_delay
+			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+					PALMAS_GPADC_RT_CTRL,
+					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
+		if (ret < 0) {
+			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
+			return ret;
+		}
+
+		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
+			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
+			PALMAS_GPADC_CTRL1_GPADC_FORCE);
+		val = (adc->ch0_current
+			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
+		val |= (adc->ch3_current
+			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
+		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_CTRL1, mask, val);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"Failed to update current setting: %d\n", ret);
+			return ret;
+		}
+
+		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
+			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
+		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT, mask, val);
+		if (ret < 0) {
+			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
+			return ret;
+		}
+	} else {
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT, 0);
+		if (ret < 0)
+			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
+
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_CTRL1,
+				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+		if (ret < 0) {
+			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
+{
+	int ret;
+
+	ret = palmas_gpadc_enable(adc, adc_chan, true);
+	if (ret < 0)
+		return ret;
+
+	return palmas_gpadc_start_mask_interrupt(adc, 0);
+}
+
+static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
+{
+	palmas_gpadc_start_mask_interrupt(adc, 1);
+	palmas_gpadc_enable(adc, adc_chan, false);
+}
+
+static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
+{
+	int k;
+	int d1;
+	int d2;
+	int ret;
+	int gain;
+	int x1 =  adc->adc_info[adc_chan].x1;
+	int x2 =  adc->adc_info[adc_chan].x2;
+	int v1 = adc->adc_info[adc_chan].v1;
+	int v2 = adc->adc_info[adc_chan].v2;
+
+	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
+				adc->adc_info[adc_chan].trim1_reg, &d1);
+	if (ret < 0) {
+		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
+		goto scrub;
+	}
+
+	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
+				adc->adc_info[adc_chan].trim2_reg, &d2);
+	if (ret < 0) {
+		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
+		goto scrub;
+	}
+
+	/*Gain error Calculation*/
+	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
+
+	/*Gain Calculation*/
+	gain = ((v2 - v1) * 1000) / (x2 - x1);
+
+	adc->adc_info[adc_chan].gain_error = k;
+	adc->adc_info[adc_chan].gain = gain;
+	/*offset Calculation*/
+	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
+
+scrub:
+	return ret;
+}
+
+static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
+{
+	unsigned int val;
+	int ret;
+
+	init_completion(&adc->conv_completion);
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT,
+				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
+				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
+	if (ret < 0) {
+		dev_err(adc->dev, "ADC_SW_START write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = wait_for_completion_timeout(&adc->conv_completion,
+				ADC_CONVERSION_TIMEOUT);
+	if (ret == 0) {
+		dev_err(adc->dev, "ADC conversion not completed\n");
+		ret = -ETIMEDOUT;
+		return ret;
+	}
+
+	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
+	if (ret < 0) {
+		dev_err(adc->dev, "ADCDATA read failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = (val & 0xFFF);
+	return ret;
+}
+
+static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
+						int adc_chan, int val)
+{
+	if (((val*1000) - adc->adc_info[adc_chan].offset) < 0) {
+		dev_err(adc->dev, "No Input Connected\n");
+		return 0;
+	}
+
+	if (!(adc->adc_info[adc_chan].is_correct_code))
+		val  = ((val*1000) - adc->adc_info[adc_chan].offset) /
+					adc->adc_info[adc_chan].gain_error;
+
+	val = (val * adc->adc_info[adc_chan].gain) / 1000;
+	return val;
+}
+
+static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+	struct  palmas_gpadc *adc = iio_priv(indio_dev);
+	int adc_chan = chan->channel;
+	int ret = 0;
+
+	if (adc_chan > PALMAS_ADC_CH_MAX)
+		return -EINVAL;
+
+	mutex_lock(&indio_dev->mlock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = palmas_gpadc_read_prepare(adc, adc_chan);
+		if (ret < 0)
+			goto out;
+
+		ret = palmas_gpadc_start_conversion(adc, adc_chan);
+		if (ret < 0) {
+			dev_err(adc->dev,
+			"ADC start coversion failed\n");
+			goto out;
+		}
+
+		if (mask == IIO_CHAN_INFO_PROCESSED)
+			ret = palmas_gpadc_get_calibrated_code(
+							adc, adc_chan, ret);
+
+		*val = ret;
+
+		ret = IIO_VAL_INT;
+		goto out;
+	}
+
+	mutex_unlock(&indio_dev->mlock);
+	return ret;
+
+out:
+	palmas_gpadc_read_done(adc, adc_chan);
+	mutex_unlock(&indio_dev->mlock);
+	return ret;
+}
+
+static const struct iio_info palmas_gpadc_iio_info = {
+	.read_raw = palmas_gpadc_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+#define PALMAS_ADC_CHAN_IIO(chan, _type)			\
+{									\
+	.datasheet_name = PALMAS_DATASHEET_NAME(chan),			\
+	.type = _type,							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
+			BIT(IIO_CHAN_INFO_PROCESSED),			\
+	.indexed = 1,							\
+	.channel = PALMAS_ADC_CH_##chan,				\
+}
+
+static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
+	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN3, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN12, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN13, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE),
+	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
+};
+
+static int palmas_gpadc_probe(struct platform_device *pdev)
+{
+	struct palmas_gpadc *adc;
+	struct palmas_platform_data *pdata;
+	struct palmas_gpadc_platform_data *adc_pdata;
+	struct iio_dev *iodev;
+	int ret, i;
+
+	pdata = dev_get_platdata(pdev->dev.parent);
+	if (!pdata || !pdata->gpadc_pdata) {
+		dev_err(&pdev->dev, "No platform data\n");
+		return -ENODEV;
+	}
+	adc_pdata = pdata->gpadc_pdata;
+
+	iodev = iio_device_alloc(sizeof(*adc));
+	if (!iodev) {
+		dev_err(&pdev->dev, "iio_device_alloc failed\n");
+		return -ENOMEM;
+	}
+
+	if (adc_pdata->iio_maps) {
+		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "iio_map_array_register failed\n");
+			goto out;
+		}
+	}
+
+	adc = iio_priv(iodev);
+	adc->dev = &pdev->dev;
+	adc->palmas = dev_get_drvdata(pdev->dev.parent);
+	adc->adc_info = palmas_gpadc_info;
+	init_completion(&adc->conv_completion);
+	dev_set_drvdata(&pdev->dev, iodev);
+
+	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
+	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
+	ret = request_threaded_irq(adc->irq, NULL,
+		palmas_gpadc_irq,
+		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
+		adc);
+	if (ret < 0) {
+		dev_err(adc->dev,
+			"request irq %d failed: %dn", adc->irq, ret);
+		goto out_unregister_map;
+	}
+
+	if (adc_pdata->adc_wakeup1_data) {
+		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
+			sizeof(adc->wakeup1_data));
+		adc->wakeup1_enable = true;
+		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
+		ret = request_threaded_irq(adc->irq_auto_0, NULL,
+				palmas_gpadc_irq_auto,
+				IRQF_ONESHOT | IRQF_EARLY_RESUME,
+				"palmas-adc-auto-0", adc);
+		if (ret < 0) {
+			dev_err(adc->dev, "request auto0 irq %d failed: %dn",
+				adc->irq_auto_0, ret);
+			goto out_irq_free;
+		}
+	}
+
+	if (adc_pdata->adc_wakeup2_data) {
+		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
+				sizeof(adc->wakeup2_data));
+		adc->wakeup2_enable = true;
+		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
+		ret = request_threaded_irq(adc->irq_auto_1, NULL,
+				palmas_gpadc_irq_auto,
+				IRQF_ONESHOT | IRQF_EARLY_RESUME,
+				"palmas-adc-auto-1", adc);
+		if (ret < 0) {
+			dev_err(adc->dev, "request auto1 irq %d failed: %dn",
+				adc->irq_auto_1, ret);
+			goto out_irq_auto0_free;
+		}
+	}
+
+	if (adc_pdata->ch0_current == 0)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
+	else if (adc_pdata->ch0_current <= 5)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
+	else if (adc_pdata->ch0_current <= 15)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
+	else
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
+
+	if (adc_pdata->ch3_current == 0)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
+	else if (adc_pdata->ch3_current <= 10)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
+	else if (adc_pdata->ch3_current <= 400)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
+	else
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
+
+	adc->extended_delay = adc_pdata->extended_delay;
+
+	iodev->name = MOD_NAME;
+	iodev->dev.parent = &pdev->dev;
+	iodev->info = &palmas_gpadc_iio_info;
+	iodev->modes = INDIO_DIRECT_MODE;
+	iodev->channels = palmas_gpadc_iio_channel;
+	iodev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
+
+	ret = iio_device_register(iodev);
+	if (ret < 0) {
+		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
+		goto out_irq_auto1_free;
+	}
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
+		if (!(adc->adc_info[i].is_correct_code))
+			palmas_gpadc_calibrate(adc, i);
+	}
+
+	if (adc->wakeup1_enable || adc->wakeup2_enable)
+		device_wakeup_enable(&pdev->dev);
+
+	return 0;
+
+out_irq_auto1_free:
+	if (adc_pdata->adc_wakeup2_data)
+		free_irq(adc->irq_auto_1, adc);
+out_irq_auto0_free:
+	if (adc_pdata->adc_wakeup1_data)
+		free_irq(adc->irq_auto_0, adc);
+out_irq_free:
+	free_irq(adc->irq, adc);
+out_unregister_map:
+	if (adc_pdata->iio_maps)
+		iio_map_array_unregister(iodev);
+out:
+	iio_device_free(iodev);
+	return ret;
+}
+
+static int palmas_gpadc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *iodev = dev_to_iio_dev(&pdev->dev);
+	struct palmas_gpadc *adc = iio_priv(iodev);
+	struct palmas_platform_data *pdata = dev_get_platdata(pdev->dev.parent);
+
+	if (pdata->gpadc_pdata->iio_maps)
+		iio_map_array_unregister(iodev);
+	iio_device_unregister(iodev);
+	free_irq(adc->irq, adc);
+	if (adc->wakeup1_enable)
+		free_irq(adc->irq_auto_0, adc);
+	if (adc->wakeup2_enable)
+		free_irq(adc->irq_auto_1, adc);
+	iio_device_free(iodev);
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
+{
+	int adc_period, conv;
+	int i;
+	int ch0 = 0, ch1 = 0;
+	int thres;
+	int ret;
+
+	adc_period = adc->auto_conversion_period;
+	for (i = 0; i < 16; ++i) {
+		if (((1000 * (1 << i))/32) < adc_period)
+			continue;
+	}
+	if (i > 0)
+		i--;
+	adc_period = i;
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
+			adc_period);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+		return ret;
+	}
+
+	conv = 0;
+	if (adc->wakeup1_enable) {
+		int is_high;
+
+		ch0 = adc->wakeup1_data.adc_channel_number;
+		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
+		if (adc->wakeup1_data.adc_high_threshold > 0) {
+			thres = adc->wakeup1_data.adc_high_threshold;
+			is_high = 0;
+		} else {
+			thres = adc->wakeup1_data.adc_low_threshold;
+			is_high = BIT(7);
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV0_LSB write failed: %d\n", ret);
+			return ret;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV0_MSB,
+				((thres >> 8) & 0xF) | is_high);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV0_MSB write failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (adc->wakeup2_enable) {
+		int is_high;
+
+		ch1 = adc->wakeup2_data.adc_channel_number;
+		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
+		if (adc->wakeup2_data.adc_high_threshold > 0) {
+			thres = adc->wakeup2_data.adc_high_threshold;
+			is_high = 0;
+		} else {
+			thres = adc->wakeup2_data.adc_low_threshold;
+			is_high = BIT(7);
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV1_LSB write failed: %d\n", ret);
+			return ret;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV1_MSB,
+				((thres >> 8) & 0xF) | is_high);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV1_MSB write failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
+			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
+{
+	int ret;
+
+	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_SELECT, 0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_disable_auto_conversion(adc);
+	if (ret < 0) {
+		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
+		return ret;
+	}
+	return 0;
+}
+
+static int palmas_gpadc_suspend(struct device *dev)
+{
+	struct iio_dev *iodev = dev_to_iio_dev(dev);
+	struct palmas_gpadc *adc = iio_priv(iodev);
+	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+	int ret;
+
+	if (!device_may_wakeup(dev) || !wakeup)
+		return 0;
+
+	ret = palmas_adc_wakeup_configure(adc);
+	if (ret < 0)
+		return ret;
+
+	if (adc->wakeup1_enable)
+		enable_irq_wake(adc->irq_auto_0);
+
+	if (adc->wakeup2_enable)
+		enable_irq_wake(adc->irq_auto_1);
+	return 0;
+}
+
+static int palmas_gpadc_resume(struct device *dev)
+{
+	struct iio_dev *iodev = dev_to_iio_dev(dev);
+	struct palmas_gpadc *adc = iio_priv(iodev);
+	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+	int ret;
+
+	if (!device_may_wakeup(dev) || !wakeup)
+		return 0;
+
+	ret = palmas_adc_wakeup_reset(adc);
+	if (ret < 0)
+		return ret;
+
+	if (adc->wakeup1_enable)
+		disable_irq_wake(adc->irq_auto_0);
+
+	if (adc->wakeup2_enable)
+		disable_irq_wake(adc->irq_auto_1);
+
+	return 0;
+};
+#endif
+
+static const struct dev_pm_ops palmas_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
+				palmas_gpadc_resume)
+};
+
+static struct platform_driver palmas_gpadc_driver = {
+	.probe = palmas_gpadc_probe,
+	.remove = palmas_gpadc_remove,
+	.driver = {
+		.name = MOD_NAME,
+		.owner = THIS_MODULE,
+		.pm = &palmas_pm_ops,
+	},
+};
+
+static int __init palmas_gpadc_init(void)
+{
+	return platform_driver_register(&palmas_gpadc_driver);
+}
+module_init(palmas_gpadc_init);
+
+static void __exit palmas_gpadc_exit(void)
+{
+	platform_driver_unregister(&palmas_gpadc_driver);
+}
+module_exit(palmas_gpadc_exit);
+
+MODULE_DESCRIPTION("palmas GPADC driver");
+MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
+MODULE_ALIAS("platform:palmas-gpadc");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
index bb270bd..60acfa2 100644
--- a/include/linux/mfd/palmas.h
+++ b/include/linux/mfd/palmas.h
@@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
			    struct regulator_config config);
};

+struct palmas_adc_wakeup_property {
+	int adc_channel_number;
+	int adc_high_threshold;
+	int adc_low_threshold;
+};
+
struct palmas_gpadc_platform_data {
	/* Channel 3 current source is only enabled during conversion */
	int ch3_current;
-
	/* Channel 0 current source can be used for battery detection.
-	 * If used for battery detection this will cause a permanent current
+	* If used for battery detection this will cause a permanent current
	 * consumption depending on current level set here.
-	 */
+		 */
	int ch0_current;
+	bool extended_delay;

	/* default BAT_REMOVAL_DAT setting on device probe */
	int bat_removal;

	/* Sets the START_POLARITY bit in the RT_CTRL register */
	int start_polarity;
+
+	struct iio_map *iio_maps;
+	int auto_conversion_period_ms;
+	struct palmas_adc_wakeup_property *adc_wakeup1_data;
+	struct palmas_adc_wakeup_property *adc_wakeup2_data;
};

struct palmas_reg_init {
@@ -403,7 +414,7 @@ struct palmas_gpadc_calibration {
	s32 gain_error;
	s32 offset_error;
};
-
+/*
struct palmas_gpadc {
	struct device *dev;
	struct palmas *palmas;
@@ -426,6 +437,9 @@ struct palmas_gpadc {
	int conv1_channel;
	int rt_channel;
};
+*/
+
+#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name

struct palmas_gpadc_result {
	s32 raw_code;
@@ -519,6 +533,42 @@ enum palmas_irqs {
	PALMAS_NUM_IRQ,
};

+/* Palma GPADC Channels */
+enum {
+	PALMAS_ADC_CH_IN0,
+	PALMAS_ADC_CH_IN1,
+	PALMAS_ADC_CH_IN2,
+	PALMAS_ADC_CH_IN3,
+	PALMAS_ADC_CH_IN4,
+	PALMAS_ADC_CH_IN5,
+	PALMAS_ADC_CH_IN6,
+	PALMAS_ADC_CH_IN7,
+	PALMAS_ADC_CH_IN8,
+	PALMAS_ADC_CH_IN9,
+	PALMAS_ADC_CH_IN10,
+	PALMAS_ADC_CH_IN11,
+	PALMAS_ADC_CH_IN12,
+	PALMAS_ADC_CH_IN13,
+	PALMAS_ADC_CH_IN14,
+	PALMAS_ADC_CH_IN15,
+	PALMAS_ADC_CH_MAX,
+};
+
+/* Palma GPADC Channel0 Current Source */
+enum {
+	PALMAS_ADC_CH0_CURRENT_SRC_0,
+	PALMAS_ADC_CH0_CURRENT_SRC_5,
+	PALMAS_ADC_CH0_CURRENT_SRC_15,
+	PALMAS_ADC_CH0_CURRENT_SRC_20,
+};
+/* Palma GPADC Channel3 Current Source */
+enum {
+	PALMAS_ADC_CH3_CURRENT_SRC_0,
+	PALMAS_ADC_CH3_CURRENT_SRC_10,
+	PALMAS_ADC_CH3_CURRENT_SRC_400,
+	PALMAS_ADC_CH3_CURRENT_SRC_800,
+};
+
struct palmas_pmic {
	struct palmas *palmas;
	struct device *dev;
@@ -2999,6 +3049,7 @@ enum usb_irq_events {
#define PALMAS_GPADC_TRIM14					0x0D
#define PALMAS_GPADC_TRIM15					0x0E
#define PALMAS_GPADC_TRIM16					0x0F
+#define PALMAS_GPADC_TRIMINVALID				-1

/* TPS659038 regen2_ctrl offset iss different from palmas */
#define TPS659038_REGEN2_CTRL					0x12
-- 
2.5.1



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

* [PATCH 2/3] iio:adc:palmas: add DT support
       [not found] ` <cover.1443012491.git.hns@goldelico.com>
  2015-09-23 12:48   ` [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
@ 2015-09-23 12:49   ` H. Nikolaus Schaller
  2015-09-27 15:37     ` Jonathan Cameron
  2015-09-23 12:49   ` [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
  2 siblings, 1 reply; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-23 12:49 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, notaz

From: Marek Belisko <marek@goldelico.com>

Code was found at:
https://android.googlesource.com/kernel/tegra/+/a90856a6626d502d42c6e7abccbdf9d730b36270%5E%21/#F1

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
[Fixed minor typos + add channels list to documentation]
Signed-off-by: Marek Belisko <marek@goldelico.com>
---
.../devicetree/bindings/iio/adc/palmas-gpadc.txt   |  67 +++++++++++
drivers/iio/adc/palmas_gpadc.c                     | 130 +++++++++++++++++----
2 files changed, 175 insertions(+), 22 deletions(-)
create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt

diff --git a/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
new file mode 100644
index 0000000..a5a33ba
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
@@ -0,0 +1,67 @@
+* Palmas general purpose ADC IP block devicetree bindings
+
+Channels list:
+	0 battery type
+        1 battery temp NTC
+	2 GP
+	3 temp (with ext. diode)
+	4 GP
+	5 GP
+	6 VBAT_SENSE
+	7 VCC_SENSE
+	8 Backup Battery voltage
+	9 external charger (VCHG)
+	10 VBUS
+	11 DC-DC current probe (how does this work?)
+	12 internal die temp
+	13 internal die temp
+	14 USB ID pin voltage
+	15 test network
+
+Required properties:
+- compatible : Must be "ti,palmas-gpadc".
+
+Optional sub-nodes:
+ti,channel0-current-microamp: Channel 0 current in uA.
+	Valid values 0uA, 5uA, 15uA, 20uA.
+ti,channel3-current-microamp: Channel 3 current in uA.
+	Valid value 0uA, 10uA, 400uA, 800uA.
+ti,enable-channel3-dual-current: Enable dual current on channel 3.
+ti,enable-extended-delay: Enable extended delay.
+
+Optional sub-node:
+The Palmas ADC node has optional subnode to define the iio mapping.
+It is the name with "iio_map". This node has again subnode to define
+the property of the channel. The sub subnode has following properties:
+- ti,adc-channel-number: ADC channel number.
+- ti,adc-consumer-device: Consumer device name.
+- ti,adc-consumer-channel: ADC consumer channel name.
+
+Example:
+
+pmic {
+	compatible = "ti,twl6035-pmic", "ti,palmas-pmic";
+	...
+	gpadc {
+		compatible = "ti,palmas-gpadc";
+		interrupts = <18 0
+			      16 0
+			      17 0>;
+		ti,channel0-current-microamp = <5>;
+		ti,channel3-current-microamp = <10>;
+		iio_map {
+			ch1 {
+				ti,adc-channel-number = <1>;
+				ti,adc-consumer-device = "generic-adc-thermal.0";
+				ti,adc-consumer-channel ="battery-temp-channel";
+			};
+
+			ch6 {
+				ti,adc-channel-number = <6>;
+				ti,adc-consumer-device = "palmas-battery";
+				ti,adc-consumer-channel ="vbat_channel";
+			};
+		};
+	};
+	...
+};
diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
index 17abb28..bc4db43 100644
--- a/drivers/iio/adc/palmas_gpadc.c
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -20,6 +20,8 @@
#include <linux/pm.h>
#include <linux/mfd/palmas.h>
#include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/iio/iio.h>
#include <linux/iio/machine.h>
#include <linux/iio/driver.h>
@@ -434,20 +436,97 @@ static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
};

+static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev,
+	struct palmas_gpadc_platform_data **gpadc_pdata)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct palmas_gpadc_platform_data *gp_data;
+	struct device_node *map_node;
+	struct device_node *child;
+	struct iio_map *palmas_iio_map;
+	int ret;
+	u32 pval;
+	int nmap, nvalid_map;
+
+	gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL);
+	if (!gp_data)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval);
+	if (!ret)
+		gp_data->ch0_current = pval;
+
+	ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval);
+	if (!ret)
+		gp_data->ch3_current = pval;
+
+	gp_data->extended_delay = of_property_read_bool(np,
+					"ti,enable-extended-delay");
+
+	map_node = of_get_child_by_name(np, "iio_map");
+	if (!map_node) {
+		dev_warn(&pdev->dev, "IIO map table not found\n");
+		goto done;
+	}
+
+	nmap = of_get_child_count(map_node);
+	if (!nmap)
+		goto done;
+
+	nmap++;
+	palmas_iio_map = devm_kzalloc(&pdev->dev,
+				sizeof(*palmas_iio_map) * nmap, GFP_KERNEL);
+	if (!palmas_iio_map)
+		goto done;
+
+	nvalid_map = 0;
+	for_each_child_of_node(map_node, child) {
+		ret = of_property_read_u32(child, "ti,adc-channel-number",
+					&pval);
+		if (!ret && pval < ARRAY_SIZE(palmas_gpadc_iio_channel))
+			palmas_iio_map[nvalid_map].adc_channel_label =
+				palmas_gpadc_iio_channel[pval].datasheet_name;
+		of_property_read_string(child, "ti,adc-consumer-device",
+				&palmas_iio_map[nvalid_map].consumer_dev_name);
+		of_property_read_string(child, "ti,adc-consumer-channel",
+				&palmas_iio_map[nvalid_map].consumer_channel);
+		dev_dbg(&pdev->dev,
+			"Channel %s consumer dev %s and consumer channel %s\n",
+				palmas_iio_map[nvalid_map].adc_channel_label,
+				palmas_iio_map[nvalid_map].consumer_dev_name,
+				palmas_iio_map[nvalid_map].consumer_channel);
+		nvalid_map++;
+	}
+	palmas_iio_map[nvalid_map].adc_channel_label = NULL;
+	palmas_iio_map[nvalid_map].consumer_dev_name = NULL;
+	palmas_iio_map[nvalid_map].consumer_channel = NULL;
+
+	gp_data->iio_maps = palmas_iio_map;
+
+done:
+	*gpadc_pdata = gp_data;
+	return 0;
+}
+
static int palmas_gpadc_probe(struct platform_device *pdev)
{
	struct palmas_gpadc *adc;
	struct palmas_platform_data *pdata;
-	struct palmas_gpadc_platform_data *adc_pdata;
+	struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
	struct iio_dev *iodev;
	int ret, i;

	pdata = dev_get_platdata(pdev->dev.parent);
-	if (!pdata || !pdata->gpadc_pdata) {
-		dev_err(&pdev->dev, "No platform data\n");
-		return -ENODEV;
+	if (pdata && pdata->gpadc_pdata)
+		gpadc_pdata = pdata->gpadc_pdata;
+
+	if (!gpadc_pdata && pdev->dev.of_node) {
+		ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata);
+		if (ret < 0)
+			return ret;
	}
-	adc_pdata = pdata->gpadc_pdata;
+	if (!gpadc_pdata)
+		return -EINVAL;

	iodev = iio_device_alloc(sizeof(*adc));
	if (!iodev) {
@@ -455,8 +534,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
		return -ENOMEM;
	}

-	if (adc_pdata->iio_maps) {
-		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
+	if (gpadc_pdata->iio_maps) {
+		ret = iio_map_array_register(iodev, gpadc_pdata->iio_maps);
		if (ret < 0) {
			dev_err(&pdev->dev, "iio_map_array_register failed\n");
			goto out;
@@ -470,7 +549,7 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
	init_completion(&adc->conv_completion);
	dev_set_drvdata(&pdev->dev, iodev);

-	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
+	adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
	ret = request_threaded_irq(adc->irq, NULL,
		palmas_gpadc_irq,
@@ -482,8 +561,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
		goto out_unregister_map;
	}

-	if (adc_pdata->adc_wakeup1_data) {
-		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
+	if (gpadc_pdata->adc_wakeup1_data) {
+		memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
			sizeof(adc->wakeup1_data));
		adc->wakeup1_enable = true;
		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
@@ -498,8 +577,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
		}
	}

-	if (adc_pdata->adc_wakeup2_data) {
-		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
+	if (gpadc_pdata->adc_wakeup2_data) {
+		memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
				sizeof(adc->wakeup2_data));
		adc->wakeup2_enable = true;
		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
@@ -514,25 +593,25 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
		}
	}

-	if (adc_pdata->ch0_current == 0)
+	if (gpadc_pdata->ch0_current == 0)
		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
-	else if (adc_pdata->ch0_current <= 5)
+	else if (gpadc_pdata->ch0_current <= 5)
		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
-	else if (adc_pdata->ch0_current <= 15)
+	else if (gpadc_pdata->ch0_current <= 15)
		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
	else
		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;

-	if (adc_pdata->ch3_current == 0)
+	if (gpadc_pdata->ch3_current == 0)
		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
-	else if (adc_pdata->ch3_current <= 10)
+	else if (gpadc_pdata->ch3_current <= 10)
		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
-	else if (adc_pdata->ch3_current <= 400)
+	else if (gpadc_pdata->ch3_current <= 400)
		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
	else
		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;

-	adc->extended_delay = adc_pdata->extended_delay;
+	adc->extended_delay = gpadc_pdata->extended_delay;

	iodev->name = MOD_NAME;
	iodev->dev.parent = &pdev->dev;
@@ -559,15 +638,15 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
	return 0;

out_irq_auto1_free:
-	if (adc_pdata->adc_wakeup2_data)
+	if (gpadc_pdata->adc_wakeup2_data)
		free_irq(adc->irq_auto_1, adc);
out_irq_auto0_free:
-	if (adc_pdata->adc_wakeup1_data)
+	if (gpadc_pdata->adc_wakeup1_data)
		free_irq(adc->irq_auto_0, adc);
out_irq_free:
	free_irq(adc->irq, adc);
out_unregister_map:
-	if (adc_pdata->iio_maps)
+	if (gpadc_pdata->iio_maps)
		iio_map_array_unregister(iodev);
out:
	iio_device_free(iodev);
@@ -769,6 +848,12 @@ static const struct dev_pm_ops palmas_pm_ops = {
				palmas_gpadc_resume)
};

+static const struct of_device_id of_palmas_gpadc_match_tbl[] = {
+	{ .compatible = "ti,palmas-gpadc", },
+	{ /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl);
+
static struct platform_driver palmas_gpadc_driver = {
	.probe = palmas_gpadc_probe,
	.remove = palmas_gpadc_remove,
@@ -776,6 +861,7 @@ static struct platform_driver palmas_gpadc_driver = {
		.name = MOD_NAME,
		.owner = THIS_MODULE,
		.pm = &palmas_pm_ops,
+		.of_match_table = of_palmas_gpadc_match_tbl,
	},
};

-- 
2.5.1



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

* [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas
       [not found] ` <cover.1443012491.git.hns@goldelico.com>
  2015-09-23 12:48   ` [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
  2015-09-23 12:49   ` [PATCH 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
@ 2015-09-23 12:49   ` H. Nikolaus Schaller
  2015-09-23 16:56     ` Tony Lindgren
  2 siblings, 1 reply; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-23 12:49 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, notaz

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
arch/arm/boot/dts/omap5-uevm.dts | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)

diff --git a/arch/arm/boot/dts/omap5-uevm.dts b/arch/arm/boot/dts/omap5-uevm.dts
index 3b16e8f..0d4c8ff 100644
--- a/arch/arm/boot/dts/omap5-uevm.dts
+++ b/arch/arm/boot/dts/omap5-uevm.dts
@@ -342,6 +342,28 @@

			ti,ldo6-vibrator;

+			gpadc {
+				compatible = "ti,palmas-gpadc";
+				interrupts = <18 0
+					      16 0
+					      17 0>;
+				ti,channel0-current-microamp = <5>;
+				ti,channel3-current-microamp = <10>;
+				iio_map {
+					ch1 {
+						ti,adc-channel-number = <1>;
+						ti,adc-consumer-device = "generic-adc-thermal.0";
+						ti,adc-consumer-channel ="battery-temp-channel";
+					};
+
+					ch6 {
+						ti,adc-channel-number = <6>;
+						ti,adc-consumer-device = "palmas-battery";
+						ti,adc-consumer-channel ="vbat_channel";
+					};
+				};
+			};
+
			regulators {
				smps123_reg: smps123 {
					/* VDD_OPP_MPU */
-- 
2.5.1


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

* Re: [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-09-23 12:48   ` [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
@ 2015-09-23 13:26     ` Peter Meerwald
  2015-09-24  8:59       ` H. Nikolaus Schaller
  2015-09-27 15:21     ` Jonathan Cameron
  1 sibling, 1 reply; 25+ messages in thread
From: Peter Meerwald @ 2015-09-23 13:26 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Marek Belisko, Pradeep Goudagunta, Laxman Dewangan, gg, jic23,
	devicetree, linux-kernel, linux-omap, linux-iio, notaz


> This driver code was found as:
> 
> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
> 
> Fixed various compilation issues and test this driver on omap5 evm.

several minor comments below
probably the mfd part should be split out in a separate patch
 
> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> Signed-off-by: Marek Belisko <marek@goldelico.com>
> ---
> drivers/iio/adc/Kconfig        |   9 +
> drivers/iio/adc/Makefile       |   1 +
> drivers/iio/adc/palmas_gpadc.c | 797 +++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/palmas.h     |  59 ++-
> 4 files changed, 862 insertions(+), 4 deletions(-)
> create mode 100644 drivers/iio/adc/palmas_gpadc.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index eb0cd89..f6df9db 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -242,6 +242,15 @@ config NAU7802
> 	  To compile this driver as a module, choose M here: the
> 	  module will be called nau7802.
> 
> +config PALMAS_GPADC
> +	tristate "TI Palmas General Purpose ADC"
> +	depends on MFD_PALMAS
> +	help
> +	  Palmas series pmic chip by texas Instruments (twl6035/6037)
> +	  is used in smartphones and tablets and supports a 16 channel
> +	  general purpose ADC. Add iio driver to read different channel
> +	  of the GPADCs.

PMIC
_T_exas

drop meaningless "Add iio driver to read different channel of the GPADCs."

> +
> config QCOM_SPMI_IADC
> 	tristate "Qualcomm SPMI PMIC current ADC"
> 	depends on SPMI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index a096210..716f112 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
> obj-$(CONFIG_MCP3422) += mcp3422.o
> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
> obj-$(CONFIG_NAU7802) += nau7802.o
> +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
> new file mode 100644
> index 0000000..17abb28
> --- /dev/null
> +++ b/drivers/iio/adc/palmas_gpadc.c
> @@ -0,0 +1,797 @@
> +/*
> + * palmas-adc.c -- TI PALMAS GPADC.
> + *
> + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
> + *
> + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + */

newline

> +#include <linux/module.h>
> +#include <linux/err.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/pm.h>
> +#include <linux/mfd/palmas.h>
> +#include <linux/completion.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +#define MOD_NAME "palmas-gpadc"
> +#define ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
> +#define TO_BE_CALCULATED 0

add prefix PALMAS_

> +
> +struct palmas_gpadc_info {
> +/* calibration codes and regs */
> +	int x1;
> +	int x2;
> +	int v1;
> +	int v2;
> +	u8 trim1_reg;
> +	u8 trim2_reg;
> +	int gain;
> +	int offset;
> +	int gain_error;
> +	bool is_correct_code;
> +};
> +
> +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_correct_code)\

whitespace and indentation

> +[PALMAS_ADC_CH_##_chan] = {						\
> +		.x1 = _x1,						\
> +		.x2 = _x2,						\
> +		.v1 = _v1,						\
> +		.v2 = _v2,						\
> +		.gain = TO_BE_CALCULATED,				\
> +		.offset = TO_BE_CALCULATED,				\
> +		.gain_error = TO_BE_CALCULATED,				\
> +		.trim1_reg = PALMAS_GPADC_TRIM##_t1,			\
> +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,			\
> +		.is_correct_code = _is_correct_code			\
> +	}
> +
> +static struct palmas_gpadc_info palmas_gpadc_info[] = {
> +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
> +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
> +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
> +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
> +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
> +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
> +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
> +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
> +};
> +
> +struct palmas_gpadc {
> +	struct device			*dev;
> +	struct palmas			*palmas;
> +	u8				ch0_current;
> +	u8				ch3_current;
> +	bool				extended_delay;
> +	int				irq;
> +	int				irq_auto_0;
> +	int				irq_auto_1;
> +	struct palmas_gpadc_info	*adc_info;
> +	struct completion		conv_completion;
> +	struct palmas_adc_wakeup_property wakeup1_data;
> +	struct palmas_adc_wakeup_property wakeup2_data;
> +	bool				wakeup1_enable;
> +	bool				wakeup2_enable;
> +	int				auto_conversion_period;
> +};
> +
> +/*
> + * GPADC lock issue in AUTO mode.
> + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
> + *	   mode feature.
> + * Details:
> + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
> + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
> + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
> + *	conversion mechanism can be seen as locked meaning that all following
> + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
> + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
> + *	the GPADC.
> + *
> + * Workaround(s):
> + *	To avoid the lock mechanism, the workaround to follow before any stop
> + *	conversion request is:
> + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
> + *		GPADC_FORCE bit = 1
> + *	Shutdown the GPADC AUTO conversion using
> + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
> + *	After 100us, force the GPADC state machine to be OFF by using the
> + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
> + */
> +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
> +{
> +	int ret;
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_CTRL1,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
> +			0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	udelay(100);
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_CTRL1,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
> +{
> +	struct palmas_gpadc *adc = data;
> +
> +	complete(&adc->conv_completion);
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
> +{
> +	struct palmas_gpadc *adc = data;
> +
> +	dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);

is the message needed except for debugging?

> +	palmas_disable_auto_conversion(adc);
> +	return IRQ_HANDLED;
> +}
> +
> +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)
> +{
> +	int ret;
> +
> +	if (!mask)
> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> +					PALMAS_INT3_MASK,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
> +	else
> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> +					PALMAS_INT3_MASK,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW);
> +	if (ret < 0)
> +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
> +			       int enable)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	if (enable) {
> +		val = (adc->extended_delay
> +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +					PALMAS_GPADC_RT_CTRL,
> +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
> +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> +		val = (adc->ch0_current
> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
> +		val |= (adc->ch3_current
> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
> +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_CTRL1, mask, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"Failed to update current setting: %d\n", ret);
> +			return ret;
> +		}
> +
> +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
> +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT, mask, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
> +			return ret;
> +		}
> +	} else {
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT, 0);
> +		if (ret < 0)
> +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
> +
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_CTRL1,
> +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	int ret;
> +
> +	ret = palmas_gpadc_enable(adc, adc_chan, true);
> +	if (ret < 0)
> +		return ret;
> +
> +	return palmas_gpadc_start_mask_interrupt(adc, 0);
> +}
> +
> +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	palmas_gpadc_start_mask_interrupt(adc, 1);
> +	palmas_gpadc_enable(adc, adc_chan, false);
> +}
> +
> +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	int k;
> +	int d1;
> +	int d2;
> +	int ret;
> +	int gain;
> +	int x1 =  adc->adc_info[adc_chan].x1;
> +	int x2 =  adc->adc_info[adc_chan].x2;
> +	int v1 = adc->adc_info[adc_chan].v1;
> +	int v2 = adc->adc_info[adc_chan].v2;
> +
> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> +				adc->adc_info[adc_chan].trim1_reg, &d1);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> +				adc->adc_info[adc_chan].trim2_reg, &d2);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	/*Gain error Calculation*/

ugly, add whitespace /* bla */

> +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
> +
> +	/*Gain Calculation*/
> +	gain = ((v2 - v1) * 1000) / (x2 - x1);
> +
> +	adc->adc_info[adc_chan].gain_error = k;
> +	adc->adc_info[adc_chan].gain = gain;
> +	/*offset Calculation*/
> +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
> +
> +scrub:
> +	return ret;
> +}
> +
> +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	init_completion(&adc->conv_completion);
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT,
> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "ADC_SW_START write failed: %d\n", ret);

generally, the ADC_ prefix is not logged

> +		return ret;
> +	}
> +
> +	ret = wait_for_completion_timeout(&adc->conv_completion,
> +				ADC_CONVERSION_TIMEOUT);
> +	if (ret == 0) {
> +		dev_err(adc->dev, "ADC conversion not completed\n");
> +		ret = -ETIMEDOUT;
> +		return ret;
> +	}
> +
> +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "ADCDATA read failed: %d\n", ret);

SW_CONV0_LSB read failed

> +		return ret;
> +	}
> +
> +	ret = (val & 0xFFF);

parenthesis not needed, there are plenty of redundant parenthesis below...

> +	return ret;
> +}
> +
> +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
> +						int adc_chan, int val)
> +{
> +	if (((val*1000) - adc->adc_info[adc_chan].offset) < 0) {
> +		dev_err(adc->dev, "No Input Connected\n");
> +		return 0;
> +	}
> +
> +	if (!(adc->adc_info[adc_chan].is_correct_code))
> +		val  = ((val*1000) - adc->adc_info[adc_chan].offset) /
> +					adc->adc_info[adc_chan].gain_error;
> +
> +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
> +	return val;
> +}
> +
> +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> +{
> +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
> +	int adc_chan = chan->channel;
> +	int ret = 0;
> +
> +	if (adc_chan > PALMAS_ADC_CH_MAX)
> +		return -EINVAL;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
> +		if (ret < 0)
> +			goto out;
> +
> +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +			"ADC start coversion failed\n");

typo: conversion

> +			goto out;
> +		}
> +
> +		if (mask == IIO_CHAN_INFO_PROCESSED)
> +			ret = palmas_gpadc_get_calibrated_code(
> +							adc, adc_chan, ret);
> +
> +		*val = ret;
> +
> +		ret = IIO_VAL_INT;
> +		goto out;
> +	}
> +
> +	mutex_unlock(&indio_dev->mlock);
> +	return ret;
> +
> +out:
> +	palmas_gpadc_read_done(adc, adc_chan);
> +	mutex_unlock(&indio_dev->mlock);
> +	return ret;
> +}
> +
> +static const struct iio_info palmas_gpadc_iio_info = {
> +	.read_raw = palmas_gpadc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define PALMAS_ADC_CHAN_IIO(chan, _type)			\
> +{									\
> +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),			\
> +	.type = _type,							\

_type is always IIO_VOLTAGE, no need for parameter

> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
> +			BIT(IIO_CHAN_INFO_PROCESSED),			\
> +	.indexed = 1,							\
> +	.channel = PALMAS_ADC_CH_##chan,				\
> +}
> +
> +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
> +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN3, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN12, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN13, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
> +};
> +
> +static int palmas_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct palmas_gpadc *adc;
> +	struct palmas_platform_data *pdata;
> +	struct palmas_gpadc_platform_data *adc_pdata;
> +	struct iio_dev *iodev;
> +	int ret, i;
> +
> +	pdata = dev_get_platdata(pdev->dev.parent);
> +	if (!pdata || !pdata->gpadc_pdata) {
> +		dev_err(&pdev->dev, "No platform data\n");
> +		return -ENODEV;
> +	}
> +	adc_pdata = pdata->gpadc_pdata;
> +
> +	iodev = iio_device_alloc(sizeof(*adc));

use devm_iio_device_alloc()
indio_dev is the preferred name instead of iodev

> +	if (!iodev) {
> +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	if (adc_pdata->iio_maps) {
> +		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
> +		if (ret < 0) {
> +			dev_err(&pdev->dev, "iio_map_array_register failed\n");
> +			goto out;
> +		}
> +	}
> +
> +	adc = iio_priv(iodev);
> +	adc->dev = &pdev->dev;
> +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
> +	adc->adc_info = palmas_gpadc_info;
> +	init_completion(&adc->conv_completion);
> +	dev_set_drvdata(&pdev->dev, iodev);
> +
> +	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
> +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);

palmas_irq_get_virq() can fail, check needed

> +	ret = request_threaded_irq(adc->irq, NULL,
> +		palmas_gpadc_irq,
> +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
> +		adc);
> +	if (ret < 0) {
> +		dev_err(adc->dev,
> +			"request irq %d failed: %dn", adc->irq, ret);

format string shoule end in %d\n I guess

> +		goto out_unregister_map;
> +	}
> +
> +	if (adc_pdata->adc_wakeup1_data) {
> +		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
> +			sizeof(adc->wakeup1_data));
> +		adc->wakeup1_enable = true;
> +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
> +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
> +				palmas_gpadc_irq_auto,
> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> +				"palmas-adc-auto-0", adc);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "request auto0 irq %d failed: %dn",

\n

> +				adc->irq_auto_0, ret);
> +			goto out_irq_free;
> +		}
> +	}
> +
> +	if (adc_pdata->adc_wakeup2_data) {
> +		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
> +				sizeof(adc->wakeup2_data));
> +		adc->wakeup2_enable = true;
> +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
> +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
> +				palmas_gpadc_irq_auto,
> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> +				"palmas-adc-auto-1", adc);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "request auto1 irq %d failed: %dn",

\n

> +				adc->irq_auto_1, ret);
> +			goto out_irq_auto0_free;
> +		}
> +	}
> +
> +	if (adc_pdata->ch0_current == 0)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
> +	else if (adc_pdata->ch0_current <= 5)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
> +	else if (adc_pdata->ch0_current <= 15)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
> +	else
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
> +
> +	if (adc_pdata->ch3_current == 0)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
> +	else if (adc_pdata->ch3_current <= 10)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
> +	else if (adc_pdata->ch3_current <= 400)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
> +	else
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
> +
> +	adc->extended_delay = adc_pdata->extended_delay;
> +
> +	iodev->name = MOD_NAME;
> +	iodev->dev.parent = &pdev->dev;
> +	iodev->info = &palmas_gpadc_iio_info;
> +	iodev->modes = INDIO_DIRECT_MODE;
> +	iodev->channels = palmas_gpadc_iio_channel;
> +	iodev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
> +
> +	ret = iio_device_register(iodev);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
> +		goto out_irq_auto1_free;
> +	}
> +
> +	device_set_wakeup_capable(&pdev->dev, 1);
> +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
> +		if (!(adc->adc_info[i].is_correct_code))
> +			palmas_gpadc_calibrate(adc, i);
> +	}
> +
> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
> +		device_wakeup_enable(&pdev->dev);
> +
> +	return 0;
> +
> +out_irq_auto1_free:
> +	if (adc_pdata->adc_wakeup2_data)
> +		free_irq(adc->irq_auto_1, adc);
> +out_irq_auto0_free:
> +	if (adc_pdata->adc_wakeup1_data)
> +		free_irq(adc->irq_auto_0, adc);
> +out_irq_free:
> +	free_irq(adc->irq, adc);
> +out_unregister_map:
> +	if (adc_pdata->iio_maps)
> +		iio_map_array_unregister(iodev);
> +out:
> +	iio_device_free(iodev);
> +	return ret;
> +}
> +
> +static int palmas_gpadc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *iodev = dev_to_iio_dev(&pdev->dev);
> +	struct palmas_gpadc *adc = iio_priv(iodev);
> +	struct palmas_platform_data *pdata = dev_get_platdata(pdev->dev.parent);
> +
> +	if (pdata->gpadc_pdata->iio_maps)
> +		iio_map_array_unregister(iodev);
> +	iio_device_unregister(iodev);
> +	free_irq(adc->irq, adc);
> +	if (adc->wakeup1_enable)
> +		free_irq(adc->irq_auto_0, adc);
> +	if (adc->wakeup2_enable)
> +		free_irq(adc->irq_auto_1, adc);
> +	iio_device_free(iodev);
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
> +{
> +	int adc_period, conv;
> +	int i;
> +	int ch0 = 0, ch1 = 0;
> +	int thres;
> +	int ret;
> +
> +	adc_period = adc->auto_conversion_period;
> +	for (i = 0; i < 16; ++i) {
> +		if (((1000 * (1 << i))/32) < adc_period)
> +			continue;
> +	}
> +	if (i > 0)
> +		i--;
> +	adc_period = i;
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
> +			adc_period);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	conv = 0;
> +	if (adc->wakeup1_enable) {
> +		int is_high;
> +
> +		ch0 = adc->wakeup1_data.adc_channel_number;
> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
> +		if (adc->wakeup1_data.adc_high_threshold > 0) {
> +			thres = adc->wakeup1_data.adc_high_threshold;
> +			is_high = 0;
> +		} else {
> +			thres = adc->wakeup1_data.adc_low_threshold;
> +			is_high = BIT(7);
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV0_LSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV0_MSB,
> +				((thres >> 8) & 0xF) | is_high);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV0_MSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	if (adc->wakeup2_enable) {
> +		int is_high;
> +
> +		ch1 = adc->wakeup2_data.adc_channel_number;
> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
> +		if (adc->wakeup2_data.adc_high_threshold > 0) {
> +			thres = adc->wakeup2_data.adc_high_threshold;
> +			is_high = 0;
> +		} else {
> +			thres = adc->wakeup2_data.adc_low_threshold;
> +			is_high = BIT(7);
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV1_LSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV1_MSB,
> +				((thres >> 8) & 0xF) | is_high);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV1_MSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
> +{
> +	int ret;
> +
> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_SELECT, 0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_disable_auto_conversion(adc);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static int palmas_gpadc_suspend(struct device *dev)
> +{
> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
> +	struct palmas_gpadc *adc = iio_priv(iodev);
> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> +	int ret;
> +
> +	if (!device_may_wakeup(dev) || !wakeup)
> +		return 0;
> +
> +	ret = palmas_adc_wakeup_configure(adc);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (adc->wakeup1_enable)
> +		enable_irq_wake(adc->irq_auto_0);
> +
> +	if (adc->wakeup2_enable)
> +		enable_irq_wake(adc->irq_auto_1);
> +	return 0;
> +}
> +
> +static int palmas_gpadc_resume(struct device *dev)
> +{
> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
> +	struct palmas_gpadc *adc = iio_priv(iodev);
> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> +	int ret;
> +
> +	if (!device_may_wakeup(dev) || !wakeup)
> +		return 0;
> +
> +	ret = palmas_adc_wakeup_reset(adc);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (adc->wakeup1_enable)
> +		disable_irq_wake(adc->irq_auto_0);
> +
> +	if (adc->wakeup2_enable)
> +		disable_irq_wake(adc->irq_auto_1);
> +
> +	return 0;
> +};
> +#endif
> +
> +static const struct dev_pm_ops palmas_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
> +				palmas_gpadc_resume)
> +};
> +
> +static struct platform_driver palmas_gpadc_driver = {
> +	.probe = palmas_gpadc_probe,
> +	.remove = palmas_gpadc_remove,
> +	.driver = {
> +		.name = MOD_NAME,
> +		.owner = THIS_MODULE,
> +		.pm = &palmas_pm_ops,
> +	},
> +};
> +
> +static int __init palmas_gpadc_init(void)
> +{
> +	return platform_driver_register(&palmas_gpadc_driver);
> +}
> +module_init(palmas_gpadc_init);
> +
> +static void __exit palmas_gpadc_exit(void)
> +{
> +	platform_driver_unregister(&palmas_gpadc_driver);
> +}
> +module_exit(palmas_gpadc_exit);
> +
> +MODULE_DESCRIPTION("palmas GPADC driver");
> +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
> +MODULE_ALIAS("platform:palmas-gpadc");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
> index bb270bd..60acfa2 100644
> --- a/include/linux/mfd/palmas.h
> +++ b/include/linux/mfd/palmas.h
> @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
> 			    struct regulator_config config);
> };
> 
> +struct palmas_adc_wakeup_property {
> +	int adc_channel_number;
> +	int adc_high_threshold;
> +	int adc_low_threshold;
> +};
> +
> struct palmas_gpadc_platform_data {
> 	/* Channel 3 current source is only enabled during conversion */
> 	int ch3_current;
> -
> 	/* Channel 0 current source can be used for battery detection.
> -	 * If used for battery detection this will cause a permanent current
> +	* If used for battery detection this will cause a permanent current
> 	 * consumption depending on current level set here.
> -	 */
> +		 */
> 	int ch0_current;
> +	bool extended_delay;
> 
> 	/* default BAT_REMOVAL_DAT setting on device probe */
> 	int bat_removal;
> 
> 	/* Sets the START_POLARITY bit in the RT_CTRL register */
> 	int start_polarity;
> +
> +	struct iio_map *iio_maps;
> +	int auto_conversion_period_ms;
> +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
> +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
> };
> 
> struct palmas_reg_init {
> @@ -403,7 +414,7 @@ struct palmas_gpadc_calibration {
> 	s32 gain_error;
> 	s32 offset_error;
> };
> -
> +/*
> struct palmas_gpadc {
> 	struct device *dev;
> 	struct palmas *palmas;
> @@ -426,6 +437,9 @@ struct palmas_gpadc {
> 	int conv1_channel;
> 	int rt_channel;
> };
> +*/
> +
> +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
> 
> struct palmas_gpadc_result {
> 	s32 raw_code;
> @@ -519,6 +533,42 @@ enum palmas_irqs {
> 	PALMAS_NUM_IRQ,
> };
> 
> +/* Palma GPADC Channels */

Palmas, here and below

> +enum {
> +	PALMAS_ADC_CH_IN0,
> +	PALMAS_ADC_CH_IN1,
> +	PALMAS_ADC_CH_IN2,
> +	PALMAS_ADC_CH_IN3,
> +	PALMAS_ADC_CH_IN4,
> +	PALMAS_ADC_CH_IN5,
> +	PALMAS_ADC_CH_IN6,
> +	PALMAS_ADC_CH_IN7,
> +	PALMAS_ADC_CH_IN8,
> +	PALMAS_ADC_CH_IN9,
> +	PALMAS_ADC_CH_IN10,
> +	PALMAS_ADC_CH_IN11,
> +	PALMAS_ADC_CH_IN12,
> +	PALMAS_ADC_CH_IN13,
> +	PALMAS_ADC_CH_IN14,
> +	PALMAS_ADC_CH_IN15,
> +	PALMAS_ADC_CH_MAX,
> +};
> +
> +/* Palma GPADC Channel0 Current Source */
> +enum {
> +	PALMAS_ADC_CH0_CURRENT_SRC_0,
> +	PALMAS_ADC_CH0_CURRENT_SRC_5,
> +	PALMAS_ADC_CH0_CURRENT_SRC_15,
> +	PALMAS_ADC_CH0_CURRENT_SRC_20,
> +};
> +/* Palma GPADC Channel3 Current Source */
> +enum {
> +	PALMAS_ADC_CH3_CURRENT_SRC_0,
> +	PALMAS_ADC_CH3_CURRENT_SRC_10,
> +	PALMAS_ADC_CH3_CURRENT_SRC_400,
> +	PALMAS_ADC_CH3_CURRENT_SRC_800,
> +};
> +
> struct palmas_pmic {
> 	struct palmas *palmas;
> 	struct device *dev;
> @@ -2999,6 +3049,7 @@ enum usb_irq_events {
> #define PALMAS_GPADC_TRIM14					0x0D
> #define PALMAS_GPADC_TRIM15					0x0E
> #define PALMAS_GPADC_TRIM16					0x0F
> +#define PALMAS_GPADC_TRIMINVALID				-1
> 
> /* TPS659038 regen2_ctrl offset iss different from palmas */
> #define TPS659038_REGEN2_CTRL					0x12
> 

-- 

Peter Meerwald
+43-664-2444418 (mobile)

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

* Re: [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas
  2015-09-23 12:49   ` [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
@ 2015-09-23 16:56     ` Tony Lindgren
  2015-09-23 17:03       ` H. Nikolaus Schaller
  0 siblings, 1 reply; 25+ messages in thread
From: Tony Lindgren @ 2015-09-23 16:56 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Russell King, Marek Belisko,
	Pradeep Goudagunta, Laxman Dewangan, gg, jic23, devicetree,
	linux-kernel, linux-omap, linux-iio, notaz

* H. Nikolaus Schaller <hns@goldelico.com> [150923 05:53]:

Missing description? BTW, this I want to queue separately
as I have patches in works to support omap5 variants with
omap5-board-common.dtsi to avoid duplicating things as
we get omap5 better supported for things like regulators.

Regards,

Tony

> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
> arch/arm/boot/dts/omap5-uevm.dts | 22 ++++++++++++++++++++++
> 1 file changed, 22 insertions(+)
> 
> diff --git a/arch/arm/boot/dts/omap5-uevm.dts b/arch/arm/boot/dts/omap5-uevm.dts
> index 3b16e8f..0d4c8ff 100644
> --- a/arch/arm/boot/dts/omap5-uevm.dts
> +++ b/arch/arm/boot/dts/omap5-uevm.dts
> @@ -342,6 +342,28 @@
> 
> 			ti,ldo6-vibrator;
> 
> +			gpadc {
> +				compatible = "ti,palmas-gpadc";
> +				interrupts = <18 0
> +					      16 0
> +					      17 0>;
> +				ti,channel0-current-microamp = <5>;
> +				ti,channel3-current-microamp = <10>;
> +				iio_map {
> +					ch1 {
> +						ti,adc-channel-number = <1>;
> +						ti,adc-consumer-device = "generic-adc-thermal.0";
> +						ti,adc-consumer-channel ="battery-temp-channel";
> +					};
> +
> +					ch6 {
> +						ti,adc-channel-number = <6>;
> +						ti,adc-consumer-device = "palmas-battery";
> +						ti,adc-consumer-channel ="vbat_channel";
> +					};
> +				};
> +			};
> +
> 			regulators {
> 				smps123_reg: smps123 {
> 					/* VDD_OPP_MPU */
> -- 
> 2.5.1
> 

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

* Re: [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas
  2015-09-23 16:56     ` Tony Lindgren
@ 2015-09-23 17:03       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-23 17:03 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Russell King, Marek Belisko,
	Pradeep Goudagunta, Laxman Dewangan, gg, jic23, devicetree,
	linux-kernel, linux-omap, linux-iio, notaz

Hi,

Am 23.09.2015 um 18:56 schrieb Tony Lindgren <tony@atomide.com>:

> * H. Nikolaus Schaller <hns@goldelico.com> [150923 05:53]:
> 
> Missing description?

well, description is “ARM: dts: omap5-uevm: enable iio gpadc for Palmas"

and I didn’t catch that the commit is otherwise empty. Sorry.

> BTW, this I want to queue separately
> as I have patches in works to support omap5 variants with
> omap5-board-common.dtsi to avoid duplicating things as
> we get omap5 better supported for things like regulators.

Yes, it should work for all Palmas based OMAP5 boards and a 
omap5-board-common.dtsi would be the right place (after
it comes to existence).

> 
> Regards,
> 
> Tony
> 
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> ---
>> arch/arm/boot/dts/omap5-uevm.dts | 22 ++++++++++++++++++++++
>> 1 file changed, 22 insertions(+)
>> 
>> diff --git a/arch/arm/boot/dts/omap5-uevm.dts b/arch/arm/boot/dts/omap5-uevm.dts
>> index 3b16e8f..0d4c8ff 100644
>> --- a/arch/arm/boot/dts/omap5-uevm.dts
>> +++ b/arch/arm/boot/dts/omap5-uevm.dts
>> @@ -342,6 +342,28 @@
>> 
>> 			ti,ldo6-vibrator;
>> 
>> +			gpadc {
>> +				compatible = "ti,palmas-gpadc";
>> +				interrupts = <18 0
>> +					      16 0
>> +					      17 0>;
>> +				ti,channel0-current-microamp = <5>;
>> +				ti,channel3-current-microamp = <10>;
>> +				iio_map {
>> +					ch1 {
>> +						ti,adc-channel-number = <1>;
>> +						ti,adc-consumer-device = "generic-adc-thermal.0";
>> +						ti,adc-consumer-channel ="battery-temp-channel";
>> +					};
>> +
>> +					ch6 {
>> +						ti,adc-channel-number = <6>;
>> +						ti,adc-consumer-device = "palmas-battery";
>> +						ti,adc-consumer-channel ="vbat_channel";
>> +					};
>> +				};
>> +			};
>> +
>> 			regulators {
>> 				smps123_reg: smps123 {
>> 					/* VDD_OPP_MPU */
>> -- 
>> 2.5.1
>> 

BR,
Nikolaus


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

* Re: [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-09-23 13:26     ` Peter Meerwald
@ 2015-09-24  8:59       ` H. Nikolaus Schaller
  0 siblings, 0 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-24  8:59 UTC (permalink / raw)
  To: Peter Meerwald
  Cc: Marek Belisko, Pradeep Goudagunta, Laxman Dewangan,
	Graeme Gregory, jic23, devicetree, LKML, linux-omap, linux-iio,
	Grazvydas Ignotas


Am 23.09.2015 um 15:26 schrieb Peter Meerwald <pmeerw@pmeerw.net>:

> 
>> This driver code was found as:
>> 
>> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
>> 
>> Fixed various compilation issues and test this driver on omap5 evm.
> 
> several minor comments below

thanks!
I have worked in for a V2 but will wait a little for further comments until I post it.

> probably the mfd part should be split out in a separate patch

well, I think those changes should stay bundled in a single patch
since they depend on each other.

> 
>> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> Signed-off-by: Marek Belisko <marek@goldelico.com>
>> ---
>> drivers/iio/adc/Kconfig        |   9 +
>> drivers/iio/adc/Makefile       |   1 +
>> drivers/iio/adc/palmas_gpadc.c | 797 +++++++++++++++++++++++++++++++++++++++++
>> include/linux/mfd/palmas.h     |  59 ++-
>> 4 files changed, 862 insertions(+), 4 deletions(-)
>> create mode 100644 drivers/iio/adc/palmas_gpadc.c
>> 
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index eb0cd89..f6df9db 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -242,6 +242,15 @@ config NAU7802
>> 	  To compile this driver as a module, choose M here: the
>> 	  module will be called nau7802.
>> 
>> +config PALMAS_GPADC
>> +	tristate "TI Palmas General Purpose ADC"
>> +	depends on MFD_PALMAS
>> +	help
>> +	  Palmas series pmic chip by texas Instruments (twl6035/6037)
>> +	  is used in smartphones and tablets and supports a 16 channel
>> +	  general purpose ADC. Add iio driver to read different channel
>> +	  of the GPADCs.
> 
> PMIC
> _T_exas
> 
> drop meaningless "Add iio driver to read different channel of the GPADCs."
> 
>> +
>> config QCOM_SPMI_IADC
>> 	tristate "Qualcomm SPMI PMIC current ADC"
>> 	depends on SPMI
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index a096210..716f112 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
>> obj-$(CONFIG_MCP3422) += mcp3422.o
>> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>> obj-$(CONFIG_NAU7802) += nau7802.o
>> +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>> obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
>> new file mode 100644
>> index 0000000..17abb28
>> --- /dev/null
>> +++ b/drivers/iio/adc/palmas_gpadc.c
>> @@ -0,0 +1,797 @@
>> +/*
>> + * palmas-adc.c -- TI PALMAS GPADC.
>> + *
>> + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
>> + *
>> + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation version 2.
>> + */
> 
> newline
> 
>> +#include <linux/module.h>
>> +#include <linux/err.h>
>> +#include <linux/irq.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/delay.h>
>> +#include <linux/i2c.h>
>> +#include <linux/pm.h>
>> +#include <linux/mfd/palmas.h>
>> +#include <linux/completion.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/iio/driver.h>
>> +
>> +#define MOD_NAME "palmas-gpadc"
>> +#define ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
>> +#define TO_BE_CALCULATED 0
> 
> add prefix PALMAS_
> 
>> +
>> +struct palmas_gpadc_info {
>> +/* calibration codes and regs */
>> +	int x1;
>> +	int x2;
>> +	int v1;
>> +	int v2;
>> +	u8 trim1_reg;
>> +	u8 trim2_reg;
>> +	int gain;
>> +	int offset;
>> +	int gain_error;
>> +	bool is_correct_code;
>> +};
>> +
>> +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_correct_code)\
> 
> whitespace and indentation
> 
>> +[PALMAS_ADC_CH_##_chan] = {						\
>> +		.x1 = _x1,						\
>> +		.x2 = _x2,						\
>> +		.v1 = _v1,						\
>> +		.v2 = _v2,						\
>> +		.gain = TO_BE_CALCULATED,				\
>> +		.offset = TO_BE_CALCULATED,				\
>> +		.gain_error = TO_BE_CALCULATED,				\
>> +		.trim1_reg = PALMAS_GPADC_TRIM##_t1,			\
>> +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,			\
>> +		.is_correct_code = _is_correct_code			\
>> +	}
>> +
>> +static struct palmas_gpadc_info palmas_gpadc_info[] = {
>> +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
>> +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
>> +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
>> +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
>> +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
>> +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
>> +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
>> +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
>> +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
>> +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
>> +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
>> +};
>> +
>> +struct palmas_gpadc {
>> +	struct device			*dev;
>> +	struct palmas			*palmas;
>> +	u8				ch0_current;
>> +	u8				ch3_current;
>> +	bool				extended_delay;
>> +	int				irq;
>> +	int				irq_auto_0;
>> +	int				irq_auto_1;
>> +	struct palmas_gpadc_info	*adc_info;
>> +	struct completion		conv_completion;
>> +	struct palmas_adc_wakeup_property wakeup1_data;
>> +	struct palmas_adc_wakeup_property wakeup2_data;
>> +	bool				wakeup1_enable;
>> +	bool				wakeup2_enable;
>> +	int				auto_conversion_period;
>> +};
>> +
>> +/*
>> + * GPADC lock issue in AUTO mode.
>> + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
>> + *	   mode feature.
>> + * Details:
>> + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
>> + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
>> + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
>> + *	conversion mechanism can be seen as locked meaning that all following
>> + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
>> + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
>> + *	the GPADC.
>> + *
>> + * Workaround(s):
>> + *	To avoid the lock mechanism, the workaround to follow before any stop
>> + *	conversion request is:
>> + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
>> + *		GPADC_FORCE bit = 1
>> + *	Shutdown the GPADC AUTO conversion using
>> + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
>> + *	After 100us, force the GPADC state machine to be OFF by using the
>> + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
>> + */
>> +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
>> +{
>> +	int ret;
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_CTRL1,
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_CTRL,
>> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
>> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
>> +			0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	udelay(100);
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_CTRL1,
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +	return 0;
>> +}
>> +
>> +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
>> +{
>> +	struct palmas_gpadc *adc = data;
>> +
>> +	complete(&adc->conv_completion);
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
>> +{
>> +	struct palmas_gpadc *adc = data;
>> +
>> +	dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
> 
> is the message needed except for debugging?
> 
>> +	palmas_disable_auto_conversion(adc);
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)
>> +{
>> +	int ret;
>> +
>> +	if (!mask)
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
>> +					PALMAS_INT3_MASK,
>> +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
>> +	else
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
>> +					PALMAS_INT3_MASK,
>> +					PALMAS_INT3_MASK_GPADC_EOC_SW,
>> +					PALMAS_INT3_MASK_GPADC_EOC_SW);
>> +	if (ret < 0)
>> +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
>> +
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
>> +			       int enable)
>> +{
>> +	unsigned int mask, val;
>> +	int ret;
>> +
>> +	if (enable) {
>> +		val = (adc->extended_delay
>> +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +					PALMAS_GPADC_RT_CTRL,
>> +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
>> +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
>> +		val = (adc->ch0_current
>> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
>> +		val |= (adc->ch3_current
>> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
>> +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_CTRL1, mask, val);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"Failed to update current setting: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
>> +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
>> +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_SELECT, mask, val);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	} else {
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_SELECT, 0);
>> +		if (ret < 0)
>> +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
>> +
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_CTRL1,
>> +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	int ret;
>> +
>> +	ret = palmas_gpadc_enable(adc, adc_chan, true);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return palmas_gpadc_start_mask_interrupt(adc, 0);
>> +}
>> +
>> +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	palmas_gpadc_start_mask_interrupt(adc, 1);
>> +	palmas_gpadc_enable(adc, adc_chan, false);
>> +}
>> +
>> +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	int k;
>> +	int d1;
>> +	int d2;
>> +	int ret;
>> +	int gain;
>> +	int x1 =  adc->adc_info[adc_chan].x1;
>> +	int x2 =  adc->adc_info[adc_chan].x2;
>> +	int v1 = adc->adc_info[adc_chan].v1;
>> +	int v2 = adc->adc_info[adc_chan].v2;
>> +
>> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
>> +				adc->adc_info[adc_chan].trim1_reg, &d1);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
>> +		goto scrub;
>> +	}
>> +
>> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
>> +				adc->adc_info[adc_chan].trim2_reg, &d2);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
>> +		goto scrub;
>> +	}
>> +
>> +	/*Gain error Calculation*/
> 
> ugly, add whitespace /* bla */
> 
>> +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
>> +
>> +	/*Gain Calculation*/
>> +	gain = ((v2 - v1) * 1000) / (x2 - x1);
>> +
>> +	adc->adc_info[adc_chan].gain_error = k;
>> +	adc->adc_info[adc_chan].gain = gain;
>> +	/*offset Calculation*/
>> +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
>> +
>> +scrub:
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	unsigned int val;
>> +	int ret;
>> +
>> +	init_completion(&adc->conv_completion);
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_SELECT,
>> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
>> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "ADC_SW_START write failed: %d\n", ret);
> 
> generally, the ADC_ prefix is not logged
> 
>> +		return ret;
>> +	}
>> +
>> +	ret = wait_for_completion_timeout(&adc->conv_completion,
>> +				ADC_CONVERSION_TIMEOUT);
>> +	if (ret == 0) {
>> +		dev_err(adc->dev, "ADC conversion not completed\n");
>> +		ret = -ETIMEDOUT;
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "ADCDATA read failed: %d\n", ret);
> 
> SW_CONV0_LSB read failed
> 
>> +		return ret;
>> +	}
>> +
>> +	ret = (val & 0xFFF);
> 
> parenthesis not needed, there are plenty of redundant parenthesis below...
> 
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
>> +						int adc_chan, int val)
>> +{
>> +	if (((val*1000) - adc->adc_info[adc_chan].offset) < 0) {
>> +		dev_err(adc->dev, "No Input Connected\n");
>> +		return 0;
>> +	}
>> +
>> +	if (!(adc->adc_info[adc_chan].is_correct_code))
>> +		val  = ((val*1000) - adc->adc_info[adc_chan].offset) /
>> +					adc->adc_info[adc_chan].gain_error;
>> +
>> +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
>> +	return val;
>> +}
>> +
>> +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
>> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
>> +{
>> +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
>> +	int adc_chan = chan->channel;
>> +	int ret = 0;
>> +
>> +	if (adc_chan > PALMAS_ADC_CH_MAX)
>> +		return -EINVAL;
>> +
>> +	mutex_lock(&indio_dev->mlock);
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_RAW:
>> +	case IIO_CHAN_INFO_PROCESSED:
>> +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
>> +		if (ret < 0)
>> +			goto out;
>> +
>> +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +			"ADC start coversion failed\n");
> 
> typo: conversion
> 
>> +			goto out;
>> +		}
>> +
>> +		if (mask == IIO_CHAN_INFO_PROCESSED)
>> +			ret = palmas_gpadc_get_calibrated_code(
>> +							adc, adc_chan, ret);
>> +
>> +		*val = ret;
>> +
>> +		ret = IIO_VAL_INT;
>> +		goto out;
>> +	}
>> +
>> +	mutex_unlock(&indio_dev->mlock);
>> +	return ret;
>> +
>> +out:
>> +	palmas_gpadc_read_done(adc, adc_chan);
>> +	mutex_unlock(&indio_dev->mlock);
>> +	return ret;
>> +}
>> +
>> +static const struct iio_info palmas_gpadc_iio_info = {
>> +	.read_raw = palmas_gpadc_read_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +#define PALMAS_ADC_CHAN_IIO(chan, _type)			\
>> +{									\
>> +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),			\
>> +	.type = _type,							\
> 
> _type is always IIO_VOLTAGE, no need for parameter

here we forgot a minor change to define IIO_TEMP for some
channels.

> 
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
>> +			BIT(IIO_CHAN_INFO_PROCESSED),			\
>> +	.indexed = 1,							\
>> +	.channel = PALMAS_ADC_CH_##chan,				\
>> +}
>> +
>> +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
>> +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN3, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN12, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN13, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
>> +};
>> +
>> +static int palmas_gpadc_probe(struct platform_device *pdev)
>> +{
>> +	struct palmas_gpadc *adc;
>> +	struct palmas_platform_data *pdata;
>> +	struct palmas_gpadc_platform_data *adc_pdata;
>> +	struct iio_dev *iodev;
>> +	int ret, i;
>> +
>> +	pdata = dev_get_platdata(pdev->dev.parent);
>> +	if (!pdata || !pdata->gpadc_pdata) {
>> +		dev_err(&pdev->dev, "No platform data\n");
>> +		return -ENODEV;
>> +	}
>> +	adc_pdata = pdata->gpadc_pdata;
>> +
>> +	iodev = iio_device_alloc(sizeof(*adc));
> 
> use devm_iio_device_alloc()
> indio_dev is the preferred name instead of iodev
> 
>> +	if (!iodev) {
>> +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
>> +		return -ENOMEM;
>> +	}
>> +
>> +	if (adc_pdata->iio_maps) {
>> +		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
>> +		if (ret < 0) {
>> +			dev_err(&pdev->dev, "iio_map_array_register failed\n");
>> +			goto out;
>> +		}
>> +	}
>> +
>> +	adc = iio_priv(iodev);
>> +	adc->dev = &pdev->dev;
>> +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
>> +	adc->adc_info = palmas_gpadc_info;
>> +	init_completion(&adc->conv_completion);
>> +	dev_set_drvdata(&pdev->dev, iodev);
>> +
>> +	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
>> +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
> 
> palmas_irq_get_virq() can fail, check needed
> 
>> +	ret = request_threaded_irq(adc->irq, NULL,
>> +		palmas_gpadc_irq,
>> +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
>> +		adc);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev,
>> +			"request irq %d failed: %dn", adc->irq, ret);
> 
> format string shoule end in %d\n I guess
> 
>> +		goto out_unregister_map;
>> +	}
>> +
>> +	if (adc_pdata->adc_wakeup1_data) {
>> +		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
>> +			sizeof(adc->wakeup1_data));
>> +		adc->wakeup1_enable = true;
>> +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
>> +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
>> +				palmas_gpadc_irq_auto,
>> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
>> +				"palmas-adc-auto-0", adc);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "request auto0 irq %d failed: %dn",
> 
> \n
> 
>> +				adc->irq_auto_0, ret);
>> +			goto out_irq_free;
>> +		}
>> +	}
>> +
>> +	if (adc_pdata->adc_wakeup2_data) {
>> +		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
>> +				sizeof(adc->wakeup2_data));
>> +		adc->wakeup2_enable = true;
>> +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
>> +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
>> +				palmas_gpadc_irq_auto,
>> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
>> +				"palmas-adc-auto-1", adc);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "request auto1 irq %d failed: %dn",
> 
> \n
> 
>> +				adc->irq_auto_1, ret);
>> +			goto out_irq_auto0_free;
>> +		}
>> +	}
>> +
>> +	if (adc_pdata->ch0_current == 0)
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
>> +	else if (adc_pdata->ch0_current <= 5)
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
>> +	else if (adc_pdata->ch0_current <= 15)
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
>> +	else
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
>> +
>> +	if (adc_pdata->ch3_current == 0)
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
>> +	else if (adc_pdata->ch3_current <= 10)
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
>> +	else if (adc_pdata->ch3_current <= 400)
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
>> +	else
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
>> +
>> +	adc->extended_delay = adc_pdata->extended_delay;
>> +
>> +	iodev->name = MOD_NAME;
>> +	iodev->dev.parent = &pdev->dev;
>> +	iodev->info = &palmas_gpadc_iio_info;
>> +	iodev->modes = INDIO_DIRECT_MODE;
>> +	iodev->channels = palmas_gpadc_iio_channel;
>> +	iodev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
>> +
>> +	ret = iio_device_register(iodev);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
>> +		goto out_irq_auto1_free;
>> +	}
>> +
>> +	device_set_wakeup_capable(&pdev->dev, 1);
>> +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
>> +		if (!(adc->adc_info[i].is_correct_code))
>> +			palmas_gpadc_calibrate(adc, i);
>> +	}
>> +
>> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
>> +		device_wakeup_enable(&pdev->dev);
>> +
>> +	return 0;
>> +
>> +out_irq_auto1_free:
>> +	if (adc_pdata->adc_wakeup2_data)
>> +		free_irq(adc->irq_auto_1, adc);
>> +out_irq_auto0_free:
>> +	if (adc_pdata->adc_wakeup1_data)
>> +		free_irq(adc->irq_auto_0, adc);
>> +out_irq_free:
>> +	free_irq(adc->irq, adc);
>> +out_unregister_map:
>> +	if (adc_pdata->iio_maps)
>> +		iio_map_array_unregister(iodev);
>> +out:
>> +	iio_device_free(iodev);
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_remove(struct platform_device *pdev)
>> +{
>> +	struct iio_dev *iodev = dev_to_iio_dev(&pdev->dev);
>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>> +	struct palmas_platform_data *pdata = dev_get_platdata(pdev->dev.parent);
>> +
>> +	if (pdata->gpadc_pdata->iio_maps)
>> +		iio_map_array_unregister(iodev);
>> +	iio_device_unregister(iodev);
>> +	free_irq(adc->irq, adc);
>> +	if (adc->wakeup1_enable)
>> +		free_irq(adc->irq_auto_0, adc);
>> +	if (adc->wakeup2_enable)
>> +		free_irq(adc->irq_auto_1, adc);
>> +	iio_device_free(iodev);
>> +	return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
>> +{
>> +	int adc_period, conv;
>> +	int i;
>> +	int ch0 = 0, ch1 = 0;
>> +	int thres;
>> +	int ret;
>> +
>> +	adc_period = adc->auto_conversion_period;
>> +	for (i = 0; i < 16; ++i) {
>> +		if (((1000 * (1 << i))/32) < adc_period)
>> +			continue;
>> +	}
>> +	if (i > 0)
>> +		i--;
>> +	adc_period = i;
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_CTRL,
>> +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
>> +			adc_period);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	conv = 0;
>> +	if (adc->wakeup1_enable) {
>> +		int is_high;
>> +
>> +		ch0 = adc->wakeup1_data.adc_channel_number;
>> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
>> +		if (adc->wakeup1_data.adc_high_threshold > 0) {
>> +			thres = adc->wakeup1_data.adc_high_threshold;
>> +			is_high = 0;
>> +		} else {
>> +			thres = adc->wakeup1_data.adc_low_threshold;
>> +			is_high = BIT(7);
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV0_LSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV0_MSB,
>> +				((thres >> 8) & 0xF) | is_high);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV0_MSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	if (adc->wakeup2_enable) {
>> +		int is_high;
>> +
>> +		ch1 = adc->wakeup2_data.adc_channel_number;
>> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
>> +		if (adc->wakeup2_data.adc_high_threshold > 0) {
>> +			thres = adc->wakeup2_data.adc_high_threshold;
>> +			is_high = 0;
>> +		} else {
>> +			thres = adc->wakeup2_data.adc_low_threshold;
>> +			is_high = BIT(7);
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV1_LSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV1_MSB,
>> +				((thres >> 8) & 0xF) | is_high);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV1_MSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_CTRL,
>> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
>> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +	return 0;
>> +}
>> +
>> +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
>> +{
>> +	int ret;
>> +
>> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_SELECT, 0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_disable_auto_conversion(adc);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +	return 0;
>> +}
>> +
>> +static int palmas_gpadc_suspend(struct device *dev)
>> +{
>> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
>> +	int ret;
>> +
>> +	if (!device_may_wakeup(dev) || !wakeup)
>> +		return 0;
>> +
>> +	ret = palmas_adc_wakeup_configure(adc);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	if (adc->wakeup1_enable)
>> +		enable_irq_wake(adc->irq_auto_0);
>> +
>> +	if (adc->wakeup2_enable)
>> +		enable_irq_wake(adc->irq_auto_1);
>> +	return 0;
>> +}
>> +
>> +static int palmas_gpadc_resume(struct device *dev)
>> +{
>> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
>> +	int ret;
>> +
>> +	if (!device_may_wakeup(dev) || !wakeup)
>> +		return 0;
>> +
>> +	ret = palmas_adc_wakeup_reset(adc);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	if (adc->wakeup1_enable)
>> +		disable_irq_wake(adc->irq_auto_0);
>> +
>> +	if (adc->wakeup2_enable)
>> +		disable_irq_wake(adc->irq_auto_1);
>> +
>> +	return 0;
>> +};
>> +#endif
>> +
>> +static const struct dev_pm_ops palmas_pm_ops = {
>> +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
>> +				palmas_gpadc_resume)
>> +};
>> +
>> +static struct platform_driver palmas_gpadc_driver = {
>> +	.probe = palmas_gpadc_probe,
>> +	.remove = palmas_gpadc_remove,
>> +	.driver = {
>> +		.name = MOD_NAME,
>> +		.owner = THIS_MODULE,
>> +		.pm = &palmas_pm_ops,
>> +	},
>> +};
>> +
>> +static int __init palmas_gpadc_init(void)
>> +{
>> +	return platform_driver_register(&palmas_gpadc_driver);
>> +}
>> +module_init(palmas_gpadc_init);
>> +
>> +static void __exit palmas_gpadc_exit(void)
>> +{
>> +	platform_driver_unregister(&palmas_gpadc_driver);
>> +}
>> +module_exit(palmas_gpadc_exit);
>> +
>> +MODULE_DESCRIPTION("palmas GPADC driver");
>> +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
>> +MODULE_ALIAS("platform:palmas-gpadc");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
>> index bb270bd..60acfa2 100644
>> --- a/include/linux/mfd/palmas.h
>> +++ b/include/linux/mfd/palmas.h
>> @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
>> 			    struct regulator_config config);
>> };
>> 
>> +struct palmas_adc_wakeup_property {
>> +	int adc_channel_number;
>> +	int adc_high_threshold;
>> +	int adc_low_threshold;
>> +};
>> +
>> struct palmas_gpadc_platform_data {
>> 	/* Channel 3 current source is only enabled during conversion */
>> 	int ch3_current;
>> -
>> 	/* Channel 0 current source can be used for battery detection.
>> -	 * If used for battery detection this will cause a permanent current
>> +	* If used for battery detection this will cause a permanent current
>> 	 * consumption depending on current level set here.
>> -	 */
>> +		 */
>> 	int ch0_current;
>> +	bool extended_delay;
>> 
>> 	/* default BAT_REMOVAL_DAT setting on device probe */
>> 	int bat_removal;
>> 
>> 	/* Sets the START_POLARITY bit in the RT_CTRL register */
>> 	int start_polarity;
>> +
>> +	struct iio_map *iio_maps;
>> +	int auto_conversion_period_ms;
>> +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
>> +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
>> };
>> 
>> struct palmas_reg_init {
>> @@ -403,7 +414,7 @@ struct palmas_gpadc_calibration {
>> 	s32 gain_error;
>> 	s32 offset_error;
>> };
>> -
>> +/*
>> struct palmas_gpadc {
>> 	struct device *dev;
>> 	struct palmas *palmas;
>> @@ -426,6 +437,9 @@ struct palmas_gpadc {
>> 	int conv1_channel;
>> 	int rt_channel;
>> };
>> +*/
>> +
>> +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
>> 
>> struct palmas_gpadc_result {
>> 	s32 raw_code;
>> @@ -519,6 +533,42 @@ enum palmas_irqs {
>> 	PALMAS_NUM_IRQ,
>> };
>> 
>> +/* Palma GPADC Channels */
> 
> Palmas, here and below
> 
>> +enum {
>> +	PALMAS_ADC_CH_IN0,
>> +	PALMAS_ADC_CH_IN1,
>> +	PALMAS_ADC_CH_IN2,
>> +	PALMAS_ADC_CH_IN3,
>> +	PALMAS_ADC_CH_IN4,
>> +	PALMAS_ADC_CH_IN5,
>> +	PALMAS_ADC_CH_IN6,
>> +	PALMAS_ADC_CH_IN7,
>> +	PALMAS_ADC_CH_IN8,
>> +	PALMAS_ADC_CH_IN9,
>> +	PALMAS_ADC_CH_IN10,
>> +	PALMAS_ADC_CH_IN11,
>> +	PALMAS_ADC_CH_IN12,
>> +	PALMAS_ADC_CH_IN13,
>> +	PALMAS_ADC_CH_IN14,
>> +	PALMAS_ADC_CH_IN15,
>> +	PALMAS_ADC_CH_MAX,
>> +};
>> +
>> +/* Palma GPADC Channel0 Current Source */
>> +enum {
>> +	PALMAS_ADC_CH0_CURRENT_SRC_0,
>> +	PALMAS_ADC_CH0_CURRENT_SRC_5,
>> +	PALMAS_ADC_CH0_CURRENT_SRC_15,
>> +	PALMAS_ADC_CH0_CURRENT_SRC_20,
>> +};
>> +/* Palma GPADC Channel3 Current Source */
>> +enum {
>> +	PALMAS_ADC_CH3_CURRENT_SRC_0,
>> +	PALMAS_ADC_CH3_CURRENT_SRC_10,
>> +	PALMAS_ADC_CH3_CURRENT_SRC_400,
>> +	PALMAS_ADC_CH3_CURRENT_SRC_800,
>> +};
>> +
>> struct palmas_pmic {
>> 	struct palmas *palmas;
>> 	struct device *dev;
>> @@ -2999,6 +3049,7 @@ enum usb_irq_events {
>> #define PALMAS_GPADC_TRIM14					0x0D
>> #define PALMAS_GPADC_TRIM15					0x0E
>> #define PALMAS_GPADC_TRIM16					0x0F
>> +#define PALMAS_GPADC_TRIMINVALID				-1
>> 
>> /* TPS659038 regen2_ctrl offset iss different from palmas */
>> #define TPS659038_REGEN2_CTRL					0x12
>> 
> 
> -- 
> 
> Peter Meerwald
> +43-664-2444418 (mobile)

BR and tnx,
Nikolaus Schaller


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

* Re: [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-09-23 12:48   ` [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
  2015-09-23 13:26     ` Peter Meerwald
@ 2015-09-27 15:21     ` Jonathan Cameron
  2015-09-28 20:54       ` H. Nikolaus Schaller
       [not found]       ` <95BEC150-7B77-48BF-B11D-929090A1CC11@goldelico.com>
  1 sibling, 2 replies; 25+ messages in thread
From: Jonathan Cameron @ 2015-09-27 15:21 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Benoît Cousson, Tony Lindgren,
	Russell King, Marek Belisko, Pradeep Goudagunta, Laxman Dewangan,
	gg, jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, notaz

On 23/09/15 13:48, H. Nikolaus Schaller wrote:
> This driver code was found as:
> 
> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
> 
> Fixed various compilation issues and test this driver on omap5 evm.
> 
> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> Signed-off-by: Marek Belisko <marek@goldelico.com>
Various bits inline.

Jonathan
> ---
> drivers/iio/adc/Kconfig        |   9 +
> drivers/iio/adc/Makefile       |   1 +
> drivers/iio/adc/palmas_gpadc.c | 797 +++++++++++++++++++++++++++++++++++++++++
> include/linux/mfd/palmas.h     |  59 ++-
> 4 files changed, 862 insertions(+), 4 deletions(-)
> create mode 100644 drivers/iio/adc/palmas_gpadc.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index eb0cd89..f6df9db 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -242,6 +242,15 @@ config NAU7802
> 	  To compile this driver as a module, choose M here: the
> 	  module will be called nau7802.
> 
> +config PALMAS_GPADC
> +	tristate "TI Palmas General Purpose ADC"
> +	depends on MFD_PALMAS
> +	help
> +	  Palmas series pmic chip by texas Instruments (twl6035/6037)
> +	  is used in smartphones and tablets and supports a 16 channel
> +	  general purpose ADC. Add iio driver to read different channel
> +	  of the GPADCs.
> +
> config QCOM_SPMI_IADC
> 	tristate "Qualcomm SPMI PMIC current ADC"
> 	depends on SPMI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index a096210..716f112 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
> obj-$(CONFIG_MCP3422) += mcp3422.o
> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
> obj-$(CONFIG_NAU7802) += nau7802.o
> +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
> new file mode 100644
> index 0000000..17abb28
> --- /dev/null
> +++ b/drivers/iio/adc/palmas_gpadc.c
> @@ -0,0 +1,797 @@
> +/*
> + * palmas-adc.c -- TI PALMAS GPADC.
> + *
> + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
> + *
> + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + */
> +#include <linux/module.h>
> +#include <linux/err.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/pm.h>
> +#include <linux/mfd/palmas.h>
> +#include <linux/completion.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +#define MOD_NAME "palmas-gpadc"
> +#define ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
> +#define TO_BE_CALCULATED 0
> +
> +struct palmas_gpadc_info {
> +/* calibration codes and regs */
Full docs on this would be appreciated.
> +	int x1;
> +	int x2;
> +	int v1;
> +	int v2;
> +	u8 trim1_reg;
> +	u8 trim2_reg;
> +	int gain;
> +	int offset;
> +	int gain_error;
> +	bool is_correct_code;
> +};
> +
> +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_correct_code)\
> +[PALMAS_ADC_CH_##_chan] = {						\
> +		.x1 = _x1,						\
> +		.x2 = _x2,						\
> +		.v1 = _v1,						\
> +		.v2 = _v2,						\
> +		.gain = TO_BE_CALCULATED,				\
> +		.offset = TO_BE_CALCULATED,				\
> +		.gain_error = TO_BE_CALCULATED,				\
> +		.trim1_reg = PALMAS_GPADC_TRIM##_t1,			\
> +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,			\
> +		.is_correct_code = _is_correct_code			\
> +	}
> +
> +static struct palmas_gpadc_info palmas_gpadc_info[] = {
> +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
> +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
> +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
> +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
> +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
> +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
> +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
> +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
> +};
> +
> +struct palmas_gpadc {
> +	struct device			*dev;
> +	struct palmas			*palmas;
As there are some non obvious elements in here (such as the next two)
kernel-doc for the whole stucture would be good.

> +	u8				ch0_current;
> +	u8				ch3_current;
> +	bool				extended_delay;
> +	int				irq;
> +	int				irq_auto_0;
> +	int				irq_auto_1;
> +	struct palmas_gpadc_info	*adc_info;
> +	struct completion		conv_completion;
> +	struct palmas_adc_wakeup_property wakeup1_data;
> +	struct palmas_adc_wakeup_property wakeup2_data;
> +	bool				wakeup1_enable;
> +	bool				wakeup2_enable;
> +	int				auto_conversion_period;
> +};
> +
> +/*
> + * GPADC lock issue in AUTO mode.
> + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
> + *	   mode feature.
> + * Details:
> + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
> + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
> + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
> + *	conversion mechanism can be seen as locked meaning that all following
> + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
> + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
> + *	the GPADC.
> + *
> + * Workaround(s):
> + *	To avoid the lock mechanism, the workaround to follow before any stop
> + *	conversion request is:
> + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
> + *		GPADC_FORCE bit = 1
> + *	Shutdown the GPADC AUTO conversion using
> + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
> + *	After 100us, force the GPADC state machine to be OFF by using the
> + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
> + */
> +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
> +{
> +	int ret;
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_CTRL1,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
> +			0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	udelay(100);
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_CTRL1,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> +		return ret;
> +	}
return ret and drop the return above.  Coccicheck will moan at you about
this.
> +	return 0;
> +}
> +
> +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
> +{
> +	struct palmas_gpadc *adc = data;
> +
> +	complete(&adc->conv_completion);
blank line.
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
> +{
> +	struct palmas_gpadc *adc = data;
> +
> +	dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
Could indicate this to userspace... other than through the logs.

> +	palmas_disable_auto_conversion(adc);
blank line generally before all function returns..
> +	return IRQ_HANDLED;
> +}
> +
> +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)

mask is boolean.  Make it explicitly so for readability.

> +{
> +	int ret;
> +
> +	if (!mask)
> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> +					PALMAS_INT3_MASK,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
> +	else
> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> +					PALMAS_INT3_MASK,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW);
> +	if (ret < 0)
> +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
> +			       int enable)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	if (enable) {
> +		val = (adc->extended_delay
> +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +					PALMAS_GPADC_RT_CTRL,
> +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
> +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> +		val = (adc->ch0_current
> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
> +		val |= (adc->ch3_current
> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
> +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_CTRL1, mask, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"Failed to update current setting: %d\n", ret);
> +			return ret;
> +		}
> +
> +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
> +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT, mask, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
> +			return ret;
> +		}
> +	} else {
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT, 0);
> +		if (ret < 0)
> +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
> +
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_CTRL1,
> +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	int ret;
> +
> +	ret = palmas_gpadc_enable(adc, adc_chan, true);
> +	if (ret < 0)
> +		return ret;
> +
> +	return palmas_gpadc_start_mask_interrupt(adc, 0);
> +}
> +
> +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	palmas_gpadc_start_mask_interrupt(adc, 1);
> +	palmas_gpadc_enable(adc, adc_chan, false);
> +}
> +
> +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	int k;
> +	int d1;
> +	int d2;
> +	int ret;
> +	int gain;
> +	int x1 =  adc->adc_info[adc_chan].x1;
> +	int x2 =  adc->adc_info[adc_chan].x2;
> +	int v1 = adc->adc_info[adc_chan].v1;
> +	int v2 = adc->adc_info[adc_chan].v2;
> +
> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> +				adc->adc_info[adc_chan].trim1_reg, &d1);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> +				adc->adc_info[adc_chan].trim2_reg, &d2);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	/*Gain error Calculation*/
> +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
> +
> +	/*Gain Calculation*/
> +	gain = ((v2 - v1) * 1000) / (x2 - x1);
> +
> +	adc->adc_info[adc_chan].gain_error = k;
> +	adc->adc_info[adc_chan].gain = gain;
> +	/*offset Calculation*/
> +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
> +
> +scrub:
> +	return ret;
> +}
> +
> +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	init_completion(&adc->conv_completion);
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT,
> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "ADC_SW_START write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wait_for_completion_timeout(&adc->conv_completion,
> +				ADC_CONVERSION_TIMEOUT);
> +	if (ret == 0) {
> +		dev_err(adc->dev, "ADC conversion not completed\n");
> +		ret = -ETIMEDOUT;
> +		return ret;
return -ETIMEDOUT;   Might be worth you setting up to do sparse, smatch and
coccicheck runs on the code as they'll pick up on a lot of issues like this.

> +	}
> +
> +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "ADCDATA read failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = (val & 0xFFF);
nitpick : blank line here.
> +	return ret;
> +}
> +
> +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
> +						int adc_chan, int val)
> +{
> +	if (((val*1000) - adc->adc_info[adc_chan].offset) < 0) {
> +		dev_err(adc->dev, "No Input Connected\n");
> +		return 0;
> +	}
Interesting, but should this not be caught for raw channel reads as well?
> +
Umm. what is is_correct_code? Should be documented somewhere
> +	if (!(adc->adc_info[adc_chan].is_correct_code))
> +		val  = ((val*1000) - adc->adc_info[adc_chan].offset) /
> +					adc->adc_info[adc_chan].gain_error;
> +
> +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
> +	return val;
> +}
> +
> +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> +{
> +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
> +	int adc_chan = chan->channel;
> +	int ret = 0;
> +
> +	if (adc_chan > PALMAS_ADC_CH_MAX)
>= given I think your channels are 0 indexed?
> +		return -EINVAL;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +	case IIO_CHAN_INFO_PROCESSED:
I'd be tempted to split the two code paths here as that will be slightly
easier to read (if longer).

I'm also highly suspicious of any driver that does processed output
and has a return type of IIO_VAL_INT.  Are you processed values in the
units documented in Documentation/ABI/testing/sysfs-bus-iio?
They could be, but I would like a comment saying that.

> +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
> +		if (ret < 0)
> +			goto out;
> +
> +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +			"ADC start coversion failed\n");
> +			goto out;
> +		}
> +
> +		if (mask == IIO_CHAN_INFO_PROCESSED)
> +			ret = palmas_gpadc_get_calibrated_code(
> +							adc, adc_chan, ret);
> +
> +		*val = ret;
> +
> +		ret = IIO_VAL_INT;
> +		goto out;
> +	}
> +
> +	mutex_unlock(&indio_dev->mlock);
> +	return ret;
> +
> +out:
> +	palmas_gpadc_read_done(adc, adc_chan);
> +	mutex_unlock(&indio_dev->mlock);
> +	return ret;
> +}
> +
> +static const struct iio_info palmas_gpadc_iio_info = {
> +	.read_raw = palmas_gpadc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define PALMAS_ADC_CHAN_IIO(chan, _type)			\
> +{									\
> +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),			\
> +	.type = _type,							\
Right now they are all voltage channels, why have that specifiable in this
macro?
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
> +			BIT(IIO_CHAN_INFO_PROCESSED),			\
Hmm. This is very very rarely justified.  Either the driver has
nice linear relationship between the raw value and the processed one, in which
case you should be providing *_RAW, *_OFFSET and *_SCALE and leaving the maths
to userspace (or the in kernel wrappers), or they are non linear in which case
only the processed value is of interest and the raw one not directly so.
(the only exception to this is light sensors where the processed channel
is often a computed channel from several input _raw readings).

> +	.indexed = 1,							\
> +	.channel = PALMAS_ADC_CH_##chan,				\
Given this maps back to the value of chan, just put that in as a number
and loose the enum.  Channel is used in the naming so it doesn't
make sense to obscure that behind an enum anyway.
> +}
> +
> +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
> +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN3, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN12, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN13, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE),
> +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
> +};
> +
> +static int palmas_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct palmas_gpadc *adc;
> +	struct palmas_platform_data *pdata;
> +	struct palmas_gpadc_platform_data *adc_pdata;
> +	struct iio_dev *iodev;
> +	int ret, i;
> +
> +	pdata = dev_get_platdata(pdev->dev.parent);
> +	if (!pdata || !pdata->gpadc_pdata) {
> +		dev_err(&pdev->dev, "No platform data\n");
> +		return -ENODEV;
> +	}
> +	adc_pdata = pdata->gpadc_pdata;
> +
> +	iodev = iio_device_alloc(sizeof(*adc));
> +	if (!iodev) {
> +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	if (adc_pdata->iio_maps) {
> +		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
> +		if (ret < 0) {
> +			dev_err(&pdev->dev, "iio_map_array_register failed\n");
> +			goto out;
> +		}
> +	}
> +
> +	adc = iio_priv(iodev);
> +	adc->dev = &pdev->dev;
> +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
> +	adc->adc_info = palmas_gpadc_info;
> +	init_completion(&adc->conv_completion);
> +	dev_set_drvdata(&pdev->dev, iodev);
> +
> +	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
> +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
> +	ret = request_threaded_irq(adc->irq, NULL,
> +		palmas_gpadc_irq,
> +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
> +		adc);
> +	if (ret < 0) {
> +		dev_err(adc->dev,
> +			"request irq %d failed: %dn", adc->irq, ret);
> +		goto out_unregister_map;
> +	}
> +
> +	if (adc_pdata->adc_wakeup1_data) {
> +		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
> +			sizeof(adc->wakeup1_data));
> +		adc->wakeup1_enable = true;
> +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
> +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
> +				palmas_gpadc_irq_auto,
> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> +				"palmas-adc-auto-0", adc);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "request auto0 irq %d failed: %dn",
> +				adc->irq_auto_0, ret);
> +			goto out_irq_free;
> +		}
> +	}
> +
> +	if (adc_pdata->adc_wakeup2_data) {
> +		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
> +				sizeof(adc->wakeup2_data));
> +		adc->wakeup2_enable = true;
> +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
> +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
> +				palmas_gpadc_irq_auto,
> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> +				"palmas-adc-auto-1", adc);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "request auto1 irq %d failed: %dn",
> +				adc->irq_auto_1, ret);
> +			goto out_irq_auto0_free;
> +		}
> +	}
> +
I've requested kernel-doc above for ch0_current, but if that doesn't
make it clear what matic is going on here then some comments here
would be good.
> +	if (adc_pdata->ch0_current == 0)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
> +	else if (adc_pdata->ch0_current <= 5)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
> +	else if (adc_pdata->ch0_current <= 15)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
> +	else
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
> +
> +	if (adc_pdata->ch3_current == 0)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
> +	else if (adc_pdata->ch3_current <= 10)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
> +	else if (adc_pdata->ch3_current <= 400)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
> +	else
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
> +
> +	adc->extended_delay = adc_pdata->extended_delay;
> +
> +	iodev->name = MOD_NAME;
> +	iodev->dev.parent = &pdev->dev;
> +	iodev->info = &palmas_gpadc_iio_info;
> +	iodev->modes = INDIO_DIRECT_MODE;
> +	iodev->channels = palmas_gpadc_iio_channel;
> +	iodev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
> +
> +	ret = iio_device_register(iodev);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
> +		goto out_irq_auto1_free;
> +	}
> +
> +	device_set_wakeup_capable(&pdev->dev, 1);
> +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
> +		if (!(adc->adc_info[i].is_correct_code))
> +			palmas_gpadc_calibrate(adc, i);
> +	}
> +
> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
> +		device_wakeup_enable(&pdev->dev);
There is no match for this in the remove... Should there be one?
(not an interface I know anything about!)
> +
> +	return 0;
> +
> +out_irq_auto1_free:
> +	if (adc_pdata->adc_wakeup2_data)
> +		free_irq(adc->irq_auto_1, adc);
> +out_irq_auto0_free:
> +	if (adc_pdata->adc_wakeup1_data)
> +		free_irq(adc->irq_auto_0, adc);
> +out_irq_free:
> +	free_irq(adc->irq, adc);
> +out_unregister_map:
> +	if (adc_pdata->iio_maps)
> +		iio_map_array_unregister(iodev);
> +out:
> +	iio_device_free(iodev);
> +	return ret;
> +}
> +
> +static int palmas_gpadc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *iodev = dev_to_iio_dev(&pdev->dev);
> +	struct palmas_gpadc *adc = iio_priv(iodev);
> +	struct palmas_platform_data *pdata = dev_get_platdata(pdev->dev.parent);
> +
> +	if (pdata->gpadc_pdata->iio_maps)
> +		iio_map_array_unregister(iodev);
> +	iio_device_unregister(iodev);
> +	free_irq(adc->irq, adc);
> +	if (adc->wakeup1_enable)
> +		free_irq(adc->irq_auto_0, adc);
> +	if (adc->wakeup2_enable)
> +		free_irq(adc->irq_auto_1, adc);
> +	iio_device_free(iodev);
Could use demv_iio_device_alloc and not need the free here or on error path.
Given location of irq frees you could do devm allocations of them as well
which would cut out a fair bit of code without reordering anything.

nit: blank line here.
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
> +{
> +	int adc_period, conv;
> +	int i;
> +	int ch0 = 0, ch1 = 0;
> +	int thres;
> +	int ret;
> +
> +	adc_period = adc->auto_conversion_period;
> +	for (i = 0; i < 16; ++i) {
spacing around the /
> +		if (((1000 * (1 << i))/32) < adc_period)
> +			continue;
> +	}
> +	if (i > 0)
> +		i--;
> +	adc_period = i;
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
> +			adc_period);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	conv = 0;
> +	if (adc->wakeup1_enable) {
> +		int is_high;
> +
> +		ch0 = adc->wakeup1_data.adc_channel_number;
> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
> +		if (adc->wakeup1_data.adc_high_threshold > 0) {
> +			thres = adc->wakeup1_data.adc_high_threshold;
> +			is_high = 0;
> +		} else {
> +			thres = adc->wakeup1_data.adc_low_threshold;
> +			is_high = BIT(7);
BIT(7) is a bit random, so I'd suggest defining an appropriate macro
for it this register field.

> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV0_LSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV0_MSB,
> +				((thres >> 8) & 0xF) | is_high);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV0_MSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	if (adc->wakeup2_enable) {
> +		int is_high;
> +
> +		ch1 = adc->wakeup2_data.adc_channel_number;
> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
> +		if (adc->wakeup2_data.adc_high_threshold > 0) {
> +			thres = adc->wakeup2_data.adc_high_threshold;
> +			is_high = 0;
> +		} else {
> +			thres = adc->wakeup2_data.adc_low_threshold;
> +			is_high = BIT(7);
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV1_LSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV1_MSB,
> +				((thres >> 8) & 0xF) | is_high);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV1_MSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> +		return ret;
> +	}
nitpick. Blank line here please.
> +	return 0;
> +}
> +
> +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
> +{
> +	int ret;
> +
> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_SELECT, 0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_disable_auto_conversion(adc);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +static int palmas_gpadc_suspend(struct device *dev)
> +{
> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
> +	struct palmas_gpadc *adc = iio_priv(iodev);
> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> +	int ret;
> +
> +	if (!device_may_wakeup(dev) || !wakeup)
> +		return 0;
> +
> +	ret = palmas_adc_wakeup_configure(adc);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (adc->wakeup1_enable)
> +		enable_irq_wake(adc->irq_auto_0);
> +
> +	if (adc->wakeup2_enable)
> +		enable_irq_wake(adc->irq_auto_1);
nitpick. Blank line here please.
> +	return 0;
> +}
> +
> +static int palmas_gpadc_resume(struct device *dev)
> +{
> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
> +	struct palmas_gpadc *adc = iio_priv(iodev);
> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> +	int ret;
> +
> +	if (!device_may_wakeup(dev) || !wakeup)
> +		return 0;
> +
> +	ret = palmas_adc_wakeup_reset(adc);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (adc->wakeup1_enable)
> +		disable_irq_wake(adc->irq_auto_0);
> +
> +	if (adc->wakeup2_enable)
> +		disable_irq_wake(adc->irq_auto_1);
> +
> +	return 0;
> +};
> +#endif
> +
> +static const struct dev_pm_ops palmas_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
> +				palmas_gpadc_resume)
> +};
> +
> +static struct platform_driver palmas_gpadc_driver = {
> +	.probe = palmas_gpadc_probe,
> +	.remove = palmas_gpadc_remove,
> +	.driver = {
> +		.name = MOD_NAME,
> +		.owner = THIS_MODULE,
> +		.pm = &palmas_pm_ops,
> +	},
> +};
> +
> +static int __init palmas_gpadc_init(void)
> +{
> +	return platform_driver_register(&palmas_gpadc_driver);
> +}
> +module_init(palmas_gpadc_init);
> +
> +static void __exit palmas_gpadc_exit(void)
> +{
> +	platform_driver_unregister(&palmas_gpadc_driver);
> +}
> +module_exit(palmas_gpadc_exit);
> +
> +MODULE_DESCRIPTION("palmas GPADC driver");
> +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
> +MODULE_ALIAS("platform:palmas-gpadc");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
> index bb270bd..60acfa2 100644
> --- a/include/linux/mfd/palmas.h
> +++ b/include/linux/mfd/palmas.h
> @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
> 			    struct regulator_config config);
> };
> 
> +struct palmas_adc_wakeup_property {
> +	int adc_channel_number;
> +	int adc_high_threshold;
> +	int adc_low_threshold;
> +};
> +
> struct palmas_gpadc_platform_data {
> 	/* Channel 3 current source is only enabled during conversion */
> 	int ch3_current;
> -
> 	/* Channel 0 current source can be used for battery detection.
> -	 * If used for battery detection this will cause a permanent current
> +	* If used for battery detection this will cause a permanent current
> 	 * consumption depending on current level set here.
> -	 */
> +		 */
Some oddness occured here in patch creation.  Do check your own patches
as we all end up with stuff like this from time to time that should
be picked up on beofre sending out.  It just adds noise.
> 	int ch0_current;
> +	bool extended_delay;
> 
> 	/* default BAT_REMOVAL_DAT setting on device probe */
> 	int bat_removal;
> 
> 	/* Sets the START_POLARITY bit in the RT_CTRL register */
> 	int start_polarity;
> +
> +	struct iio_map *iio_maps;
> +	int auto_conversion_period_ms;
> +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
> +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
> };
> 
> struct palmas_reg_init {
> @@ -403,7 +414,7 @@ struct palmas_gpadc_calibration {
> 	s32 gain_error;
> 	s32 offset_error;
> };
> -
> +/*
> struct palmas_gpadc {
> 	struct device *dev;
> 	struct palmas *palmas;
> @@ -426,6 +437,9 @@ struct palmas_gpadc {
> 	int conv1_channel;
> 	int rt_channel;
> };
> +*/
So there's a commented out bit of code in this patch?  Sort
that out before the next version.

> +
> +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
> 
> struct palmas_gpadc_result {
> 	s32 raw_code;
> @@ -519,6 +533,42 @@ enum palmas_irqs {
> 	PALMAS_NUM_IRQ,
> };
> 
> +/* Palma GPADC Channels */
> +enum {
> +	PALMAS_ADC_CH_IN0,
> +	PALMAS_ADC_CH_IN1,
> +	PALMAS_ADC_CH_IN2,
> +	PALMAS_ADC_CH_IN3,
> +	PALMAS_ADC_CH_IN4,
> +	PALMAS_ADC_CH_IN5,
> +	PALMAS_ADC_CH_IN6,
> +	PALMAS_ADC_CH_IN7,
> +	PALMAS_ADC_CH_IN8,
> +	PALMAS_ADC_CH_IN9,
> +	PALMAS_ADC_CH_IN10,
> +	PALMAS_ADC_CH_IN11,
> +	PALMAS_ADC_CH_IN12,
> +	PALMAS_ADC_CH_IN13,
> +	PALMAS_ADC_CH_IN14,
> +	PALMAS_ADC_CH_IN15,
This does rather feel like an enum that doesn't add anything
as the enum values = the index given at the end anyway...
> +	PALMAS_ADC_CH_MAX,
> +};
> +
> +/* Palma GPADC Channel0 Current Source */
> +enum {
> +	PALMAS_ADC_CH0_CURRENT_SRC_0,
> +	PALMAS_ADC_CH0_CURRENT_SRC_5,
> +	PALMAS_ADC_CH0_CURRENT_SRC_15,
> +	PALMAS_ADC_CH0_CURRENT_SRC_20,
> +};
Nitpick: new line here.
> +/* Palma GPADC Channel3 Current Source */
> +enum {
> +	PALMAS_ADC_CH3_CURRENT_SRC_0,
> +	PALMAS_ADC_CH3_CURRENT_SRC_10,
> +	PALMAS_ADC_CH3_CURRENT_SRC_400,
> +	PALMAS_ADC_CH3_CURRENT_SRC_800,
> +};
> +
> struct palmas_pmic {
> 	struct palmas *palmas;
> 	struct device *dev;
> @@ -2999,6 +3049,7 @@ enum usb_irq_events {
> #define PALMAS_GPADC_TRIM14					0x0D
> #define PALMAS_GPADC_TRIM15					0x0E
> #define PALMAS_GPADC_TRIM16					0x0F
> +#define PALMAS_GPADC_TRIMINVALID				-1
> 
> /* TPS659038 regen2_ctrl offset iss different from palmas */
> #define TPS659038_REGEN2_CTRL					0x12
> 


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

* Re: [PATCH 2/3] iio:adc:palmas: add DT support
  2015-09-23 12:49   ` [PATCH 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
@ 2015-09-27 15:37     ` Jonathan Cameron
  0 siblings, 0 replies; 25+ messages in thread
From: Jonathan Cameron @ 2015-09-27 15:37 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Benoît Cousson, Tony Lindgren,
	Russell King, Marek Belisko, Pradeep Goudagunta, Laxman Dewangan,
	gg, jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, notaz

On 23/09/15 13:49, H. Nikolaus Schaller wrote:
> From: Marek Belisko <marek@goldelico.com>
> 
> Code was found at:
> https://android.googlesource.com/kernel/tegra/+/a90856a6626d502d42c6e7abccbdf9d730b36270%5E%21/#F1
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> [Fixed minor typos + add channels list to documentation]
> Signed-off-by: Marek Belisko <marek@goldelico.com>
Various commments inline.  You've run into the region of previous arguments
over driver bindings...  Mark Rutland, one for you to comment on as I know
you love our bindings ;) and yes we haven't done anything about them in two
years or so since you last moaned about them to me :(

> ---
> .../devicetree/bindings/iio/adc/palmas-gpadc.txt   |  67 +++++++++++
> drivers/iio/adc/palmas_gpadc.c                     | 130 +++++++++++++++++----
> 2 files changed, 175 insertions(+), 22 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
> new file mode 100644
> index 0000000..a5a33ba
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
> @@ -0,0 +1,67 @@
> +* Palmas general purpose ADC IP block devicetree bindings
> +
> +Channels list:
> +	0 battery type
> +        1 battery temp NTC
tab vs space fun.
> +	2 GP
> +	3 temp (with ext. diode)
> +	4 GP
> +	5 GP
> +	6 VBAT_SENSE
> +	7 VCC_SENSE
> +	8 Backup Battery voltage
> +	9 external charger (VCHG)
> +	10 VBUS
> +	11 DC-DC current probe (how does this work?)
> +	12 internal die temp
> +	13 internal die temp
> +	14 USB ID pin voltage
> +	15 test network
> +
> +Required properties:
> +- compatible : Must be "ti,palmas-gpadc".
> +
> +Optional sub-nodes:
> +ti,channel0-current-microamp: Channel 0 current in uA.
> +	Valid values 0uA, 5uA, 15uA, 20uA.
> +ti,channel3-current-microamp: Channel 3 current in uA.
> +	Valid value 0uA, 10uA, 400uA, 800uA.
> +ti,enable-channel3-dual-current: Enable dual current on channel 3.
> +ti,enable-extended-delay: Enable extended delay.
> +
> +Optional sub-node:
> +The Palmas ADC node has optional subnode to define the iio mapping.
> +It is the name with "iio_map". This node has again subnode to define
> +the property of the channel. The sub subnode has following properties:
> +- ti,adc-channel-number: ADC channel number.
> +- ti,adc-consumer-device: Consumer device name.
> +- ti,adc-consumer-channel: ADC consumer channel name.
> +
> +Example:
> +
> +pmic {
> +	compatible = "ti,twl6035-pmic", "ti,palmas-pmic";
> +	...
> +	gpadc {
> +		compatible = "ti,palmas-gpadc";
> +		interrupts = <18 0
> +			      16 0
> +			      17 0>;
> +		ti,channel0-current-microamp = <5>;
> +		ti,channel3-current-microamp = <10>;
> +		iio_map {
> +			ch1 {
> +				ti,adc-channel-number = <1>;
> +				ti,adc-consumer-device = "generic-adc-thermal.0";
> +				ti,adc-consumer-channel ="battery-temp-channel";
> +			};
> +
> +			ch6 {
> +				ti,adc-channel-number = <6>;
> +				ti,adc-consumer-device = "palmas-battery";
> +				ti,adc-consumer-channel ="vbat_channel";
> +			};
See comments below.  There is an existing iio-consumer binding.
Various uses of it have caused complaints from the device tree guys in the
past.  I'd be interested to get their feedback on this use case.
> +		};
> +	};
> +	...
> +};
> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
> index 17abb28..bc4db43 100644
> --- a/drivers/iio/adc/palmas_gpadc.c
> +++ b/drivers/iio/adc/palmas_gpadc.c
> @@ -20,6 +20,8 @@
> #include <linux/pm.h>
> #include <linux/mfd/palmas.h>
> #include <linux/completion.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> #include <linux/iio/iio.h>
> #include <linux/iio/machine.h>
> #include <linux/iio/driver.h>
> @@ -434,20 +436,97 @@ static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
> 	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
> };
> 
> +static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev,
> +	struct palmas_gpadc_platform_data **gpadc_pdata)
> +{
> +	struct device_node *np = pdev->dev.of_node;
> +	struct palmas_gpadc_platform_data *gp_data;
> +	struct device_node *map_node;
> +	struct device_node *child;
> +	struct iio_map *palmas_iio_map;
> +	int ret;
> +	u32 pval;
> +	int nmap, nvalid_map;
> +
> +	gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL);
> +	if (!gp_data)
> +		return -ENOMEM;
> +
> +	ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval);
> +	if (!ret)
> +		gp_data->ch0_current = pval;
> +
> +	ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval);
> +	if (!ret)
> +		gp_data->ch3_current = pval;
> +
> +	gp_data->extended_delay = of_property_read_bool(np,
> +					"ti,enable-extended-delay");
> +
> +	map_node = of_get_child_by_name(np, "iio_map");
> +	if (!map_node) {
> +		dev_warn(&pdev->dev, "IIO map table not found\n");
> +		goto done;
> +	}
> +
> +	nmap = of_get_child_count(map_node);
> +	if (!nmap)
> +		goto done;
> +
> +	nmap++;
> +	palmas_iio_map = devm_kzalloc(&pdev->dev,
> +				sizeof(*palmas_iio_map) * nmap, GFP_KERNEL);
> +	if (!palmas_iio_map)
> +		goto done;
> +
> +	nvalid_map = 0;
> +	for_each_child_of_node(map_node, child) {
> +		ret = of_property_read_u32(child, "ti,adc-channel-number",
> +					&pval);
> +		if (!ret && pval < ARRAY_SIZE(palmas_gpadc_iio_channel))
> +			palmas_iio_map[nvalid_map].adc_channel_label =
> +				palmas_gpadc_iio_channel[pval].datasheet_name;
> +		of_property_read_string(child, "ti,adc-consumer-device",
> +				&palmas_iio_map[nvalid_map].consumer_dev_name);
> +		of_property_read_string(child, "ti,adc-consumer-channel",
> +				&palmas_iio_map[nvalid_map].consumer_channel);
Hmm.  These aren't TI specific, and there are already IIO bindings
documented for consumers at Documentation/devicetree/bindings/iio/iio-bindings.txt.

They have caused some issues in the past due to binding to devices that are
linux specific convenience functions.  I fear that is sort of the case here
with your thermal binding, but we can see what response we get.

Mark Rutland is IIRC the most vocal on this :)

The current plan for iio-hwmon which caused all the controversy is to look
at moving the decision to create the mapping entirely into userspace
via configfs and hence make it a clear policy decision rather than claiming
that changing user interface is a hardware mapping.

> +		dev_dbg(&pdev->dev,
> +			"Channel %s consumer dev %s and consumer channel %s\n",
> +				palmas_iio_map[nvalid_map].adc_channel_label,
> +				palmas_iio_map[nvalid_map].consumer_dev_name,
> +				palmas_iio_map[nvalid_map].consumer_channel);
> +		nvalid_map++;
> +	}
> +	palmas_iio_map[nvalid_map].adc_channel_label = NULL;
> +	palmas_iio_map[nvalid_map].consumer_dev_name = NULL;
> +	palmas_iio_map[nvalid_map].consumer_channel = NULL;
> +
> +	gp_data->iio_maps = palmas_iio_map;
> +
> +done:
> +	*gpadc_pdata = gp_data;
blank line here.
> +	return 0;
> +}
> +
> static int palmas_gpadc_probe(struct platform_device *pdev)
> {
> 	struct palmas_gpadc *adc;
> 	struct palmas_platform_data *pdata;
> -	struct palmas_gpadc_platform_data *adc_pdata;
> +	struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
This rename should not be here. It adds a lot of churn to the patch
and makes it harder to review.  Fix it in the previous patch.

> 	struct iio_dev *iodev;
> 	int ret, i;
> 
> 	pdata = dev_get_platdata(pdev->dev.parent);
> -	if (!pdata || !pdata->gpadc_pdata) {
> -		dev_err(&pdev->dev, "No platform data\n");
> -		return -ENODEV;
> +	if (pdata && pdata->gpadc_pdata)
> +		gpadc_pdata = pdata->gpadc_pdata;
> +
> +	if (!gpadc_pdata && pdev->dev.of_node) {
> +		ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata);
> +		if (ret < 0)
> +			return ret;
> 	}
> -	adc_pdata = pdata->gpadc_pdata;
> +	if (!gpadc_pdata)
> +		return -EINVAL;
> 
> 	iodev = iio_device_alloc(sizeof(*adc));
> 	if (!iodev) {
> @@ -455,8 +534,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> 		return -ENOMEM;
> 	}
> 
> -	if (adc_pdata->iio_maps) {
> -		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
> +	if (gpadc_pdata->iio_maps) {
> +		ret = iio_map_array_register(iodev, gpadc_pdata->iio_maps);
> 		if (ret < 0) {
> 			dev_err(&pdev->dev, "iio_map_array_register failed\n");
> 			goto out;
> @@ -470,7 +549,7 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> 	init_completion(&adc->conv_completion);
> 	dev_set_drvdata(&pdev->dev, iodev);
> 
> -	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
> +	adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
> 	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
> 	ret = request_threaded_irq(adc->irq, NULL,
> 		palmas_gpadc_irq,
> @@ -482,8 +561,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> 		goto out_unregister_map;
> 	}
> 
> -	if (adc_pdata->adc_wakeup1_data) {
> -		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
> +	if (gpadc_pdata->adc_wakeup1_data) {
> +		memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
> 			sizeof(adc->wakeup1_data));
> 		adc->wakeup1_enable = true;
> 		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
> @@ -498,8 +577,8 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> 		}
> 	}
> 
> -	if (adc_pdata->adc_wakeup2_data) {
> -		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
> +	if (gpadc_pdata->adc_wakeup2_data) {
> +		memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
> 				sizeof(adc->wakeup2_data));
> 		adc->wakeup2_enable = true;
> 		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
> @@ -514,25 +593,25 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> 		}
> 	}
> 
> -	if (adc_pdata->ch0_current == 0)
> +	if (gpadc_pdata->ch0_current == 0)
> 		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
> -	else if (adc_pdata->ch0_current <= 5)
> +	else if (gpadc_pdata->ch0_current <= 5)
> 		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
> -	else if (adc_pdata->ch0_current <= 15)
> +	else if (gpadc_pdata->ch0_current <= 15)
> 		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
> 	else
> 		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
> 
> -	if (adc_pdata->ch3_current == 0)
> +	if (gpadc_pdata->ch3_current == 0)
> 		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
> -	else if (adc_pdata->ch3_current <= 10)
> +	else if (gpadc_pdata->ch3_current <= 10)
> 		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
> -	else if (adc_pdata->ch3_current <= 400)
> +	else if (gpadc_pdata->ch3_current <= 400)
> 		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
> 	else
> 		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
> 
> -	adc->extended_delay = adc_pdata->extended_delay;
> +	adc->extended_delay = gpadc_pdata->extended_delay;
> 
> 	iodev->name = MOD_NAME;
> 	iodev->dev.parent = &pdev->dev;
> @@ -559,15 +638,15 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
> 	return 0;
> 
> out_irq_auto1_free:
> -	if (adc_pdata->adc_wakeup2_data)
> +	if (gpadc_pdata->adc_wakeup2_data)
> 		free_irq(adc->irq_auto_1, adc);
> out_irq_auto0_free:
> -	if (adc_pdata->adc_wakeup1_data)
> +	if (gpadc_pdata->adc_wakeup1_data)
> 		free_irq(adc->irq_auto_0, adc);
> out_irq_free:
> 	free_irq(adc->irq, adc);
> out_unregister_map:
> -	if (adc_pdata->iio_maps)
> +	if (gpadc_pdata->iio_maps)
> 		iio_map_array_unregister(iodev);
> out:
> 	iio_device_free(iodev);
> @@ -769,6 +848,12 @@ static const struct dev_pm_ops palmas_pm_ops = {
> 				palmas_gpadc_resume)
> };
> 
> +static const struct of_device_id of_palmas_gpadc_match_tbl[] = {
> +	{ .compatible = "ti,palmas-gpadc", },
> +	{ /* end */ }
> +};
> +MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl);
> +
> static struct platform_driver palmas_gpadc_driver = {
> 	.probe = palmas_gpadc_probe,
> 	.remove = palmas_gpadc_remove,
> @@ -776,6 +861,7 @@ static struct platform_driver palmas_gpadc_driver = {
> 		.name = MOD_NAME,
> 		.owner = THIS_MODULE,
> 		.pm = &palmas_pm_ops,
> +		.of_match_table = of_palmas_gpadc_match_tbl,
> 	},
> };
> 


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

* Re: [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-09-27 15:21     ` Jonathan Cameron
@ 2015-09-28 20:54       ` H. Nikolaus Schaller
       [not found]       ` <95BEC150-7B77-48BF-B11D-929090A1CC11@goldelico.com>
  1 sibling, 0 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-09-28 20:54 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Laxman Dewangan, gg, jic23, devicetree,
	linux-kernel, linux-omap, linux-iio, notaz

Hi,

Am 27.09.2015 um 17:21 schrieb Jonathan Cameron <jic23@kernel.org>:

> On 23/09/15 13:48, H. Nikolaus Schaller wrote:
>> This driver code was found as:
>> 
>> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
>> 
>> Fixed various compilation issues and test this driver on omap5 evm.
>> 
>> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>> Signed-off-by: Marek Belisko <marek@goldelico.com>
> Various bits inline.

thanks for the valuable comments!

Will work into V2.

Nikolaus

> 
> Jonathan
>> ---
>> drivers/iio/adc/Kconfig        |   9 +
>> drivers/iio/adc/Makefile       |   1 +
>> drivers/iio/adc/palmas_gpadc.c | 797 +++++++++++++++++++++++++++++++++++++++++
>> include/linux/mfd/palmas.h     |  59 ++-
>> 4 files changed, 862 insertions(+), 4 deletions(-)
>> create mode 100644 drivers/iio/adc/palmas_gpadc.c
>> 
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index eb0cd89..f6df9db 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -242,6 +242,15 @@ config NAU7802
>> 	  To compile this driver as a module, choose M here: the
>> 	  module will be called nau7802.
>> 
>> +config PALMAS_GPADC
>> +	tristate "TI Palmas General Purpose ADC"
>> +	depends on MFD_PALMAS
>> +	help
>> +	  Palmas series pmic chip by texas Instruments (twl6035/6037)
>> +	  is used in smartphones and tablets and supports a 16 channel
>> +	  general purpose ADC. Add iio driver to read different channel
>> +	  of the GPADCs.
>> +
>> config QCOM_SPMI_IADC
>> 	tristate "Qualcomm SPMI PMIC current ADC"
>> 	depends on SPMI
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index a096210..716f112 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
>> obj-$(CONFIG_MCP3422) += mcp3422.o
>> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>> obj-$(CONFIG_NAU7802) += nau7802.o
>> +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>> obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
>> new file mode 100644
>> index 0000000..17abb28
>> --- /dev/null
>> +++ b/drivers/iio/adc/palmas_gpadc.c
>> @@ -0,0 +1,797 @@
>> +/*
>> + * palmas-adc.c -- TI PALMAS GPADC.
>> + *
>> + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
>> + *
>> + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation version 2.
>> + */
>> +#include <linux/module.h>
>> +#include <linux/err.h>
>> +#include <linux/irq.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +#include <linux/delay.h>
>> +#include <linux/i2c.h>
>> +#include <linux/pm.h>
>> +#include <linux/mfd/palmas.h>
>> +#include <linux/completion.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/iio/machine.h>
>> +#include <linux/iio/driver.h>
>> +
>> +#define MOD_NAME "palmas-gpadc"
>> +#define ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
>> +#define TO_BE_CALCULATED 0
>> +
>> +struct palmas_gpadc_info {
>> +/* calibration codes and regs */
> Full docs on this would be appreciated.
>> +	int x1;
>> +	int x2;
>> +	int v1;
>> +	int v2;
>> +	u8 trim1_reg;
>> +	u8 trim2_reg;
>> +	int gain;
>> +	int offset;
>> +	int gain_error;
>> +	bool is_correct_code;
>> +};
>> +
>> +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_correct_code)\
>> +[PALMAS_ADC_CH_##_chan] = {						\
>> +		.x1 = _x1,						\
>> +		.x2 = _x2,						\
>> +		.v1 = _v1,						\
>> +		.v2 = _v2,						\
>> +		.gain = TO_BE_CALCULATED,				\
>> +		.offset = TO_BE_CALCULATED,				\
>> +		.gain_error = TO_BE_CALCULATED,				\
>> +		.trim1_reg = PALMAS_GPADC_TRIM##_t1,			\
>> +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,			\
>> +		.is_correct_code = _is_correct_code			\
>> +	}
>> +
>> +static struct palmas_gpadc_info palmas_gpadc_info[] = {
>> +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
>> +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
>> +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
>> +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
>> +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
>> +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
>> +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
>> +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
>> +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
>> +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
>> +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
>> +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
>> +};
>> +
>> +struct palmas_gpadc {
>> +	struct device			*dev;
>> +	struct palmas			*palmas;
> As there are some non obvious elements in here (such as the next two)
> kernel-doc for the whole stucture would be good.
> 
>> +	u8				ch0_current;
>> +	u8				ch3_current;
>> +	bool				extended_delay;
>> +	int				irq;
>> +	int				irq_auto_0;
>> +	int				irq_auto_1;
>> +	struct palmas_gpadc_info	*adc_info;
>> +	struct completion		conv_completion;
>> +	struct palmas_adc_wakeup_property wakeup1_data;
>> +	struct palmas_adc_wakeup_property wakeup2_data;
>> +	bool				wakeup1_enable;
>> +	bool				wakeup2_enable;
>> +	int				auto_conversion_period;
>> +};
>> +
>> +/*
>> + * GPADC lock issue in AUTO mode.
>> + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
>> + *	   mode feature.
>> + * Details:
>> + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
>> + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
>> + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
>> + *	conversion mechanism can be seen as locked meaning that all following
>> + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
>> + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
>> + *	the GPADC.
>> + *
>> + * Workaround(s):
>> + *	To avoid the lock mechanism, the workaround to follow before any stop
>> + *	conversion request is:
>> + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
>> + *		GPADC_FORCE bit = 1
>> + *	Shutdown the GPADC AUTO conversion using
>> + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
>> + *	After 100us, force the GPADC state machine to be OFF by using the
>> + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
>> + */
>> +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
>> +{
>> +	int ret;
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_CTRL1,
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_CTRL,
>> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
>> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
>> +			0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	udelay(100);
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_CTRL1,
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
>> +		return ret;
>> +	}
> return ret and drop the return above.  Coccicheck will moan at you about
> this.
>> +	return 0;
>> +}
>> +
>> +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
>> +{
>> +	struct palmas_gpadc *adc = data;
>> +
>> +	complete(&adc->conv_completion);
> blank line.
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
>> +{
>> +	struct palmas_gpadc *adc = data;
>> +
>> +	dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
> Could indicate this to userspace... other than through the logs.
> 
>> +	palmas_disable_auto_conversion(adc);
> blank line generally before all function returns..
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)
> 
> mask is boolean.  Make it explicitly so for readability.
> 
>> +{
>> +	int ret;
>> +
>> +	if (!mask)
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
>> +					PALMAS_INT3_MASK,
>> +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
>> +	else
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
>> +					PALMAS_INT3_MASK,
>> +					PALMAS_INT3_MASK_GPADC_EOC_SW,
>> +					PALMAS_INT3_MASK_GPADC_EOC_SW);
>> +	if (ret < 0)
>> +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
>> +
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
>> +			       int enable)
>> +{
>> +	unsigned int mask, val;
>> +	int ret;
>> +
>> +	if (enable) {
>> +		val = (adc->extended_delay
>> +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +					PALMAS_GPADC_RT_CTRL,
>> +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
>> +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
>> +		val = (adc->ch0_current
>> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
>> +		val |= (adc->ch3_current
>> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
>> +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_CTRL1, mask, val);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"Failed to update current setting: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
>> +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
>> +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_SELECT, mask, val);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	} else {
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_SELECT, 0);
>> +		if (ret < 0)
>> +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
>> +
>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_CTRL1,
>> +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	int ret;
>> +
>> +	ret = palmas_gpadc_enable(adc, adc_chan, true);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	return palmas_gpadc_start_mask_interrupt(adc, 0);
>> +}
>> +
>> +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	palmas_gpadc_start_mask_interrupt(adc, 1);
>> +	palmas_gpadc_enable(adc, adc_chan, false);
>> +}
>> +
>> +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	int k;
>> +	int d1;
>> +	int d2;
>> +	int ret;
>> +	int gain;
>> +	int x1 =  adc->adc_info[adc_chan].x1;
>> +	int x2 =  adc->adc_info[adc_chan].x2;
>> +	int v1 = adc->adc_info[adc_chan].v1;
>> +	int v2 = adc->adc_info[adc_chan].v2;
>> +
>> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
>> +				adc->adc_info[adc_chan].trim1_reg, &d1);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
>> +		goto scrub;
>> +	}
>> +
>> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
>> +				adc->adc_info[adc_chan].trim2_reg, &d2);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
>> +		goto scrub;
>> +	}
>> +
>> +	/*Gain error Calculation*/
>> +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
>> +
>> +	/*Gain Calculation*/
>> +	gain = ((v2 - v1) * 1000) / (x2 - x1);
>> +
>> +	adc->adc_info[adc_chan].gain_error = k;
>> +	adc->adc_info[adc_chan].gain = gain;
>> +	/*offset Calculation*/
>> +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
>> +
>> +scrub:
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
>> +{
>> +	unsigned int val;
>> +	int ret;
>> +
>> +	init_completion(&adc->conv_completion);
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_SELECT,
>> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
>> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "ADC_SW_START write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = wait_for_completion_timeout(&adc->conv_completion,
>> +				ADC_CONVERSION_TIMEOUT);
>> +	if (ret == 0) {
>> +		dev_err(adc->dev, "ADC conversion not completed\n");
>> +		ret = -ETIMEDOUT;
>> +		return ret;
> return -ETIMEDOUT;   Might be worth you setting up to do sparse, smatch and
> coccicheck runs on the code as they'll pick up on a lot of issues like this.
> 
>> +	}
>> +
>> +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "ADCDATA read failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = (val & 0xFFF);
> nitpick : blank line here.
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
>> +						int adc_chan, int val)
>> +{
>> +	if (((val*1000) - adc->adc_info[adc_chan].offset) < 0) {
>> +		dev_err(adc->dev, "No Input Connected\n");
>> +		return 0;
>> +	}
> Interesting, but should this not be caught for raw channel reads as well?
>> +
> Umm. what is is_correct_code? Should be documented somewhere
>> +	if (!(adc->adc_info[adc_chan].is_correct_code))
>> +		val  = ((val*1000) - adc->adc_info[adc_chan].offset) /
>> +					adc->adc_info[adc_chan].gain_error;
>> +
>> +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
>> +	return val;
>> +}
>> +
>> +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
>> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
>> +{
>> +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
>> +	int adc_chan = chan->channel;
>> +	int ret = 0;
>> +
>> +	if (adc_chan > PALMAS_ADC_CH_MAX)
>> = given I think your channels are 0 indexed?
>> +		return -EINVAL;
>> +
>> +	mutex_lock(&indio_dev->mlock);
>> +
>> +	switch (mask) {
>> +	case IIO_CHAN_INFO_RAW:
>> +	case IIO_CHAN_INFO_PROCESSED:
> I'd be tempted to split the two code paths here as that will be slightly
> easier to read (if longer).
> 
> I'm also highly suspicious of any driver that does processed output
> and has a return type of IIO_VAL_INT.  Are you processed values in the
> units documented in Documentation/ABI/testing/sysfs-bus-iio?
> They could be, but I would like a comment saying that.
> 
>> +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
>> +		if (ret < 0)
>> +			goto out;
>> +
>> +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +			"ADC start coversion failed\n");
>> +			goto out;
>> +		}
>> +
>> +		if (mask == IIO_CHAN_INFO_PROCESSED)
>> +			ret = palmas_gpadc_get_calibrated_code(
>> +							adc, adc_chan, ret);
>> +
>> +		*val = ret;
>> +
>> +		ret = IIO_VAL_INT;
>> +		goto out;
>> +	}
>> +
>> +	mutex_unlock(&indio_dev->mlock);
>> +	return ret;
>> +
>> +out:
>> +	palmas_gpadc_read_done(adc, adc_chan);
>> +	mutex_unlock(&indio_dev->mlock);
>> +	return ret;
>> +}
>> +
>> +static const struct iio_info palmas_gpadc_iio_info = {
>> +	.read_raw = palmas_gpadc_read_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +#define PALMAS_ADC_CHAN_IIO(chan, _type)			\
>> +{									\
>> +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),			\
>> +	.type = _type,							\
> Right now they are all voltage channels, why have that specifiable in this
> macro?
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
>> +			BIT(IIO_CHAN_INFO_PROCESSED),			\
> Hmm. This is very very rarely justified.  Either the driver has
> nice linear relationship between the raw value and the processed one, in which
> case you should be providing *_RAW, *_OFFSET and *_SCALE and leaving the maths
> to userspace (or the in kernel wrappers), or they are non linear in which case
> only the processed value is of interest and the raw one not directly so.
> (the only exception to this is light sensors where the processed channel
> is often a computed channel from several input _raw readings).
> 
>> +	.indexed = 1,							\
>> +	.channel = PALMAS_ADC_CH_##chan,				\
> Given this maps back to the value of chan, just put that in as a number
> and loose the enum.  Channel is used in the naming so it doesn't
> make sense to obscure that behind an enum anyway.
>> +}
>> +
>> +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
>> +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN3, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN12, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN13, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE),
>> +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
>> +};
>> +
>> +static int palmas_gpadc_probe(struct platform_device *pdev)
>> +{
>> +	struct palmas_gpadc *adc;
>> +	struct palmas_platform_data *pdata;
>> +	struct palmas_gpadc_platform_data *adc_pdata;
>> +	struct iio_dev *iodev;
>> +	int ret, i;
>> +
>> +	pdata = dev_get_platdata(pdev->dev.parent);
>> +	if (!pdata || !pdata->gpadc_pdata) {
>> +		dev_err(&pdev->dev, "No platform data\n");
>> +		return -ENODEV;
>> +	}
>> +	adc_pdata = pdata->gpadc_pdata;
>> +
>> +	iodev = iio_device_alloc(sizeof(*adc));
>> +	if (!iodev) {
>> +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
>> +		return -ENOMEM;
>> +	}
>> +
>> +	if (adc_pdata->iio_maps) {
>> +		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
>> +		if (ret < 0) {
>> +			dev_err(&pdev->dev, "iio_map_array_register failed\n");
>> +			goto out;
>> +		}
>> +	}
>> +
>> +	adc = iio_priv(iodev);
>> +	adc->dev = &pdev->dev;
>> +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
>> +	adc->adc_info = palmas_gpadc_info;
>> +	init_completion(&adc->conv_completion);
>> +	dev_set_drvdata(&pdev->dev, iodev);
>> +
>> +	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
>> +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
>> +	ret = request_threaded_irq(adc->irq, NULL,
>> +		palmas_gpadc_irq,
>> +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
>> +		adc);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev,
>> +			"request irq %d failed: %dn", adc->irq, ret);
>> +		goto out_unregister_map;
>> +	}
>> +
>> +	if (adc_pdata->adc_wakeup1_data) {
>> +		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
>> +			sizeof(adc->wakeup1_data));
>> +		adc->wakeup1_enable = true;
>> +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
>> +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
>> +				palmas_gpadc_irq_auto,
>> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
>> +				"palmas-adc-auto-0", adc);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "request auto0 irq %d failed: %dn",
>> +				adc->irq_auto_0, ret);
>> +			goto out_irq_free;
>> +		}
>> +	}
>> +
>> +	if (adc_pdata->adc_wakeup2_data) {
>> +		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
>> +				sizeof(adc->wakeup2_data));
>> +		adc->wakeup2_enable = true;
>> +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
>> +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
>> +				palmas_gpadc_irq_auto,
>> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
>> +				"palmas-adc-auto-1", adc);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev, "request auto1 irq %d failed: %dn",
>> +				adc->irq_auto_1, ret);
>> +			goto out_irq_auto0_free;
>> +		}
>> +	}
>> +
> I've requested kernel-doc above for ch0_current, but if that doesn't
> make it clear what matic is going on here then some comments here
> would be good.
>> +	if (adc_pdata->ch0_current == 0)
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
>> +	else if (adc_pdata->ch0_current <= 5)
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
>> +	else if (adc_pdata->ch0_current <= 15)
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
>> +	else
>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
>> +
>> +	if (adc_pdata->ch3_current == 0)
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
>> +	else if (adc_pdata->ch3_current <= 10)
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
>> +	else if (adc_pdata->ch3_current <= 400)
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
>> +	else
>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
>> +
>> +	adc->extended_delay = adc_pdata->extended_delay;
>> +
>> +	iodev->name = MOD_NAME;
>> +	iodev->dev.parent = &pdev->dev;
>> +	iodev->info = &palmas_gpadc_iio_info;
>> +	iodev->modes = INDIO_DIRECT_MODE;
>> +	iodev->channels = palmas_gpadc_iio_channel;
>> +	iodev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
>> +
>> +	ret = iio_device_register(iodev);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
>> +		goto out_irq_auto1_free;
>> +	}
>> +
>> +	device_set_wakeup_capable(&pdev->dev, 1);
>> +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
>> +		if (!(adc->adc_info[i].is_correct_code))
>> +			palmas_gpadc_calibrate(adc, i);
>> +	}
>> +
>> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
>> +		device_wakeup_enable(&pdev->dev);
> There is no match for this in the remove... Should there be one?
> (not an interface I know anything about!)
>> +
>> +	return 0;
>> +
>> +out_irq_auto1_free:
>> +	if (adc_pdata->adc_wakeup2_data)
>> +		free_irq(adc->irq_auto_1, adc);
>> +out_irq_auto0_free:
>> +	if (adc_pdata->adc_wakeup1_data)
>> +		free_irq(adc->irq_auto_0, adc);
>> +out_irq_free:
>> +	free_irq(adc->irq, adc);
>> +out_unregister_map:
>> +	if (adc_pdata->iio_maps)
>> +		iio_map_array_unregister(iodev);
>> +out:
>> +	iio_device_free(iodev);
>> +	return ret;
>> +}
>> +
>> +static int palmas_gpadc_remove(struct platform_device *pdev)
>> +{
>> +	struct iio_dev *iodev = dev_to_iio_dev(&pdev->dev);
>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>> +	struct palmas_platform_data *pdata = dev_get_platdata(pdev->dev.parent);
>> +
>> +	if (pdata->gpadc_pdata->iio_maps)
>> +		iio_map_array_unregister(iodev);
>> +	iio_device_unregister(iodev);
>> +	free_irq(adc->irq, adc);
>> +	if (adc->wakeup1_enable)
>> +		free_irq(adc->irq_auto_0, adc);
>> +	if (adc->wakeup2_enable)
>> +		free_irq(adc->irq_auto_1, adc);
>> +	iio_device_free(iodev);
> Could use demv_iio_device_alloc and not need the free here or on error path.
> Given location of irq frees you could do devm allocations of them as well
> which would cut out a fair bit of code without reordering anything.
> 
> nit: blank line here.
>> +	return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
>> +{
>> +	int adc_period, conv;
>> +	int i;
>> +	int ch0 = 0, ch1 = 0;
>> +	int thres;
>> +	int ret;
>> +
>> +	adc_period = adc->auto_conversion_period;
>> +	for (i = 0; i < 16; ++i) {
> spacing around the /
>> +		if (((1000 * (1 << i))/32) < adc_period)
>> +			continue;
>> +	}
>> +	if (i > 0)
>> +		i--;
>> +	adc_period = i;
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_CTRL,
>> +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
>> +			adc_period);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	conv = 0;
>> +	if (adc->wakeup1_enable) {
>> +		int is_high;
>> +
>> +		ch0 = adc->wakeup1_data.adc_channel_number;
>> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
>> +		if (adc->wakeup1_data.adc_high_threshold > 0) {
>> +			thres = adc->wakeup1_data.adc_high_threshold;
>> +			is_high = 0;
>> +		} else {
>> +			thres = adc->wakeup1_data.adc_low_threshold;
>> +			is_high = BIT(7);
> BIT(7) is a bit random, so I'd suggest defining an appropriate macro
> for it this register field.
> 
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV0_LSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV0_MSB,
>> +				((thres >> 8) & 0xF) | is_high);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV0_MSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	if (adc->wakeup2_enable) {
>> +		int is_high;
>> +
>> +		ch1 = adc->wakeup2_data.adc_channel_number;
>> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
>> +		if (adc->wakeup2_data.adc_high_threshold > 0) {
>> +			thres = adc->wakeup2_data.adc_high_threshold;
>> +			is_high = 0;
>> +		} else {
>> +			thres = adc->wakeup2_data.adc_low_threshold;
>> +			is_high = BIT(7);
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV1_LSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +
>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +				PALMAS_GPADC_THRES_CONV1_MSB,
>> +				((thres >> 8) & 0xF) | is_high);
>> +		if (ret < 0) {
>> +			dev_err(adc->dev,
>> +				"THRES_CONV1_MSB write failed: %d\n", ret);
>> +			return ret;
>> +		}
>> +	}
>> +
>> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_CTRL,
>> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
>> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
>> +		return ret;
>> +	}
> nitpick. Blank line here please.
>> +	return 0;
>> +}
>> +
>> +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
>> +{
>> +	int ret;
>> +
>> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>> +			PALMAS_GPADC_AUTO_SELECT, 0);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = palmas_disable_auto_conversion(adc);
>> +	if (ret < 0) {
>> +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +	return 0;
>> +}
>> +
>> +static int palmas_gpadc_suspend(struct device *dev)
>> +{
>> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
>> +	int ret;
>> +
>> +	if (!device_may_wakeup(dev) || !wakeup)
>> +		return 0;
>> +
>> +	ret = palmas_adc_wakeup_configure(adc);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	if (adc->wakeup1_enable)
>> +		enable_irq_wake(adc->irq_auto_0);
>> +
>> +	if (adc->wakeup2_enable)
>> +		enable_irq_wake(adc->irq_auto_1);
> nitpick. Blank line here please.
>> +	return 0;
>> +}
>> +
>> +static int palmas_gpadc_resume(struct device *dev)
>> +{
>> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
>> +	int ret;
>> +
>> +	if (!device_may_wakeup(dev) || !wakeup)
>> +		return 0;
>> +
>> +	ret = palmas_adc_wakeup_reset(adc);
>> +	if (ret < 0)
>> +		return ret;
>> +
>> +	if (adc->wakeup1_enable)
>> +		disable_irq_wake(adc->irq_auto_0);
>> +
>> +	if (adc->wakeup2_enable)
>> +		disable_irq_wake(adc->irq_auto_1);
>> +
>> +	return 0;
>> +};
>> +#endif
>> +
>> +static const struct dev_pm_ops palmas_pm_ops = {
>> +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
>> +				palmas_gpadc_resume)
>> +};
>> +
>> +static struct platform_driver palmas_gpadc_driver = {
>> +	.probe = palmas_gpadc_probe,
>> +	.remove = palmas_gpadc_remove,
>> +	.driver = {
>> +		.name = MOD_NAME,
>> +		.owner = THIS_MODULE,
>> +		.pm = &palmas_pm_ops,
>> +	},
>> +};
>> +
>> +static int __init palmas_gpadc_init(void)
>> +{
>> +	return platform_driver_register(&palmas_gpadc_driver);
>> +}
>> +module_init(palmas_gpadc_init);
>> +
>> +static void __exit palmas_gpadc_exit(void)
>> +{
>> +	platform_driver_unregister(&palmas_gpadc_driver);
>> +}
>> +module_exit(palmas_gpadc_exit);
>> +
>> +MODULE_DESCRIPTION("palmas GPADC driver");
>> +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
>> +MODULE_ALIAS("platform:palmas-gpadc");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
>> index bb270bd..60acfa2 100644
>> --- a/include/linux/mfd/palmas.h
>> +++ b/include/linux/mfd/palmas.h
>> @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
>> 			    struct regulator_config config);
>> };
>> 
>> +struct palmas_adc_wakeup_property {
>> +	int adc_channel_number;
>> +	int adc_high_threshold;
>> +	int adc_low_threshold;
>> +};
>> +
>> struct palmas_gpadc_platform_data {
>> 	/* Channel 3 current source is only enabled during conversion */
>> 	int ch3_current;
>> -
>> 	/* Channel 0 current source can be used for battery detection.
>> -	 * If used for battery detection this will cause a permanent current
>> +	* If used for battery detection this will cause a permanent current
>> 	 * consumption depending on current level set here.
>> -	 */
>> +		 */
> Some oddness occured here in patch creation.  Do check your own patches
> as we all end up with stuff like this from time to time that should
> be picked up on beofre sending out.  It just adds noise.
>> 	int ch0_current;
>> +	bool extended_delay;
>> 
>> 	/* default BAT_REMOVAL_DAT setting on device probe */
>> 	int bat_removal;
>> 
>> 	/* Sets the START_POLARITY bit in the RT_CTRL register */
>> 	int start_polarity;
>> +
>> +	struct iio_map *iio_maps;
>> +	int auto_conversion_period_ms;
>> +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
>> +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
>> };
>> 
>> struct palmas_reg_init {
>> @@ -403,7 +414,7 @@ struct palmas_gpadc_calibration {
>> 	s32 gain_error;
>> 	s32 offset_error;
>> };
>> -
>> +/*
>> struct palmas_gpadc {
>> 	struct device *dev;
>> 	struct palmas *palmas;
>> @@ -426,6 +437,9 @@ struct palmas_gpadc {
>> 	int conv1_channel;
>> 	int rt_channel;
>> };
>> +*/
> So there's a commented out bit of code in this patch?  Sort
> that out before the next version.
> 
>> +
>> +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
>> 
>> struct palmas_gpadc_result {
>> 	s32 raw_code;
>> @@ -519,6 +533,42 @@ enum palmas_irqs {
>> 	PALMAS_NUM_IRQ,
>> };
>> 
>> +/* Palma GPADC Channels */
>> +enum {
>> +	PALMAS_ADC_CH_IN0,
>> +	PALMAS_ADC_CH_IN1,
>> +	PALMAS_ADC_CH_IN2,
>> +	PALMAS_ADC_CH_IN3,
>> +	PALMAS_ADC_CH_IN4,
>> +	PALMAS_ADC_CH_IN5,
>> +	PALMAS_ADC_CH_IN6,
>> +	PALMAS_ADC_CH_IN7,
>> +	PALMAS_ADC_CH_IN8,
>> +	PALMAS_ADC_CH_IN9,
>> +	PALMAS_ADC_CH_IN10,
>> +	PALMAS_ADC_CH_IN11,
>> +	PALMAS_ADC_CH_IN12,
>> +	PALMAS_ADC_CH_IN13,
>> +	PALMAS_ADC_CH_IN14,
>> +	PALMAS_ADC_CH_IN15,
> This does rather feel like an enum that doesn't add anything
> as the enum values = the index given at the end anyway...
>> +	PALMAS_ADC_CH_MAX,
>> +};
>> +
>> +/* Palma GPADC Channel0 Current Source */
>> +enum {
>> +	PALMAS_ADC_CH0_CURRENT_SRC_0,
>> +	PALMAS_ADC_CH0_CURRENT_SRC_5,
>> +	PALMAS_ADC_CH0_CURRENT_SRC_15,
>> +	PALMAS_ADC_CH0_CURRENT_SRC_20,
>> +};
> Nitpick: new line here.
>> +/* Palma GPADC Channel3 Current Source */
>> +enum {
>> +	PALMAS_ADC_CH3_CURRENT_SRC_0,
>> +	PALMAS_ADC_CH3_CURRENT_SRC_10,
>> +	PALMAS_ADC_CH3_CURRENT_SRC_400,
>> +	PALMAS_ADC_CH3_CURRENT_SRC_800,
>> +};
>> +
>> struct palmas_pmic {
>> 	struct palmas *palmas;
>> 	struct device *dev;
>> @@ -2999,6 +3049,7 @@ enum usb_irq_events {
>> #define PALMAS_GPADC_TRIM14					0x0D
>> #define PALMAS_GPADC_TRIM15					0x0E
>> #define PALMAS_GPADC_TRIM16					0x0F
>> +#define PALMAS_GPADC_TRIMINVALID				-1
>> 
>> /* TPS659038 regen2_ctrl offset iss different from palmas */
>> #define TPS659038_REGEN2_CTRL					0x12
>> 
> 


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

* [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
       [not found]   ` <cover.1443973837.git.hns@goldelico.com>
@ 2015-10-04 16:05     ` H. Nikolaus Schaller
       [not found]     ` <6C211191-87B8-4444-ADA7-8BE454C2B3D3@goldelico.com>
  1 sibling, 0 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-10-04 16:05 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas

This driver code was found as:

https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc

Fixed various compilation issues and test this driver on omap5 evm.

Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Signed-off-by: Marek Belisko <marek@goldelico.com>
---
drivers/iio/adc/Kconfig        |   9 +
drivers/iio/adc/Makefile       |   1 +
drivers/iio/adc/palmas_gpadc.c | 818 +++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/palmas.h     |  75 ++--
4 files changed, 879 insertions(+), 24 deletions(-)
create mode 100644 drivers/iio/adc/palmas_gpadc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index eb0cd89..05a0368 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -242,6 +242,15 @@ config NAU7802
	  To compile this driver as a module, choose M here: the
	  module will be called nau7802.

+config PALMAS_GPADC
+	tristate "TI Palmas General Purpose ADC"
+	depends on MFD_PALMAS
+	help
+	  Say yes here to build support for Texas Instruments Palmas.
+
+	  Palmas series pmic chip (twl6035/6037) is used in smartphones and
+	  tablets and supports a 16 channel general purpose ADC.
+
config QCOM_SPMI_IADC
	tristate "Qualcomm SPMI PMIC current ADC"
	depends on SPMI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index a096210..716f112 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
obj-$(CONFIG_MCP3422) += mcp3422.o
obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
obj-$(CONFIG_NAU7802) += nau7802.o
+obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
new file mode 100644
index 0000000..6805d81
--- /dev/null
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -0,0 +1,818 @@
+/*
+ * palmas-adc.c -- TI PALMAS GPADC.
+ *
+ * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
+ *
+ * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/pm.h>
+#include <linux/mfd/palmas.h>
+#include <linux/completion.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+#define MOD_NAME "palmas-gpadc"
+#define PALMAS_ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
+#define PALMAS_TO_BE_CALCULATED 0
+#define PALMAS_GPADC_TRIMINVALID	-1
+
+struct palmas_gpadc_info {
+/* calibration codes and regs */
+	int x1;	/* lower ideal code */
+	int x2;	/* higher ideal code */
+	int v1;	/* expected lower volt reading */
+	int v2;	/* expected higher volt reading */
+	u8 trim1_reg;	/* register number for lower trim */
+	u8 trim2_reg;	/* register number for upper trim */
+	int gain;	/* calculated from above (after reading trim regs) */
+	int offset;	/* calculated from above (after reading trim regs) */
+	int gain_error;	/* calculated from above (after reading trim regs) */
+	bool is_uncalibrated;	/* if channel has calibration data */
+};
+
+#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \
+	[PALMAS_ADC_CH_##_chan] = { \
+		.x1 = _x1, \
+		.x2 = _x2, \
+		.v1 = _v1, \
+		.v2 = _v2, \
+		.gain = PALMAS_TO_BE_CALCULATED, \
+		.offset = PALMAS_TO_BE_CALCULATED, \
+		.gain_error = PALMAS_TO_BE_CALCULATED, \
+		.trim1_reg = PALMAS_GPADC_TRIM##_t1, \
+		.trim2_reg = PALMAS_GPADC_TRIM##_t2,  \
+		.is_uncalibrated = _is_uncalibrated \
+	}
+
+static struct palmas_gpadc_info palmas_gpadc_info[] = {
+	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
+	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
+	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
+	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
+	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
+	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
+	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
+	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
+};
+
+/**
+ * struct palmas_gpadc - the palmas_gpadc structure
+ * @ch0_current:	channel 0 current source setting
+ *    0: 0 uA
+ *    1: 5 uA
+ *    2: 15 uA
+ *    3: 20 uA
+ * @ch3_current:	channel 3 current source setting
+ *    0: 0 uA
+ *    1: 10 uA
+ *    2: 400 uA
+ *    3: 800 uA
+ * @extended_delay:	enable the gpadc extended delay mode
+ * @auto_conversion_period:	define the auto_conversion_period
+ *
+ * This is the palmas_gpadc structure to store run-time information
+ * and pointers for this driver instance.
+ */
+
+struct palmas_gpadc {
+	struct device			*dev;
+	struct palmas			*palmas;
+	u8				ch0_current;
+	u8				ch3_current;
+	bool				extended_delay;
+	int				irq;
+	int				irq_auto_0;
+	int				irq_auto_1;
+	struct palmas_gpadc_info	*adc_info;
+	struct completion		conv_completion;
+	struct palmas_adc_wakeup_property wakeup1_data;
+	struct palmas_adc_wakeup_property wakeup2_data;
+	bool				wakeup1_enable;
+	bool				wakeup2_enable;
+	int				auto_conversion_period;
+};
+
+/*
+ * GPADC lock issue in AUTO mode.
+ * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
+ *	   mode feature.
+ * Details:
+ *	When the AUTO mode is the only conversion mode enabled, if the AUTO
+ *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
+ *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
+ *	conversion mechanism can be seen as locked meaning that all following
+ *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
+ *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
+ *	the GPADC.
+ *
+ * Workaround(s):
+ *	To avoid the lock mechanism, the workaround to follow before any stop
+ *	conversion request is:
+ *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
+ *		GPADC_FORCE bit = 1
+ *	Shutdown the GPADC AUTO conversion using
+ *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
+ *	After 100us, force the GPADC state machine to be OFF by using the
+ *		GPADC_CTRL1.  GPADC_FORCE bit = 0
+ */
+
+static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
+{
+	int ret;
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_CTRL1,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE);
+	if (ret < 0) {
+		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
+			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
+			0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
+		return ret;
+	}
+
+	udelay(100);
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_CTRL1,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+	if (ret < 0)
+		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+
+	return ret;
+}
+
+static irqreturn_t palmas_gpadc_irq(int irq, void *data)
+{
+	struct palmas_gpadc *adc = data;
+
+	complete(&adc->conv_completion);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
+{
+	struct palmas_gpadc *adc = data;
+
+	dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq);
+	palmas_disable_auto_conversion(adc);
+
+	return IRQ_HANDLED;
+}
+
+static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc,
+							bool mask)
+{
+	int ret;
+
+	if (!mask)
+		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
+					PALMAS_INT3_MASK,
+					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
+	else
+		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
+					PALMAS_INT3_MASK,
+					PALMAS_INT3_MASK_GPADC_EOC_SW,
+					PALMAS_INT3_MASK_GPADC_EOC_SW);
+	if (ret < 0)
+		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
+			       int enable)
+{
+	unsigned int mask, val;
+	int ret;
+
+	if (enable) {
+		val = (adc->extended_delay
+			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+					PALMAS_GPADC_RT_CTRL,
+					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
+		if (ret < 0) {
+			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
+			return ret;
+		}
+
+		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
+			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
+			PALMAS_GPADC_CTRL1_GPADC_FORCE);
+		val = (adc->ch0_current
+			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
+		val |= (adc->ch3_current
+			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
+		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_CTRL1, mask, val);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"Failed to update current setting: %d\n", ret);
+			return ret;
+		}
+
+		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
+			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
+		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT, mask, val);
+		if (ret < 0) {
+			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
+			return ret;
+		}
+	} else {
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT, 0);
+		if (ret < 0)
+			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
+
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_CTRL1,
+				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+		if (ret < 0) {
+			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
+{
+	int ret;
+
+	ret = palmas_gpadc_enable(adc, adc_chan, true);
+	if (ret < 0)
+		return ret;
+
+	return palmas_gpadc_start_mask_interrupt(adc, 0);
+}
+
+static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
+{
+	palmas_gpadc_start_mask_interrupt(adc, 1);
+	palmas_gpadc_enable(adc, adc_chan, false);
+}
+
+static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
+{
+	int k;
+	int d1;
+	int d2;
+	int ret;
+	int gain;
+	int x1 =  adc->adc_info[adc_chan].x1;
+	int x2 =  adc->adc_info[adc_chan].x2;
+	int v1 = adc->adc_info[adc_chan].v1;
+	int v2 = adc->adc_info[adc_chan].v2;
+
+	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
+				adc->adc_info[adc_chan].trim1_reg, &d1);
+	if (ret < 0) {
+		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
+		goto scrub;
+	}
+
+	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
+				adc->adc_info[adc_chan].trim2_reg, &d2);
+	if (ret < 0) {
+		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
+		goto scrub;
+	}
+
+	/* gain error calculation */
+	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
+
+	/* gain calculation */
+	gain = ((v2 - v1) * 1000) / (x2 - x1);
+
+	adc->adc_info[adc_chan].gain_error = k;
+	adc->adc_info[adc_chan].gain = gain;
+	/* offset Calculation */
+	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
+
+scrub:
+	return ret;
+}
+
+static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
+{
+	unsigned int val;
+	int ret;
+
+	init_completion(&adc->conv_completion);
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT,
+				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
+				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
+	if (ret < 0) {
+		dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = wait_for_completion_timeout(&adc->conv_completion,
+				PALMAS_ADC_CONVERSION_TIMEOUT);
+	if (ret == 0) {
+		dev_err(adc->dev, "conversion not completed\n");
+		return -ETIMEDOUT;
+	}
+
+	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
+	if (ret < 0) {
+		dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = val & 0xFFF;
+
+	return ret;
+}
+
+static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
+						int adc_chan, int val)
+{
+	if (!adc->adc_info[adc_chan].is_uncalibrated)
+		val  = (val*1000 - adc->adc_info[adc_chan].offset) /
+					adc->adc_info[adc_chan].gain_error;
+
+	if (val < 0) {
+		dev_err(adc->dev, "Mismatch with calibration\n");
+		return 0;
+	}
+
+	val = (val * adc->adc_info[adc_chan].gain) / 1000;
+
+	return val;
+}
+
+static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+	struct  palmas_gpadc *adc = iio_priv(indio_dev);
+	int adc_chan = chan->channel;
+	int ret = 0;
+
+	if (adc_chan > PALMAS_ADC_CH_MAX)
+		return -EINVAL;
+
+	mutex_lock(&indio_dev->mlock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = palmas_gpadc_read_prepare(adc, adc_chan);
+		if (ret < 0)
+			goto out;
+
+		ret = palmas_gpadc_start_conversion(adc, adc_chan);
+		if (ret < 0) {
+			dev_err(adc->dev,
+			"ADC start conversion failed\n");
+			goto out;
+		}
+
+		if (mask == IIO_CHAN_INFO_PROCESSED)
+			ret = palmas_gpadc_get_calibrated_code(
+							adc, adc_chan, ret);
+
+		*val = ret;
+
+		ret = IIO_VAL_INT;
+		goto out;
+	}
+
+	mutex_unlock(&indio_dev->mlock);
+	return ret;
+
+out:
+	palmas_gpadc_read_done(adc, adc_chan);
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info palmas_gpadc_iio_info = {
+	.read_raw = palmas_gpadc_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info)	\
+{							\
+	.datasheet_name = PALMAS_DATASHEET_NAME(chan),	\
+	.type = _type,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
+			BIT(chan_info),			\
+	.indexed = 1,					\
+	.channel = PALMAS_ADC_CH_##chan,		\
+}
+
+static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
+	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+};
+
+static int palmas_gpadc_probe(struct platform_device *pdev)
+{
+	struct palmas_gpadc *adc;
+	struct palmas_platform_data *pdata;
+	struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
+	struct iio_dev *indio_dev;
+	int ret, i;
+
+	pdata = dev_get_platdata(pdev->dev.parent);
+	if (!pdata || !pdata->gpadc_pdata) {
+		dev_err(&pdev->dev, "No platform data\n");
+		return -ENODEV;
+	}
+
+	gpadc_pdata = pdata->gpadc_pdata;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "iio_device_alloc failed\n");
+		return -ENOMEM;
+	}
+
+	adc = iio_priv(indio_dev);
+	adc->dev = &pdev->dev;
+	adc->palmas = dev_get_drvdata(pdev->dev.parent);
+	adc->adc_info = palmas_gpadc_info;
+	init_completion(&adc->conv_completion);
+	dev_set_drvdata(&pdev->dev, indio_dev);
+
+	adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
+	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
+	if (adc->irq < 0) {
+		dev_err(adc->dev,
+			"get virq failed: %d\n", adc->irq);
+		ret = adc->irq;
+		goto out;
+	}
+	ret = request_threaded_irq(adc->irq, NULL,
+		palmas_gpadc_irq,
+		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
+		adc);
+	if (ret < 0) {
+		dev_err(adc->dev,
+			"request irq %d failed: %d\n", adc->irq, ret);
+		goto out;
+	}
+
+	if (gpadc_pdata->adc_wakeup1_data) {
+		memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
+			sizeof(adc->wakeup1_data));
+		adc->wakeup1_enable = true;
+		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
+		ret = request_threaded_irq(adc->irq_auto_0, NULL,
+				palmas_gpadc_irq_auto,
+				IRQF_ONESHOT | IRQF_EARLY_RESUME,
+				"palmas-adc-auto-0", adc);
+		if (ret < 0) {
+			dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
+				adc->irq_auto_0, ret);
+			goto out_irq_free;
+		}
+	}
+
+	if (gpadc_pdata->adc_wakeup2_data) {
+		memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
+				sizeof(adc->wakeup2_data));
+		adc->wakeup2_enable = true;
+		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
+		ret = request_threaded_irq(adc->irq_auto_1, NULL,
+				palmas_gpadc_irq_auto,
+				IRQF_ONESHOT | IRQF_EARLY_RESUME,
+				"palmas-adc-auto-1", adc);
+		if (ret < 0) {
+			dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
+				adc->irq_auto_1, ret);
+			goto out_irq_auto0_free;
+		}
+	}
+
+	/* set the current source 0 (value 0/5/15/20 uA => 0..3) */
+	if (gpadc_pdata->ch0_current <= 1)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
+	else if (gpadc_pdata->ch0_current <= 5)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
+	else if (gpadc_pdata->ch0_current <= 15)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
+	else
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
+
+	/* set the current source 3 (value 0/10/400/800 uA => 0..3) */
+	if (gpadc_pdata->ch3_current <= 1)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
+	else if (gpadc_pdata->ch3_current <= 10)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
+	else if (gpadc_pdata->ch3_current <= 400)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
+	else
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
+
+	adc->extended_delay = gpadc_pdata->extended_delay;
+
+	indio_dev->name = MOD_NAME;
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->info = &palmas_gpadc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = palmas_gpadc_iio_channel;
+	indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0) {
+		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
+		goto out_irq_auto1_free;
+	}
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
+		if (!(adc->adc_info[i].is_uncalibrated))
+			palmas_gpadc_calibrate(adc, i);
+	}
+
+	if (adc->wakeup1_enable || adc->wakeup2_enable)
+		device_wakeup_enable(&pdev->dev);
+
+	return 0;
+
+out_irq_auto1_free:
+	if (gpadc_pdata->adc_wakeup2_data)
+		free_irq(adc->irq_auto_1, adc);
+out_irq_auto0_free:
+	if (gpadc_pdata->adc_wakeup1_data)
+		free_irq(adc->irq_auto_0, adc);
+out_irq_free:
+	free_irq(adc->irq, adc);
+out:
+	return ret;
+}
+
+static int palmas_gpadc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev);
+	struct palmas_gpadc *adc = iio_priv(indio_dev);
+
+	if (adc->wakeup1_enable || adc->wakeup2_enable)
+		device_wakeup_disable(&pdev->dev);
+	iio_device_unregister(indio_dev);
+	free_irq(adc->irq, adc);
+	if (adc->wakeup1_enable)
+		free_irq(adc->irq_auto_0, adc);
+	if (adc->wakeup2_enable)
+		free_irq(adc->irq_auto_1, adc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
+{
+	int adc_period, conv;
+	int i;
+	int ch0 = 0, ch1 = 0;
+	int thres;
+	int ret;
+
+	adc_period = adc->auto_conversion_period;
+	for (i = 0; i < 16; ++i) {
+		if (((1000 * (1 << i)) / 32) < adc_period)
+			continue;
+	}
+	if (i > 0)
+		i--;
+	adc_period = i;
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
+			adc_period);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+		return ret;
+	}
+
+	conv = 0;
+	if (adc->wakeup1_enable) {
+		int polarity;
+
+		ch0 = adc->wakeup1_data.adc_channel_number;
+		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
+		if (adc->wakeup1_data.adc_high_threshold > 0) {
+			thres = adc->wakeup1_data.adc_high_threshold;
+			polarity = 0;
+		} else {
+			thres = adc->wakeup1_data.adc_low_threshold;
+			polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV0_LSB write failed: %d\n", ret);
+			return ret;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV0_MSB,
+				((thres >> 8) & 0xF) | polarity);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV0_MSB write failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (adc->wakeup2_enable) {
+		int polarity;
+
+		ch1 = adc->wakeup2_data.adc_channel_number;
+		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
+		if (adc->wakeup2_data.adc_high_threshold > 0) {
+			thres = adc->wakeup2_data.adc_high_threshold;
+			polarity = 0;
+		} else {
+			thres = adc->wakeup2_data.adc_low_threshold;
+			polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV1_LSB write failed: %d\n", ret);
+			return ret;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV1_MSB,
+				((thres >> 8) & 0xF) | polarity);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV1_MSB write failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
+			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
+	if (ret < 0)
+		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
+{
+	int ret;
+
+	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_SELECT, 0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_disable_auto_conversion(adc);
+	if (ret < 0)
+		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_gpadc_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct palmas_gpadc *adc = iio_priv(indio_dev);
+	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+	int ret;
+
+	if (!device_may_wakeup(dev) || !wakeup)
+		return 0;
+
+	ret = palmas_adc_wakeup_configure(adc);
+	if (ret < 0)
+		return ret;
+
+	if (adc->wakeup1_enable)
+		enable_irq_wake(adc->irq_auto_0);
+
+	if (adc->wakeup2_enable)
+		enable_irq_wake(adc->irq_auto_1);
+
+	return 0;
+}
+
+static int palmas_gpadc_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct palmas_gpadc *adc = iio_priv(indio_dev);
+	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+	int ret;
+
+	if (!device_may_wakeup(dev) || !wakeup)
+		return 0;
+
+	ret = palmas_adc_wakeup_reset(adc);
+	if (ret < 0)
+		return ret;
+
+	if (adc->wakeup1_enable)
+		disable_irq_wake(adc->irq_auto_0);
+
+	if (adc->wakeup2_enable)
+		disable_irq_wake(adc->irq_auto_1);
+
+	return 0;
+};
+#endif
+
+static const struct dev_pm_ops palmas_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
+				palmas_gpadc_resume)
+};
+
+static struct platform_driver palmas_gpadc_driver = {
+	.probe = palmas_gpadc_probe,
+	.remove = palmas_gpadc_remove,
+	.driver = {
+		.name = MOD_NAME,
+		.owner = THIS_MODULE,
+		.pm = &palmas_pm_ops,
+	},
+};
+
+static int __init palmas_gpadc_init(void)
+{
+	return platform_driver_register(&palmas_gpadc_driver);
+}
+module_init(palmas_gpadc_init);
+
+static void __exit palmas_gpadc_exit(void)
+{
+	platform_driver_unregister(&palmas_gpadc_driver);
+}
+module_exit(palmas_gpadc_exit);
+
+MODULE_DESCRIPTION("palmas GPADC driver");
+MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
+MODULE_ALIAS("platform:palmas-gpadc");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
index bb270bd..2fcd70e 100644
--- a/include/linux/mfd/palmas.h
+++ b/include/linux/mfd/palmas.h
@@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
			    struct regulator_config config);
};

+struct palmas_adc_wakeup_property {
+	int adc_channel_number;
+	int adc_high_threshold;
+	int adc_low_threshold;
+};
+
struct palmas_gpadc_platform_data {
	/* Channel 3 current source is only enabled during conversion */
-	int ch3_current;
+	int ch3_current;	/* 0: off; 1: 10uA; 2: 400uA; 3: 800 uA */

	/* Channel 0 current source can be used for battery detection.
	 * If used for battery detection this will cause a permanent current
	 * consumption depending on current level set here.
	 */
-	int ch0_current;
+	int ch0_current;	/* 0: off; 1: 5uA; 2: 15uA; 3: 20 uA */
+	bool extended_delay;	/* use extended delay for conversion */

	/* default BAT_REMOVAL_DAT setting on device probe */
	int bat_removal;

	/* Sets the START_POLARITY bit in the RT_CTRL register */
	int start_polarity;
+
+	int auto_conversion_period_ms;
+	struct palmas_adc_wakeup_property *adc_wakeup1_data;
+	struct palmas_adc_wakeup_property *adc_wakeup2_data;
};

struct palmas_reg_init {
@@ -404,28 +415,7 @@ struct palmas_gpadc_calibration {
	s32 offset_error;
};

-struct palmas_gpadc {
-	struct device *dev;
-	struct palmas *palmas;
-
-	int ch3_current;
-	int ch0_current;
-
-	int gpadc_force;
-
-	int bat_removal;
-
-	struct mutex reading_lock;
-	struct completion irq_complete;
-
-	int eoc_sw_irq;
-
-	struct palmas_gpadc_calibration *palmas_cal_tbl;
-
-	int conv0_channel;
-	int conv1_channel;
-	int rt_channel;
-};
+#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name

struct palmas_gpadc_result {
	s32 raw_code;
@@ -519,6 +509,43 @@ enum palmas_irqs {
	PALMAS_NUM_IRQ,
};

+/* Palmas GPADC Channels */
+enum {
+	PALMAS_ADC_CH_IN0,
+	PALMAS_ADC_CH_IN1,
+	PALMAS_ADC_CH_IN2,
+	PALMAS_ADC_CH_IN3,
+	PALMAS_ADC_CH_IN4,
+	PALMAS_ADC_CH_IN5,
+	PALMAS_ADC_CH_IN6,
+	PALMAS_ADC_CH_IN7,
+	PALMAS_ADC_CH_IN8,
+	PALMAS_ADC_CH_IN9,
+	PALMAS_ADC_CH_IN10,
+	PALMAS_ADC_CH_IN11,
+	PALMAS_ADC_CH_IN12,
+	PALMAS_ADC_CH_IN13,
+	PALMAS_ADC_CH_IN14,
+	PALMAS_ADC_CH_IN15,
+	PALMAS_ADC_CH_MAX,
+};
+
+/* Palmas GPADC Channel0 Current Source */
+enum {
+	PALMAS_ADC_CH0_CURRENT_SRC_0,
+	PALMAS_ADC_CH0_CURRENT_SRC_5,
+	PALMAS_ADC_CH0_CURRENT_SRC_15,
+	PALMAS_ADC_CH0_CURRENT_SRC_20,
+};
+
+/* Palmas GPADC Channel3 Current Source */
+enum {
+	PALMAS_ADC_CH3_CURRENT_SRC_0,
+	PALMAS_ADC_CH3_CURRENT_SRC_10,
+	PALMAS_ADC_CH3_CURRENT_SRC_400,
+	PALMAS_ADC_CH3_CURRENT_SRC_800,
+};
+
struct palmas_pmic {
	struct palmas *palmas;
	struct device *dev;
-- 
2.5.1



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

* [PATCH v2 0/3] Add Palmas iio gpadc
  2015-09-23 12:48 ` [PATCH 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
       [not found]   ` <cover.1443973837.git.hns@goldelico.com>
@ 2015-10-05  6:14   ` H. Nikolaus Schaller
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
                       ` (2 more replies)
  1 sibling, 3 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-10-05  6:14 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, H. Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas

V2 changes:
- worked in review comments by Peter Meerwald
- made channels 1 and 3 report (raw) temperature values
- moved PALMAS_GPADC_TRIMINVALID to driver core
- worked in review comments by Jonathan Cameron
- iio_map removed

Add iio driver for the TI Palmas (twl6035, 6037) including device tree bindings.
It enables the gpadc for the OMAP5 uevm.

This patch series is based on original code taken from Android Tegra kernels:
(https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc)

Tegra code was developed by:
Pradeep Goudagunta <pgoudagunta@nvidia.com>
Laxman Dewangan <ldewangan@nvidia.com>

Edited and extended for mainline by:
H. Nikolaus Schaller <hns@goldelico.com>
Marek Belisko <marek@goldelico.com>


H. Nikolaus Schaller (2):
  iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  ARM: dts: omap5-uevm: enable iio gpadc for Palmas

Marek Belisko (1):
  iio:adc:palmas: add DT support

 .../devicetree/bindings/iio/adc/palmas-gpadc.txt   |  46 ++
 arch/arm/boot/dts/omap5-uevm.dts                   |   9 +
 drivers/iio/adc/Kconfig                            |   9 +
 drivers/iio/adc/Makefile                           |   1 +
 drivers/iio/adc/palmas_gpadc.c                     | 860 +++++++++++++++++++++
 include/linux/mfd/palmas.h                         |  75 +-
 6 files changed, 976 insertions(+), 24 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
 create mode 100644 drivers/iio/adc/palmas_gpadc.c

-- 
2.5.1


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

* [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-10-05  6:14   ` [PATCH v2 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
@ 2015-10-05  6:14     ` H. Nikolaus Schaller
  2015-10-05  6:54       ` kbuild test robot
                         ` (3 more replies)
  2015-10-05  6:14     ` [PATCH v2 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
  2015-10-05  6:14     ` [PATCH v2 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
  2 siblings, 4 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-10-05  6:14 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, H. Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas

This driver code was found as:

https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc

Fixed various compilation issues and test this driver on omap5 evm.

Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Signed-off-by: Marek Belisko <marek@goldelico.com>
---
 drivers/iio/adc/Kconfig        |   9 +
 drivers/iio/adc/Makefile       |   1 +
 drivers/iio/adc/palmas_gpadc.c | 818 +++++++++++++++++++++++++++++++++++++++++
 include/linux/mfd/palmas.h     |  75 ++--
 4 files changed, 879 insertions(+), 24 deletions(-)
 create mode 100644 drivers/iio/adc/palmas_gpadc.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index eb0cd89..05a0368 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -242,6 +242,15 @@ config NAU7802
 	  To compile this driver as a module, choose M here: the
 	  module will be called nau7802.
 
+config PALMAS_GPADC
+	tristate "TI Palmas General Purpose ADC"
+	depends on MFD_PALMAS
+	help
+	  Say yes here to build support for Texas Instruments Palmas.
+
+	  Palmas series pmic chip (twl6035/6037) is used in smartphones and
+	  tablets and supports a 16 channel general purpose ADC.
+
 config QCOM_SPMI_IADC
 	tristate "Qualcomm SPMI PMIC current ADC"
 	depends on SPMI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index a096210..716f112 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
 obj-$(CONFIG_MCP3422) += mcp3422.o
 obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
+obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
 obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
 obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
 obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
new file mode 100644
index 0000000..6805d81
--- /dev/null
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -0,0 +1,818 @@
+/*
+ * palmas-adc.c -- TI PALMAS GPADC.
+ *
+ * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
+ *
+ * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/pm.h>
+#include <linux/mfd/palmas.h>
+#include <linux/completion.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/machine.h>
+#include <linux/iio/driver.h>
+
+#define MOD_NAME "palmas-gpadc"
+#define PALMAS_ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
+#define PALMAS_TO_BE_CALCULATED 0
+#define PALMAS_GPADC_TRIMINVALID	-1
+
+struct palmas_gpadc_info {
+/* calibration codes and regs */
+	int x1;	/* lower ideal code */
+	int x2;	/* higher ideal code */
+	int v1;	/* expected lower volt reading */
+	int v2;	/* expected higher volt reading */
+	u8 trim1_reg;	/* register number for lower trim */
+	u8 trim2_reg;	/* register number for upper trim */
+	int gain;	/* calculated from above (after reading trim regs) */
+	int offset;	/* calculated from above (after reading trim regs) */
+	int gain_error;	/* calculated from above (after reading trim regs) */
+	bool is_uncalibrated;	/* if channel has calibration data */
+};
+
+#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \
+	[PALMAS_ADC_CH_##_chan] = { \
+		.x1 = _x1, \
+		.x2 = _x2, \
+		.v1 = _v1, \
+		.v2 = _v2, \
+		.gain = PALMAS_TO_BE_CALCULATED, \
+		.offset = PALMAS_TO_BE_CALCULATED, \
+		.gain_error = PALMAS_TO_BE_CALCULATED, \
+		.trim1_reg = PALMAS_GPADC_TRIM##_t1, \
+		.trim2_reg = PALMAS_GPADC_TRIM##_t2,  \
+		.is_uncalibrated = _is_uncalibrated \
+	}
+
+static struct palmas_gpadc_info palmas_gpadc_info[] = {
+	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
+	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
+	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
+	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
+	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
+	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
+	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
+	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
+	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
+	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
+};
+
+/**
+ * struct palmas_gpadc - the palmas_gpadc structure
+ * @ch0_current:	channel 0 current source setting
+ *    0: 0 uA
+ *    1: 5 uA
+ *    2: 15 uA
+ *    3: 20 uA
+ * @ch3_current:	channel 3 current source setting
+ *    0: 0 uA
+ *    1: 10 uA
+ *    2: 400 uA
+ *    3: 800 uA
+ * @extended_delay:	enable the gpadc extended delay mode
+ * @auto_conversion_period:	define the auto_conversion_period
+ *
+ * This is the palmas_gpadc structure to store run-time information
+ * and pointers for this driver instance.
+ */
+
+struct palmas_gpadc {
+	struct device			*dev;
+	struct palmas			*palmas;
+	u8				ch0_current;
+	u8				ch3_current;
+	bool				extended_delay;
+	int				irq;
+	int				irq_auto_0;
+	int				irq_auto_1;
+	struct palmas_gpadc_info	*adc_info;
+	struct completion		conv_completion;
+	struct palmas_adc_wakeup_property wakeup1_data;
+	struct palmas_adc_wakeup_property wakeup2_data;
+	bool				wakeup1_enable;
+	bool				wakeup2_enable;
+	int				auto_conversion_period;
+};
+
+/*
+ * GPADC lock issue in AUTO mode.
+ * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
+ *	   mode feature.
+ * Details:
+ *	When the AUTO mode is the only conversion mode enabled, if the AUTO
+ *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
+ *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
+ *	conversion mechanism can be seen as locked meaning that all following
+ *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
+ *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
+ *	the GPADC.
+ *
+ * Workaround(s):
+ *	To avoid the lock mechanism, the workaround to follow before any stop
+ *	conversion request is:
+ *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
+ *		GPADC_FORCE bit = 1
+ *	Shutdown the GPADC AUTO conversion using
+ *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
+ *	After 100us, force the GPADC state machine to be OFF by using the
+ *		GPADC_CTRL1.  GPADC_FORCE bit = 0
+ */
+
+static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
+{
+	int ret;
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_CTRL1,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE);
+	if (ret < 0) {
+		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
+			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
+			0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
+		return ret;
+	}
+
+	udelay(100);
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_CTRL1,
+			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+	if (ret < 0)
+		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
+
+	return ret;
+}
+
+static irqreturn_t palmas_gpadc_irq(int irq, void *data)
+{
+	struct palmas_gpadc *adc = data;
+
+	complete(&adc->conv_completion);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
+{
+	struct palmas_gpadc *adc = data;
+
+	dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq);
+	palmas_disable_auto_conversion(adc);
+
+	return IRQ_HANDLED;
+}
+
+static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc,
+							bool mask)
+{
+	int ret;
+
+	if (!mask)
+		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
+					PALMAS_INT3_MASK,
+					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
+	else
+		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
+					PALMAS_INT3_MASK,
+					PALMAS_INT3_MASK_GPADC_EOC_SW,
+					PALMAS_INT3_MASK_GPADC_EOC_SW);
+	if (ret < 0)
+		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
+			       int enable)
+{
+	unsigned int mask, val;
+	int ret;
+
+	if (enable) {
+		val = (adc->extended_delay
+			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+					PALMAS_GPADC_RT_CTRL,
+					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
+		if (ret < 0) {
+			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
+			return ret;
+		}
+
+		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
+			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
+			PALMAS_GPADC_CTRL1_GPADC_FORCE);
+		val = (adc->ch0_current
+			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
+		val |= (adc->ch3_current
+			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
+		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_CTRL1, mask, val);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"Failed to update current setting: %d\n", ret);
+			return ret;
+		}
+
+		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
+			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
+		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT, mask, val);
+		if (ret < 0) {
+			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
+			return ret;
+		}
+	} else {
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT, 0);
+		if (ret < 0)
+			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
+
+		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_CTRL1,
+				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
+		if (ret < 0) {
+			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
+{
+	int ret;
+
+	ret = palmas_gpadc_enable(adc, adc_chan, true);
+	if (ret < 0)
+		return ret;
+
+	return palmas_gpadc_start_mask_interrupt(adc, 0);
+}
+
+static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
+{
+	palmas_gpadc_start_mask_interrupt(adc, 1);
+	palmas_gpadc_enable(adc, adc_chan, false);
+}
+
+static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
+{
+	int k;
+	int d1;
+	int d2;
+	int ret;
+	int gain;
+	int x1 =  adc->adc_info[adc_chan].x1;
+	int x2 =  adc->adc_info[adc_chan].x2;
+	int v1 = adc->adc_info[adc_chan].v1;
+	int v2 = adc->adc_info[adc_chan].v2;
+
+	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
+				adc->adc_info[adc_chan].trim1_reg, &d1);
+	if (ret < 0) {
+		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
+		goto scrub;
+	}
+
+	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
+				adc->adc_info[adc_chan].trim2_reg, &d2);
+	if (ret < 0) {
+		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
+		goto scrub;
+	}
+
+	/* gain error calculation */
+	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
+
+	/* gain calculation */
+	gain = ((v2 - v1) * 1000) / (x2 - x1);
+
+	adc->adc_info[adc_chan].gain_error = k;
+	adc->adc_info[adc_chan].gain = gain;
+	/* offset Calculation */
+	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
+
+scrub:
+	return ret;
+}
+
+static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
+{
+	unsigned int val;
+	int ret;
+
+	init_completion(&adc->conv_completion);
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_SELECT,
+				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
+				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
+	if (ret < 0) {
+		dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = wait_for_completion_timeout(&adc->conv_completion,
+				PALMAS_ADC_CONVERSION_TIMEOUT);
+	if (ret == 0) {
+		dev_err(adc->dev, "conversion not completed\n");
+		return -ETIMEDOUT;
+	}
+
+	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
+	if (ret < 0) {
+		dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = val & 0xFFF;
+
+	return ret;
+}
+
+static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
+						int adc_chan, int val)
+{
+	if (!adc->adc_info[adc_chan].is_uncalibrated)
+		val  = (val*1000 - adc->adc_info[adc_chan].offset) /
+					adc->adc_info[adc_chan].gain_error;
+
+	if (val < 0) {
+		dev_err(adc->dev, "Mismatch with calibration\n");
+		return 0;
+	}
+
+	val = (val * adc->adc_info[adc_chan].gain) / 1000;
+
+	return val;
+}
+
+static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
+	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
+{
+	struct  palmas_gpadc *adc = iio_priv(indio_dev);
+	int adc_chan = chan->channel;
+	int ret = 0;
+
+	if (adc_chan > PALMAS_ADC_CH_MAX)
+		return -EINVAL;
+
+	mutex_lock(&indio_dev->mlock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = palmas_gpadc_read_prepare(adc, adc_chan);
+		if (ret < 0)
+			goto out;
+
+		ret = palmas_gpadc_start_conversion(adc, adc_chan);
+		if (ret < 0) {
+			dev_err(adc->dev,
+			"ADC start conversion failed\n");
+			goto out;
+		}
+
+		if (mask == IIO_CHAN_INFO_PROCESSED)
+			ret = palmas_gpadc_get_calibrated_code(
+							adc, adc_chan, ret);
+
+		*val = ret;
+
+		ret = IIO_VAL_INT;
+		goto out;
+	}
+
+	mutex_unlock(&indio_dev->mlock);
+	return ret;
+
+out:
+	palmas_gpadc_read_done(adc, adc_chan);
+	mutex_unlock(&indio_dev->mlock);
+
+	return ret;
+}
+
+static const struct iio_info palmas_gpadc_iio_info = {
+	.read_raw = palmas_gpadc_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info)	\
+{							\
+	.datasheet_name = PALMAS_DATASHEET_NAME(chan),	\
+	.type = _type,					\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
+			BIT(chan_info),			\
+	.indexed = 1,					\
+	.channel = PALMAS_ADC_CH_##chan,		\
+}
+
+static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
+	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW),
+	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
+};
+
+static int palmas_gpadc_probe(struct platform_device *pdev)
+{
+	struct palmas_gpadc *adc;
+	struct palmas_platform_data *pdata;
+	struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
+	struct iio_dev *indio_dev;
+	int ret, i;
+
+	pdata = dev_get_platdata(pdev->dev.parent);
+	if (!pdata || !pdata->gpadc_pdata) {
+		dev_err(&pdev->dev, "No platform data\n");
+		return -ENODEV;
+	}
+
+	gpadc_pdata = pdata->gpadc_pdata;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
+	if (!indio_dev) {
+		dev_err(&pdev->dev, "iio_device_alloc failed\n");
+		return -ENOMEM;
+	}
+
+	adc = iio_priv(indio_dev);
+	adc->dev = &pdev->dev;
+	adc->palmas = dev_get_drvdata(pdev->dev.parent);
+	adc->adc_info = palmas_gpadc_info;
+	init_completion(&adc->conv_completion);
+	dev_set_drvdata(&pdev->dev, indio_dev);
+
+	adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
+	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
+	if (adc->irq < 0) {
+		dev_err(adc->dev,
+			"get virq failed: %d\n", adc->irq);
+		ret = adc->irq;
+		goto out;
+	}
+	ret = request_threaded_irq(adc->irq, NULL,
+		palmas_gpadc_irq,
+		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
+		adc);
+	if (ret < 0) {
+		dev_err(adc->dev,
+			"request irq %d failed: %d\n", adc->irq, ret);
+		goto out;
+	}
+
+	if (gpadc_pdata->adc_wakeup1_data) {
+		memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
+			sizeof(adc->wakeup1_data));
+		adc->wakeup1_enable = true;
+		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
+		ret = request_threaded_irq(adc->irq_auto_0, NULL,
+				palmas_gpadc_irq_auto,
+				IRQF_ONESHOT | IRQF_EARLY_RESUME,
+				"palmas-adc-auto-0", adc);
+		if (ret < 0) {
+			dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
+				adc->irq_auto_0, ret);
+			goto out_irq_free;
+		}
+	}
+
+	if (gpadc_pdata->adc_wakeup2_data) {
+		memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
+				sizeof(adc->wakeup2_data));
+		adc->wakeup2_enable = true;
+		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
+		ret = request_threaded_irq(adc->irq_auto_1, NULL,
+				palmas_gpadc_irq_auto,
+				IRQF_ONESHOT | IRQF_EARLY_RESUME,
+				"palmas-adc-auto-1", adc);
+		if (ret < 0) {
+			dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
+				adc->irq_auto_1, ret);
+			goto out_irq_auto0_free;
+		}
+	}
+
+	/* set the current source 0 (value 0/5/15/20 uA => 0..3) */
+	if (gpadc_pdata->ch0_current <= 1)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
+	else if (gpadc_pdata->ch0_current <= 5)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
+	else if (gpadc_pdata->ch0_current <= 15)
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
+	else
+		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
+
+	/* set the current source 3 (value 0/10/400/800 uA => 0..3) */
+	if (gpadc_pdata->ch3_current <= 1)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
+	else if (gpadc_pdata->ch3_current <= 10)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
+	else if (gpadc_pdata->ch3_current <= 400)
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
+	else
+		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
+
+	adc->extended_delay = gpadc_pdata->extended_delay;
+
+	indio_dev->name = MOD_NAME;
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->info = &palmas_gpadc_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = palmas_gpadc_iio_channel;
+	indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
+
+	ret = iio_device_register(indio_dev);
+	if (ret < 0) {
+		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
+		goto out_irq_auto1_free;
+	}
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
+		if (!(adc->adc_info[i].is_uncalibrated))
+			palmas_gpadc_calibrate(adc, i);
+	}
+
+	if (adc->wakeup1_enable || adc->wakeup2_enable)
+		device_wakeup_enable(&pdev->dev);
+
+	return 0;
+
+out_irq_auto1_free:
+	if (gpadc_pdata->adc_wakeup2_data)
+		free_irq(adc->irq_auto_1, adc);
+out_irq_auto0_free:
+	if (gpadc_pdata->adc_wakeup1_data)
+		free_irq(adc->irq_auto_0, adc);
+out_irq_free:
+	free_irq(adc->irq, adc);
+out:
+	return ret;
+}
+
+static int palmas_gpadc_remove(struct platform_device *pdev)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev);
+	struct palmas_gpadc *adc = iio_priv(indio_dev);
+
+	if (adc->wakeup1_enable || adc->wakeup2_enable)
+		device_wakeup_disable(&pdev->dev);
+	iio_device_unregister(indio_dev);
+	free_irq(adc->irq, adc);
+	if (adc->wakeup1_enable)
+		free_irq(adc->irq_auto_0, adc);
+	if (adc->wakeup2_enable)
+		free_irq(adc->irq_auto_1, adc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
+{
+	int adc_period, conv;
+	int i;
+	int ch0 = 0, ch1 = 0;
+	int thres;
+	int ret;
+
+	adc_period = adc->auto_conversion_period;
+	for (i = 0; i < 16; ++i) {
+		if (((1000 * (1 << i)) / 32) < adc_period)
+			continue;
+	}
+	if (i > 0)
+		i--;
+	adc_period = i;
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
+			adc_period);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+		return ret;
+	}
+
+	conv = 0;
+	if (adc->wakeup1_enable) {
+		int polarity;
+
+		ch0 = adc->wakeup1_data.adc_channel_number;
+		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
+		if (adc->wakeup1_data.adc_high_threshold > 0) {
+			thres = adc->wakeup1_data.adc_high_threshold;
+			polarity = 0;
+		} else {
+			thres = adc->wakeup1_data.adc_low_threshold;
+			polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV0_LSB write failed: %d\n", ret);
+			return ret;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV0_MSB,
+				((thres >> 8) & 0xF) | polarity);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV0_MSB write failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (adc->wakeup2_enable) {
+		int polarity;
+
+		ch1 = adc->wakeup2_data.adc_channel_number;
+		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
+		if (adc->wakeup2_data.adc_high_threshold > 0) {
+			thres = adc->wakeup2_data.adc_high_threshold;
+			polarity = 0;
+		} else {
+			thres = adc->wakeup2_data.adc_low_threshold;
+			polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV1_LSB write failed: %d\n", ret);
+			return ret;
+		}
+
+		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+				PALMAS_GPADC_THRES_CONV1_MSB,
+				((thres >> 8) & 0xF) | polarity);
+		if (ret < 0) {
+			dev_err(adc->dev,
+				"THRES_CONV1_MSB write failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_CTRL,
+			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
+			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
+	if (ret < 0)
+		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
+{
+	int ret;
+
+	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
+			PALMAS_GPADC_AUTO_SELECT, 0);
+	if (ret < 0) {
+		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = palmas_disable_auto_conversion(adc);
+	if (ret < 0)
+		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
+
+	return ret;
+}
+
+static int palmas_gpadc_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct palmas_gpadc *adc = iio_priv(indio_dev);
+	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+	int ret;
+
+	if (!device_may_wakeup(dev) || !wakeup)
+		return 0;
+
+	ret = palmas_adc_wakeup_configure(adc);
+	if (ret < 0)
+		return ret;
+
+	if (adc->wakeup1_enable)
+		enable_irq_wake(adc->irq_auto_0);
+
+	if (adc->wakeup2_enable)
+		enable_irq_wake(adc->irq_auto_1);
+
+	return 0;
+}
+
+static int palmas_gpadc_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct palmas_gpadc *adc = iio_priv(indio_dev);
+	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
+	int ret;
+
+	if (!device_may_wakeup(dev) || !wakeup)
+		return 0;
+
+	ret = palmas_adc_wakeup_reset(adc);
+	if (ret < 0)
+		return ret;
+
+	if (adc->wakeup1_enable)
+		disable_irq_wake(adc->irq_auto_0);
+
+	if (adc->wakeup2_enable)
+		disable_irq_wake(adc->irq_auto_1);
+
+	return 0;
+};
+#endif
+
+static const struct dev_pm_ops palmas_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
+				palmas_gpadc_resume)
+};
+
+static struct platform_driver palmas_gpadc_driver = {
+	.probe = palmas_gpadc_probe,
+	.remove = palmas_gpadc_remove,
+	.driver = {
+		.name = MOD_NAME,
+		.owner = THIS_MODULE,
+		.pm = &palmas_pm_ops,
+	},
+};
+
+static int __init palmas_gpadc_init(void)
+{
+	return platform_driver_register(&palmas_gpadc_driver);
+}
+module_init(palmas_gpadc_init);
+
+static void __exit palmas_gpadc_exit(void)
+{
+	platform_driver_unregister(&palmas_gpadc_driver);
+}
+module_exit(palmas_gpadc_exit);
+
+MODULE_DESCRIPTION("palmas GPADC driver");
+MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
+MODULE_ALIAS("platform:palmas-gpadc");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
index bb270bd..2fcd70e 100644
--- a/include/linux/mfd/palmas.h
+++ b/include/linux/mfd/palmas.h
@@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
 			    struct regulator_config config);
 };
 
+struct palmas_adc_wakeup_property {
+	int adc_channel_number;
+	int adc_high_threshold;
+	int adc_low_threshold;
+};
+
 struct palmas_gpadc_platform_data {
 	/* Channel 3 current source is only enabled during conversion */
-	int ch3_current;
+	int ch3_current;	/* 0: off; 1: 10uA; 2: 400uA; 3: 800 uA */
 
 	/* Channel 0 current source can be used for battery detection.
 	 * If used for battery detection this will cause a permanent current
 	 * consumption depending on current level set here.
 	 */
-	int ch0_current;
+	int ch0_current;	/* 0: off; 1: 5uA; 2: 15uA; 3: 20 uA */
+	bool extended_delay;	/* use extended delay for conversion */
 
 	/* default BAT_REMOVAL_DAT setting on device probe */
 	int bat_removal;
 
 	/* Sets the START_POLARITY bit in the RT_CTRL register */
 	int start_polarity;
+
+	int auto_conversion_period_ms;
+	struct palmas_adc_wakeup_property *adc_wakeup1_data;
+	struct palmas_adc_wakeup_property *adc_wakeup2_data;
 };
 
 struct palmas_reg_init {
@@ -404,28 +415,7 @@ struct palmas_gpadc_calibration {
 	s32 offset_error;
 };
 
-struct palmas_gpadc {
-	struct device *dev;
-	struct palmas *palmas;
-
-	int ch3_current;
-	int ch0_current;
-
-	int gpadc_force;
-
-	int bat_removal;
-
-	struct mutex reading_lock;
-	struct completion irq_complete;
-
-	int eoc_sw_irq;
-
-	struct palmas_gpadc_calibration *palmas_cal_tbl;
-
-	int conv0_channel;
-	int conv1_channel;
-	int rt_channel;
-};
+#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
 
 struct palmas_gpadc_result {
 	s32 raw_code;
@@ -519,6 +509,43 @@ enum palmas_irqs {
 	PALMAS_NUM_IRQ,
 };
 
+/* Palmas GPADC Channels */
+enum {
+	PALMAS_ADC_CH_IN0,
+	PALMAS_ADC_CH_IN1,
+	PALMAS_ADC_CH_IN2,
+	PALMAS_ADC_CH_IN3,
+	PALMAS_ADC_CH_IN4,
+	PALMAS_ADC_CH_IN5,
+	PALMAS_ADC_CH_IN6,
+	PALMAS_ADC_CH_IN7,
+	PALMAS_ADC_CH_IN8,
+	PALMAS_ADC_CH_IN9,
+	PALMAS_ADC_CH_IN10,
+	PALMAS_ADC_CH_IN11,
+	PALMAS_ADC_CH_IN12,
+	PALMAS_ADC_CH_IN13,
+	PALMAS_ADC_CH_IN14,
+	PALMAS_ADC_CH_IN15,
+	PALMAS_ADC_CH_MAX,
+};
+
+/* Palmas GPADC Channel0 Current Source */
+enum {
+	PALMAS_ADC_CH0_CURRENT_SRC_0,
+	PALMAS_ADC_CH0_CURRENT_SRC_5,
+	PALMAS_ADC_CH0_CURRENT_SRC_15,
+	PALMAS_ADC_CH0_CURRENT_SRC_20,
+};
+
+/* Palmas GPADC Channel3 Current Source */
+enum {
+	PALMAS_ADC_CH3_CURRENT_SRC_0,
+	PALMAS_ADC_CH3_CURRENT_SRC_10,
+	PALMAS_ADC_CH3_CURRENT_SRC_400,
+	PALMAS_ADC_CH3_CURRENT_SRC_800,
+};
+
 struct palmas_pmic {
 	struct palmas *palmas;
 	struct device *dev;
-- 
2.5.1


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

* [PATCH v2 2/3] iio:adc:palmas: add DT support
  2015-10-05  6:14   ` [PATCH v2 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
@ 2015-10-05  6:14     ` H. Nikolaus Schaller
  2015-10-05 10:53       ` Laxman Dewangan
  2015-10-05  6:14     ` [PATCH v2 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
  2 siblings, 1 reply; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-10-05  6:14 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, H. Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas

From: Marek Belisko <marek@goldelico.com>

Code was found at:
https://android.googlesource.com/kernel/tegra/+/a90856a6626d502d42c6e7abccbdf9d730b36270%5E%21/#F1

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
[Fixed minor typos + add channels list to documentation]
Signed-off-by: Marek Belisko <marek@goldelico.com>
---
 .../devicetree/bindings/iio/adc/palmas-gpadc.txt   | 46 +++++++++++++++++++
 drivers/iio/adc/palmas_gpadc.c                     | 52 +++++++++++++++++++---
 2 files changed, 93 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt

diff --git a/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
new file mode 100644
index 0000000..2149afe
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
@@ -0,0 +1,46 @@
+* Palmas general purpose ADC IP block devicetree bindings
+
+Channels list:
+	0 battery type
+	1 battery temp NTC (optional current source)
+	2 GP
+	3 temp (with ext. diode, optional current source)
+	4 GP
+	5 GP
+	6 VBAT_SENSE
+	7 VCC_SENSE
+	8 Backup Battery voltage
+	9 external charger (VCHG)
+	10 VBUS
+	11 DC-DC current probe (how does this work?)
+	12 internal die temp
+	13 internal die temp
+	14 USB ID pin voltage
+	15 test network
+
+Required properties:
+- compatible : Must be "ti,palmas-gpadc".
+
+Optional sub-nodes:
+ti,channel0-current-microamp: Channel 0 current in uA.
+	Values are rounded to derive 0uA, 5uA, 15uA, 20uA.
+ti,channel3-current-microamp: Channel 3 current in uA.
+	Valid are rounded to derive 0uA, 10uA, 400uA, 800uA.
+ti,enable-extended-delay: Enable extended delay.
+
+Example:
+
+pmic {
+	compatible = "ti,twl6035-pmic", "ti,palmas-pmic";
+	...
+	gpadc {
+		compatible = "ti,palmas-gpadc";
+		interrupts = <18 0
+			      16 0
+			      17 0>;
+		ti,channel0-current-microamp = <5>;
+		ti,channel3-current-microamp = <10>;
+		};
+	};
+	...
+};
diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
index 6805d81..1297c81 100644
--- a/drivers/iio/adc/palmas_gpadc.c
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -21,6 +21,8 @@
 #include <linux/pm.h>
 #include <linux/mfd/palmas.h>
 #include <linux/completion.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/machine.h>
 #include <linux/iio/driver.h>
@@ -460,6 +462,34 @@ static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
 	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
 };
 
+static int palmas_gpadc_get_adc_dt_data(struct platform_device *pdev,
+	struct palmas_gpadc_platform_data **gpadc_pdata)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct palmas_gpadc_platform_data *gp_data;
+	int ret;
+	u32 pval;
+
+	gp_data = devm_kzalloc(&pdev->dev, sizeof(*gp_data), GFP_KERNEL);
+	if (!gp_data)
+		return -ENOMEM;
+
+	ret = of_property_read_u32(np, "ti,channel0-current-microamp", &pval);
+	if (!ret)
+		gp_data->ch0_current = pval;
+
+	ret = of_property_read_u32(np, "ti,channel3-current-microamp", &pval);
+	if (!ret)
+		gp_data->ch3_current = pval;
+
+	gp_data->extended_delay = of_property_read_bool(np,
+					"ti,enable-extended-delay");
+
+	*gpadc_pdata = gp_data;
+
+	return 0;
+}
+
 static int palmas_gpadc_probe(struct platform_device *pdev)
 {
 	struct palmas_gpadc *adc;
@@ -469,12 +499,17 @@ static int palmas_gpadc_probe(struct platform_device *pdev)
 	int ret, i;
 
 	pdata = dev_get_platdata(pdev->dev.parent);
-	if (!pdata || !pdata->gpadc_pdata) {
-		dev_err(&pdev->dev, "No platform data\n");
-		return -ENODEV;
-	}
 
-	gpadc_pdata = pdata->gpadc_pdata;
+	if (pdata && pdata->gpadc_pdata)
+		gpadc_pdata = pdata->gpadc_pdata;
+
+	if (!gpadc_pdata && pdev->dev.of_node) {
+		ret = palmas_gpadc_get_adc_dt_data(pdev, &gpadc_pdata);
+		if (ret < 0)
+			return ret;
+	}
+	if (!gpadc_pdata)
+		return -EINVAL;
 
 	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
 	if (!indio_dev) {
@@ -790,6 +825,12 @@ static const struct dev_pm_ops palmas_pm_ops = {
 				palmas_gpadc_resume)
 };
 
+static const struct of_device_id of_palmas_gpadc_match_tbl[] = {
+	{ .compatible = "ti,palmas-gpadc", },
+	{ /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_palmas_gpadc_match_tbl);
+
 static struct platform_driver palmas_gpadc_driver = {
 	.probe = palmas_gpadc_probe,
 	.remove = palmas_gpadc_remove,
@@ -797,6 +838,7 @@ static struct platform_driver palmas_gpadc_driver = {
 		.name = MOD_NAME,
 		.owner = THIS_MODULE,
 		.pm = &palmas_pm_ops,
+		.of_match_table = of_palmas_gpadc_match_tbl,
 	},
 };
 
-- 
2.5.1


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

* [PATCH v2 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas
  2015-10-05  6:14   ` [PATCH v2 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
  2015-10-05  6:14     ` [PATCH v2 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
@ 2015-10-05  6:14     ` H. Nikolaus Schaller
  2 siblings, 0 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-10-05  6:14 UTC (permalink / raw)
  To: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, H. Nikolaus Schaller, Laxman Dewangan, gg,
	jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 arch/arm/boot/dts/omap5-uevm.dts | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/arm/boot/dts/omap5-uevm.dts b/arch/arm/boot/dts/omap5-uevm.dts
index 3b16e8f..c78ee2f 100644
--- a/arch/arm/boot/dts/omap5-uevm.dts
+++ b/arch/arm/boot/dts/omap5-uevm.dts
@@ -342,6 +342,15 @@
 
 			ti,ldo6-vibrator;
 
+			gpadc {
+				compatible = "ti,palmas-gpadc";
+				interrupts = <18 0
+					      16 0
+					      17 0>;
+				ti,channel0-current-microamp = <5>;
+				ti,channel3-current-microamp = <10>;
+			};
+
 			regulators {
 				smps123_reg: smps123 {
 					/* VDD_OPP_MPU */
-- 
2.5.1


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

* [PATCH] iio:adc: fix platform_no_drv_owner.cocci warnings
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
  2015-10-05  6:54       ` kbuild test robot
@ 2015-10-05  6:54       ` kbuild test robot
  2015-10-05 10:48       ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc Laxman Dewangan
  2015-10-11 14:27       ` Jonathan Cameron
  3 siblings, 0 replies; 25+ messages in thread
From: kbuild test robot @ 2015-10-05  6:54 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: kbuild-all, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Benoît Cousson, Tony Lindgren, Russell King,
	Marek Belisko, Pradeep Goudagunta, H. Nikolaus Schaller,
	Laxman Dewangan, gg, jic23, devicetree, linux-kernel, linux-omap,
	linux-iio, Grazvydas Ignotas

drivers/iio/adc/palmas_gpadc.c:798:3-8: No need to set .owner here. The core will do it.

 Remove .owner field if calls are used which set it automatically

Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci

CC: H. Nikolaus Schaller <hns@goldelico.com>
Signed-off-by: Fengguang Wu <fengguang.wu@intel.com>
---

 palmas_gpadc.c |    1 -
 1 file changed, 1 deletion(-)

--- a/drivers/iio/adc/palmas_gpadc.c
+++ b/drivers/iio/adc/palmas_gpadc.c
@@ -795,7 +795,6 @@ static struct platform_driver palmas_gpa
 	.remove = palmas_gpadc_remove,
 	.driver = {
 		.name = MOD_NAME,
-		.owner = THIS_MODULE,
 		.pm = &palmas_pm_ops,
 	},
 };

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

* Re: [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
@ 2015-10-05  6:54       ` kbuild test robot
  2015-10-05  6:54       ` [PATCH] iio:adc: fix platform_no_drv_owner.cocci warnings kbuild test robot
                         ` (2 subsequent siblings)
  3 siblings, 0 replies; 25+ messages in thread
From: kbuild test robot @ 2015-10-05  6:54 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: kbuild-all, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Benoît Cousson, Tony Lindgren, Russell King,
	Marek Belisko, Pradeep Goudagunta, H. Nikolaus Schaller,
	Laxman Dewangan, gg, jic23, devicetree, linux-kernel, linux-omap,
	linux-iio, Grazvydas Ignotas

Hi Nikolaus,

[auto build test WARNING on v4.3-rc4 -- if it's inappropriate base, please ignore]


coccinelle warnings: (new ones prefixed by >>)

>> drivers/iio/adc/palmas_gpadc.c:798:3-8: No need to set .owner here. The core will do it.

Please review and possibly fold the followup patch.

---
0-DAY kernel test infrastructure                Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all                   Intel Corporation

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

* Re: [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
  2015-10-05  6:54       ` kbuild test robot
  2015-10-05  6:54       ` [PATCH] iio:adc: fix platform_no_drv_owner.cocci warnings kbuild test robot
@ 2015-10-05 10:48       ` Laxman Dewangan
  2015-10-11 14:27       ` Jonathan Cameron
  3 siblings, 0 replies; 25+ messages in thread
From: Laxman Dewangan @ 2015-10-05 10:48 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Benoît Cousson, Tony Lindgren,
	Russell King, Marek Belisko, Pradeep Goudagunta, gg, jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas


On Monday 05 October 2015 11:44 AM, H. Nikolaus Schaller wrote:
> This driver code was found as:
>
> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
>
> Fixed various compilation issues and test this driver on omap5 evm.
>
> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> Signed-off-by: Marek Belisko <marek@goldelico.com>
>

Acked-by: Laxman Dewangan <ldewangan@nvidia.com>

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

* Re: [PATCH v2 2/3] iio:adc:palmas: add DT support
  2015-10-05  6:14     ` [PATCH v2 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
@ 2015-10-05 10:53       ` Laxman Dewangan
  0 siblings, 0 replies; 25+ messages in thread
From: Laxman Dewangan @ 2015-10-05 10:53 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Benoît Cousson, Tony Lindgren,
	Russell King, Marek Belisko, Pradeep Goudagunta, gg, jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas


On Monday 05 October 2015 11:44 AM, H. Nikolaus Schaller wrote:
> From: Marek Belisko <marek@goldelico.com>
>
> Code was found at:
> https://android.googlesource.com/kernel/tegra/+/a90856a6626d502d42c6e7abccbdf9d730b36270%5E%21/#F1
>
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> [Fixed minor typos + add channels list to documentation]
> Signed-off-by: Marek Belisko <marek@goldelico.com>
> ---

Acked-by: Laxman Dewangan <ldewangan@nvidia.com>

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

* Re: [PATCH v2 2/3] iio:adc:palmas: add DT support
       [not found]     ` <6C211191-87B8-4444-ADA7-8BE454C2B3D3@goldelico.com>
@ 2015-10-05 11:17       ` Mark Rutland
  2015-10-05 14:26         ` H. Nikolaus Schaller
  0 siblings, 1 reply; 25+ messages in thread
From: Mark Rutland @ 2015-10-05 11:17 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Laxman Dewangan, gg, jic23, devicetree,
	linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas

On Sun, Oct 04, 2015 at 06:05:59PM +0200, H. Nikolaus Schaller wrote:
> From: Marek Belisko <marek@goldelico.com>
> 
> Code was found at:
> https://android.googlesource.com/kernel/tegra/+/a90856a6626d502d42c6e7abccbdf9d730b36270%5E%21/#F1
> 
> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
> [Fixed minor typos + add channels list to documentation]
> Signed-off-by: Marek Belisko <marek@goldelico.com>
> ---
> .../devicetree/bindings/iio/adc/palmas-gpadc.txt   | 46 +++++++++++++++++++
> drivers/iio/adc/palmas_gpadc.c                     | 52 +++++++++++++++++++---
> 2 files changed, 93 insertions(+), 5 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
> new file mode 100644
> index 0000000..2149afe
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
> @@ -0,0 +1,46 @@
> +* Palmas general purpose ADC IP block devicetree bindings
> +
> +Channels list:
> +	0 battery type
> +	1 battery temp NTC (optional current source)
> +	2 GP
> +	3 temp (with ext. diode, optional current source)
> +	4 GP
> +	5 GP
> +	6 VBAT_SENSE
> +	7 VCC_SENSE
> +	8 Backup Battery voltage
> +	9 external charger (VCHG)
> +	10 VBUS
> +	11 DC-DC current probe (how does this work?)
> +	12 internal die temp
> +	13 internal die temp
> +	14 USB ID pin voltage
> +	15 test network
> +
> +Required properties:
> +- compatible : Must be "ti,palmas-gpadc".
> +
> +Optional sub-nodes:
> +ti,channel0-current-microamp: Channel 0 current in uA.
> +	Values are rounded to derive 0uA, 5uA, 15uA, 20uA.
> +ti,channel3-current-microamp: Channel 3 current in uA.
> +	Valid are rounded to derive 0uA, 10uA, 400uA, 800uA.

It's only possible to configure channels 0 and 3 in this manner?

> +ti,enable-extended-delay: Enable extended delay.

What is this? When would I select it? Why does it belong in the DT
rather than being a runtime option?

> +Example:
> +
> +pmic {
> +	compatible = "ti,twl6035-pmic", "ti,palmas-pmic";
> +	...
> +	gpadc {
> +		compatible = "ti,palmas-gpadc";
> +		interrupts = <18 0
> +			      16 0
> +			      17 0>;
> +		ti,channel0-current-microamp = <5>;
> +		ti,channel3-current-microamp = <10>;
> +		};
> +	};
> +	...
> +};

I thought you needed #iio-cells for encoding the channel?

Mark.

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

* Re: [PATCH v2 2/3] iio:adc:palmas: add DT support
  2015-10-05 11:17       ` [PATCH v2 2/3] iio:adc:palmas: add DT support Mark Rutland
@ 2015-10-05 14:26         ` H. Nikolaus Schaller
  0 siblings, 0 replies; 25+ messages in thread
From: H. Nikolaus Schaller @ 2015-10-05 14:26 UTC (permalink / raw)
  To: Mark Rutland
  Cc: Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Laxman Dewangan, gg, jic23, devicetree,
	linux-kernel, linux-omap, linux-iio, Grazvydas Ignotas


Am 05.10.2015 um 13:17 schrieb Mark Rutland <mark.rutland@arm.com>:

> On Sun, Oct 04, 2015 at 06:05:59PM +0200, H. Nikolaus Schaller wrote:
>> From: Marek Belisko <marek@goldelico.com>
>> 
>> Code was found at:
>> https://android.googlesource.com/kernel/tegra/+/a90856a6626d502d42c6e7abccbdf9d730b36270%5E%21/#F1
>> 
>> Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
>> [Fixed minor typos + add channels list to documentation]
>> Signed-off-by: Marek Belisko <marek@goldelico.com>
>> ---
>> .../devicetree/bindings/iio/adc/palmas-gpadc.txt   | 46 +++++++++++++++++++
>> drivers/iio/adc/palmas_gpadc.c                     | 52 +++++++++++++++++++---
>> 2 files changed, 93 insertions(+), 5 deletions(-)
>> create mode 100644 Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
>> 
>> diff --git a/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
>> new file mode 100644
>> index 0000000..2149afe
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/iio/adc/palmas-gpadc.txt
>> @@ -0,0 +1,46 @@
>> +* Palmas general purpose ADC IP block devicetree bindings
>> +
>> +Channels list:
>> +	0 battery type
>> +	1 battery temp NTC (optional current source)
>> +	2 GP
>> +	3 temp (with ext. diode, optional current source)
>> +	4 GP
>> +	5 GP
>> +	6 VBAT_SENSE
>> +	7 VCC_SENSE
>> +	8 Backup Battery voltage
>> +	9 external charger (VCHG)
>> +	10 VBUS
>> +	11 DC-DC current probe (how does this work?)
>> +	12 internal die temp
>> +	13 internal die temp
>> +	14 USB ID pin voltage
>> +	15 test network
>> +
>> +Required properties:
>> +- compatible : Must be "ti,palmas-gpadc".
>> +
>> +Optional sub-nodes:
>> +ti,channel0-current-microamp: Channel 0 current in uA.
>> +	Values are rounded to derive 0uA, 5uA, 15uA, 20uA.
>> +ti,channel3-current-microamp: Channel 3 current in uA.
>> +	Valid are rounded to derive 0uA, 10uA, 400uA, 800uA.
> 
> It's only possible to configure channels 0 and 3 in this manner?

Yes. The other channels have no built-in current source, i.e. these
channels are special.

> 
>> +ti,enable-extended-delay: Enable extended delay.
> 
> What is this? When would I select it? Why does it belong in the DT
> rather than being a runtime option?

The chip allows to extend the time window between channel selection
and sampling by 400µs (according to data sheet). But for all channels
and not each one. As far as I understand, this is - depending on hardware
setup - to get more stable ADC readings.

Most channels have a fixed function (e.g. battery voltage, USB VBUS,
temperature NTC) so it can't be arbitrarily chosen and depends on how
the Palmas is embedded (it is a PMIC with a bank of ADCs and not a
general purpose ADC chip).

So I think it is really a hardware dependent parameter and not something
the user should be able to change.

> 
>> +Example:
>> +
>> +pmic {
>> +	compatible = "ti,twl6035-pmic", "ti,palmas-pmic";
>> +	...
>> +	gpadc {
>> +		compatible = "ti,palmas-gpadc";
>> +		interrupts = <18 0
>> +			      16 0
>> +			      17 0>;
>> +		ti,channel0-current-microamp = <5>;
>> +		ti,channel3-current-microamp = <10>;
>> +		};
>> +	};
>> +	...
>> +};
> 
> I thought you needed #iio-cells for encoding the channel?

Yes, we forgot (we don't have a client for it in our setup yet - so did not test :)

> 
> Mark.

Thanks,
Nikolaus Schaller


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

* Re: [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
                         ` (2 preceding siblings ...)
  2015-10-05 10:48       ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc Laxman Dewangan
@ 2015-10-11 14:27       ` Jonathan Cameron
  2015-10-13  8:14         ` Lee Jones
  3 siblings, 1 reply; 25+ messages in thread
From: Jonathan Cameron @ 2015-10-11 14:27 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Benoît Cousson, Tony Lindgren,
	Russell King, Marek Belisko, Pradeep Goudagunta, Laxman Dewangan,
	gg, jic23
  Cc: devicetree, linux-kernel, linux-omap, linux-iio,
	Grazvydas Ignotas, Lee Jones, Samuel Ortiz

On 05/10/15 07:14, H. Nikolaus Schaller wrote:
> This driver code was found as:
> 
> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
> 
> Fixed various compilation issues and test this driver on omap5 evm.
> 
> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> Signed-off-by: Marek Belisko <marek@goldelico.com>

I'm pretty much fine with this.  However, there are some edits within the
existing mfd support so I will want acks for that or for the driver to go
through the MFD tree (note that as it touched that, even if only a header,
Lee and Samuel should have been cc'd).

One thing that slightly confuses me is there seems to be hints of support in the
mfd driver already... I can't find any sign of the child device however so
I guess this is fine from that point of view.

Reviewed-by: Jonathan Cameron <jic23@kernel.org>
> ---
>  drivers/iio/adc/Kconfig        |   9 +
>  drivers/iio/adc/Makefile       |   1 +
>  drivers/iio/adc/palmas_gpadc.c | 818 +++++++++++++++++++++++++++++++++++++++++
>  include/linux/mfd/palmas.h     |  75 ++--
>  4 files changed, 879 insertions(+), 24 deletions(-)
>  create mode 100644 drivers/iio/adc/palmas_gpadc.c
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index eb0cd89..05a0368 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -242,6 +242,15 @@ config NAU7802
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called nau7802.
>  
> +config PALMAS_GPADC
> +	tristate "TI Palmas General Purpose ADC"
> +	depends on MFD_PALMAS
> +	help
> +	  Say yes here to build support for Texas Instruments Palmas.
> +
> +	  Palmas series pmic chip (twl6035/6037) is used in smartphones and
> +	  tablets and supports a 16 channel general purpose ADC.
> +
>  config QCOM_SPMI_IADC
>  	tristate "Qualcomm SPMI PMIC current ADC"
>  	depends on SPMI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index a096210..716f112 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
>  obj-$(CONFIG_MCP3422) += mcp3422.o
>  obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>  obj-$(CONFIG_NAU7802) += nau7802.o
> +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
> new file mode 100644
> index 0000000..6805d81
> --- /dev/null
> +++ b/drivers/iio/adc/palmas_gpadc.c
> @@ -0,0 +1,818 @@
> +/*
> + * palmas-adc.c -- TI PALMAS GPADC.
> + *
> + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
> + *
> + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation version 2.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/err.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/i2c.h>
> +#include <linux/pm.h>
> +#include <linux/mfd/palmas.h>
> +#include <linux/completion.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/machine.h>
> +#include <linux/iio/driver.h>
> +
> +#define MOD_NAME "palmas-gpadc"
> +#define PALMAS_ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
> +#define PALMAS_TO_BE_CALCULATED 0
> +#define PALMAS_GPADC_TRIMINVALID	-1
> +
> +struct palmas_gpadc_info {
> +/* calibration codes and regs */
> +	int x1;	/* lower ideal code */
> +	int x2;	/* higher ideal code */
> +	int v1;	/* expected lower volt reading */
> +	int v2;	/* expected higher volt reading */
> +	u8 trim1_reg;	/* register number for lower trim */
> +	u8 trim2_reg;	/* register number for upper trim */
> +	int gain;	/* calculated from above (after reading trim regs) */
> +	int offset;	/* calculated from above (after reading trim regs) */
> +	int gain_error;	/* calculated from above (after reading trim regs) */
> +	bool is_uncalibrated;	/* if channel has calibration data */
> +};
> +
> +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \
> +	[PALMAS_ADC_CH_##_chan] = { \
> +		.x1 = _x1, \
> +		.x2 = _x2, \
> +		.v1 = _v1, \
> +		.v2 = _v2, \
> +		.gain = PALMAS_TO_BE_CALCULATED, \
> +		.offset = PALMAS_TO_BE_CALCULATED, \
> +		.gain_error = PALMAS_TO_BE_CALCULATED, \
> +		.trim1_reg = PALMAS_GPADC_TRIM##_t1, \
> +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,  \
> +		.is_uncalibrated = _is_uncalibrated \
> +	}
> +
> +static struct palmas_gpadc_info palmas_gpadc_info[] = {
> +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
> +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
> +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
> +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
> +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
> +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
> +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
> +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
> +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
> +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
> +};
> +
> +/**
> + * struct palmas_gpadc - the palmas_gpadc structure
> + * @ch0_current:	channel 0 current source setting
> + *    0: 0 uA
> + *    1: 5 uA
> + *    2: 15 uA
> + *    3: 20 uA
> + * @ch3_current:	channel 3 current source setting
> + *    0: 0 uA
> + *    1: 10 uA
> + *    2: 400 uA
> + *    3: 800 uA
> + * @extended_delay:	enable the gpadc extended delay mode
> + * @auto_conversion_period:	define the auto_conversion_period
> + *
> + * This is the palmas_gpadc structure to store run-time information
> + * and pointers for this driver instance.
> + */
> +
> +struct palmas_gpadc {
> +	struct device			*dev;
> +	struct palmas			*palmas;
> +	u8				ch0_current;
> +	u8				ch3_current;
> +	bool				extended_delay;
> +	int				irq;
> +	int				irq_auto_0;
> +	int				irq_auto_1;
> +	struct palmas_gpadc_info	*adc_info;
> +	struct completion		conv_completion;
> +	struct palmas_adc_wakeup_property wakeup1_data;
> +	struct palmas_adc_wakeup_property wakeup2_data;
> +	bool				wakeup1_enable;
> +	bool				wakeup2_enable;
> +	int				auto_conversion_period;
> +};
> +
> +/*
> + * GPADC lock issue in AUTO mode.
> + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
> + *	   mode feature.
> + * Details:
> + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
> + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
> + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
> + *	conversion mechanism can be seen as locked meaning that all following
> + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
> + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
> + *	the GPADC.
> + *
> + * Workaround(s):
> + *	To avoid the lock mechanism, the workaround to follow before any stop
> + *	conversion request is:
> + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
> + *		GPADC_FORCE bit = 1
> + *	Shutdown the GPADC AUTO conversion using
> + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
> + *	After 100us, force the GPADC state machine to be OFF by using the
> + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
> + */
> +
> +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
> +{
> +	int ret;
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_CTRL1,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
> +			0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	udelay(100);
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_CTRL1,
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> +	if (ret < 0)
> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
> +{
> +	struct palmas_gpadc *adc = data;
> +
> +	complete(&adc->conv_completion);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
> +{
> +	struct palmas_gpadc *adc = data;
> +
> +	dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq);
> +	palmas_disable_auto_conversion(adc);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc,
> +							bool mask)
> +{
> +	int ret;
> +
> +	if (!mask)
> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> +					PALMAS_INT3_MASK,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
> +	else
> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> +					PALMAS_INT3_MASK,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW,
> +					PALMAS_INT3_MASK_GPADC_EOC_SW);
> +	if (ret < 0)
> +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
> +			       int enable)
> +{
> +	unsigned int mask, val;
> +	int ret;
> +
> +	if (enable) {
> +		val = (adc->extended_delay
> +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +					PALMAS_GPADC_RT_CTRL,
> +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
> +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> +		val = (adc->ch0_current
> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
> +		val |= (adc->ch3_current
> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
> +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_CTRL1, mask, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"Failed to update current setting: %d\n", ret);
> +			return ret;
> +		}
> +
> +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
> +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT, mask, val);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
> +			return ret;
> +		}
> +	} else {
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT, 0);
> +		if (ret < 0)
> +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
> +
> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_CTRL1,
> +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	int ret;
> +
> +	ret = palmas_gpadc_enable(adc, adc_chan, true);
> +	if (ret < 0)
> +		return ret;
> +
> +	return palmas_gpadc_start_mask_interrupt(adc, 0);
> +}
> +
> +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	palmas_gpadc_start_mask_interrupt(adc, 1);
> +	palmas_gpadc_enable(adc, adc_chan, false);
> +}
> +
> +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	int k;
> +	int d1;
> +	int d2;
> +	int ret;
> +	int gain;
> +	int x1 =  adc->adc_info[adc_chan].x1;
> +	int x2 =  adc->adc_info[adc_chan].x2;
> +	int v1 = adc->adc_info[adc_chan].v1;
> +	int v2 = adc->adc_info[adc_chan].v2;
> +
> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> +				adc->adc_info[adc_chan].trim1_reg, &d1);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> +				adc->adc_info[adc_chan].trim2_reg, &d2);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> +		goto scrub;
> +	}
> +
> +	/* gain error calculation */
> +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
> +
> +	/* gain calculation */
> +	gain = ((v2 - v1) * 1000) / (x2 - x1);
> +
> +	adc->adc_info[adc_chan].gain_error = k;
> +	adc->adc_info[adc_chan].gain = gain;
> +	/* offset Calculation */
> +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
> +
> +scrub:
> +	return ret;
> +}
> +
> +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
> +{
> +	unsigned int val;
> +	int ret;
> +
> +	init_completion(&adc->conv_completion);
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_SELECT,
> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = wait_for_completion_timeout(&adc->conv_completion,
> +				PALMAS_ADC_CONVERSION_TIMEOUT);
> +	if (ret == 0) {
> +		dev_err(adc->dev, "conversion not completed\n");
> +		return -ETIMEDOUT;
> +	}
> +
> +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = val & 0xFFF;
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
> +						int adc_chan, int val)
> +{
> +	if (!adc->adc_info[adc_chan].is_uncalibrated)
> +		val  = (val*1000 - adc->adc_info[adc_chan].offset) /
> +					adc->adc_info[adc_chan].gain_error;
> +
> +	if (val < 0) {
> +		dev_err(adc->dev, "Mismatch with calibration\n");
> +		return 0;
> +	}
> +
> +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
> +
> +	return val;
> +}
> +
> +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> +{
> +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
> +	int adc_chan = chan->channel;
> +	int ret = 0;
> +
> +	if (adc_chan > PALMAS_ADC_CH_MAX)
> +		return -EINVAL;
> +
> +	mutex_lock(&indio_dev->mlock);
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +	case IIO_CHAN_INFO_PROCESSED:
> +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
> +		if (ret < 0)
> +			goto out;
> +
> +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +			"ADC start conversion failed\n");
> +			goto out;
> +		}
> +
> +		if (mask == IIO_CHAN_INFO_PROCESSED)
> +			ret = palmas_gpadc_get_calibrated_code(
> +							adc, adc_chan, ret);
> +
> +		*val = ret;
> +
> +		ret = IIO_VAL_INT;
> +		goto out;
> +	}
> +
> +	mutex_unlock(&indio_dev->mlock);
> +	return ret;
> +
> +out:
> +	palmas_gpadc_read_done(adc, adc_chan);
> +	mutex_unlock(&indio_dev->mlock);
> +
> +	return ret;
> +}
> +
> +static const struct iio_info palmas_gpadc_iio_info = {
> +	.read_raw = palmas_gpadc_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info)	\
> +{							\
> +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),	\
> +	.type = _type,					\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
> +			BIT(chan_info),			\
> +	.indexed = 1,					\
> +	.channel = PALMAS_ADC_CH_##chan,		\
> +}
> +
> +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
> +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW),
> +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW),
> +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW),
> +	PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW),
> +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> +};
> +
> +static int palmas_gpadc_probe(struct platform_device *pdev)
> +{
> +	struct palmas_gpadc *adc;
> +	struct palmas_platform_data *pdata;
> +	struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
> +	struct iio_dev *indio_dev;
> +	int ret, i;
> +
> +	pdata = dev_get_platdata(pdev->dev.parent);
> +	if (!pdata || !pdata->gpadc_pdata) {
> +		dev_err(&pdev->dev, "No platform data\n");
> +		return -ENODEV;
> +	}
> +
> +	gpadc_pdata = pdata->gpadc_pdata;
> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
> +	if (!indio_dev) {
> +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	adc = iio_priv(indio_dev);
> +	adc->dev = &pdev->dev;
> +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
> +	adc->adc_info = palmas_gpadc_info;
> +	init_completion(&adc->conv_completion);
> +	dev_set_drvdata(&pdev->dev, indio_dev);
> +
> +	adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
> +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
> +	if (adc->irq < 0) {
> +		dev_err(adc->dev,
> +			"get virq failed: %d\n", adc->irq);
> +		ret = adc->irq;
> +		goto out;
> +	}
> +	ret = request_threaded_irq(adc->irq, NULL,
> +		palmas_gpadc_irq,
> +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
> +		adc);
> +	if (ret < 0) {
> +		dev_err(adc->dev,
> +			"request irq %d failed: %d\n", adc->irq, ret);
> +		goto out;
> +	}
> +
> +	if (gpadc_pdata->adc_wakeup1_data) {
> +		memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
> +			sizeof(adc->wakeup1_data));
> +		adc->wakeup1_enable = true;
> +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
> +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
> +				palmas_gpadc_irq_auto,
> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> +				"palmas-adc-auto-0", adc);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
> +				adc->irq_auto_0, ret);
> +			goto out_irq_free;
> +		}
> +	}
> +
> +	if (gpadc_pdata->adc_wakeup2_data) {
> +		memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
> +				sizeof(adc->wakeup2_data));
> +		adc->wakeup2_enable = true;
> +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
> +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
> +				palmas_gpadc_irq_auto,
> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> +				"palmas-adc-auto-1", adc);
> +		if (ret < 0) {
> +			dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
> +				adc->irq_auto_1, ret);
> +			goto out_irq_auto0_free;
> +		}
> +	}
> +
> +	/* set the current source 0 (value 0/5/15/20 uA => 0..3) */
> +	if (gpadc_pdata->ch0_current <= 1)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
> +	else if (gpadc_pdata->ch0_current <= 5)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
> +	else if (gpadc_pdata->ch0_current <= 15)
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
> +	else
> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
> +
> +	/* set the current source 3 (value 0/10/400/800 uA => 0..3) */
> +	if (gpadc_pdata->ch3_current <= 1)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
> +	else if (gpadc_pdata->ch3_current <= 10)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
> +	else if (gpadc_pdata->ch3_current <= 400)
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
> +	else
> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
> +
> +	adc->extended_delay = gpadc_pdata->extended_delay;
> +
> +	indio_dev->name = MOD_NAME;
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->info = &palmas_gpadc_iio_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = palmas_gpadc_iio_channel;
> +	indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
> +		goto out_irq_auto1_free;
> +	}
> +
> +	device_set_wakeup_capable(&pdev->dev, 1);
> +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
> +		if (!(adc->adc_info[i].is_uncalibrated))
> +			palmas_gpadc_calibrate(adc, i);
> +	}
> +
> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
> +		device_wakeup_enable(&pdev->dev);
> +
> +	return 0;
> +
> +out_irq_auto1_free:
> +	if (gpadc_pdata->adc_wakeup2_data)
> +		free_irq(adc->irq_auto_1, adc);
> +out_irq_auto0_free:
> +	if (gpadc_pdata->adc_wakeup1_data)
> +		free_irq(adc->irq_auto_0, adc);
> +out_irq_free:
> +	free_irq(adc->irq, adc);
> +out:
> +	return ret;
> +}
> +
> +static int palmas_gpadc_remove(struct platform_device *pdev)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev);
> +	struct palmas_gpadc *adc = iio_priv(indio_dev);
> +
> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
> +		device_wakeup_disable(&pdev->dev);
> +	iio_device_unregister(indio_dev);
> +	free_irq(adc->irq, adc);
> +	if (adc->wakeup1_enable)
> +		free_irq(adc->irq_auto_0, adc);
> +	if (adc->wakeup2_enable)
> +		free_irq(adc->irq_auto_1, adc);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
> +{
> +	int adc_period, conv;
> +	int i;
> +	int ch0 = 0, ch1 = 0;
> +	int thres;
> +	int ret;
> +
> +	adc_period = adc->auto_conversion_period;
> +	for (i = 0; i < 16; ++i) {
> +		if (((1000 * (1 << i)) / 32) < adc_period)
> +			continue;
> +	}
> +	if (i > 0)
> +		i--;
> +	adc_period = i;
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
> +			adc_period);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	conv = 0;
> +	if (adc->wakeup1_enable) {
> +		int polarity;
> +
> +		ch0 = adc->wakeup1_data.adc_channel_number;
> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
> +		if (adc->wakeup1_data.adc_high_threshold > 0) {
> +			thres = adc->wakeup1_data.adc_high_threshold;
> +			polarity = 0;
> +		} else {
> +			thres = adc->wakeup1_data.adc_low_threshold;
> +			polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV0_LSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV0_MSB,
> +				((thres >> 8) & 0xF) | polarity);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV0_MSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	if (adc->wakeup2_enable) {
> +		int polarity;
> +
> +		ch1 = adc->wakeup2_data.adc_channel_number;
> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
> +		if (adc->wakeup2_data.adc_high_threshold > 0) {
> +			thres = adc->wakeup2_data.adc_high_threshold;
> +			polarity = 0;
> +		} else {
> +			thres = adc->wakeup2_data.adc_low_threshold;
> +			polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV1_LSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +
> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +				PALMAS_GPADC_THRES_CONV1_MSB,
> +				((thres >> 8) & 0xF) | polarity);
> +		if (ret < 0) {
> +			dev_err(adc->dev,
> +				"THRES_CONV1_MSB write failed: %d\n", ret);
> +			return ret;
> +		}
> +	}
> +
> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_CTRL,
> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
> +	if (ret < 0)
> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
> +{
> +	int ret;
> +
> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> +			PALMAS_GPADC_AUTO_SELECT, 0);
> +	if (ret < 0) {
> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = palmas_disable_auto_conversion(adc);
> +	if (ret < 0)
> +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int palmas_gpadc_suspend(struct device *dev)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct palmas_gpadc *adc = iio_priv(indio_dev);
> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> +	int ret;
> +
> +	if (!device_may_wakeup(dev) || !wakeup)
> +		return 0;
> +
> +	ret = palmas_adc_wakeup_configure(adc);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (adc->wakeup1_enable)
> +		enable_irq_wake(adc->irq_auto_0);
> +
> +	if (adc->wakeup2_enable)
> +		enable_irq_wake(adc->irq_auto_1);
> +
> +	return 0;
> +}
> +
> +static int palmas_gpadc_resume(struct device *dev)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct palmas_gpadc *adc = iio_priv(indio_dev);
> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> +	int ret;
> +
> +	if (!device_may_wakeup(dev) || !wakeup)
> +		return 0;
> +
> +	ret = palmas_adc_wakeup_reset(adc);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (adc->wakeup1_enable)
> +		disable_irq_wake(adc->irq_auto_0);
> +
> +	if (adc->wakeup2_enable)
> +		disable_irq_wake(adc->irq_auto_1);
> +
> +	return 0;
> +};
> +#endif
> +
> +static const struct dev_pm_ops palmas_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
> +				palmas_gpadc_resume)
> +};
> +
> +static struct platform_driver palmas_gpadc_driver = {
> +	.probe = palmas_gpadc_probe,
> +	.remove = palmas_gpadc_remove,
> +	.driver = {
> +		.name = MOD_NAME,
> +		.owner = THIS_MODULE,
> +		.pm = &palmas_pm_ops,
> +	},
> +};
> +
> +static int __init palmas_gpadc_init(void)
> +{
> +	return platform_driver_register(&palmas_gpadc_driver);
> +}
> +module_init(palmas_gpadc_init);
> +
> +static void __exit palmas_gpadc_exit(void)
> +{
> +	platform_driver_unregister(&palmas_gpadc_driver);
> +}
> +module_exit(palmas_gpadc_exit);
> +
> +MODULE_DESCRIPTION("palmas GPADC driver");
> +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
> +MODULE_ALIAS("platform:palmas-gpadc");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
> index bb270bd..2fcd70e 100644
> --- a/include/linux/mfd/palmas.h
> +++ b/include/linux/mfd/palmas.h
> @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
>  			    struct regulator_config config);
>  };
>  
> +struct palmas_adc_wakeup_property {
> +	int adc_channel_number;
> +	int adc_high_threshold;
> +	int adc_low_threshold;
> +};
> +
>  struct palmas_gpadc_platform_data {
>  	/* Channel 3 current source is only enabled during conversion */
> -	int ch3_current;
> +	int ch3_current;	/* 0: off; 1: 10uA; 2: 400uA; 3: 800 uA */
>  
>  	/* Channel 0 current source can be used for battery detection.
>  	 * If used for battery detection this will cause a permanent current
>  	 * consumption depending on current level set here.
>  	 */
> -	int ch0_current;
> +	int ch0_current;	/* 0: off; 1: 5uA; 2: 15uA; 3: 20 uA */
> +	bool extended_delay;	/* use extended delay for conversion */
>  
>  	/* default BAT_REMOVAL_DAT setting on device probe */
>  	int bat_removal;
>  
>  	/* Sets the START_POLARITY bit in the RT_CTRL register */
>  	int start_polarity;
> +
> +	int auto_conversion_period_ms;
> +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
> +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
>  };
>  
>  struct palmas_reg_init {
> @@ -404,28 +415,7 @@ struct palmas_gpadc_calibration {
>  	s32 offset_error;
>  };
>  
> -struct palmas_gpadc {
> -	struct device *dev;
> -	struct palmas *palmas;
> -
> -	int ch3_current;
> -	int ch0_current;
> -
> -	int gpadc_force;
> -
> -	int bat_removal;
> -
> -	struct mutex reading_lock;
> -	struct completion irq_complete;
> -
> -	int eoc_sw_irq;
> -
> -	struct palmas_gpadc_calibration *palmas_cal_tbl;
> -
> -	int conv0_channel;
> -	int conv1_channel;
> -	int rt_channel;
> -};
> +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
>  
>  struct palmas_gpadc_result {
>  	s32 raw_code;
> @@ -519,6 +509,43 @@ enum palmas_irqs {
>  	PALMAS_NUM_IRQ,
>  };
>  
> +/* Palmas GPADC Channels */
> +enum {
> +	PALMAS_ADC_CH_IN0,
> +	PALMAS_ADC_CH_IN1,
> +	PALMAS_ADC_CH_IN2,
> +	PALMAS_ADC_CH_IN3,
> +	PALMAS_ADC_CH_IN4,
> +	PALMAS_ADC_CH_IN5,
> +	PALMAS_ADC_CH_IN6,
> +	PALMAS_ADC_CH_IN7,
> +	PALMAS_ADC_CH_IN8,
> +	PALMAS_ADC_CH_IN9,
> +	PALMAS_ADC_CH_IN10,
> +	PALMAS_ADC_CH_IN11,
> +	PALMAS_ADC_CH_IN12,
> +	PALMAS_ADC_CH_IN13,
> +	PALMAS_ADC_CH_IN14,
> +	PALMAS_ADC_CH_IN15,
> +	PALMAS_ADC_CH_MAX,
> +};
> +
> +/* Palmas GPADC Channel0 Current Source */
> +enum {
> +	PALMAS_ADC_CH0_CURRENT_SRC_0,
> +	PALMAS_ADC_CH0_CURRENT_SRC_5,
> +	PALMAS_ADC_CH0_CURRENT_SRC_15,
> +	PALMAS_ADC_CH0_CURRENT_SRC_20,
> +};
> +
> +/* Palmas GPADC Channel3 Current Source */
> +enum {
> +	PALMAS_ADC_CH3_CURRENT_SRC_0,
> +	PALMAS_ADC_CH3_CURRENT_SRC_10,
> +	PALMAS_ADC_CH3_CURRENT_SRC_400,
> +	PALMAS_ADC_CH3_CURRENT_SRC_800,
> +};
> +
>  struct palmas_pmic {
>  	struct palmas *palmas;
>  	struct device *dev;
> 


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

* Re: [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
       [not found]       ` <95BEC150-7B77-48BF-B11D-929090A1CC11@goldelico.com>
@ 2015-10-11 14:33         ` Jonathan Cameron
  0 siblings, 0 replies; 25+ messages in thread
From: Jonathan Cameron @ 2015-10-11 14:33 UTC (permalink / raw)
  To: H. Nikolaus Schaller
  Cc: Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala,
	Benoît Cousson, Tony Lindgren, Russell King, Marek Belisko,
	Pradeep Goudagunta, Laxman Dewangan, gg, jic23, devicetree,
	linux-kernel, linux-omap, linux-iio, notaz

On 04/10/15 17:04, H. Nikolaus Schaller wrote:
> 
> Am 27.09.2015 um 17:21 schrieb Jonathan Cameron <jic23@kernel.org>:
> 
>> On 23/09/15 13:48, H. Nikolaus Schaller wrote:
>>> This driver code was found as:
>>>
>>> https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
>>>
>>> Fixed various compilation issues and test this driver on omap5 evm.
>>>
>>> Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
>>> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
>>> Signed-off-by: Marek Belisko <marek@goldelico.com>
>> Various bits inline.
> 
> Thanks again!
> 
> Worked into V2 (coming right after this mail).
> Comments below, where/why we have not exactly followed your feedback.
Responses inline, but we haven't disagreed on anything important
so none of it really matters!

Jonathan
> 
> BR,
> Nikolaus
> 
>>
>> Jonathan
>>> ---
>>> drivers/iio/adc/Kconfig        |   9 +
>>> drivers/iio/adc/Makefile       |   1 +
>>> drivers/iio/adc/palmas_gpadc.c | 797 +++++++++++++++++++++++++++++++++++++++++
>>> include/linux/mfd/palmas.h     |  59 ++-
>>> 4 files changed, 862 insertions(+), 4 deletions(-)
>>> create mode 100644 drivers/iio/adc/palmas_gpadc.c
>>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index eb0cd89..f6df9db 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -242,6 +242,15 @@ config NAU7802
>>> 	  To compile this driver as a module, choose M here: the
>>> 	  module will be called nau7802.
>>>
>>> +config PALMAS_GPADC
>>> +	tristate "TI Palmas General Purpose ADC"
>>> +	depends on MFD_PALMAS
>>> +	help
>>> +	  Palmas series pmic chip by texas Instruments (twl6035/6037)
>>> +	  is used in smartphones and tablets and supports a 16 channel
>>> +	  general purpose ADC. Add iio driver to read different channel
>>> +	  of the GPADCs.
>>> +
>>> config QCOM_SPMI_IADC
>>> 	tristate "Qualcomm SPMI PMIC current ADC"
>>> 	depends on SPMI
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index a096210..716f112 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
>>> obj-$(CONFIG_MCP3422) += mcp3422.o
>>> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>>> obj-$(CONFIG_NAU7802) += nau7802.o
>>> +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>>> obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>> diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
>>> new file mode 100644
>>> index 0000000..17abb28
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/palmas_gpadc.c
>>> @@ -0,0 +1,797 @@
>>> +/*
>>> + * palmas-adc.c -- TI PALMAS GPADC.
>>> + *
>>> + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
>>> + *
>>> + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
>>> + *
>>> + * This program is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU General Public License as
>>> + * published by the Free Software Foundation version 2.
>>> + */
>>> +#include <linux/module.h>
>>> +#include <linux/err.h>
>>> +#include <linux/irq.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/i2c.h>
>>> +#include <linux/pm.h>
>>> +#include <linux/mfd/palmas.h>
>>> +#include <linux/completion.h>
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/iio/driver.h>
>>> +
>>> +#define MOD_NAME "palmas-gpadc"
>>> +#define ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
>>> +#define TO_BE_CALCULATED 0
>>> +
>>> +struct palmas_gpadc_info {
>>> +/* calibration codes and regs */
>> Full docs on this would be appreciated.
> Is mostly defined in the Palmas data sheet but I have added some comments.
>>> +	int x1;
>>> +	int x2;
>>> +	int v1;
>>> +	int v2;
>>> +	u8 trim1_reg;
>>> +	u8 trim2_reg;
>>> +	int gain;
>>> +	int offset;
>>> +	int gain_error;
>>> +	bool is_correct_code;
>>> +};
>>> +
>>> +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_correct_code)\
>>> +[PALMAS_ADC_CH_##_chan] = {						\
>>> +		.x1 = _x1,						\
>>> +		.x2 = _x2,						\
>>> +		.v1 = _v1,						\
>>> +		.v2 = _v2,						\
>>> +		.gain = TO_BE_CALCULATED,				\
>>> +		.offset = TO_BE_CALCULATED,				\
>>> +		.gain_error = TO_BE_CALCULATED,				\
>>> +		.trim1_reg = PALMAS_GPADC_TRIM##_t1,			\
>>> +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,			\
>>> +		.is_correct_code = _is_correct_code			\
>>> +	}
>>> +
>>> +static struct palmas_gpadc_info palmas_gpadc_info[] = {
>>> +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
>>> +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
>>> +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
>>> +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
>>> +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
>>> +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
>>> +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
>>> +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
>>> +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
>>> +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
>>> +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
>>> +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
>>> +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
>>> +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
>>> +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
>>> +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
>>> +};
>>> +
>>> +struct palmas_gpadc {
>>> +	struct device			*dev;
>>> +	struct palmas			*palmas;
>> As there are some non obvious elements in here (such as the next two)
>> kernel-doc for the whole stucture would be good.
> 
> I don't know what all of them are doing, so I have only added the next two and some more.
> 
>>
>>> +	u8				ch0_current;
>>> +	u8				ch3_current;
> 
> If I understand them correctly, they just store 2 bits each written into the
> current source registers. The value is only calculated in the probe function
> and internal to the driver. The bit pattern required is defined by the data sheet.
> 
> All TWL4030/TWL603x have such current sources. In the DT, just uA are specified
> and this is to temporarily store the bit pattern until it is sent to the chip. So it is quite
> a deep driver internal, but should be obvious to everyone who has the data sheet.
> 
>>> +	bool				extended_delay;
>>> +	int				irq;
>>> +	int				irq_auto_0;
>>> +	int				irq_auto_1;
>>> +	struct palmas_gpadc_info	*adc_info;
>>> +	struct completion		conv_completion;
>>> +	struct palmas_adc_wakeup_property wakeup1_data;
>>> +	struct palmas_adc_wakeup_property wakeup2_data;
>>> +	bool				wakeup1_enable;
>>> +	bool				wakeup2_enable;
>>> +	int				auto_conversion_period;
>>> +};
>>> +
>>> +/*
>>> + * GPADC lock issue in AUTO mode.
>>> + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
>>> + *	   mode feature.
>>> + * Details:
>>> + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
>>> + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
>>> + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
>>> + *	conversion mechanism can be seen as locked meaning that all following
>>> + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
>>> + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
>>> + *	the GPADC.
>>> + *
>>> + * Workaround(s):
>>> + *	To avoid the lock mechanism, the workaround to follow before any stop
>>> + *	conversion request is:
>>> + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
>>> + *		GPADC_FORCE bit = 1
>>> + *	Shutdown the GPADC AUTO conversion using
>>> + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
>>> + *	After 100us, force the GPADC state machine to be OFF by using the
>>> + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
>>> + */
>>> +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_CTRL1,
>>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
>>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_AUTO_CTRL,
>>> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
>>> +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
>>> +			0);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	udelay(100);
>>> +
>>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_CTRL1,
>>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>> return ret and drop the return above.  Coccicheck will moan at you about
>> this.
>>> +	return 0;
>>> +}
>>> +
>>> +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
>>> +{
>>> +	struct palmas_gpadc *adc = data;
>>> +
>>> +	complete(&adc->conv_completion);
>> blank line.
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
>>> +{
>>> +	struct palmas_gpadc *adc = data;
>>> +
>>> +	dev_info(adc->dev, "Threshold interrupt %d occurs\n", irq);
>> Could indicate this to userspace... other than through the logs.
> 
> Since I am not the original author and don't know how to present that to user space,
> I would leave this open for future improvements.

Fair enough.

> 
>>
>>> +	palmas_disable_auto_conversion(adc);
>> blank line generally before all function returns..
>>> +	return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc, int mask)
>>
>> mask is boolean.  Make it explicitly so for readability.
>>
>>> +{
>>> +	int ret;
>>> +
>>> +	if (!mask)
>>> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
>>> +					PALMAS_INT3_MASK,
>>> +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
>>> +	else
>>> +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
>>> +					PALMAS_INT3_MASK,
>>> +					PALMAS_INT3_MASK_GPADC_EOC_SW,
>>> +					PALMAS_INT3_MASK_GPADC_EOC_SW);
>>> +	if (ret < 0)
>>> +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
>>> +			       int enable)
>>> +{
>>> +	unsigned int mask, val;
>>> +	int ret;
>>> +
>>> +	if (enable) {
>>> +		val = (adc->extended_delay
>>> +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
>>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +					PALMAS_GPADC_RT_CTRL,
>>> +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +
>>> +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
>>> +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
>>> +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
>>> +		val = (adc->ch0_current
>>> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
>>> +		val |= (adc->ch3_current
>>> +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
>>> +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
>>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_CTRL1, mask, val);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev,
>>> +				"Failed to update current setting: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +
>>> +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
>>> +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
>>> +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
>>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_SW_SELECT, mask, val);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +	} else {
>>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_SW_SELECT, 0);
>>> +		if (ret < 0)
>>> +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
>>> +
>>> +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_CTRL1,
>>> +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = palmas_gpadc_enable(adc, adc_chan, true);
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	return palmas_gpadc_start_mask_interrupt(adc, 0);
>>> +}
>>> +
>>> +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
>>> +{
>>> +	palmas_gpadc_start_mask_interrupt(adc, 1);
>>> +	palmas_gpadc_enable(adc, adc_chan, false);
>>> +}
>>> +
>>> +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
>>> +{
>>> +	int k;
>>> +	int d1;
>>> +	int d2;
>>> +	int ret;
>>> +	int gain;
>>> +	int x1 =  adc->adc_info[adc_chan].x1;
>>> +	int x2 =  adc->adc_info[adc_chan].x2;
>>> +	int v1 = adc->adc_info[adc_chan].v1;
>>> +	int v2 = adc->adc_info[adc_chan].v2;
>>> +
>>> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
>>> +				adc->adc_info[adc_chan].trim1_reg, &d1);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
>>> +		goto scrub;
>>> +	}
>>> +
>>> +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
>>> +				adc->adc_info[adc_chan].trim2_reg, &d2);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
>>> +		goto scrub;
>>> +	}
>>> +
>>> +	/*Gain error Calculation*/
>>> +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
>>> +
>>> +	/*Gain Calculation*/
>>> +	gain = ((v2 - v1) * 1000) / (x2 - x1);
>>> +
>>> +	adc->adc_info[adc_chan].gain_error = k;
>>> +	adc->adc_info[adc_chan].gain = gain;
>>> +	/*offset Calculation*/
>>> +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
>>> +
>>> +scrub:
>>> +	return ret;
>>> +}
>>> +
>>> +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
>>> +{
>>> +	unsigned int val;
>>> +	int ret;
>>> +
>>> +	init_completion(&adc->conv_completion);
>>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_SW_SELECT,
>>> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
>>> +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "ADC_SW_START write failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = wait_for_completion_timeout(&adc->conv_completion,
>>> +				ADC_CONVERSION_TIMEOUT);
>>> +	if (ret == 0) {
>>> +		dev_err(adc->dev, "ADC conversion not completed\n");
>>> +		ret = -ETIMEDOUT;
>>> +		return ret;
>> return -ETIMEDOUT;   Might be worth you setting up to do sparse, smatch and
>> coccicheck runs on the code as they'll pick up on a lot of issues like this.
>>
>>> +	}
>>> +
>>> +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "ADCDATA read failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = (val & 0xFFF);
>> nitpick : blank line here.
>>> +	return ret;
>>> +}
>>> +
>>> +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
>>> +						int adc_chan, int val)
>>> +{
>>> +	if (((val*1000) - adc->adc_info[adc_chan].offset) < 0) {
>>> +		dev_err(adc->dev, "No Input Connected\n");
>>> +		return 0;
>>> +	}
>> Interesting, but should this not be caught for raw channel reads as well?
> 
> It appears that this function is only called for calibrated values.
> 
> The condition appears to detect that the ADC has reported a value smaller than
> the calibration allows.
> 
> This indicates to be some error condition (maybe a floating ADC input?). But
> I don't know enough of the Palmas details to judge this. So I have only moved
> it behind the (optional) calibration calculation and detect if either would report
> negative values. So it essentially clamps voltage reports at 0V.
> 
>>> +
>> Umm. what is is_correct_code? Should be documented somewhere
> 
> have renamed it to is_uncalibrated which means that there is no register
> on the chip to read out calibration data for that channel.
> 
>>> +	if (!(adc->adc_info[adc_chan].is_correct_code))
>>> +		val  = ((val*1000) - adc->adc_info[adc_chan].offset) /
>>> +					adc->adc_info[adc_chan].gain_error;
>>> +
>>> +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
>>> +	return val;
>>> +}
>>> +
>>> +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
>>> +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
>>> +{
>>> +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
>>> +	int adc_chan = chan->channel;
>>> +	int ret = 0;
>>> +
>>> +	if (adc_chan > PALMAS_ADC_CH_MAX)
>>> = given I think your channels are 0 indexed?
>>> +		return -EINVAL;
>>> +
>>> +	mutex_lock(&indio_dev->mlock);
>>> +
>>> +	switch (mask) {
>>> +	case IIO_CHAN_INFO_RAW:
>>> +	case IIO_CHAN_INFO_PROCESSED:
>> I'd be tempted to split the two code paths here as that will be slightly
>> easier to read (if longer).
> 
> IMHO would duplicate code (not sure if gcc can detect). Therefore I have left it as is.
> 
>>
>> I'm also highly suspicious of any driver that does processed output
>> and has a return type of IIO_VAL_INT.  Are you processed values in the
>> units documented in Documentation/ABI/testing/sysfs-bus-iio?
> 
> "Units after application of scale and offset are millivolts."
> 
> The driver reports millivolts on OMAP5 uEVM, e.g. VBUS = 5013 (mV).
cool
> 
>> They could be, but I would like a comment saying that.
>>
>>> +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
>>> +		if (ret < 0)
>>> +			goto out;
>>> +
>>> +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev,
>>> +			"ADC start coversion failed\n");
>>> +			goto out;
>>> +		}
>>> +
>>> +		if (mask == IIO_CHAN_INFO_PROCESSED)
>>> +			ret = palmas_gpadc_get_calibrated_code(
>>> +							adc, adc_chan, ret);
>>> +
>>> +		*val = ret;
>>> +
>>> +		ret = IIO_VAL_INT;
>>> +		goto out;
>>> +	}
>>> +
>>> +	mutex_unlock(&indio_dev->mlock);
>>> +	return ret;
>>> +
>>> +out:
>>> +	palmas_gpadc_read_done(adc, adc_chan);
>>> +	mutex_unlock(&indio_dev->mlock);
>>> +	return ret;
>>> +}
>>> +
>>> +static const struct iio_info palmas_gpadc_iio_info = {
>>> +	.read_raw = palmas_gpadc_read_raw,
>>> +	.driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +#define PALMAS_ADC_CHAN_IIO(chan, _type)			\
>>> +{									\
>>> +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),			\
>>> +	.type = _type,							\
>> Right now they are all voltage channels, why have that specifiable in this
>> macro?
> 
> There are two channels that are or can be temperature values. Presented as
> voltages across an NTC/Diode. Since the NTC is outside the Palmas chip the
> conversion into temperature is left to user-space. The type value is in preparation
> for this and I have changed it to IIO_TEMPERATURE.
> 
>>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
>>> +			BIT(IIO_CHAN_INFO_PROCESSED),			\
>> Hmm. This is very very rarely justified.  Either the driver has
>> nice linear relationship between the raw value and the processed one, in which
>> case you should be providing *_RAW, *_OFFSET and *_SCALE and leaving the maths
>> to userspace (or the in kernel wrappers), or they are non linear in which case
>> only the processed value is of interest and the raw one not directly so.
>> (the only exception to this is light sensors where the processed channel
>> is often a computed channel from several input _raw readings).
> 
> If I understand the driver correctty (I am not the original author), the calibration
> is indeed linear, but translating the calibration data into *_RAW, *_OFFSET and *_SCALE
> is different from how Texas suggests to do the calculation (maybe avoiding rounding
> and truncation errors).
> 
> Well, we could simply remove the _RAW values - but IMHO it is sometimes good
> to be able to see what is going on. They can still be ignored by the consumer.
hmm.. I'm not totally happy with having both, but will let it go I think.
> 
>>
>>> +	.indexed = 1,							\
>>> +	.channel = PALMAS_ADC_CH_##chan,				\
>> Given this maps back to the value of chan, just put that in as a number
>> and loose the enum.  Channel is used in the naming so it doesn't
>> make sense to obscure that behind an enum anyway.
> 
> Well, enum constants allow to check against typos and that it matches the
> channel numbers defined in the header. 
> 
> So PALMAS_ADC_CHAN_IIO(IN24, ...) would make gcc warn while.
> PALMAS_ADC_CHAN_IIO(24, ...) would fail in operation.
> 
> Of course we have no typo, because it is already checked to compile :)
> 
> So I understand by which good C coding practise it was probably introduced
> and am undecided if it is good to keep or not.
hmm. not writing silly bugs seems like a better idea to me, or convoluted
tricky to check code.  I'm not that fussed however.
> 
>>> +}
>>> +
>>> +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
>>> +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN1, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN3, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN12, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN13, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE),
>>> +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE),
>>> +};
>>> +
>>> +static int palmas_gpadc_probe(struct platform_device *pdev)
>>> +{
>>> +	struct palmas_gpadc *adc;
>>> +	struct palmas_platform_data *pdata;
>>> +	struct palmas_gpadc_platform_data *adc_pdata;
>>> +	struct iio_dev *iodev;
>>> +	int ret, i;
>>> +
>>> +	pdata = dev_get_platdata(pdev->dev.parent);
>>> +	if (!pdata || !pdata->gpadc_pdata) {
>>> +		dev_err(&pdev->dev, "No platform data\n");
>>> +		return -ENODEV;
>>> +	}
>>> +	adc_pdata = pdata->gpadc_pdata;
>>> +
>>> +	iodev = iio_device_alloc(sizeof(*adc));
>>> +	if (!iodev) {
>>> +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
>>> +		return -ENOMEM;
>>> +	}
>>> +
>>> +	if (adc_pdata->iio_maps) {
>>> +		ret = iio_map_array_register(iodev, adc_pdata->iio_maps);
>>> +		if (ret < 0) {
>>> +			dev_err(&pdev->dev, "iio_map_array_register failed\n");
>>> +			goto out;
>>> +		}
>>> +	}
>>> +
>>> +	adc = iio_priv(iodev);
>>> +	adc->dev = &pdev->dev;
>>> +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
>>> +	adc->adc_info = palmas_gpadc_info;
>>> +	init_completion(&adc->conv_completion);
>>> +	dev_set_drvdata(&pdev->dev, iodev);
>>> +
>>> +	adc->auto_conversion_period = adc_pdata->auto_conversion_period_ms;
>>> +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
>>> +	ret = request_threaded_irq(adc->irq, NULL,
>>> +		palmas_gpadc_irq,
>>> +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
>>> +		adc);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev,
>>> +			"request irq %d failed: %dn", adc->irq, ret);
>>> +		goto out_unregister_map;
>>> +	}
>>> +
>>> +	if (adc_pdata->adc_wakeup1_data) {
>>> +		memcpy(&adc->wakeup1_data, adc_pdata->adc_wakeup1_data,
>>> +			sizeof(adc->wakeup1_data));
>>> +		adc->wakeup1_enable = true;
>>> +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
>>> +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
>>> +				palmas_gpadc_irq_auto,
>>> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
>>> +				"palmas-adc-auto-0", adc);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev, "request auto0 irq %d failed: %dn",
>>> +				adc->irq_auto_0, ret);
>>> +			goto out_irq_free;
>>> +		}
>>> +	}
>>> +
>>> +	if (adc_pdata->adc_wakeup2_data) {
>>> +		memcpy(&adc->wakeup2_data, adc_pdata->adc_wakeup2_data,
>>> +				sizeof(adc->wakeup2_data));
>>> +		adc->wakeup2_enable = true;
>>> +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
>>> +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
>>> +				palmas_gpadc_irq_auto,
>>> +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
>>> +				"palmas-adc-auto-1", adc);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev, "request auto1 irq %d failed: %dn",
>>> +				adc->irq_auto_1, ret);
>>> +			goto out_irq_auto0_free;
>>> +		}
>>> +	}
>>> +
>> I've requested kernel-doc above for ch0_current, but if that doesn't
>> make it clear what matic is going on here then some comments here
>> would be good.
> 
> I think the best location to make that clear is the DT bindings where you
> can set the values (in microamperes and not encoded values).
> 
> Basically you specify in the adc_pdata the uA you want and this function
> does a quantization to perpare 2 bits that can be written into the control
> registers later on.
> 
>>> +	if (adc_pdata->ch0_current == 0)
>>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
>>> +	else if (adc_pdata->ch0_current <= 5)
>>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
>>> +	else if (adc_pdata->ch0_current <= 15)
>>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
>>> +	else
>>> +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
>>> +
>>> +	if (adc_pdata->ch3_current == 0)
>>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
>>> +	else if (adc_pdata->ch3_current <= 10)
>>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
>>> +	else if (adc_pdata->ch3_current <= 400)
>>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
>>> +	else
>>> +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
>>> +
>>> +	adc->extended_delay = adc_pdata->extended_delay;
>>> +
>>> +	iodev->name = MOD_NAME;
>>> +	iodev->dev.parent = &pdev->dev;
>>> +	iodev->info = &palmas_gpadc_iio_info;
>>> +	iodev->modes = INDIO_DIRECT_MODE;
>>> +	iodev->channels = palmas_gpadc_iio_channel;
>>> +	iodev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
>>> +
>>> +	ret = iio_device_register(iodev);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
>>> +		goto out_irq_auto1_free;
>>> +	}
>>> +
>>> +	device_set_wakeup_capable(&pdev->dev, 1);
>>> +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
>>> +		if (!(adc->adc_info[i].is_correct_code))
>>> +			palmas_gpadc_calibrate(adc, i);
>>> +	}
>>> +
>>> +	if (adc->wakeup1_enable || adc->wakeup2_enable)
>>> +		device_wakeup_enable(&pdev->dev);
>> There is no match for this in the remove... Should there be one?
>> (not an interface I know anything about!)
> 
> Same for me and I have no idea how to test. But it looks reasonable to call
> device_wakeup_disable(). Therefore I have added.
> 
>>> +
>>> +	return 0;
>>> +
>>> +out_irq_auto1_free:
>>> +	if (adc_pdata->adc_wakeup2_data)
>>> +		free_irq(adc->irq_auto_1, adc);
>>> +out_irq_auto0_free:
>>> +	if (adc_pdata->adc_wakeup1_data)
>>> +		free_irq(adc->irq_auto_0, adc);
>>> +out_irq_free:
>>> +	free_irq(adc->irq, adc);
>>> +out_unregister_map:
>>> +	if (adc_pdata->iio_maps)
>>> +		iio_map_array_unregister(iodev);
>>> +out:
>>> +	iio_device_free(iodev);
>>> +	return ret;
>>> +}
>>> +
>>> +static int palmas_gpadc_remove(struct platform_device *pdev)
>>> +{
>>> +	struct iio_dev *iodev = dev_to_iio_dev(&pdev->dev);
>>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>>> +	struct palmas_platform_data *pdata = dev_get_platdata(pdev->dev.parent);
>>> +
>>> +	if (pdata->gpadc_pdata->iio_maps)
>>> +		iio_map_array_unregister(iodev);
>>> +	iio_device_unregister(iodev);
>>> +	free_irq(adc->irq, adc);
>>> +	if (adc->wakeup1_enable)
>>> +		free_irq(adc->irq_auto_0, adc);
>>> +	if (adc->wakeup2_enable)
>>> +		free_irq(adc->irq_auto_1, adc);
>>> +	iio_device_free(iodev);
>> Could use demv_iio_device_alloc and not need the free here or on error path.
>> Given location of irq frees you could do devm allocations of them as well
>> which would cut out a fair bit of code without reordering anything.
> 
> has also been proposed by Peter Meerwald and has been reworked.
> 
>>
>> nit: blank line here.
>>> +	return 0;
>>> +}
>>> +
>>> +#ifdef CONFIG_PM_SLEEP
>>> +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
>>> +{
>>> +	int adc_period, conv;
>>> +	int i;
>>> +	int ch0 = 0, ch1 = 0;
>>> +	int thres;
>>> +	int ret;
>>> +
>>> +	adc_period = adc->auto_conversion_period;
>>> +	for (i = 0; i < 16; ++i) {
>> spacing around the /
>>> +		if (((1000 * (1 << i))/32) < adc_period)
>>> +			continue;
>>> +	}
>>> +	if (i > 0)
>>> +		i--;
>>> +	adc_period = i;
>>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_AUTO_CTRL,
>>> +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
>>> +			adc_period);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	conv = 0;
>>> +	if (adc->wakeup1_enable) {
>>> +		int is_high;
>>> +
>>> +		ch0 = adc->wakeup1_data.adc_channel_number;
>>> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
>>> +		if (adc->wakeup1_data.adc_high_threshold > 0) {
>>> +			thres = adc->wakeup1_data.adc_high_threshold;
>>> +			is_high = 0;
>>> +		} else {
>>> +			thres = adc->wakeup1_data.adc_low_threshold;
>>> +			is_high = BIT(7);
>> BIT(7) is a bit random, so I'd suggest defining an appropriate macro
>> for it this register field.
> 
> There is indeed a proper macro in palmas.h
> 
>>
>>> +		}
>>> +
>>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev,
>>> +				"THRES_CONV0_LSB write failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +
>>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_THRES_CONV0_MSB,
>>> +				((thres >> 8) & 0xF) | is_high);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev,
>>> +				"THRES_CONV0_MSB write failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +	}
>>> +
>>> +	if (adc->wakeup2_enable) {
>>> +		int is_high;
>>> +
>>> +		ch1 = adc->wakeup2_data.adc_channel_number;
>>> +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
>>> +		if (adc->wakeup2_data.adc_high_threshold > 0) {
>>> +			thres = adc->wakeup2_data.adc_high_threshold;
>>> +			is_high = 0;
>>> +		} else {
>>> +			thres = adc->wakeup2_data.adc_low_threshold;
>>> +			is_high = BIT(7);
>>> +		}
>>> +
>>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev,
>>> +				"THRES_CONV1_LSB write failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +
>>> +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +				PALMAS_GPADC_THRES_CONV1_MSB,
>>> +				((thres >> 8) & 0xF) | is_high);
>>> +		if (ret < 0) {
>>> +			dev_err(adc->dev,
>>> +				"THRES_CONV1_MSB write failed: %d\n", ret);
>>> +			return ret;
>>> +		}
>>> +	}
>>> +
>>> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_AUTO_CTRL,
>>> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
>>> +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>> nitpick. Blank line here please.
>>> +	return 0;
>>> +}
>>> +
>>> +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
>>> +			PALMAS_GPADC_AUTO_SELECT, 0);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +
>>> +	ret = palmas_disable_auto_conversion(adc);
>>> +	if (ret < 0) {
>>> +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
>>> +		return ret;
>>> +	}
>>> +	return 0;
>>> +}
>>> +
>>> +static int palmas_gpadc_suspend(struct device *dev)
>>> +{
>>> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
>>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>>> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
>>> +	int ret;
>>> +
>>> +	if (!device_may_wakeup(dev) || !wakeup)
>>> +		return 0;
>>> +
>>> +	ret = palmas_adc_wakeup_configure(adc);
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	if (adc->wakeup1_enable)
>>> +		enable_irq_wake(adc->irq_auto_0);
>>> +
>>> +	if (adc->wakeup2_enable)
>>> +		enable_irq_wake(adc->irq_auto_1);
>> nitpick. Blank line here please.
>>> +	return 0;
>>> +}
>>> +
>>> +static int palmas_gpadc_resume(struct device *dev)
>>> +{
>>> +	struct iio_dev *iodev = dev_to_iio_dev(dev);
>>> +	struct palmas_gpadc *adc = iio_priv(iodev);
>>> +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
>>> +	int ret;
>>> +
>>> +	if (!device_may_wakeup(dev) || !wakeup)
>>> +		return 0;
>>> +
>>> +	ret = palmas_adc_wakeup_reset(adc);
>>> +	if (ret < 0)
>>> +		return ret;
>>> +
>>> +	if (adc->wakeup1_enable)
>>> +		disable_irq_wake(adc->irq_auto_0);
>>> +
>>> +	if (adc->wakeup2_enable)
>>> +		disable_irq_wake(adc->irq_auto_1);
>>> +
>>> +	return 0;
>>> +};
>>> +#endif
>>> +
>>> +static const struct dev_pm_ops palmas_pm_ops = {
>>> +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
>>> +				palmas_gpadc_resume)
>>> +};
>>> +
>>> +static struct platform_driver palmas_gpadc_driver = {
>>> +	.probe = palmas_gpadc_probe,
>>> +	.remove = palmas_gpadc_remove,
>>> +	.driver = {
>>> +		.name = MOD_NAME,
>>> +		.owner = THIS_MODULE,
>>> +		.pm = &palmas_pm_ops,
>>> +	},
>>> +};
>>> +
>>> +static int __init palmas_gpadc_init(void)
>>> +{
>>> +	return platform_driver_register(&palmas_gpadc_driver);
>>> +}
>>> +module_init(palmas_gpadc_init);
>>> +
>>> +static void __exit palmas_gpadc_exit(void)
>>> +{
>>> +	platform_driver_unregister(&palmas_gpadc_driver);
>>> +}
>>> +module_exit(palmas_gpadc_exit);
>>> +
>>> +MODULE_DESCRIPTION("palmas GPADC driver");
>>> +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
>>> +MODULE_ALIAS("platform:palmas-gpadc");
>>> +MODULE_LICENSE("GPL v2");
>>> diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
>>> index bb270bd..60acfa2 100644
>>> --- a/include/linux/mfd/palmas.h
>>> +++ b/include/linux/mfd/palmas.h
>>> @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
>>> 			    struct regulator_config config);
>>> };
>>>
>>> +struct palmas_adc_wakeup_property {
>>> +	int adc_channel_number;
>>> +	int adc_high_threshold;
>>> +	int adc_low_threshold;
>>> +};
>>> +
>>> struct palmas_gpadc_platform_data {
>>> 	/* Channel 3 current source is only enabled during conversion */
>>> 	int ch3_current;
>>> -
>>> 	/* Channel 0 current source can be used for battery detection.
>>> -	 * If used for battery detection this will cause a permanent current
>>> +	* If used for battery detection this will cause a permanent current
>>> 	 * consumption depending on current level set here.
>>> -	 */
>>> +		 */
>> Some oddness occured here in patch creation.  Do check your own patches
>> as we all end up with stuff like this from time to time that should
>> be picked up on beofre sending out.  It just adds noise.
>>> 	int ch0_current;
>>> +	bool extended_delay;
>>>
>>> 	/* default BAT_REMOVAL_DAT setting on device probe */
>>> 	int bat_removal;
>>>
>>> 	/* Sets the START_POLARITY bit in the RT_CTRL register */
>>> 	int start_polarity;
>>> +
>>> +	struct iio_map *iio_maps;
>>> +	int auto_conversion_period_ms;
>>> +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
>>> +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
>>> };
>>>
>>> struct palmas_reg_init {
>>> @@ -403,7 +414,7 @@ struct palmas_gpadc_calibration {
>>> 	s32 gain_error;
>>> 	s32 offset_error;
>>> };
>>> -
>>> +/*
>>> struct palmas_gpadc {
>>> 	struct device *dev;
>>> 	struct palmas *palmas;
>>> @@ -426,6 +437,9 @@ struct palmas_gpadc {
>>> 	int conv1_channel;
>>> 	int rt_channel;
>>> };
>>> +*/
>> So there's a commented out bit of code in this patch?  Sort
>> that out before the next version.
> 
> We forgot to delete...
> 
>>
>>> +
>>> +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
>>>
>>> struct palmas_gpadc_result {
>>> 	s32 raw_code;
>>> @@ -519,6 +533,42 @@ enum palmas_irqs {
>>> 	PALMAS_NUM_IRQ,
>>> };
>>>
>>> +/* Palma GPADC Channels */
>>> +enum {
>>> +	PALMAS_ADC_CH_IN0,
>>> +	PALMAS_ADC_CH_IN1,
>>> +	PALMAS_ADC_CH_IN2,
>>> +	PALMAS_ADC_CH_IN3,
>>> +	PALMAS_ADC_CH_IN4,
>>> +	PALMAS_ADC_CH_IN5,
>>> +	PALMAS_ADC_CH_IN6,
>>> +	PALMAS_ADC_CH_IN7,
>>> +	PALMAS_ADC_CH_IN8,
>>> +	PALMAS_ADC_CH_IN9,
>>> +	PALMAS_ADC_CH_IN10,
>>> +	PALMAS_ADC_CH_IN11,
>>> +	PALMAS_ADC_CH_IN12,
>>> +	PALMAS_ADC_CH_IN13,
>>> +	PALMAS_ADC_CH_IN14,
>>> +	PALMAS_ADC_CH_IN15,
>> This does rather feel like an enum that doesn't add anything
>> as the enum values = the index given at the end anyway...
> 
> As said it adds some (questionable) check by the compiler,
> 
>>> +	PALMAS_ADC_CH_MAX,
> 
> and this constant is indirectly defined.
> 
>>> +};
>>> +
>>> +/* Palma GPADC Channel0 Current Source */
>>> +enum {
>>> +	PALMAS_ADC_CH0_CURRENT_SRC_0,
>>> +	PALMAS_ADC_CH0_CURRENT_SRC_5,
>>> +	PALMAS_ADC_CH0_CURRENT_SRC_15,
>>> +	PALMAS_ADC_CH0_CURRENT_SRC_20,
>>> +};
>> Nitpick: new line here.
>>> +/* Palma GPADC Channel3 Current Source */
>>> +enum {
>>> +	PALMAS_ADC_CH3_CURRENT_SRC_0,
>>> +	PALMAS_ADC_CH3_CURRENT_SRC_10,
>>> +	PALMAS_ADC_CH3_CURRENT_SRC_400,
>>> +	PALMAS_ADC_CH3_CURRENT_SRC_800,
>>> +};
>>> +
>>> struct palmas_pmic {
>>> 	struct palmas *palmas;
>>> 	struct device *dev;
>>> @@ -2999,6 +3049,7 @@ enum usb_irq_events {
>>> #define PALMAS_GPADC_TRIM14					0x0D
>>> #define PALMAS_GPADC_TRIM15					0x0E
>>> #define PALMAS_GPADC_TRIM16					0x0F
>>> +#define PALMAS_GPADC_TRIMINVALID				-1
>>>
>>> /* TPS659038 regen2_ctrl offset iss different from palmas */
>>> #define TPS659038_REGEN2_CTRL					0x12
>>>
>>
> 
> 


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

* Re: [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc
  2015-10-11 14:27       ` Jonathan Cameron
@ 2015-10-13  8:14         ` Lee Jones
  0 siblings, 0 replies; 25+ messages in thread
From: Lee Jones @ 2015-10-13  8:14 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: H. Nikolaus Schaller, Rob Herring, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Benoît Cousson, Tony Lindgren,
	Russell King, Marek Belisko, Pradeep Goudagunta, Laxman Dewangan,
	gg, jic23, devicetree, linux-kernel, linux-omap, linux-iio,
	Grazvydas Ignotas, Samuel Ortiz

On Sun, 11 Oct 2015, Jonathan Cameron wrote:

> On 05/10/15 07:14, H. Nikolaus Schaller wrote:
> > This driver code was found as:
> > 
> > https://android.googlesource.com/kernel/tegra/+/aaabb2e045f31e5a970109ffdaae900dd403d17e/drivers/staging/iio/adc
> > 
> > Fixed various compilation issues and test this driver on omap5 evm.
> > 
> > Signed-off-by: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> > Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> > Signed-off-by: Marek Belisko <marek@goldelico.com>
> 
> I'm pretty much fine with this.  However, there are some edits within the
> existing mfd support so I will want acks for that or for the driver to go
> through the MFD tree (note that as it touched that, even if only a header,
> Lee and Samuel should have been cc'd).
> 
> One thing that slightly confuses me is there seems to be hints of support in the
> mfd driver already... I can't find any sign of the child device however so
> I guess this is fine from that point of view.
> 
> Reviewed-by: Jonathan Cameron <jic23@kernel.org>
> > ---
> >  drivers/iio/adc/Kconfig        |   9 +
> >  drivers/iio/adc/Makefile       |   1 +
> >  drivers/iio/adc/palmas_gpadc.c | 818 +++++++++++++++++++++++++++++++++++++++++
> >  include/linux/mfd/palmas.h     |  75 ++--

Acked-by: Lee Jones <lee.jones@linaro.org>

> >  4 files changed, 879 insertions(+), 24 deletions(-)
> >  create mode 100644 drivers/iio/adc/palmas_gpadc.c
> > 
> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> > index eb0cd89..05a0368 100644
> > --- a/drivers/iio/adc/Kconfig
> > +++ b/drivers/iio/adc/Kconfig
> > @@ -242,6 +242,15 @@ config NAU7802
> >  	  To compile this driver as a module, choose M here: the
> >  	  module will be called nau7802.
> >  
> > +config PALMAS_GPADC
> > +	tristate "TI Palmas General Purpose ADC"
> > +	depends on MFD_PALMAS
> > +	help
> > +	  Say yes here to build support for Texas Instruments Palmas.
> > +
> > +	  Palmas series pmic chip (twl6035/6037) is used in smartphones and
> > +	  tablets and supports a 16 channel general purpose ADC.
> > +
> >  config QCOM_SPMI_IADC
> >  	tristate "Qualcomm SPMI PMIC current ADC"
> >  	depends on SPMI
> > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> > index a096210..716f112 100644
> > --- a/drivers/iio/adc/Makefile
> > +++ b/drivers/iio/adc/Makefile
> > @@ -26,6 +26,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
> >  obj-$(CONFIG_MCP3422) += mcp3422.o
> >  obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
> >  obj-$(CONFIG_NAU7802) += nau7802.o
> > +obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
> >  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
> >  obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
> >  obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
> > diff --git a/drivers/iio/adc/palmas_gpadc.c b/drivers/iio/adc/palmas_gpadc.c
> > new file mode 100644
> > index 0000000..6805d81
> > --- /dev/null
> > +++ b/drivers/iio/adc/palmas_gpadc.c
> > @@ -0,0 +1,818 @@
> > +/*
> > + * palmas-adc.c -- TI PALMAS GPADC.
> > + *
> > + * Copyright (c) 2013, NVIDIA Corporation. All rights reserved.
> > + *
> > + * Author: Pradeep Goudagunta <pgoudagunta@nvidia.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation version 2.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/err.h>
> > +#include <linux/irq.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/slab.h>
> > +#include <linux/delay.h>
> > +#include <linux/i2c.h>
> > +#include <linux/pm.h>
> > +#include <linux/mfd/palmas.h>
> > +#include <linux/completion.h>
> > +#include <linux/iio/iio.h>
> > +#include <linux/iio/machine.h>
> > +#include <linux/iio/driver.h>
> > +
> > +#define MOD_NAME "palmas-gpadc"
> > +#define PALMAS_ADC_CONVERSION_TIMEOUT	(msecs_to_jiffies(5000))
> > +#define PALMAS_TO_BE_CALCULATED 0
> > +#define PALMAS_GPADC_TRIMINVALID	-1
> > +
> > +struct palmas_gpadc_info {
> > +/* calibration codes and regs */
> > +	int x1;	/* lower ideal code */
> > +	int x2;	/* higher ideal code */
> > +	int v1;	/* expected lower volt reading */
> > +	int v2;	/* expected higher volt reading */
> > +	u8 trim1_reg;	/* register number for lower trim */
> > +	u8 trim2_reg;	/* register number for upper trim */
> > +	int gain;	/* calculated from above (after reading trim regs) */
> > +	int offset;	/* calculated from above (after reading trim regs) */
> > +	int gain_error;	/* calculated from above (after reading trim regs) */
> > +	bool is_uncalibrated;	/* if channel has calibration data */
> > +};
> > +
> > +#define PALMAS_ADC_INFO(_chan, _x1, _x2, _v1, _v2, _t1, _t2, _is_uncalibrated) \
> > +	[PALMAS_ADC_CH_##_chan] = { \
> > +		.x1 = _x1, \
> > +		.x2 = _x2, \
> > +		.v1 = _v1, \
> > +		.v2 = _v2, \
> > +		.gain = PALMAS_TO_BE_CALCULATED, \
> > +		.offset = PALMAS_TO_BE_CALCULATED, \
> > +		.gain_error = PALMAS_TO_BE_CALCULATED, \
> > +		.trim1_reg = PALMAS_GPADC_TRIM##_t1, \
> > +		.trim2_reg = PALMAS_GPADC_TRIM##_t2,  \
> > +		.is_uncalibrated = _is_uncalibrated \
> > +	}
> > +
> > +static struct palmas_gpadc_info palmas_gpadc_info[] = {
> > +	PALMAS_ADC_INFO(IN0, 2064, 3112, 630, 950, 1, 2, false),
> > +	PALMAS_ADC_INFO(IN1, 2064, 3112, 630, 950, 1, 2, false),
> > +	PALMAS_ADC_INFO(IN2, 2064, 3112, 1260, 1900, 3, 4, false),
> > +	PALMAS_ADC_INFO(IN3, 2064, 3112, 630, 950, 1, 2, false),
> > +	PALMAS_ADC_INFO(IN4, 2064, 3112, 630, 950, 1, 2, false),
> > +	PALMAS_ADC_INFO(IN5, 2064, 3112, 630, 950, 1, 2, false),
> > +	PALMAS_ADC_INFO(IN6, 2064, 3112, 2520, 3800, 5, 6, false),
> > +	PALMAS_ADC_INFO(IN7, 2064, 3112, 2520, 3800, 7, 8, false),
> > +	PALMAS_ADC_INFO(IN8, 2064, 3112, 3150, 4750, 9, 10, false),
> > +	PALMAS_ADC_INFO(IN9, 2064, 3112, 5670, 8550, 11, 12, false),
> > +	PALMAS_ADC_INFO(IN10, 2064, 3112, 3465, 5225, 13, 14, false),
> > +	PALMAS_ADC_INFO(IN11, 0, 0, 0, 0, INVALID, INVALID, true),
> > +	PALMAS_ADC_INFO(IN12, 0, 0, 0, 0, INVALID, INVALID, true),
> > +	PALMAS_ADC_INFO(IN13, 0, 0, 0, 0, INVALID, INVALID, true),
> > +	PALMAS_ADC_INFO(IN14, 2064, 3112, 3645, 5225, 15, 16, false),
> > +	PALMAS_ADC_INFO(IN15, 0, 0, 0, 0, INVALID, INVALID, true),
> > +};
> > +
> > +/**
> > + * struct palmas_gpadc - the palmas_gpadc structure
> > + * @ch0_current:	channel 0 current source setting
> > + *    0: 0 uA
> > + *    1: 5 uA
> > + *    2: 15 uA
> > + *    3: 20 uA
> > + * @ch3_current:	channel 3 current source setting
> > + *    0: 0 uA
> > + *    1: 10 uA
> > + *    2: 400 uA
> > + *    3: 800 uA
> > + * @extended_delay:	enable the gpadc extended delay mode
> > + * @auto_conversion_period:	define the auto_conversion_period
> > + *
> > + * This is the palmas_gpadc structure to store run-time information
> > + * and pointers for this driver instance.
> > + */
> > +
> > +struct palmas_gpadc {
> > +	struct device			*dev;
> > +	struct palmas			*palmas;
> > +	u8				ch0_current;
> > +	u8				ch3_current;
> > +	bool				extended_delay;
> > +	int				irq;
> > +	int				irq_auto_0;
> > +	int				irq_auto_1;
> > +	struct palmas_gpadc_info	*adc_info;
> > +	struct completion		conv_completion;
> > +	struct palmas_adc_wakeup_property wakeup1_data;
> > +	struct palmas_adc_wakeup_property wakeup2_data;
> > +	bool				wakeup1_enable;
> > +	bool				wakeup2_enable;
> > +	int				auto_conversion_period;
> > +};
> > +
> > +/*
> > + * GPADC lock issue in AUTO mode.
> > + * Impact: In AUTO mode, GPADC conversion can be locked after disabling AUTO
> > + *	   mode feature.
> > + * Details:
> > + *	When the AUTO mode is the only conversion mode enabled, if the AUTO
> > + *	mode feature is disabled with bit GPADC_AUTO_CTRL.  AUTO_CONV1_EN = 0
> > + *	or bit GPADC_AUTO_CTRL.  AUTO_CONV0_EN = 0 during a conversion, the
> > + *	conversion mechanism can be seen as locked meaning that all following
> > + *	conversion will give 0 as a result.  Bit GPADC_STATUS.GPADC_AVAILABLE
> > + *	will stay at 0 meaning that GPADC is busy.  An RT conversion can unlock
> > + *	the GPADC.
> > + *
> > + * Workaround(s):
> > + *	To avoid the lock mechanism, the workaround to follow before any stop
> > + *	conversion request is:
> > + *	Force the GPADC state machine to be ON by using the GPADC_CTRL1.
> > + *		GPADC_FORCE bit = 1
> > + *	Shutdown the GPADC AUTO conversion using
> > + *		GPADC_AUTO_CTRL.SHUTDOWN_CONV[01] = 0.
> > + *	After 100us, force the GPADC state machine to be OFF by using the
> > + *		GPADC_CTRL1.  GPADC_FORCE bit = 0
> > + */
> > +
> > +static int palmas_disable_auto_conversion(struct palmas_gpadc *adc)
> > +{
> > +	int ret;
> > +
> > +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_CTRL1,
> > +			PALMAS_GPADC_CTRL1_GPADC_FORCE,
> > +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_AUTO_CTRL,
> > +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV1 |
> > +			PALMAS_GPADC_AUTO_CTRL_SHUTDOWN_CONV0,
> > +			0);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "AUTO_CTRL update failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	udelay(100);
> > +
> > +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_CTRL1,
> > +			PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> > +	if (ret < 0)
> > +		dev_err(adc->dev, "GPADC_CTRL1 update failed: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static irqreturn_t palmas_gpadc_irq(int irq, void *data)
> > +{
> > +	struct palmas_gpadc *adc = data;
> > +
> > +	complete(&adc->conv_completion);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static irqreturn_t palmas_gpadc_irq_auto(int irq, void *data)
> > +{
> > +	struct palmas_gpadc *adc = data;
> > +
> > +	dev_dbg(adc->dev, "Threshold interrupt %d occurs\n", irq);
> > +	palmas_disable_auto_conversion(adc);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static int palmas_gpadc_start_mask_interrupt(struct palmas_gpadc *adc,
> > +							bool mask)
> > +{
> > +	int ret;
> > +
> > +	if (!mask)
> > +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> > +					PALMAS_INT3_MASK,
> > +					PALMAS_INT3_MASK_GPADC_EOC_SW, 0);
> > +	else
> > +		ret = palmas_update_bits(adc->palmas, PALMAS_INTERRUPT_BASE,
> > +					PALMAS_INT3_MASK,
> > +					PALMAS_INT3_MASK_GPADC_EOC_SW,
> > +					PALMAS_INT3_MASK_GPADC_EOC_SW);
> > +	if (ret < 0)
> > +		dev_err(adc->dev, "GPADC INT MASK update failed: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int palmas_gpadc_enable(struct palmas_gpadc *adc, int adc_chan,
> > +			       int enable)
> > +{
> > +	unsigned int mask, val;
> > +	int ret;
> > +
> > +	if (enable) {
> > +		val = (adc->extended_delay
> > +			<< PALMAS_GPADC_RT_CTRL_EXTEND_DELAY_SHIFT);
> > +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +					PALMAS_GPADC_RT_CTRL,
> > +					PALMAS_GPADC_RT_CTRL_EXTEND_DELAY, val);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev, "RT_CTRL update failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +
> > +		mask = (PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_MASK |
> > +			PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_MASK |
> > +			PALMAS_GPADC_CTRL1_GPADC_FORCE);
> > +		val = (adc->ch0_current
> > +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH0_SHIFT);
> > +		val |= (adc->ch3_current
> > +			<< PALMAS_GPADC_CTRL1_CURRENT_SRC_CH3_SHIFT);
> > +		val |= PALMAS_GPADC_CTRL1_GPADC_FORCE;
> > +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_CTRL1, mask, val);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev,
> > +				"Failed to update current setting: %d\n", ret);
> > +			return ret;
> > +		}
> > +
> > +		mask = (PALMAS_GPADC_SW_SELECT_SW_CONV0_SEL_MASK |
> > +			PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> > +		val = (adc_chan | PALMAS_GPADC_SW_SELECT_SW_CONV_EN);
> > +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_SW_SELECT, mask, val);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev, "SW_SELECT update failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +	} else {
> > +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_SW_SELECT, 0);
> > +		if (ret < 0)
> > +			dev_err(adc->dev, "SW_SELECT write failed: %d\n", ret);
> > +
> > +		ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_CTRL1,
> > +				PALMAS_GPADC_CTRL1_GPADC_FORCE, 0);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev, "CTRL1 update failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int palmas_gpadc_read_prepare(struct palmas_gpadc *adc, int adc_chan)
> > +{
> > +	int ret;
> > +
> > +	ret = palmas_gpadc_enable(adc, adc_chan, true);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	return palmas_gpadc_start_mask_interrupt(adc, 0);
> > +}
> > +
> > +static void palmas_gpadc_read_done(struct palmas_gpadc *adc, int adc_chan)
> > +{
> > +	palmas_gpadc_start_mask_interrupt(adc, 1);
> > +	palmas_gpadc_enable(adc, adc_chan, false);
> > +}
> > +
> > +static int palmas_gpadc_calibrate(struct palmas_gpadc *adc, int adc_chan)
> > +{
> > +	int k;
> > +	int d1;
> > +	int d2;
> > +	int ret;
> > +	int gain;
> > +	int x1 =  adc->adc_info[adc_chan].x1;
> > +	int x2 =  adc->adc_info[adc_chan].x2;
> > +	int v1 = adc->adc_info[adc_chan].v1;
> > +	int v2 = adc->adc_info[adc_chan].v2;
> > +
> > +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> > +				adc->adc_info[adc_chan].trim1_reg, &d1);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> > +		goto scrub;
> > +	}
> > +
> > +	ret = palmas_read(adc->palmas, PALMAS_TRIM_GPADC_BASE,
> > +				adc->adc_info[adc_chan].trim2_reg, &d2);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "TRIM read failed: %d\n", ret);
> > +		goto scrub;
> > +	}
> > +
> > +	/* gain error calculation */
> > +	k = (1000 + (1000 * (d2 - d1)) / (x2 - x1));
> > +
> > +	/* gain calculation */
> > +	gain = ((v2 - v1) * 1000) / (x2 - x1);
> > +
> > +	adc->adc_info[adc_chan].gain_error = k;
> > +	adc->adc_info[adc_chan].gain = gain;
> > +	/* offset Calculation */
> > +	adc->adc_info[adc_chan].offset = (d1 * 1000) - ((k - 1000) * x1);
> > +
> > +scrub:
> > +	return ret;
> > +}
> > +
> > +static int palmas_gpadc_start_conversion(struct palmas_gpadc *adc, int adc_chan)
> > +{
> > +	unsigned int val;
> > +	int ret;
> > +
> > +	init_completion(&adc->conv_completion);
> > +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_SW_SELECT,
> > +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0,
> > +				PALMAS_GPADC_SW_SELECT_SW_START_CONV0);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "SELECT_SW_START write failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = wait_for_completion_timeout(&adc->conv_completion,
> > +				PALMAS_ADC_CONVERSION_TIMEOUT);
> > +	if (ret == 0) {
> > +		dev_err(adc->dev, "conversion not completed\n");
> > +		return -ETIMEDOUT;
> > +	}
> > +
> > +	ret = palmas_bulk_read(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_SW_CONV0_LSB, &val, 2);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "SW_CONV0_LSB read failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = val & 0xFFF;
> > +
> > +	return ret;
> > +}
> > +
> > +static int palmas_gpadc_get_calibrated_code(struct palmas_gpadc *adc,
> > +						int adc_chan, int val)
> > +{
> > +	if (!adc->adc_info[adc_chan].is_uncalibrated)
> > +		val  = (val*1000 - adc->adc_info[adc_chan].offset) /
> > +					adc->adc_info[adc_chan].gain_error;
> > +
> > +	if (val < 0) {
> > +		dev_err(adc->dev, "Mismatch with calibration\n");
> > +		return 0;
> > +	}
> > +
> > +	val = (val * adc->adc_info[adc_chan].gain) / 1000;
> > +
> > +	return val;
> > +}
> > +
> > +static int palmas_gpadc_read_raw(struct iio_dev *indio_dev,
> > +	struct iio_chan_spec const *chan, int *val, int *val2, long mask)
> > +{
> > +	struct  palmas_gpadc *adc = iio_priv(indio_dev);
> > +	int adc_chan = chan->channel;
> > +	int ret = 0;
> > +
> > +	if (adc_chan > PALMAS_ADC_CH_MAX)
> > +		return -EINVAL;
> > +
> > +	mutex_lock(&indio_dev->mlock);
> > +
> > +	switch (mask) {
> > +	case IIO_CHAN_INFO_RAW:
> > +	case IIO_CHAN_INFO_PROCESSED:
> > +		ret = palmas_gpadc_read_prepare(adc, adc_chan);
> > +		if (ret < 0)
> > +			goto out;
> > +
> > +		ret = palmas_gpadc_start_conversion(adc, adc_chan);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev,
> > +			"ADC start conversion failed\n");
> > +			goto out;
> > +		}
> > +
> > +		if (mask == IIO_CHAN_INFO_PROCESSED)
> > +			ret = palmas_gpadc_get_calibrated_code(
> > +							adc, adc_chan, ret);
> > +
> > +		*val = ret;
> > +
> > +		ret = IIO_VAL_INT;
> > +		goto out;
> > +	}
> > +
> > +	mutex_unlock(&indio_dev->mlock);
> > +	return ret;
> > +
> > +out:
> > +	palmas_gpadc_read_done(adc, adc_chan);
> > +	mutex_unlock(&indio_dev->mlock);
> > +
> > +	return ret;
> > +}
> > +
> > +static const struct iio_info palmas_gpadc_iio_info = {
> > +	.read_raw = palmas_gpadc_read_raw,
> > +	.driver_module = THIS_MODULE,
> > +};
> > +
> > +#define PALMAS_ADC_CHAN_IIO(chan, _type, chan_info)	\
> > +{							\
> > +	.datasheet_name = PALMAS_DATASHEET_NAME(chan),	\
> > +	.type = _type,					\
> > +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |	\
> > +			BIT(chan_info),			\
> > +	.indexed = 1,					\
> > +	.channel = PALMAS_ADC_CH_##chan,		\
> > +}
> > +
> > +static const struct iio_chan_spec palmas_gpadc_iio_channel[] = {
> > +	PALMAS_ADC_CHAN_IIO(IN0, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN1, IIO_TEMP, IIO_CHAN_INFO_RAW),
> > +	PALMAS_ADC_CHAN_IIO(IN2, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN3, IIO_TEMP, IIO_CHAN_INFO_RAW),
> > +	PALMAS_ADC_CHAN_IIO(IN4, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN5, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN6, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN7, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN8, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN9, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN10, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN11, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN12, IIO_TEMP, IIO_CHAN_INFO_RAW),
> > +	PALMAS_ADC_CHAN_IIO(IN13, IIO_TEMP, IIO_CHAN_INFO_RAW),
> > +	PALMAS_ADC_CHAN_IIO(IN14, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +	PALMAS_ADC_CHAN_IIO(IN15, IIO_VOLTAGE, IIO_CHAN_INFO_PROCESSED),
> > +};
> > +
> > +static int palmas_gpadc_probe(struct platform_device *pdev)
> > +{
> > +	struct palmas_gpadc *adc;
> > +	struct palmas_platform_data *pdata;
> > +	struct palmas_gpadc_platform_data *gpadc_pdata = NULL;
> > +	struct iio_dev *indio_dev;
> > +	int ret, i;
> > +
> > +	pdata = dev_get_platdata(pdev->dev.parent);
> > +	if (!pdata || !pdata->gpadc_pdata) {
> > +		dev_err(&pdev->dev, "No platform data\n");
> > +		return -ENODEV;
> > +	}
> > +
> > +	gpadc_pdata = pdata->gpadc_pdata;
> > +
> > +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*adc));
> > +	if (!indio_dev) {
> > +		dev_err(&pdev->dev, "iio_device_alloc failed\n");
> > +		return -ENOMEM;
> > +	}
> > +
> > +	adc = iio_priv(indio_dev);
> > +	adc->dev = &pdev->dev;
> > +	adc->palmas = dev_get_drvdata(pdev->dev.parent);
> > +	adc->adc_info = palmas_gpadc_info;
> > +	init_completion(&adc->conv_completion);
> > +	dev_set_drvdata(&pdev->dev, indio_dev);
> > +
> > +	adc->auto_conversion_period = gpadc_pdata->auto_conversion_period_ms;
> > +	adc->irq = palmas_irq_get_virq(adc->palmas, PALMAS_GPADC_EOC_SW_IRQ);
> > +	if (adc->irq < 0) {
> > +		dev_err(adc->dev,
> > +			"get virq failed: %d\n", adc->irq);
> > +		ret = adc->irq;
> > +		goto out;
> > +	}
> > +	ret = request_threaded_irq(adc->irq, NULL,
> > +		palmas_gpadc_irq,
> > +		IRQF_ONESHOT | IRQF_EARLY_RESUME, dev_name(adc->dev),
> > +		adc);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev,
> > +			"request irq %d failed: %d\n", adc->irq, ret);
> > +		goto out;
> > +	}
> > +
> > +	if (gpadc_pdata->adc_wakeup1_data) {
> > +		memcpy(&adc->wakeup1_data, gpadc_pdata->adc_wakeup1_data,
> > +			sizeof(adc->wakeup1_data));
> > +		adc->wakeup1_enable = true;
> > +		adc->irq_auto_0 =  platform_get_irq(pdev, 1);
> > +		ret = request_threaded_irq(adc->irq_auto_0, NULL,
> > +				palmas_gpadc_irq_auto,
> > +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> > +				"palmas-adc-auto-0", adc);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev, "request auto0 irq %d failed: %d\n",
> > +				adc->irq_auto_0, ret);
> > +			goto out_irq_free;
> > +		}
> > +	}
> > +
> > +	if (gpadc_pdata->adc_wakeup2_data) {
> > +		memcpy(&adc->wakeup2_data, gpadc_pdata->adc_wakeup2_data,
> > +				sizeof(adc->wakeup2_data));
> > +		adc->wakeup2_enable = true;
> > +		adc->irq_auto_1 =  platform_get_irq(pdev, 2);
> > +		ret = request_threaded_irq(adc->irq_auto_1, NULL,
> > +				palmas_gpadc_irq_auto,
> > +				IRQF_ONESHOT | IRQF_EARLY_RESUME,
> > +				"palmas-adc-auto-1", adc);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev, "request auto1 irq %d failed: %d\n",
> > +				adc->irq_auto_1, ret);
> > +			goto out_irq_auto0_free;
> > +		}
> > +	}
> > +
> > +	/* set the current source 0 (value 0/5/15/20 uA => 0..3) */
> > +	if (gpadc_pdata->ch0_current <= 1)
> > +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_0;
> > +	else if (gpadc_pdata->ch0_current <= 5)
> > +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_5;
> > +	else if (gpadc_pdata->ch0_current <= 15)
> > +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_15;
> > +	else
> > +		adc->ch0_current = PALMAS_ADC_CH0_CURRENT_SRC_20;
> > +
> > +	/* set the current source 3 (value 0/10/400/800 uA => 0..3) */
> > +	if (gpadc_pdata->ch3_current <= 1)
> > +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_0;
> > +	else if (gpadc_pdata->ch3_current <= 10)
> > +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_10;
> > +	else if (gpadc_pdata->ch3_current <= 400)
> > +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_400;
> > +	else
> > +		adc->ch3_current = PALMAS_ADC_CH3_CURRENT_SRC_800;
> > +
> > +	adc->extended_delay = gpadc_pdata->extended_delay;
> > +
> > +	indio_dev->name = MOD_NAME;
> > +	indio_dev->dev.parent = &pdev->dev;
> > +	indio_dev->info = &palmas_gpadc_iio_info;
> > +	indio_dev->modes = INDIO_DIRECT_MODE;
> > +	indio_dev->channels = palmas_gpadc_iio_channel;
> > +	indio_dev->num_channels = ARRAY_SIZE(palmas_gpadc_iio_channel);
> > +
> > +	ret = iio_device_register(indio_dev);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "iio_device_register() failed: %d\n", ret);
> > +		goto out_irq_auto1_free;
> > +	}
> > +
> > +	device_set_wakeup_capable(&pdev->dev, 1);
> > +	for (i = 0; i < PALMAS_ADC_CH_MAX; i++) {
> > +		if (!(adc->adc_info[i].is_uncalibrated))
> > +			palmas_gpadc_calibrate(adc, i);
> > +	}
> > +
> > +	if (adc->wakeup1_enable || adc->wakeup2_enable)
> > +		device_wakeup_enable(&pdev->dev);
> > +
> > +	return 0;
> > +
> > +out_irq_auto1_free:
> > +	if (gpadc_pdata->adc_wakeup2_data)
> > +		free_irq(adc->irq_auto_1, adc);
> > +out_irq_auto0_free:
> > +	if (gpadc_pdata->adc_wakeup1_data)
> > +		free_irq(adc->irq_auto_0, adc);
> > +out_irq_free:
> > +	free_irq(adc->irq, adc);
> > +out:
> > +	return ret;
> > +}
> > +
> > +static int palmas_gpadc_remove(struct platform_device *pdev)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(&pdev->dev);
> > +	struct palmas_gpadc *adc = iio_priv(indio_dev);
> > +
> > +	if (adc->wakeup1_enable || adc->wakeup2_enable)
> > +		device_wakeup_disable(&pdev->dev);
> > +	iio_device_unregister(indio_dev);
> > +	free_irq(adc->irq, adc);
> > +	if (adc->wakeup1_enable)
> > +		free_irq(adc->irq_auto_0, adc);
> > +	if (adc->wakeup2_enable)
> > +		free_irq(adc->irq_auto_1, adc);
> > +
> > +	return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int palmas_adc_wakeup_configure(struct palmas_gpadc *adc)
> > +{
> > +	int adc_period, conv;
> > +	int i;
> > +	int ch0 = 0, ch1 = 0;
> > +	int thres;
> > +	int ret;
> > +
> > +	adc_period = adc->auto_conversion_period;
> > +	for (i = 0; i < 16; ++i) {
> > +		if (((1000 * (1 << i)) / 32) < adc_period)
> > +			continue;
> > +	}
> > +	if (i > 0)
> > +		i--;
> > +	adc_period = i;
> > +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_AUTO_CTRL,
> > +			PALMAS_GPADC_AUTO_CTRL_COUNTER_CONV_MASK,
> > +			adc_period);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	conv = 0;
> > +	if (adc->wakeup1_enable) {
> > +		int polarity;
> > +
> > +		ch0 = adc->wakeup1_data.adc_channel_number;
> > +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN;
> > +		if (adc->wakeup1_data.adc_high_threshold > 0) {
> > +			thres = adc->wakeup1_data.adc_high_threshold;
> > +			polarity = 0;
> > +		} else {
> > +			thres = adc->wakeup1_data.adc_low_threshold;
> > +			polarity = PALMAS_GPADC_THRES_CONV0_MSB_THRES_CONV0_POL;
> > +		}
> > +
> > +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_THRES_CONV0_LSB, thres & 0xFF);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev,
> > +				"THRES_CONV0_LSB write failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +
> > +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_THRES_CONV0_MSB,
> > +				((thres >> 8) & 0xF) | polarity);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev,
> > +				"THRES_CONV0_MSB write failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	if (adc->wakeup2_enable) {
> > +		int polarity;
> > +
> > +		ch1 = adc->wakeup2_data.adc_channel_number;
> > +		conv |= PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN;
> > +		if (adc->wakeup2_data.adc_high_threshold > 0) {
> > +			thres = adc->wakeup2_data.adc_high_threshold;
> > +			polarity = 0;
> > +		} else {
> > +			thres = adc->wakeup2_data.adc_low_threshold;
> > +			polarity = PALMAS_GPADC_THRES_CONV1_MSB_THRES_CONV1_POL;
> > +		}
> > +
> > +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_THRES_CONV1_LSB, thres & 0xFF);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev,
> > +				"THRES_CONV1_LSB write failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +
> > +		ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +				PALMAS_GPADC_THRES_CONV1_MSB,
> > +				((thres >> 8) & 0xF) | polarity);
> > +		if (ret < 0) {
> > +			dev_err(adc->dev,
> > +				"THRES_CONV1_MSB write failed: %d\n", ret);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_AUTO_SELECT, (ch1 << 4) | ch0);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = palmas_update_bits(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_AUTO_CTRL,
> > +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV1_EN |
> > +			PALMAS_GPADC_AUTO_CTRL_AUTO_CONV0_EN, conv);
> > +	if (ret < 0)
> > +		dev_err(adc->dev, "AUTO_CTRL write failed: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int palmas_adc_wakeup_reset(struct palmas_gpadc *adc)
> > +{
> > +	int ret;
> > +
> > +	ret = palmas_write(adc->palmas, PALMAS_GPADC_BASE,
> > +			PALMAS_GPADC_AUTO_SELECT, 0);
> > +	if (ret < 0) {
> > +		dev_err(adc->dev, "AUTO_SELECT write failed: %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = palmas_disable_auto_conversion(adc);
> > +	if (ret < 0)
> > +		dev_err(adc->dev, "Disable auto conversion failed: %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int palmas_gpadc_suspend(struct device *dev)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct palmas_gpadc *adc = iio_priv(indio_dev);
> > +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> > +	int ret;
> > +
> > +	if (!device_may_wakeup(dev) || !wakeup)
> > +		return 0;
> > +
> > +	ret = palmas_adc_wakeup_configure(adc);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	if (adc->wakeup1_enable)
> > +		enable_irq_wake(adc->irq_auto_0);
> > +
> > +	if (adc->wakeup2_enable)
> > +		enable_irq_wake(adc->irq_auto_1);
> > +
> > +	return 0;
> > +}
> > +
> > +static int palmas_gpadc_resume(struct device *dev)
> > +{
> > +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +	struct palmas_gpadc *adc = iio_priv(indio_dev);
> > +	int wakeup = adc->wakeup1_enable || adc->wakeup2_enable;
> > +	int ret;
> > +
> > +	if (!device_may_wakeup(dev) || !wakeup)
> > +		return 0;
> > +
> > +	ret = palmas_adc_wakeup_reset(adc);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	if (adc->wakeup1_enable)
> > +		disable_irq_wake(adc->irq_auto_0);
> > +
> > +	if (adc->wakeup2_enable)
> > +		disable_irq_wake(adc->irq_auto_1);
> > +
> > +	return 0;
> > +};
> > +#endif
> > +
> > +static const struct dev_pm_ops palmas_pm_ops = {
> > +	SET_SYSTEM_SLEEP_PM_OPS(palmas_gpadc_suspend,
> > +				palmas_gpadc_resume)
> > +};
> > +
> > +static struct platform_driver palmas_gpadc_driver = {
> > +	.probe = palmas_gpadc_probe,
> > +	.remove = palmas_gpadc_remove,
> > +	.driver = {
> > +		.name = MOD_NAME,
> > +		.owner = THIS_MODULE,
> > +		.pm = &palmas_pm_ops,
> > +	},
> > +};
> > +
> > +static int __init palmas_gpadc_init(void)
> > +{
> > +	return platform_driver_register(&palmas_gpadc_driver);
> > +}
> > +module_init(palmas_gpadc_init);
> > +
> > +static void __exit palmas_gpadc_exit(void)
> > +{
> > +	platform_driver_unregister(&palmas_gpadc_driver);
> > +}
> > +module_exit(palmas_gpadc_exit);
> > +
> > +MODULE_DESCRIPTION("palmas GPADC driver");
> > +MODULE_AUTHOR("Pradeep Goudagunta<pgoudagunta@nvidia.com>");
> > +MODULE_ALIAS("platform:palmas-gpadc");
> > +MODULE_LICENSE("GPL v2");
> > diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h
> > index bb270bd..2fcd70e 100644
> > --- a/include/linux/mfd/palmas.h
> > +++ b/include/linux/mfd/palmas.h
> > @@ -133,21 +133,32 @@ struct palmas_pmic_driver_data {
> >  			    struct regulator_config config);
> >  };
> >  
> > +struct palmas_adc_wakeup_property {
> > +	int adc_channel_number;
> > +	int adc_high_threshold;
> > +	int adc_low_threshold;
> > +};
> > +
> >  struct palmas_gpadc_platform_data {
> >  	/* Channel 3 current source is only enabled during conversion */
> > -	int ch3_current;
> > +	int ch3_current;	/* 0: off; 1: 10uA; 2: 400uA; 3: 800 uA */
> >  
> >  	/* Channel 0 current source can be used for battery detection.
> >  	 * If used for battery detection this will cause a permanent current
> >  	 * consumption depending on current level set here.
> >  	 */
> > -	int ch0_current;
> > +	int ch0_current;	/* 0: off; 1: 5uA; 2: 15uA; 3: 20 uA */
> > +	bool extended_delay;	/* use extended delay for conversion */
> >  
> >  	/* default BAT_REMOVAL_DAT setting on device probe */
> >  	int bat_removal;
> >  
> >  	/* Sets the START_POLARITY bit in the RT_CTRL register */
> >  	int start_polarity;
> > +
> > +	int auto_conversion_period_ms;
> > +	struct palmas_adc_wakeup_property *adc_wakeup1_data;
> > +	struct palmas_adc_wakeup_property *adc_wakeup2_data;
> >  };
> >  
> >  struct palmas_reg_init {
> > @@ -404,28 +415,7 @@ struct palmas_gpadc_calibration {
> >  	s32 offset_error;
> >  };
> >  
> > -struct palmas_gpadc {
> > -	struct device *dev;
> > -	struct palmas *palmas;
> > -
> > -	int ch3_current;
> > -	int ch0_current;
> > -
> > -	int gpadc_force;
> > -
> > -	int bat_removal;
> > -
> > -	struct mutex reading_lock;
> > -	struct completion irq_complete;
> > -
> > -	int eoc_sw_irq;
> > -
> > -	struct palmas_gpadc_calibration *palmas_cal_tbl;
> > -
> > -	int conv0_channel;
> > -	int conv1_channel;
> > -	int rt_channel;
> > -};
> > +#define PALMAS_DATASHEET_NAME(_name)	"palmas-gpadc-chan-"#_name
> >  
> >  struct palmas_gpadc_result {
> >  	s32 raw_code;
> > @@ -519,6 +509,43 @@ enum palmas_irqs {
> >  	PALMAS_NUM_IRQ,
> >  };
> >  
> > +/* Palmas GPADC Channels */
> > +enum {
> > +	PALMAS_ADC_CH_IN0,
> > +	PALMAS_ADC_CH_IN1,
> > +	PALMAS_ADC_CH_IN2,
> > +	PALMAS_ADC_CH_IN3,
> > +	PALMAS_ADC_CH_IN4,
> > +	PALMAS_ADC_CH_IN5,
> > +	PALMAS_ADC_CH_IN6,
> > +	PALMAS_ADC_CH_IN7,
> > +	PALMAS_ADC_CH_IN8,
> > +	PALMAS_ADC_CH_IN9,
> > +	PALMAS_ADC_CH_IN10,
> > +	PALMAS_ADC_CH_IN11,
> > +	PALMAS_ADC_CH_IN12,
> > +	PALMAS_ADC_CH_IN13,
> > +	PALMAS_ADC_CH_IN14,
> > +	PALMAS_ADC_CH_IN15,
> > +	PALMAS_ADC_CH_MAX,
> > +};
> > +
> > +/* Palmas GPADC Channel0 Current Source */
> > +enum {
> > +	PALMAS_ADC_CH0_CURRENT_SRC_0,
> > +	PALMAS_ADC_CH0_CURRENT_SRC_5,
> > +	PALMAS_ADC_CH0_CURRENT_SRC_15,
> > +	PALMAS_ADC_CH0_CURRENT_SRC_20,
> > +};
> > +
> > +/* Palmas GPADC Channel3 Current Source */
> > +enum {
> > +	PALMAS_ADC_CH3_CURRENT_SRC_0,
> > +	PALMAS_ADC_CH3_CURRENT_SRC_10,
> > +	PALMAS_ADC_CH3_CURRENT_SRC_400,
> > +	PALMAS_ADC_CH3_CURRENT_SRC_800,
> > +};
> > +
> >  struct palmas_pmic {
> >  	struct palmas *palmas;
> >  	struct device *dev;
> > 
> 

-- 
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

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

end of thread, other threads:[~2015-10-13  8:15 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <1437657169.3419965.331206849.51D66A2E@webmail.messagingengine.com>
2015-09-23 12:48 ` [PATCH 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
     [not found]   ` <cover.1443973837.git.hns@goldelico.com>
2015-10-04 16:05     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
     [not found]     ` <6C211191-87B8-4444-ADA7-8BE454C2B3D3@goldelico.com>
2015-10-05 11:17       ` [PATCH v2 2/3] iio:adc:palmas: add DT support Mark Rutland
2015-10-05 14:26         ` H. Nikolaus Schaller
2015-10-05  6:14   ` [PATCH v2 0/3] Add Palmas iio gpadc H. Nikolaus Schaller
2015-10-05  6:14     ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
2015-10-05  6:54       ` kbuild test robot
2015-10-05  6:54       ` [PATCH] iio:adc: fix platform_no_drv_owner.cocci warnings kbuild test robot
2015-10-05 10:48       ` [PATCH v2 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc Laxman Dewangan
2015-10-11 14:27       ` Jonathan Cameron
2015-10-13  8:14         ` Lee Jones
2015-10-05  6:14     ` [PATCH v2 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
2015-10-05 10:53       ` Laxman Dewangan
2015-10-05  6:14     ` [PATCH v2 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
     [not found] ` <cover.1443012491.git.hns@goldelico.com>
2015-09-23 12:48   ` [PATCH 1/3] iio:adc: add iio driver for Palmas (twl6035/7) gpadc H. Nikolaus Schaller
2015-09-23 13:26     ` Peter Meerwald
2015-09-24  8:59       ` H. Nikolaus Schaller
2015-09-27 15:21     ` Jonathan Cameron
2015-09-28 20:54       ` H. Nikolaus Schaller
     [not found]       ` <95BEC150-7B77-48BF-B11D-929090A1CC11@goldelico.com>
2015-10-11 14:33         ` Jonathan Cameron
2015-09-23 12:49   ` [PATCH 2/3] iio:adc:palmas: add DT support H. Nikolaus Schaller
2015-09-27 15:37     ` Jonathan Cameron
2015-09-23 12:49   ` [PATCH 3/3] ARM: dts: omap5-uevm: enable iio gpadc for Palmas H. Nikolaus Schaller
2015-09-23 16:56     ` Tony Lindgren
2015-09-23 17:03       ` H. Nikolaus Schaller

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).