hwmon: qpnp-adc: Add PMIC QPNP VADC Driver

The QPNP VADC driver supports the User Bank Peripheral of
the voltage ADC(VADC).

VADC is a 15 bit ADC that measures signals through the
Main analog multiplexer (AMUX) and PREMUX. The driver
arbitrates the request to issue ADC read requests.

VADC driver includes support for the conversion sequencer.
The conversion sequencer is a HW triggered signal to start
ADC measurement on trigger events for PA ON,
camera flash and TX threshold.

The AMUX supports external pull-ups simultaneously. Clients
can select the appropriate AMUX input channel to measure the
ADC for the intended pull up configuration.

Change-Id: I8886968ccec54ad03334b113b4516d4d200e0da8
Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b050db2..53fec5b 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -868,6 +868,16 @@
 	  Provides interface for measuring the current on specific power rails
 	  through the channels on ADC1158 ADC
 
+config SENSORS_QPNP_ADC_VOLTAGE
+	tristate "Support for Qualcomm QPNP Voltage ADC"
+	depends on SPMI
+	help
+	  This is the VADC arbiter driver for Qualcomm QPNP ADC Chip.
+
+	  The driver supports reading the HKADC, XOADC through the ADC AMUX arbiter.
+	  The VADC includes support for the conversion sequencer. The driver supports
+	  reading the ADC through the AMUX channels for external pull-ups simultaneously.
+
 config SENSORS_PC87360
 	tristate "National Semiconductor PC87360 family"
 	depends on !PPC
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 228c4e9..2ff9454 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -130,6 +130,7 @@
 obj-$(CONFIG_SENSORS_MSM_ADC)	+= msm_adc.o m_adcproc.o
 obj-$(CONFIG_SENSORS_PM8XXX_ADC)	+= pm8xxx-adc.o pm8xxx-adc-scale.o
 obj-$(CONFIG_SENSORS_EPM_ADC)	+= epm_adc.o
+obj-$(CONFIG_SENSORS_QPNP_ADC_VOLTAGE)	+= qpnp-adc-voltage.o qpnp-adc-common.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/qpnp-adc-common.c b/drivers/hwmon/qpnp-adc-common.c
new file mode 100644
index 0000000..c8fe798
--- /dev/null
+++ b/drivers/hwmon/qpnp-adc-common.c
@@ -0,0 +1,258 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/spmi.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/platform_device.h>
+
+/* Min ADC code represets 0V */
+#define QPNP_VADC_MIN_ADC_CODE			0x6000
+/* Max ADC code represents full-scale range of 1.8V */
+#define QPNP_VADC_MAX_ADC_CODE			0xA800
+
+int32_t qpnp_adc_scale_default(int32_t adc_code,
+		const struct qpnp_adc_properties *adc_properties,
+		const struct qpnp_vadc_chan_properties *chan_properties,
+		struct qpnp_vadc_result *adc_chan_result)
+{
+	bool negative_rawfromoffset = 0, negative_offset = 0;
+	int64_t scale_voltage = 0;
+
+	if (!chan_properties || !chan_properties->offset_gain_numerator ||
+		!chan_properties->offset_gain_denominator || !adc_properties
+		|| !adc_chan_result)
+		return -EINVAL;
+
+	scale_voltage = (adc_code -
+		chan_properties->adc_graph[CALIB_ABSOLUTE].adc_gnd)
+		* chan_properties->adc_graph[CALIB_ABSOLUTE].dx;
+	if (scale_voltage < 0) {
+		negative_offset = 1;
+		scale_voltage = -scale_voltage;
+	}
+	do_div(scale_voltage,
+		chan_properties->adc_graph[CALIB_ABSOLUTE].dy);
+	if (negative_offset)
+		scale_voltage = -scale_voltage;
+	scale_voltage += chan_properties->adc_graph[CALIB_ABSOLUTE].dx;
+
+	if (scale_voltage < 0) {
+		if (adc_properties->bipolar) {
+			scale_voltage = -scale_voltage;
+			negative_rawfromoffset = 1;
+		} else {
+			scale_voltage = 0;
+		}
+	}
+
+	adc_chan_result->measurement = scale_voltage *
+				chan_properties->offset_gain_denominator;
+
+	/* do_div only perform positive integer division! */
+	do_div(adc_chan_result->measurement,
+				chan_properties->offset_gain_numerator);
+
+	if (negative_rawfromoffset)
+		adc_chan_result->measurement = -adc_chan_result->measurement;
+
+	/*
+	 * Note: adc_chan_result->measurement is in the unit of
+	 * adc_properties.adc_reference. For generic channel processing,
+	 * channel measurement is a scale/ratio relative to the adc
+	 * reference input
+	 */
+	adc_chan_result->physical = adc_chan_result->measurement;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(qpnp_adc_scale_default);
+
+int32_t qpnp_vadc_check_result(int32_t *data)
+{
+	if (*data < QPNP_VADC_MIN_ADC_CODE)
+		*data = QPNP_VADC_MIN_ADC_CODE;
+	else if (*data > QPNP_VADC_MAX_ADC_CODE)
+		*data = QPNP_VADC_MAX_ADC_CODE;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(qpnp_vadc_check_result);
+
+int32_t qpnp_adc_get_devicetree_data(struct spmi_device *spmi,
+			struct qpnp_adc_drv *adc_qpnp)
+{
+	struct device_node *node = spmi->dev.of_node;
+	struct resource *res;
+	struct device_node *child;
+	struct qpnp_vadc_amux *adc_channel_list;
+	struct qpnp_adc_properties *adc_prop;
+	struct qpnp_vadc_amux_properties *amux_prop;
+	int count_adc_channel_list = 0, decimation, rc = 0;
+
+	if (!node)
+		return -EINVAL;
+
+	for_each_child_of_node(node, child)
+		count_adc_channel_list++;
+
+	if (!count_adc_channel_list) {
+		pr_err("No channel listing\n");
+		return -EINVAL;
+	}
+
+	adc_qpnp->spmi = spmi;
+
+	adc_prop = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_adc_properties),
+					GFP_KERNEL);
+	if (!adc_prop) {
+		dev_err(&spmi->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+	adc_channel_list = devm_kzalloc(&spmi->dev,
+		(sizeof(struct qpnp_vadc_amux) * count_adc_channel_list),
+				GFP_KERNEL);
+	if (!adc_channel_list) {
+		dev_err(&spmi->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	amux_prop = devm_kzalloc(&spmi->dev,
+		sizeof(struct qpnp_vadc_amux_properties) +
+		sizeof(struct qpnp_vadc_chan_properties), GFP_KERNEL);
+	if (!amux_prop) {
+		dev_err(&spmi->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	for_each_child_of_node(node, child) {
+		int channel_num, scaling, post_scaling, hw_settle_time;
+		int fast_avg_setup, calib_type, i = 0, rc;
+		const char *calibration_param, *channel_name;
+
+		channel_name = of_get_property(child,
+				"label", NULL) ? : child->name;
+		if (!channel_name) {
+			pr_err("Invalid channel name\n");
+			return -EINVAL;
+		}
+
+		rc = of_property_read_u32(child, "qcom,channel-num",
+								&channel_num);
+		if (rc) {
+			pr_err("Invalid channel num\n");
+			return -EINVAL;
+		}
+		rc = of_property_read_u32(child, "qcom,decimation",
+								&decimation);
+		if (rc) {
+			pr_err("Invalid channel decimation property\n");
+			return -EINVAL;
+		}
+		rc = of_property_read_u32(child,
+				"qcom,pre-div-channel-scaling", &scaling);
+		if (rc) {
+			pr_err("Invalid channel scaling property\n");
+			return -EINVAL;
+		}
+		rc = of_property_read_u32(child,
+				"qcom,scale-function", &post_scaling);
+		if (rc) {
+			pr_err("Invalid channel post scaling property\n");
+			return -EINVAL;
+		}
+		rc = of_property_read_u32(child,
+				"qcom,hw-settle-time", &hw_settle_time);
+		if (rc) {
+			pr_err("Invalid channel hw settle time property\n");
+			return -EINVAL;
+		}
+		rc = of_property_read_u32(child,
+				"qcom,fast-avg-setup", &fast_avg_setup);
+		if (rc) {
+			pr_err("Invalid channel fast average setup\n");
+			return -EINVAL;
+		}
+		calibration_param = of_get_property(child,
+				"qcom,calibration-type", NULL);
+		if (!strncmp(calibration_param, "absolute", 8))
+			calib_type = CALIB_ABSOLUTE;
+		else if (!strncmp(calibration_param, "historical", 9))
+			calib_type = CALIB_RATIOMETRIC;
+		else {
+			pr_err("%s: Invalid calibration property\n", __func__);
+			return -EINVAL;
+		}
+		/* Individual channel properties */
+		adc_channel_list[i].name = (char *)channel_name;
+		adc_channel_list[i].channel_num = channel_num;
+		adc_channel_list[i].chan_path_prescaling = scaling;
+		adc_channel_list[i].adc_decimation = decimation;
+		adc_channel_list[i].adc_scale_fn = post_scaling;
+		adc_channel_list[i].hw_settle_time = hw_settle_time;
+		adc_channel_list[i].fast_avg_setup = fast_avg_setup;
+		i++;
+	}
+	adc_qpnp->adc_channels = adc_channel_list;
+	adc_qpnp->amux_prop = amux_prop;
+
+	/* Get the ADC VDD reference voltage and ADC bit resolution */
+	rc = of_property_read_u32(node, "qcom,adc-vdd-reference",
+			&adc_prop->adc_vdd_reference);
+	if (rc) {
+		pr_err("Invalid adc vdd reference property\n");
+		return -EINVAL;
+	}
+	rc = of_property_read_u32(node, "qcom,adc-bit-resolution",
+			&adc_prop->bitresolution);
+	if (rc) {
+		pr_err("Invalid adc bit resolution property\n");
+		return -EINVAL;
+	}
+	adc_qpnp->adc_prop = adc_prop;
+
+	/* Get the peripheral address */
+	res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 0);
+	if (!res) {
+		pr_err("No base address definition\n");
+		return -EINVAL;
+	}
+
+	adc_qpnp->slave = spmi->sid;
+	adc_qpnp->offset = res->start;
+
+	/* Register the ADC peripheral interrupt */
+	adc_qpnp->adc_irq = spmi_get_irq(spmi, 0, 0);
+	if (adc_qpnp->adc_irq < 0) {
+		pr_err("Invalid irq\n");
+		return -ENXIO;
+	}
+
+	mutex_init(&adc_qpnp->adc_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(qpnp_adc_get_devicetree_data);
diff --git a/drivers/hwmon/qpnp-adc-voltage.c b/drivers/hwmon/qpnp-adc-voltage.c
new file mode 100644
index 0000000..8b2cb97
--- /dev/null
+++ b/drivers/hwmon/qpnp-adc-voltage.c
@@ -0,0 +1,784 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/spmi.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/platform_device.h>
+
+/* QPNP VADC register definition */
+#define QPNP_VADC_STATUS1					0x8
+#define QPNP_VADC_STATUS1_OP_MODE				4
+#define QPNP_VADC_STATUS1_MEAS_INTERVAL_EN_STS			BIT(2)
+#define QPNP_VADC_STATUS1_REQ_STS				BIT(1)
+#define QPNP_VADC_STATUS1_EOC					BIT(0)
+#define QPNP_VADC_STATUS2					0x9
+#define QPNP_VADC_STATUS2_CONV_SEQ_STATE				6
+#define QPNP_VADC_STATUS2_FIFO_NOT_EMPTY_FLAG			BIT(1)
+#define QPNP_VADC_STATUS2_CONV_SEQ_TIMEOUT_STS			BIT(0)
+#define QPNP_VADC_STATUS2_CONV_SEQ_STATE_SHIFT			4
+#define QPNP_VADC_CONV_TIMEOUT_ERR				2
+
+#define QPNP_VADC_INT_SET_TYPE					0x11
+#define QPNP_VADC_INT_POLARITY_HIGH				0x12
+#define QPNP_VADC_INT_POLARITY_LOW				0x13
+#define QPNP_VADC_INT_LATCHED_CLR				0x14
+#define QPNP_VADC_INT_EN_SET					0x15
+#define QPNP_VADC_INT_CLR					0x16
+#define QPNP_VADC_INT_LOW_THR_BIT				BIT(4)
+#define QPNP_VADC_INT_HIGH_THR_BIT				BIT(3)
+#define QPNP_VADC_INT_CONV_SEQ_TIMEOUT_BIT			BIT(2)
+#define QPNP_VADC_INT_FIFO_NOT_EMPTY_BIT			BIT(1)
+#define QPNP_VADC_INT_EOC_BIT					BIT(0)
+#define QPNP_VADC_INT_CLR_MASK					0x1f
+#define QPNP_VADC_MODE_CTL					0x40
+#define QPNP_VADC_OP_MODE_SHIFT					4
+#define QPNP_VADC_VREF_XO_THM_FORCE				BIT(2)
+#define QPNP_VADC_AMUX_TRIM_EN					BIT(1)
+#define QPNP_VADC_ADC_TRIM_EN					BIT(0)
+#define QPNP_VADC_EN_CTL1					0x46
+#define QPNP_VADC_ADC_EN					BIT(7)
+#define QPNP_VADC_ADC_CH_SEL_CTL					0x48
+#define QPNP_VADC_ADC_DIG_PARAM					0x50
+#define QPNP_VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT			3
+#define QPNP_VADC_HW_SETTLE_DELAY				0x51
+#define QPNP_VADC_CONV_REQ					0x52
+#define QPNP_VADC_CONV_REQ_SET					BIT(7)
+#define QPNP_VADC_CONV_SEQ_CTL					0x54
+#define QPNP_VADC_CONV_SEQ_HOLDOFF_SHIFT				4
+#define QPNP_VADC_CONV_SEQ_TRIG_CTL				0x55
+#define QPNP_VADC_CONV_SEQ_FALLING_EDGE				0x0
+#define QPNP_VADC_CONV_SEQ_RISING_EDGE				0x1
+#define QPNP_VADC_CONV_SEQ_EDGE_SHIFT				7
+#define QPNP_VADC_FAST_AVG_CTL					0x5a
+
+#define QPNP_VADC_M0_LOW_THR_LSB					0x5c
+#define QPNP_VADC_M0_LOW_THR_MSB					0x5d
+#define QPNP_VADC_M0_HIGH_THR_LSB				0x5e
+#define QPNP_VADC_M0_HIGH_THR_MSB				0x5f
+#define QPNP_VADC_M1_LOW_THR_LSB					0x69
+#define QPNP_VADC_M1_LOW_THR_MSB					0x6a
+#define QPNP_VADC_M1_HIGH_THR_LSB				0x6b
+#define QPNP_VADC_M1_HIGH_THR_MSB				0x6c
+
+#define QPNP_VADC_DATA0						0x60
+#define QPNP_VADC_DATA1						0x61
+#define QPNP_VADC_CONV_TIMEOUT_ERR				2
+#define QPNP_VADC_CONV_TIME_MIN					2000
+#define QPNP_VADC_CONV_TIME_MAX					2100
+
+#define QPNP_ADC_HWMON_NAME_LENGTH				16
+
+struct qpnp_vadc_drv {
+	struct qpnp_adc_drv		*adc;
+	struct dentry			*dent;
+	struct device			*vadc_hwmon;
+	bool				vadc_init_calib;
+	struct sensor_device_attribute		sens_attr[0];
+};
+
+struct qpnp_vadc_drv *qpnp_vadc;
+
+static struct qpnp_vadc_scale_fn vadc_scale_fn[] = {
+	[SCALE_DEFAULT] = {qpnp_adc_scale_default},
+};
+
+static int32_t qpnp_vadc_read_reg(int16_t reg, u8 *data)
+{
+	struct qpnp_vadc_drv *vadc = qpnp_vadc;
+	int rc;
+
+	rc = spmi_ext_register_readl(vadc->adc->spmi->ctrl, vadc->adc->slave,
+		reg, data, 1);
+	if (rc < 0) {
+		pr_err("qpnp adc read reg %d failed with %d\n", reg, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int32_t qpnp_vadc_write_reg(int16_t reg, u8 data)
+{
+	struct qpnp_vadc_drv *vadc = qpnp_vadc;
+	int rc;
+	u8 *buf;
+
+	buf = &data;
+
+	rc = spmi_ext_register_writel(vadc->adc->spmi->ctrl, vadc->adc->slave,
+		reg, buf, 1);
+	if (rc < 0) {
+		pr_err("qpnp adc write reg %d failed with %d\n", reg, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int32_t qpnp_vadc_configure_interrupt(void)
+{
+	int rc = 0;
+	u8 data = 0;
+
+	/* Configure interrupt as an Edge trigger */
+	rc = qpnp_vadc_write_reg(QPNP_VADC_INT_SET_TYPE,
+					QPNP_VADC_INT_CLR_MASK);
+	if (rc < 0) {
+		pr_err("%s Interrupt configure failed\n", __func__);
+		return rc;
+	}
+
+	/* Configure interrupt for rising edge trigger */
+	rc = qpnp_vadc_write_reg(QPNP_VADC_INT_POLARITY_HIGH,
+					QPNP_VADC_INT_CLR_MASK);
+	if (rc < 0) {
+		pr_err("%s Rising edge trigger configure failed\n", __func__);
+		return rc;
+	}
+
+	/* Disable low level interrupt triggering */
+	data = QPNP_VADC_INT_CLR_MASK;
+	rc = qpnp_vadc_write_reg(QPNP_VADC_INT_POLARITY_LOW,
+					(~data & QPNP_VADC_INT_CLR_MASK));
+	if (rc < 0) {
+		pr_err("%s Setting level low to disable failed\n", __func__);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int32_t qpnp_vadc_enable(bool state)
+{
+	int rc = 0;
+	u8 data = 0;
+
+	data = QPNP_VADC_ADC_EN;
+	if (state) {
+		rc = qpnp_vadc_write_reg(QPNP_VADC_EN_CTL1,
+					data);
+		if (rc < 0) {
+			pr_err("VADC enable failed\n");
+			return rc;
+		}
+	} else {
+		rc = qpnp_vadc_write_reg(QPNP_VADC_EN_CTL1,
+					(~data & QPNP_VADC_ADC_EN));
+		if (rc < 0) {
+			pr_err("VADC disable failed\n");
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int32_t qpnp_vadc_configure(
+			struct qpnp_vadc_amux_properties *chan_prop)
+{
+	u8 decimation = 0, conv_sequence = 0, conv_sequence_trig = 0;
+	int rc = 0;
+
+	rc = qpnp_vadc_write_reg(QPNP_VADC_INT_EN_SET,
+				QPNP_VADC_INT_EOC_BIT);
+	if (rc < 0) {
+		pr_err("qpnp adc configure error for interrupt setup\n");
+		return rc;
+	}
+
+	rc = qpnp_vadc_write_reg(QPNP_VADC_MODE_CTL, chan_prop->mode_sel);
+	if (rc < 0) {
+		pr_err("qpnp adc configure error for mode selection\n");
+		return rc;
+	}
+
+	rc = qpnp_vadc_write_reg(QPNP_VADC_ADC_CH_SEL_CTL,
+						chan_prop->amux_channel);
+	if (rc < 0) {
+		pr_err("qpnp adc configure error for channel selection\n");
+		return rc;
+	}
+
+	decimation |= chan_prop->decimation <<
+				QPNP_VADC_ADC_DIG_DEC_RATIO_SEL_SHIFT;
+	rc = qpnp_vadc_write_reg(QPNP_VADC_ADC_DIG_PARAM, decimation);
+	if (rc < 0) {
+		pr_err("qpnp adc configure error for digital parameter setup\n");
+		return rc;
+	}
+
+	rc = qpnp_vadc_write_reg(QPNP_VADC_HW_SETTLE_DELAY,
+						chan_prop->hw_settle_time);
+	if (rc < 0) {
+		pr_err("qpnp adc configure error for hw settling time setup\n");
+		return rc;
+	}
+
+	if (chan_prop->mode_sel == (ADC_OP_NORMAL_MODE <<
+					QPNP_VADC_OP_MODE_SHIFT)) {
+		rc = qpnp_vadc_write_reg(QPNP_VADC_FAST_AVG_CTL,
+						chan_prop->fast_avg_setup);
+		if (rc < 0) {
+			pr_err("qpnp adc fast averaging configure error\n");
+			return rc;
+		}
+	} else if (chan_prop->mode_sel == (ADC_OP_CONVERSION_SEQUENCER <<
+					QPNP_VADC_OP_MODE_SHIFT)) {
+		conv_sequence = ((ADC_SEQ_HOLD_100US <<
+				QPNP_VADC_CONV_SEQ_HOLDOFF_SHIFT) |
+				ADC_CONV_SEQ_TIMEOUT_5MS);
+		rc = qpnp_vadc_write_reg(QPNP_VADC_CONV_SEQ_CTL,
+							conv_sequence);
+		if (rc < 0) {
+			pr_err("qpnp adc conversion sequence error\n");
+			return rc;
+		}
+
+		conv_sequence_trig = ((QPNP_VADC_CONV_SEQ_RISING_EDGE <<
+				QPNP_VADC_CONV_SEQ_EDGE_SHIFT) |
+				chan_prop->trigger_channel);
+		rc = qpnp_vadc_write_reg(QPNP_VADC_CONV_SEQ_TRIG_CTL,
+							conv_sequence_trig);
+		if (rc < 0) {
+			pr_err("qpnp adc conversion trigger error\n");
+			return rc;
+		}
+	}
+
+	rc = qpnp_vadc_write_reg(QPNP_VADC_CONV_REQ, QPNP_VADC_CONV_REQ_SET);
+	if (rc < 0) {
+		pr_err("qpnp adc request conversion failed\n");
+		return rc;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(qpnp_vadc_configure);
+
+static int32_t qpnp_vadc_read_conversion_result(int32_t *data)
+{
+	uint8_t rslt_lsb, rslt_msb;
+	int rc = 0;
+
+	rc = qpnp_vadc_read_reg(QPNP_VADC_DATA0, &rslt_lsb);
+	if (rc < 0) {
+		pr_err("qpnp adc result read failed for data0 with %d\n", rc);
+		return rc;
+	}
+
+	rc = qpnp_vadc_read_reg(QPNP_VADC_DATA1, &rslt_msb);
+	if (rc < 0) {
+		pr_err("qpnp adc result read failed for data1 with %d\n", rc);
+		return rc;
+	}
+
+	*data = (rslt_msb << 8) | rslt_lsb;
+
+	rc = qpnp_vadc_check_result(data);
+	if (rc < 0) {
+		pr_err("VADC data check failed\n");
+		return rc;
+	}
+
+	return 0;
+}
+
+static int32_t qpnp_vadc_read_status(int mode_sel)
+{
+	u8 status1, status2, status2_conv_seq_state;
+	u8 status_err = QPNP_VADC_CONV_TIMEOUT_ERR;
+	int rc;
+
+	switch (mode_sel) {
+	case (ADC_OP_CONVERSION_SEQUENCER << QPNP_VADC_OP_MODE_SHIFT):
+		rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1);
+		if (rc) {
+			pr_err("qpnp_vadc read mask interrupt failed\n");
+			return rc;
+		}
+
+		rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS2, &status2);
+		if (rc) {
+			pr_err("qpnp_vadc read mask interrupt failed\n");
+			return rc;
+		}
+
+		if (!(status2 & ~QPNP_VADC_STATUS2_CONV_SEQ_TIMEOUT_STS) &&
+			(status1 & (~QPNP_VADC_STATUS1_REQ_STS |
+						QPNP_VADC_STATUS1_EOC))) {
+			rc = status_err;
+			return rc;
+		}
+
+		status2_conv_seq_state = status2 >>
+					QPNP_VADC_STATUS2_CONV_SEQ_STATE_SHIFT;
+		if (status2_conv_seq_state != ADC_CONV_SEQ_IDLE) {
+			pr_err("qpnp vadc seq error with status %d\n",
+						status2);
+			rc = -EINVAL;
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static void qpnp_vadc_work(struct work_struct *work)
+{
+	struct qpnp_vadc_drv *vadc = qpnp_vadc;
+	int rc;
+
+	rc = qpnp_vadc_write_reg(QPNP_VADC_INT_CLR, QPNP_VADC_INT_EOC_BIT);
+	if (rc)
+		pr_err("qpnp_vadc clear mask interrupt failed with %d\n", rc);
+
+	complete(&vadc->adc->adc_rslt_completion);
+
+	return;
+}
+DECLARE_WORK(trigger_completion_work, qpnp_vadc_work);
+
+static irqreturn_t qpnp_vadc_isr(int irq, void *dev_id)
+{
+	schedule_work(&trigger_completion_work);
+
+	return IRQ_HANDLED;
+}
+
+static uint32_t qpnp_vadc_calib_device(void)
+{
+	struct qpnp_vadc_drv *vadc = qpnp_vadc;
+	struct qpnp_vadc_amux_properties conv;
+	int rc, calib_read_1, calib_read_2;
+	u8 status1 = 0;
+
+	conv.amux_channel = REF_125V;
+	conv.decimation = DECIMATION_TYPE2;
+	conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT;
+	conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US;
+	conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1;
+
+	rc = qpnp_vadc_configure(&conv);
+	if (rc) {
+		pr_err("qpnp_vadc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (status1 != (~QPNP_VADC_STATUS1_REQ_STS |
+					QPNP_VADC_STATUS1_EOC)) {
+		rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1);
+		if (rc < 0)
+			return rc;
+		usleep_range(QPNP_VADC_CONV_TIME_MIN,
+					QPNP_VADC_CONV_TIME_MAX);
+	}
+
+	rc = qpnp_vadc_read_conversion_result(&calib_read_1);
+	if (rc) {
+		pr_err("qpnp adc read adc failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	conv.amux_channel = REF_625MV;
+	conv.decimation = DECIMATION_TYPE2;
+	conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT;
+	conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US;
+	conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1;
+	rc = qpnp_vadc_configure(&conv);
+	if (rc) {
+		pr_err("qpnp adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (status1 != (~QPNP_VADC_STATUS1_REQ_STS |
+					QPNP_VADC_STATUS1_EOC)) {
+		rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1);
+		if (rc < 0)
+			return rc;
+		usleep_range(QPNP_VADC_CONV_TIME_MIN,
+					QPNP_VADC_CONV_TIME_MAX);
+	}
+
+	rc = qpnp_vadc_read_conversion_result(&calib_read_1);
+	if (rc) {
+		pr_err("qpnp adc read adc failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].dy =
+					(calib_read_1 - calib_read_2);
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].dx
+						= QPNP_ADC_625_UV;
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].adc_vref =
+					calib_read_1;
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_ABSOLUTE].adc_gnd =
+					calib_read_2;
+	/* Ratiometric Calibration */
+	conv.amux_channel = VDD_VADC;
+	conv.decimation = DECIMATION_TYPE2;
+	conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT;
+	conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US;
+	conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1;
+	rc = qpnp_vadc_configure(&conv);
+	if (rc) {
+		pr_err("qpnp adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (status1 != (~QPNP_VADC_STATUS1_REQ_STS |
+					QPNP_VADC_STATUS1_EOC)) {
+		rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1);
+		if (rc < 0)
+			return rc;
+		usleep_range(QPNP_VADC_CONV_TIME_MIN,
+					QPNP_VADC_CONV_TIME_MAX);
+	}
+
+	rc = qpnp_vadc_read_conversion_result(&calib_read_1);
+	if (rc) {
+		pr_err("qpnp adc read adc failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	conv.amux_channel = VDD_VADC;
+	conv.decimation = DECIMATION_TYPE2;
+	conv.mode_sel = ADC_OP_NORMAL_MODE << QPNP_VADC_OP_MODE_SHIFT;
+	conv.hw_settle_time = ADC_CHANNEL_HW_SETTLE_DELAY_0US;
+	conv.fast_avg_setup = ADC_FAST_AVG_SAMPLE_1;
+	rc = qpnp_vadc_configure(&conv);
+	if (rc) {
+		pr_err("qpnp adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (status1 != (~QPNP_VADC_STATUS1_REQ_STS |
+					QPNP_VADC_STATUS1_EOC)) {
+		rc = qpnp_vadc_read_reg(QPNP_VADC_STATUS1, &status1);
+		if (rc < 0)
+			return rc;
+		usleep_range(QPNP_VADC_CONV_TIME_MIN,
+					QPNP_VADC_CONV_TIME_MAX);
+	}
+
+	rc = qpnp_vadc_read_conversion_result(&calib_read_1);
+	if (rc) {
+		pr_err("qpnp adc read adc failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].dy =
+					(calib_read_1 - calib_read_2);
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].dx =
+					vadc->adc->adc_prop->adc_vdd_reference;
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].adc_vref =
+					calib_read_1;
+	vadc->adc->amux_prop->chan_prop->adc_graph[CALIB_RATIOMETRIC].adc_gnd =
+					calib_read_2;
+
+calib_fail:
+	return rc;
+}
+
+int32_t qpnp_vadc_conv_seq_request(enum qpnp_vadc_trigger trigger_channel,
+					enum qpnp_vadc_channels channel,
+					struct qpnp_vadc_result *result)
+{
+	struct qpnp_vadc_drv *vadc = qpnp_vadc;
+	int rc, scale_type, amux_prescaling;
+
+	if (!vadc->vadc_init_calib) {
+		rc = qpnp_vadc_calib_device();
+		if (rc) {
+			pr_err("Calibration failed\n");
+			return rc;
+		} else
+			vadc->vadc_init_calib = true;
+	}
+
+	mutex_lock(&vadc->adc->adc_lock);
+
+	rc = qpnp_vadc_enable(true);
+	if (rc)
+		goto fail_unlock;
+
+	vadc->adc->amux_prop->amux_channel = channel;
+	vadc->adc->amux_prop->decimation =
+			vadc->adc->adc_channels[channel].adc_decimation;
+	vadc->adc->amux_prop->hw_settle_time =
+			vadc->adc->adc_channels[channel].hw_settle_time;
+	vadc->adc->amux_prop->fast_avg_setup =
+			vadc->adc->adc_channels[channel].fast_avg_setup;
+
+	if (trigger_channel < ADC_SEQ_NONE)
+		vadc->adc->amux_prop->mode_sel = (ADC_OP_CONVERSION_SEQUENCER
+						<< QPNP_VADC_OP_MODE_SHIFT);
+	else if (trigger_channel == ADC_SEQ_NONE)
+		vadc->adc->amux_prop->mode_sel = (ADC_OP_NORMAL_MODE
+						<< QPNP_VADC_OP_MODE_SHIFT);
+	else {
+		pr_err("Invalid trigger channel:%d\n", trigger_channel);
+		goto fail;
+	}
+
+	vadc->adc->amux_prop->trigger_channel = trigger_channel;
+
+	rc = qpnp_vadc_configure(vadc->adc->amux_prop);
+	if (rc) {
+		pr_info("qpnp vadc configure failed with %d\n", rc);
+		goto fail;
+	}
+
+	wait_for_completion(&vadc->adc->adc_rslt_completion);
+
+	if (trigger_channel < ADC_SEQ_NONE) {
+		rc = qpnp_vadc_read_status(vadc->adc->amux_prop->mode_sel);
+		if (rc)
+			pr_info("Conversion sequence timed out - %d\n", rc);
+	}
+
+	rc = qpnp_vadc_read_conversion_result(&result->adc_code);
+	if (rc) {
+		pr_info("qpnp vadc read adc code failed with %d\n", rc);
+		goto fail;
+	}
+
+	amux_prescaling = vadc->adc->adc_channels[channel].chan_path_prescaling;
+
+	vadc->adc->amux_prop->chan_prop->offset_gain_numerator =
+		qpnp_vadc_amux_scaling_ratio[amux_prescaling].num;
+	vadc->adc->amux_prop->chan_prop->offset_gain_denominator =
+		 qpnp_vadc_amux_scaling_ratio[amux_prescaling].den;
+
+	scale_type = vadc->adc->adc_channels[channel].adc_scale_fn;
+	if (scale_type >= SCALE_NONE) {
+		rc = -EBADF;
+		goto fail;
+	}
+
+	vadc_scale_fn[scale_type].chan(result->adc_code,
+		vadc->adc->adc_prop, vadc->adc->amux_prop->chan_prop, result);
+
+fail:
+	rc = qpnp_vadc_enable(false);
+	if (rc)
+		pr_err("Disable ADC failed during configuration\n");
+
+fail_unlock:
+	mutex_unlock(&vadc->adc->adc_lock);
+
+	return rc;
+}
+EXPORT_SYMBOL(qpnp_vadc_conv_seq_request);
+
+int32_t qpnp_vadc_read(enum qpnp_vadc_channels channel,
+				struct qpnp_vadc_result *result)
+{
+	return qpnp_vadc_conv_seq_request(ADC_SEQ_NONE,
+				channel, result);
+}
+EXPORT_SYMBOL_GPL(qpnp_vadc_read);
+
+static ssize_t qpnp_adc_show(struct device *dev,
+			struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct qpnp_vadc_result result;
+	int rc = -1;
+
+	rc = qpnp_vadc_read(attr->index, &result);
+
+	if (rc)
+		return 0;
+
+	return snprintf(buf, QPNP_ADC_HWMON_NAME_LENGTH,
+		"Result:%lld Raw:%d\n", result.physical, result.adc_code);
+}
+
+static struct sensor_device_attribute qpnp_adc_attr =
+	SENSOR_ATTR(NULL, S_IRUGO, qpnp_adc_show, NULL, 0);
+
+static int32_t qpnp_vadc_init_hwmon(struct spmi_device *spmi)
+{
+	struct qpnp_vadc_drv *vadc = qpnp_vadc;
+	struct device_node *child;
+	struct device_node *node = spmi->dev.of_node;
+	int rc = 0, i = 0, channel;
+
+	for_each_child_of_node(node, child) {
+		channel = vadc->adc->adc_channels[i].channel_num;
+		qpnp_adc_attr.index = vadc->adc->adc_channels[i].channel_num;
+		qpnp_adc_attr.dev_attr.attr.name =
+						vadc->adc->adc_channels[i].name;
+		sysfs_attr_init(&vadc->sens_attr[i].dev_attr.attr);
+		memcpy(&vadc->sens_attr[i], &qpnp_adc_attr,
+						sizeof(qpnp_adc_attr));
+		rc = device_create_file(&spmi->dev,
+				&vadc->sens_attr[i].dev_attr);
+		if (rc) {
+			dev_err(&spmi->dev,
+				"device_create_file failed for dev %s\n",
+				vadc->adc->adc_channels[i].name);
+			goto hwmon_err_sens;
+		}
+		i++;
+	}
+
+	return 0;
+hwmon_err_sens:
+	pr_info("Init HWMON failed for qpnp_adc with %d\n", rc);
+	return rc;
+}
+
+static int __devinit qpnp_vadc_probe(struct spmi_device *spmi)
+{
+	struct qpnp_vadc_drv *vadc;
+	struct qpnp_adc_drv *adc_qpnp;
+	struct device_node *node = spmi->dev.of_node;
+	struct device_node *child;
+	int rc, count_adc_channel_list = 0;
+
+	if (!node)
+		return -EINVAL;
+
+	if (qpnp_vadc) {
+		pr_err("VADC already in use\n");
+		return -EBUSY;
+	}
+
+	for_each_child_of_node(node, child)
+		count_adc_channel_list++;
+
+	if (!count_adc_channel_list) {
+		pr_err("No channel listing\n");
+		return -EINVAL;
+	}
+
+	vadc = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_vadc_drv) +
+		(sizeof(struct sensor_device_attribute) *
+				count_adc_channel_list), GFP_KERNEL);
+	if (!vadc) {
+		dev_err(&spmi->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	adc_qpnp = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_adc_drv),
+			GFP_KERNEL);
+	if (!adc_qpnp) {
+		dev_err(&spmi->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	vadc->adc = adc_qpnp;
+
+	rc = qpnp_adc_get_devicetree_data(spmi, vadc->adc);
+	if (rc) {
+		dev_err(&spmi->dev, "failed to read device tree\n");
+		return rc;
+	}
+
+	rc = devm_request_irq(&spmi->dev, vadc->adc->adc_irq,
+				qpnp_vadc_isr, IRQF_TRIGGER_RISING,
+				"qpnp_vadc_interrupt", vadc);
+	if (rc) {
+		dev_err(&spmi->dev,
+			"failed to request adc irq with error %d\n", rc);
+		return rc;
+	}
+
+	qpnp_vadc = vadc;
+	dev_set_drvdata(&spmi->dev, vadc);
+	rc = qpnp_vadc_init_hwmon(spmi);
+	if (rc) {
+		dev_err(&spmi->dev, "failed to initialize qpnp hwmon adc\n");
+		goto fail_free_irq;
+	}
+	vadc->vadc_hwmon = hwmon_device_register(&vadc->adc->spmi->dev);
+	vadc->vadc_init_calib = false;
+
+	rc = qpnp_vadc_configure_interrupt();
+	if (rc) {
+		dev_err(&spmi->dev, "failed to configure interrupt");
+		goto fail_free_irq;
+	}
+
+	return 0;
+
+fail_free_irq:
+	free_irq(vadc->adc->adc_irq, vadc);
+
+	return rc;
+}
+
+static int __devexit qpnp_vadc_remove(struct spmi_device *spmi)
+{
+	struct qpnp_vadc_drv *vadc = dev_get_drvdata(&spmi->dev);
+	struct device_node *node = spmi->dev.of_node;
+	struct device_node *child;
+	int i = 0;
+
+	for_each_child_of_node(node, child) {
+		device_remove_file(&spmi->dev,
+			&vadc->sens_attr[i].dev_attr);
+		i++;
+	}
+	free_irq(vadc->adc->adc_irq, vadc);
+	dev_set_drvdata(&spmi->dev, NULL);
+
+	return 0;
+}
+
+static const struct of_device_id qpnp_vadc_match_table[] = {
+	{	.compatible = "qcom,qpnp-vadc",
+	},
+	{}
+};
+
+static struct spmi_driver qpnp_vadc_driver = {
+	.driver		= {
+		.name	= "qcom,qpnp-vadc",
+		.of_match_table = qpnp_vadc_match_table,
+	},
+	.probe		= qpnp_vadc_probe,
+	.remove		= qpnp_vadc_remove,
+};
+
+static int __init qpnp_vadc_init(void)
+{
+	return spmi_driver_register(&qpnp_vadc_driver);
+}
+module_init(qpnp_vadc_init);
+
+static void __exit qpnp_vadc_exit(void)
+{
+	spmi_driver_unregister(&qpnp_vadc_driver);
+}
+module_exit(qpnp_vadc_exit);
+
+MODULE_DESCRIPTION("QPNP PMIC Voltage ADC driver");
+MODULE_LICENSE("GPL v2");