hwmon: pm8xxx-adc: Add PM8018 ADC Driver

PM8921 and PM8018 have a common ADC module. Difference's
are in the channel supported on individual PMIC's and
in the MPP mapping to the ADC device.
MPP configuration on PM8018 is not required. The MPP
is directly connected to the AMUX pre-mux channel.

Change-Id: I9aed3366f20e3985962119274d8b2865b7054a79
Signed-off-by: Siddartha Mohanadoss <smohanad@codeaurora.org>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index f9d671e..f3cd0ad 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -797,6 +797,15 @@
 	  The driver supports reading the HKADC, XOADC and support to set and receive
 	  temperature threshold notifications using the Battery temperature module.
 
+config SENSORS_PM8XXX_ADC
+	tristate "Support for Qualcomm PM8XXX ADC"
+	depends on MFD_PM8018_CORE
+	help
+	  This is the ADC arbiter driver for Qualcomm PM8XXX Chip.
+
+	  The driver supports reading the HKADC, XOADC and support to set and receive
+	  temperature threshold notifications using the Battery temperature module.
+
 config SENSORS_PC87360
 	tristate "National Semiconductor PC87360 family"
 	select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index c25ffcb..10d0699 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -121,6 +121,7 @@
 obj-$(CONFIG_SENSORS_WPCE775X)	+= wpce775x.o
 obj-$(CONFIG_SENSORS_MSM_ADC)	+= msm_adc.o m_adcproc.o
 obj-$(CONFIG_SENSORS_PM8921_ADC)	+= pm8921-adc.o msmproc_adc.o
+obj-$(CONFIG_SENSORS_PM8XXX_ADC)	+= pm8xxx-adc.o pm8xxx-adc-scale.o
 
 # PMBus drivers
 obj-$(CONFIG_PMBUS)		+= pmbus_core.o
diff --git a/drivers/hwmon/pm8xxx-adc-scale.c b/drivers/hwmon/pm8xxx-adc-scale.c
new file mode 100644
index 0000000..40ade69
--- /dev/null
+++ b/drivers/hwmon/pm8xxx-adc-scale.c
@@ -0,0 +1,620 @@
+/*
+ * Copyright (c) 2011, 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.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mfd/pm8xxx/pm8xxx-adc.h>
+#define KELVINMIL_DEGMIL	273160
+
+static const struct pm8xxx_adc_map_pt adcmap_batttherm[] = {
+	{41001,	-30},
+	{40017,	-20},
+	{38721,	-10},
+	{37186,	  0},
+	{35554,	 10},
+	{33980,	 20},
+	{33253,	 25},
+	{32580,	 30},
+	{31412,	 40},
+	{30481,	 50},
+	{29759,	 60},
+	{29209,	 70},
+	{28794,	 80}
+};
+
+static const struct pm8xxx_adc_map_pt adcmap_btm_threshold[] = {
+	{-30,	41001},
+	{-20,	40017},
+	{-10,	38721},
+	{0,	37186},
+	{10,	35554},
+	{11,	35392},
+	{12,	35230},
+	{13,	35070},
+	{14,	34910},
+	{15,	34751},
+	{16,	34594},
+	{17,	34438},
+	{18,	34284},
+	{19,	34131},
+	{20,	33980},
+	{21,	33830},
+	{22,	33683},
+	{23,	33538},
+	{24,	33394},
+	{25,	33253},
+	{26,	33114},
+	{27,	32977},
+	{28,	32842},
+	{29,	32710},
+	{30,	32580},
+	{31,	32452},
+	{32,	32327},
+	{33,	32204},
+	{34,	32084},
+	{35,	31966},
+	{36,	31850},
+	{37,	31737},
+	{38,	31627},
+	{39,	31518},
+	{40,	31412},
+	{41,	31309},
+	{42,	31208},
+	{43,	31109},
+	{44,	31013},
+	{45,	30918},
+	{46,	30827},
+	{47,	30737},
+	{48,	30649},
+	{49,	30564},
+	{50,	30481},
+	{51,	30400},
+	{52,	30321},
+	{53,	30244},
+	{54,	30169},
+	{55,	30096},
+	{56,	30025},
+	{57,	29956},
+	{58,	29889},
+	{59,	29823},
+	{60,	29759},
+	{61,	29697},
+	{62,	29637},
+	{63,	29578},
+	{64,	29521},
+	{65,	29465},
+	{66,	29411},
+	{67,	29359},
+	{68,	29308},
+	{69,	29258},
+	{70,	29209},
+	{71,	29162},
+	{72,	29117},
+	{73,	29072},
+	{74,	29029},
+	{75,	28987},
+	{76,	28946},
+	{77,	28906},
+	{78,	28868},
+	{79,	28830},
+	{80,	28794}
+};
+
+static const struct pm8xxx_adc_map_pt adcmap_pa_therm[] = {
+	{41350,	-30},
+	{41282,	-29},
+	{41211,	-28},
+	{41137,	-27},
+	{41060,	-26},
+	{40980,	-25},
+	{40897,	-24},
+	{40811,	-23},
+	{40721,	-22},
+	{40629,	-21},
+	{40533,	-20},
+	{40434,	-19},
+	{40331,	-18},
+	{40226,	-17},
+	{40116,	-16},
+	{40004,	-15},
+	{39888,	-14},
+	{39769,	-13},
+	{39647,	-12},
+	{39521,	-11},
+	{39392,	-10},
+	{39260,	-9},
+	{39124,	-8},
+	{38986,	-7},
+	{38845,	-6},
+	{38700,	-5},
+	{38553,	-4},
+	{38403,	-3},
+	{38250,	-2},
+	{38094,	-1},
+	{37936,	0},
+	{37776,	1},
+	{37613,	2},
+	{37448,	3},
+	{37281,	4},
+	{37112,	5},
+	{36942,	6},
+	{36770,	7},
+	{36596,	8},
+	{36421,	9},
+	{36245,	10},
+	{36068,	11},
+	{35890,	12},
+	{35712,	13},
+	{35532,	14},
+	{35353,	15},
+	{35173,	16},
+	{34993,	17},
+	{34813,	18},
+	{34634,	19},
+	{34455,	20},
+	{34276,	21},
+	{34098,	22},
+	{33921,	23},
+	{33745,	24},
+	{33569,	25},
+	{33395,	26},
+	{33223,	27},
+	{33051,	28},
+	{32881,	29},
+	{32713,	30},
+	{32547,	31},
+	{32382,	32},
+	{32219,	33},
+	{32058,	34},
+	{31899,	35},
+	{31743,	36},
+	{31588,	37},
+	{31436,	38},
+	{31285,	39},
+	{31138,	40},
+	{30992,	41},
+	{30849,	42},
+	{30708,	43},
+	{30570,	44},
+	{30434,	45},
+	{30300,	46},
+	{30169,	47},
+	{30041,	48},
+	{29915,	49},
+	{29791,	50},
+	{29670,	51},
+	{29551,	52},
+	{29435,	53},
+	{29321,	54},
+	{29210,	55},
+	{29101,	56},
+	{28994,	57},
+	{28890,	58},
+	{28788,	59},
+	{28688,	60},
+	{28590,	61},
+	{28495,	62},
+	{28402,	63},
+	{28311,	64},
+	{28222,	65},
+	{28136,	66},
+	{28051,	67},
+	{27968,	68},
+	{27888,	69},
+	{27809,	70},
+	{27732,	71},
+	{27658,	72},
+	{27584,	73},
+	{27513,	74},
+	{27444,	75},
+	{27376,	76},
+	{27310,	77},
+	{27245,	78},
+	{27183,	79},
+	{27121,	80},
+	{27062,	81},
+	{27004,	82},
+	{26947,	83},
+	{26892,	84},
+	{26838,	85},
+	{26785,	86},
+	{26734,	87},
+	{26684,	88},
+	{26636,	89},
+	{26588,	90}
+};
+
+static const struct pm8xxx_adc_map_pt adcmap_ntcg_104ef_104fb[] = {
+	{696483,	-40960},
+	{649148,	-39936},
+	{605368,	-38912},
+	{564809,	-37888},
+	{527215,	-36864},
+	{492322,	-35840},
+	{460007,	-34816},
+	{429982,	-33792},
+	{402099,	-32768},
+	{376192,	-31744},
+	{352075,	-30720},
+	{329714,	-29696},
+	{308876,	-28672},
+	{289480,	-27648},
+	{271417,	-26624},
+	{254574,	-25600},
+	{238903,	-24576},
+	{224276,	-23552},
+	{210631,	-22528},
+	{197896,	-21504},
+	{186007,	-20480},
+	{174899,	-19456},
+	{164521,	-18432},
+	{154818,	-17408},
+	{145744,	-16384},
+	{137265,	-15360},
+	{129307,	-14336},
+	{121866,	-13312},
+	{114896,	-12288},
+	{108365,	-11264},
+	{102252,	-10240},
+	{96499,		-9216},
+	{91111,		-8192},
+	{86055,		-7168},
+	{81308,		-6144},
+	{76857,		-5120},
+	{72660,		-4096},
+	{68722,		-3072},
+	{65020,		-2048},
+	{61538,		-1024},
+	{58261,		0},
+	{55177,		1024},
+	{52274,		2048},
+	{49538,		3072},
+	{46962,		4096},
+	{44531,		5120},
+	{42243,		6144},
+	{40083,		7168},
+	{38045,		8192},
+	{36122,		9216},
+	{34308,		10240},
+	{32592,		11264},
+	{30972,		12288},
+	{29442,		13312},
+	{27995,		14336},
+	{26624,		15360},
+	{25333,		16384},
+	{24109,		17408},
+	{22951,		18432},
+	{21854,		19456},
+	{20807,		20480},
+	{19831,		21504},
+	{18899,		22528},
+	{18016,		23552},
+	{17178,		24576},
+	{16384,		25600},
+	{15631,		26624},
+	{14916,		27648},
+	{14237,		28672},
+	{13593,		29696},
+	{12976,		30720},
+	{12400,		31744},
+	{11848,		32768},
+	{11324,		33792},
+	{10825,		34816},
+	{10354,		35840},
+	{9900,		36864},
+	{9471,		37888},
+	{9062,		38912},
+	{8674,		39936},
+	{8306,		40960},
+	{7951,		41984},
+	{7616,		43008},
+	{7296,		44032},
+	{6991,		45056},
+	{6701,		46080},
+	{6424,		47104},
+	{6160,		48128},
+	{5908,		49152},
+	{5667,		50176},
+	{5439,		51200},
+	{5219,		52224},
+	{5010,		53248},
+	{4810,		54272},
+	{4619,		55296},
+	{4440,		56320},
+	{4263,		57344},
+	{4097,		58368},
+	{3938,		59392},
+	{3785,		60416},
+	{3637,		61440},
+	{3501,		62464},
+	{3368,		63488},
+	{3240,		64512},
+	{3118,		65536},
+	{2998,		66560},
+	{2889,		67584},
+	{2782,		68608},
+	{2680,		69632},
+	{2581,		70656},
+	{2490,		71680},
+	{2397,		72704},
+	{2310,		73728},
+	{2227,		74752},
+	{2147,		75776},
+	{2064,		76800},
+	{1998,		77824},
+	{1927,		78848},
+	{1860,		79872},
+	{1795,		80896},
+	{1736,		81920},
+	{1673,		82944},
+	{1615,		83968},
+	{1560,		84992},
+	{1507,		86016},
+	{1456,		87040},
+	{1407,		88064},
+	{1360,		89088},
+	{1314,		90112},
+	{1271,		91136},
+	{1228,		92160},
+	{1189,		93184},
+	{1150,		94208},
+	{1112,		95232},
+	{1076,		96256},
+	{1042,		97280},
+	{1008,		98304},
+	{976,		99328},
+	{945,		100352},
+	{915,		101376},
+	{886,		102400},
+	{859,		103424},
+	{832,		104448},
+	{807,		105472},
+	{782,		106496},
+	{756,		107520},
+	{735,		108544},
+	{712,		109568},
+	{691,		110592},
+	{670,		111616},
+	{650,		112640},
+	{631,		113664},
+	{612,		114688},
+	{594,		115712},
+	{577,		116736},
+	{560,		117760},
+	{544,		118784},
+	{528,		119808},
+	{513,		120832},
+	{498,		121856},
+	{483,		122880},
+	{470,		123904},
+	{457,		124928},
+	{444,		125952},
+	{431,		126976},
+	{419,		128000}
+};
+
+static int32_t pm8xxx_adc_map_linear(const struct pm8xxx_adc_map_pt *pts,
+		uint32_t tablesize, int32_t input, int64_t *output)
+{
+	bool descending = 1;
+	uint32_t i = 0;
+
+	if ((pts == NULL) || (output == NULL))
+		return -EINVAL;
+
+	/* Check if table is descending or ascending */
+	if (tablesize > 1) {
+		if (pts[0].x < pts[1].x)
+			descending = 0;
+	}
+
+	while (i < tablesize) {
+		if ((descending == 1) && (pts[i].x < input)) {
+			/* table entry is less than measured
+				value and table is descending, stop */
+			break;
+		} else if ((descending == 0) &&
+				(pts[i].x > input)) {
+			/* table entry is greater than measured
+				value and table is ascending, stop */
+			break;
+		} else {
+			i++;
+		}
+	}
+
+	if (i == 0)
+		*output = pts[0].y;
+	else if (i == tablesize)
+		*output = pts[tablesize-1].y;
+	else {
+		/* result is between search_index and search_index-1 */
+		/* interpolate linearly */
+		*output = (((int32_t) ((pts[i].y - pts[i-1].y)*
+			(input - pts[i-1].x))/
+			(pts[i].x - pts[i-1].x))+
+			pts[i-1].y);
+	}
+
+	return 0;
+}
+
+int32_t pm8xxx_adc_scale_default(int32_t adc_code,
+		const struct pm8xxx_adc_properties *adc_properties,
+		const struct pm8xxx_adc_chan_properties *chan_properties,
+		struct pm8xxx_adc_chan_result *adc_chan_result)
+{
+	bool negative_rawfromoffset = 0;
+	int32_t rawfromoffset = 0;
+
+	if (!chan_properties || !chan_properties->offset_gain_numerator ||
+		!chan_properties->offset_gain_denominator || !adc_properties
+		|| !adc_chan_result)
+		return -EINVAL;
+
+	rawfromoffset = adc_code -
+			chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].offset;
+
+	adc_chan_result->adc_code = adc_code;
+	if (rawfromoffset < 0) {
+		if (adc_properties->bipolar) {
+			rawfromoffset = -rawfromoffset;
+			negative_rawfromoffset = 1;
+		} else {
+			rawfromoffset = 0;
+		}
+	}
+
+	if (rawfromoffset >= 1 << adc_properties->bitresolution)
+		rawfromoffset = (1 << adc_properties->bitresolution) - 1;
+
+	adc_chan_result->measurement = (int64_t)rawfromoffset *
+		chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx *
+				chan_properties->offset_gain_denominator;
+
+	/* do_div only perform positive integer division! */
+	do_div(adc_chan_result->measurement,
+		chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dy *
+				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 = (int32_t) adc_chan_result->measurement;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_default);
+
+int32_t pm8xxx_adc_scale_batt_therm(int32_t adc_code,
+		const struct pm8xxx_adc_properties *adc_properties,
+		const struct pm8xxx_adc_chan_properties *chan_properties,
+		struct pm8xxx_adc_chan_result *adc_chan_result)
+{
+	/* Note: adc_chan_result->measurement is in the unit of
+		adc_properties.adc_reference */
+	adc_chan_result->measurement = adc_code;
+	/* convert mV ---> degC using the table */
+	return pm8xxx_adc_map_linear(
+			adcmap_batttherm,
+			ARRAY_SIZE(adcmap_batttherm),
+			adc_code,
+			&adc_chan_result->physical);
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_batt_therm);
+
+int32_t pm8xxx_adc_scale_pa_therm(int32_t adc_code,
+		const struct pm8xxx_adc_properties *adc_properties,
+		const struct pm8xxx_adc_chan_properties *chan_properties,
+		struct pm8xxx_adc_chan_result *adc_chan_result)
+{
+	/* Note: adc_chan_result->measurement is in the unit of
+		adc_properties.adc_reference */
+	adc_chan_result->measurement = adc_code;
+	/* convert mV ---> degC using the table */
+	return pm8xxx_adc_map_linear(
+			adcmap_pa_therm,
+			ARRAY_SIZE(adcmap_pa_therm),
+			adc_code,
+			&adc_chan_result->physical);
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_pa_therm);
+
+int32_t pm8xxx_adc_scale_pmic_therm(int32_t adc_code,
+		const struct pm8xxx_adc_properties *adc_properties,
+		const struct pm8xxx_adc_chan_properties *chan_properties,
+		struct pm8xxx_adc_chan_result *adc_chan_result)
+{
+	int32_t rawfromoffset;
+
+	if (!chan_properties || !chan_properties->offset_gain_numerator ||
+		!chan_properties->offset_gain_denominator || !adc_properties
+		|| !adc_chan_result)
+		return -EINVAL;
+
+	adc_chan_result->adc_code = adc_code;
+	rawfromoffset = adc_code -
+			chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].offset;
+	if (rawfromoffset > 0) {
+		if (rawfromoffset >= 1 << adc_properties->bitresolution)
+			rawfromoffset = (1 << adc_properties->bitresolution)
+									- 1;
+		/* 2mV/K */
+		adc_chan_result->measurement = (int64_t)rawfromoffset*
+			chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dx *
+			chan_properties->offset_gain_denominator * 1000;
+
+		do_div(adc_chan_result->measurement,
+			chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].dy *
+			chan_properties->offset_gain_numerator*2);
+	} else {
+		adc_chan_result->measurement = 0;
+	}
+	/* Note: adc_chan_result->measurement is in the unit of
+		adc_properties.adc_reference */
+	adc_chan_result->physical = (int32_t)adc_chan_result->measurement;
+	/* Change to .001 deg C */
+	adc_chan_result->physical -= KELVINMIL_DEGMIL;
+	adc_chan_result->measurement <<= 1;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_scale_pmic_therm);
+
+/* Scales the ADC code to 0.001 degrees C using the map
+ * table for the XO thermistor.
+ */
+int32_t pm8xxx_adc_tdkntcg_therm(int32_t adc_code,
+		const struct pm8xxx_adc_properties *adc_properties,
+		const struct pm8xxx_adc_chan_properties *chan_properties,
+		struct pm8xxx_adc_chan_result *adc_chan_result)
+{
+	int32_t rt_r25;
+	int32_t offset = chan_properties->adc_graph[ADC_CALIB_ABSOLUTE].offset;
+
+	rt_r25 = adc_code - offset;
+
+	pm8xxx_adc_map_linear(adcmap_ntcg_104ef_104fb,
+		ARRAY_SIZE(adcmap_ntcg_104ef_104fb),
+		rt_r25, &adc_chan_result->physical);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_tdkntcg_therm);
+
+int32_t pm8xxx_adc_batt_scaler(struct pm8xxx_adc_arb_btm_param *btm_param)
+{
+	int rc;
+
+	rc = pm8xxx_adc_map_linear(
+		adcmap_btm_threshold,
+		ARRAY_SIZE(adcmap_btm_threshold),
+		btm_param->low_thr_temp,
+		&btm_param->low_thr_voltage);
+
+	if (!rc) {
+		rc = pm8xxx_adc_map_linear(
+			adcmap_btm_threshold,
+			ARRAY_SIZE(adcmap_btm_threshold),
+			btm_param->high_thr_temp,
+			&btm_param->high_thr_voltage);
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_batt_scaler);
diff --git a/drivers/hwmon/pm8xxx-adc.c b/drivers/hwmon/pm8xxx-adc.c
new file mode 100644
index 0000000..152eb40
--- /dev/null
+++ b/drivers/hwmon/pm8xxx-adc.c
@@ -0,0 +1,1286 @@
+/*
+ * Copyright (c) 2011, 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.
+ *
+ * Qualcomm's PM8921/PM8018 ADC Arbiter driver
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/hwmon.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/wakelock.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/pm8xxx/mpp.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/pm8xxx/pm8xxx-adc.h>
+
+/* User Bank register set */
+#define PM8XXX_ADC_ARB_USRP_CNTRL1			0x197
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB		BIT(0)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV1			BIT(1)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV2			BIT(2)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV3			BIT(3)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV4			BIT(4)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_RSV5			BIT(5)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_EOC			BIT(6)
+#define PM8XXX_ADC_ARB_USRP_CNTRL1_REQ			BIT(7)
+
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL			0x198
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_RSV0		BIT(0)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_RSV1		BIT(1)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_PREMUX0		BIT(2)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_PREMUX1		BIT(3)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL0		BIT(4)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL1		BIT(5)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL2		BIT(6)
+#define PM8XXX_ADC_ARB_USRP_AMUX_CNTRL_SEL3		BIT(7)
+
+#define PM8XXX_ADC_ARB_USRP_ANA_PARAM			0x199
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM			0x19A
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT0	BIT(0)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_SEL_SHIFT1	BIT(1)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_CLK_RATE0		BIT(2)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_CLK_RATE1		BIT(3)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_EOC		BIT(4)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0		BIT(5)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1		BIT(6)
+#define PM8XXX_ADC_ARB_USRP_DIG_PARAM_EN		BIT(7)
+
+#define PM8XXX_ADC_ARB_USRP_RSV				0x19B
+#define PM8XXX_ADC_ARB_USRP_RSV_RST			BIT(0)
+#define PM8XXX_ADC_ARB_USRP_RSV_DTEST0			BIT(1)
+#define PM8XXX_ADC_ARB_USRP_RSV_DTEST1			BIT(2)
+#define PM8XXX_ADC_ARB_USRP_RSV_OP			BIT(3)
+#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL0			BIT(4)
+#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL1			BIT(5)
+#define PM8XXX_ADC_ARB_USRP_RSV_IP_SEL2			BIT(6)
+#define PM8XXX_ADC_ARB_USRP_RSV_TRM			BIT(7)
+
+#define PM8XXX_ADC_ARB_USRP_DATA0			0x19D
+#define PM8XXX_ADC_ARB_USRP_DATA1			0x19C
+
+#define PM8XXX_ADC_ARB_BTM_CNTRL1			0x17e
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM		BIT(0)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE		BIT(1)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL1	BIT(2)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL2	BIT(3)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL3	BIT(4)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_MEAS_INTERVAL4	BIT(5)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_EOC			BIT(6)
+#define PM8XXX_ADC_ARB_BTM_CNTRL1_REQ			BIT(7)
+
+#define PM8XXX_ADC_ARB_BTM_CNTRL2			0x18c
+#define PM8XXX_ADC_ARB_BTM_AMUX_CNTRL			0x17f
+#define PM8XXX_ADC_ARB_BTM_ANA_PARAM			0x180
+#define PM8XXX_ADC_ARB_BTM_DIG_PARAM			0x181
+#define PM8XXX_ADC_ARB_BTM_RSV				0x182
+#define PM8XXX_ADC_ARB_BTM_DATA1			0x183
+#define PM8XXX_ADC_ARB_BTM_DATA0			0x184
+#define PM8XXX_ADC_ARB_BTM_BAT_COOL_THR1		0x185
+#define PM8XXX_ADC_ARB_BTM_BAT_COOL_THR0		0x186
+#define PM8XXX_ADC_ARB_BTM_BAT_WARM_THR1		0x187
+#define PM8XXX_ADC_ARB_BTM_BAT_WARM_THR0		0x188
+
+#define PM8XXX_ADC_ARB_ANA_DIG				0xa0
+#define PM8XXX_ADC_BTM_RSV				0x10
+#define PM8XXX_ADC_AMUX_MPP_SEL				2
+#define PM8XXX_ADC_AMUX_SEL				4
+#define PM8XXX_ADC_RSV_IP_SEL				4
+#define PM8XXX_ADC_BTM_CHANNEL_SEL			4
+#define PM8XXX_MAX_CHANNEL_PROPERTIES			2
+#define PM8XXX_ADC_IRQ_0				0
+#define PM8XXX_ADC_IRQ_1				1
+#define PM8XXX_ADC_IRQ_2				2
+#define PM8XXX_ADC_BTM_INTERVAL_SEL_MASK		0xF
+#define PM8XXX_ADC_BTM_INTERVAL_SEL_SHIFT		2
+#define PM8XXX_ADC_BTM_DECIMATION_SEL			5
+#define PM8XXX_ADC_MUL					10
+#define PM8XXX_ADC_CONV_TIME_MIN			2000
+#define PM8XXX_ADC_CONV_TIME_MAX			2100
+#define PM8XXX_ADC_MPP_SETTLE_TIME_MIN			200
+#define PM8XXX_ADC_MPP_SETTLE_TIME_MAX			200
+#define PM8XXX_ADC_PA_THERM_VREG_UV_MIN			1800000
+#define PM8XXX_ADC_PA_THERM_VREG_UV_MAX			1800000
+#define PM8XXX_ADC_PA_THERM_VREG_UA_LOAD		100000
+
+struct pm8xxx_adc {
+	struct device				*dev;
+	struct pm8xxx_adc_properties		*adc_prop;
+	int					adc_irq;
+	struct mutex				adc_lock;
+	struct mutex				mpp_adc_lock;
+	spinlock_t				btm_lock;
+	uint32_t				adc_num_channel;
+	uint32_t				adc_num_board_channel;
+	struct completion			adc_rslt_completion;
+	struct pm8xxx_adc_amux			*adc_channel;
+	int					btm_warm_irq;
+	int					btm_cool_irq;
+	struct dentry				*dent;
+	struct work_struct			warm_work;
+	struct work_struct			cool_work;
+	uint32_t				mpp_base;
+	struct device				*hwmon;
+	struct wake_lock			adc_wakelock;
+	int					msm_suspend_check;
+	struct pm8xxx_adc_amux_properties	*conv;
+	struct pm8xxx_adc_arb_btm_param		batt[0];
+	struct sensor_device_attribute		sens_attr[0];
+};
+
+struct pm8xxx_adc_amux_properties {
+	uint32_t				amux_channel;
+	uint32_t				decimation;
+	uint32_t				amux_ip_rsv;
+	uint32_t				amux_mpp_channel;
+	struct pm8xxx_adc_chan_properties	chan_prop[0];
+};
+
+static const struct pm8xxx_adc_scaling_ratio pm8xxx_amux_scaling_ratio[] = {
+	{1, 1},
+	{1, 3},
+	{1, 4},
+	{1, 6}
+};
+
+static struct pm8xxx_adc *pmic_adc;
+
+static struct pm8xxx_adc_scale_fn adc_scale_fn[] = {
+	[ADC_SCALE_DEFAULT] = {pm8xxx_adc_scale_default},
+	[ADC_SCALE_BATT_THERM] = {pm8xxx_adc_scale_batt_therm},
+	[ADC_SCALE_PA_THERM] = {pm8xxx_adc_scale_pa_therm},
+	[ADC_SCALE_PMIC_THERM] = {pm8xxx_adc_scale_pmic_therm},
+	[ADC_SCALE_XOTHERM] = {pm8xxx_adc_tdkntcg_therm},
+};
+
+/* On PM8921 ADC the MPP needs to first be configured
+as an analog input to the AMUX pre-mux channel before
+issuing a read request. PM8921 MPP 8 is mapped to AMUX8
+and is common between remote processor's.
+On PM8018 ADC the MPP is directly connected to the AMUX
+pre-mux. Therefore clients of the PM8018 MPP do not need
+to configure the MPP as an analog input to the pre-mux.
+Clients can directly issue request on the pre-mux AMUX
+channel to read the ADC on the MPP */
+static struct pm8xxx_mpp_config_data pm8xxx_adc_mpp_config = {
+	.type		= PM8XXX_MPP_TYPE_A_INPUT,
+	/* AMUX6 is dedicated to be used for apps processor */
+	.level		= PM8XXX_MPP_AIN_AMUX_CH6,
+	.control	= PM8XXX_MPP_AOUT_CTRL_DISABLE,
+};
+
+/* MPP Configuration for default settings */
+static struct pm8xxx_mpp_config_data pm8xxx_adc_mpp_unconfig = {
+	.type		= PM8XXX_MPP_TYPE_SINK,
+	.level		= PM8XXX_MPP_AIN_AMUX_CH5,
+	.control	= PM8XXX_MPP_AOUT_CTRL_DISABLE,
+};
+
+static bool pm8xxx_adc_calib_first_adc;
+static bool pm8xxx_adc_initialized, pm8xxx_adc_calib_device_init;
+
+static int32_t pm8xxx_adc_arb_cntrl(uint32_t arb_cntrl,
+					uint32_t channel)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int i, rc;
+	u8 data_arb_cntrl = 0;
+
+	if (arb_cntrl) {
+		if (adc_pmic->msm_suspend_check)
+			pr_err("PM8xxx ADC request made after suspend_noirq "
+					"with channel: %d\n", channel);
+		data_arb_cntrl |= PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB;
+		wake_lock(&adc_pmic->adc_wakelock);
+	}
+
+	/* Write twice to the CNTRL register for the arbiter settings
+	   to take into effect */
+	for (i = 0; i < 2; i++) {
+		rc = pm8xxx_writeb(adc_pmic->dev->parent,
+				PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl);
+		if (rc < 0) {
+			pr_err("PM8xxx arb cntrl write failed with %d\n", rc);
+			return rc;
+		}
+	}
+
+	if (arb_cntrl) {
+		data_arb_cntrl |= PM8XXX_ADC_ARB_USRP_CNTRL1_REQ;
+		rc = pm8xxx_writeb(adc_pmic->dev->parent,
+			PM8XXX_ADC_ARB_USRP_CNTRL1, data_arb_cntrl);
+	} else
+		wake_unlock(&adc_pmic->adc_wakelock);
+
+	return 0;
+}
+
+static int32_t pm8xxx_adc_patherm_power(bool on)
+{
+	static struct regulator *pa_therm;
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int rc = 0;
+	if (on) {
+		pa_therm = regulator_get(adc_pmic->dev,
+						"pa_therm");
+		if (IS_ERR(pa_therm)) {
+			rc = PTR_ERR(pa_therm);
+			pr_err("failed to request pa_therm vreg "
+					"with error %d\n", rc);
+			return rc;
+		}
+
+		rc = regulator_set_voltage(pa_therm,
+				PM8XXX_ADC_PA_THERM_VREG_UV_MIN,
+				PM8XXX_ADC_PA_THERM_VREG_UV_MAX);
+		if (rc < 0) {
+			pr_err("failed to set the voltage for "
+					"pa_therm with error %d\n", rc);
+			goto fail;
+		}
+
+		rc = regulator_set_optimum_mode(pa_therm,
+				PM8XXX_ADC_PA_THERM_VREG_UA_LOAD);
+		if (rc < 0) {
+			pr_err("failed to set optimum mode for "
+					"pa_therm with error %d\n", rc);
+			goto fail;
+		}
+
+		if (regulator_enable(pa_therm)) {
+			pr_err("failed to enable pa_therm vreg with "
+						"error %d\n", rc);
+			goto fail;
+		}
+	} else {
+		if (pa_therm != NULL) {
+			regulator_disable(pa_therm);
+			regulator_put(pa_therm);
+		}
+	}
+
+	return rc;
+fail:
+	regulator_put(pa_therm);
+	return rc;
+}
+
+static int32_t pm8xxx_adc_channel_power_enable(uint32_t channel,
+							bool power_cntrl)
+{
+	int rc = 0;
+
+	switch (channel)
+	case ADC_MPP_1_AMUX8:
+		pm8xxx_adc_patherm_power(power_cntrl);
+
+	return rc;
+}
+
+
+static uint32_t pm8xxx_adc_read_reg(uint32_t reg, u8 *data)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int rc;
+
+	rc = pm8xxx_readb(adc_pmic->dev->parent, reg, data);
+	if (rc < 0) {
+		pr_err("PM8xxx adc read reg %d failed with %d\n", reg, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static uint32_t pm8xxx_adc_write_reg(uint32_t reg, u8 data)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int rc;
+
+	rc = pm8xxx_writeb(adc_pmic->dev->parent, reg, data);
+	if (rc < 0) {
+		pr_err("PM8xxx adc write reg %d failed with %d\n", reg, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int32_t pm8xxx_adc_configure(
+				struct pm8xxx_adc_amux_properties *chan_prop)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	u8 data_amux_chan = 0, data_arb_rsv = 0, data_dig_param = 0;
+	int rc;
+
+	data_amux_chan |= chan_prop->amux_channel << PM8XXX_ADC_AMUX_SEL;
+
+	if (chan_prop->amux_mpp_channel)
+		data_amux_chan |= chan_prop->amux_mpp_channel <<
+					PM8XXX_ADC_AMUX_MPP_SEL;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_AMUX_CNTRL,
+							data_amux_chan);
+	if (rc < 0)
+		return rc;
+
+	data_arb_rsv &= (PM8XXX_ADC_ARB_USRP_RSV_RST |
+		PM8XXX_ADC_ARB_USRP_RSV_DTEST0 |
+		PM8XXX_ADC_ARB_USRP_RSV_DTEST1 |
+		PM8XXX_ADC_ARB_USRP_RSV_OP |
+		PM8XXX_ADC_ARB_USRP_RSV_TRM);
+	data_arb_rsv |= chan_prop->amux_ip_rsv << PM8XXX_ADC_RSV_IP_SEL;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_RSV, data_arb_rsv);
+	if (rc < 0)
+		return rc;
+
+	rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_DIG_PARAM,
+							&data_dig_param);
+	if (rc < 0)
+		return rc;
+
+	/* Default 2.4Mhz clock rate */
+	/* Client chooses the decimation */
+	switch (chan_prop->decimation) {
+	case ADC_DECIMATION_TYPE1:
+		data_dig_param |= PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0;
+		break;
+	case ADC_DECIMATION_TYPE2:
+		data_dig_param |= (PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0
+				| PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE1);
+		break;
+	default:
+		data_dig_param |= PM8XXX_ADC_ARB_USRP_DIG_PARAM_DEC_RATE0;
+		break;
+	}
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_DIG_PARAM,
+						PM8XXX_ADC_ARB_ANA_DIG);
+	if (rc < 0)
+		return rc;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_USRP_ANA_PARAM,
+						PM8XXX_ADC_ARB_ANA_DIG);
+	if (rc < 0)
+		return rc;
+
+	if (!pm8xxx_adc_calib_first_adc)
+		enable_irq(adc_pmic->adc_irq);
+
+	rc = pm8xxx_adc_arb_cntrl(1, data_amux_chan);
+	if (rc < 0) {
+		pr_err("Configuring ADC Arbiter"
+				"enable failed with %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static uint32_t pm8xxx_adc_read_adc_code(int32_t *data)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	uint8_t rslt_lsb, rslt_msb;
+	int32_t rc, max_ideal_adc_code = 1 << adc_pmic->adc_prop->bitresolution;
+
+	rc = pm8xxx_readb(adc_pmic->dev->parent,
+				PM8XXX_ADC_ARB_USRP_DATA0, &rslt_lsb);
+	if (rc < 0) {
+		pr_err("PM8xxx adc result read failed with %d\n", rc);
+		return rc;
+	}
+
+	rc = pm8xxx_readb(adc_pmic->dev->parent,
+				PM8XXX_ADC_ARB_USRP_DATA1, &rslt_msb);
+	if (rc < 0) {
+		pr_err("PM8xxx adc result read failed with %d\n", rc);
+		return rc;
+	}
+
+	*data = (rslt_msb << 8) | rslt_lsb;
+
+	/* Use the midpoint to determine underflow or overflow */
+	if (*data > max_ideal_adc_code + (max_ideal_adc_code >> 1))
+		*data |= ((1 << (8 * sizeof(*data) -
+			adc_pmic->adc_prop->bitresolution)) - 1) <<
+			adc_pmic->adc_prop->bitresolution;
+
+	/* Default value for switching off the arbiter after reading
+	   the ADC value. Bit 0 set to 0. */
+	rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE);
+	if (rc < 0) {
+		pr_err("%s: Configuring ADC Arbiter disable"
+					"failed\n", __func__);
+		return rc;
+	}
+
+	return 0;
+}
+
+static void pm8xxx_adc_btm_warm_scheduler_fn(struct work_struct *work)
+{
+	struct pm8xxx_adc *adc_pmic = container_of(work, struct pm8xxx_adc,
+					warm_work);
+	unsigned long flags = 0;
+	bool warm_status;
+
+	spin_lock_irqsave(&adc_pmic->btm_lock, flags);
+	warm_status = irq_read_line(adc_pmic->btm_warm_irq);
+	if (adc_pmic->batt->btm_warm_fn != NULL)
+		adc_pmic->batt->btm_warm_fn(warm_status);
+	spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+}
+
+static void pm8xxx_adc_btm_cool_scheduler_fn(struct work_struct *work)
+{
+	struct pm8xxx_adc *adc_pmic = container_of(work, struct pm8xxx_adc,
+					cool_work);
+	unsigned long flags = 0;
+	bool cool_status;
+
+	spin_lock_irqsave(&adc_pmic->btm_lock, flags);
+	cool_status = irq_read_line(adc_pmic->btm_cool_irq);
+	if (adc_pmic->batt->btm_cool_fn != NULL)
+		adc_pmic->batt->btm_cool_fn(cool_status);
+	spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+}
+
+static irqreturn_t pm8xxx_adc_isr(int irq, void *dev_id)
+{
+	struct pm8xxx_adc *adc_8xxx = dev_id;
+
+	disable_irq_nosync(adc_8xxx->adc_irq);
+
+	if (pm8xxx_adc_calib_first_adc)
+		return IRQ_HANDLED;
+	/* TODO Handle spurius interrupt condition */
+	complete(&adc_8xxx->adc_rslt_completion);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8xxx_btm_warm_isr(int irq, void *dev_id)
+{
+	struct pm8xxx_adc *btm_8xxx = dev_id;
+
+	schedule_work(&btm_8xxx->warm_work);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8xxx_btm_cool_isr(int irq, void *dev_id)
+{
+	struct pm8xxx_adc *btm_8xxx = dev_id;
+
+	schedule_work(&btm_8xxx->cool_work);
+
+	return IRQ_HANDLED;
+}
+
+static uint32_t pm8xxx_adc_calib_device(void)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	struct pm8xxx_adc_amux_properties conv;
+	int rc, offset_adc, slope_adc, calib_read_1, calib_read_2;
+	u8 data_arb_usrp_cntrl1 = 0;
+
+	conv.amux_channel = CHANNEL_125V;
+	conv.decimation = ADC_DECIMATION_TYPE2;
+	conv.amux_ip_rsv = AMUX_RSV1;
+	conv.amux_mpp_channel = PREMUX_MPP_SCALE_0;
+	pm8xxx_adc_calib_first_adc = true;
+	rc = pm8xxx_adc_configure(&conv);
+	if (rc) {
+		pr_err("pm8xxx_adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
+					PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
+		rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
+					&data_arb_usrp_cntrl1);
+		if (rc < 0)
+			return rc;
+		usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
+					PM8XXX_ADC_CONV_TIME_MAX);
+	}
+	data_arb_usrp_cntrl1 = 0;
+
+	rc = pm8xxx_adc_read_adc_code(&calib_read_1);
+	if (rc) {
+		pr_err("pm8xxx_adc read adc failed with %d\n", rc);
+		pm8xxx_adc_calib_first_adc = false;
+		goto calib_fail;
+	}
+	pm8xxx_adc_calib_first_adc = false;
+
+	conv.amux_channel = CHANNEL_625MV;
+	conv.decimation = ADC_DECIMATION_TYPE2;
+	conv.amux_ip_rsv = AMUX_RSV1;
+	conv.amux_mpp_channel = PREMUX_MPP_SCALE_0;
+	pm8xxx_adc_calib_first_adc = true;
+	rc = pm8xxx_adc_configure(&conv);
+	if (rc) {
+		pr_err("pm8xxx_adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
+					PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
+		rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
+					&data_arb_usrp_cntrl1);
+		if (rc < 0)
+			return rc;
+		usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
+					PM8XXX_ADC_CONV_TIME_MAX);
+	}
+	data_arb_usrp_cntrl1 = 0;
+
+	rc = pm8xxx_adc_read_adc_code(&calib_read_2);
+	if (rc) {
+		pr_err("pm8xxx_adc read adc failed with %d\n", rc);
+		pm8xxx_adc_calib_first_adc = false;
+		goto calib_fail;
+	}
+	pm8xxx_adc_calib_first_adc = false;
+
+	slope_adc = (((calib_read_1 - calib_read_2) << PM8XXX_ADC_MUL)/
+					PM8XXX_CHANNEL_ADC_625_MV);
+	offset_adc = calib_read_2 -
+			((slope_adc * PM8XXX_CHANNEL_ADC_625_MV) >>
+							PM8XXX_ADC_MUL);
+
+	adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].offset
+								= offset_adc;
+	adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].dy =
+					(calib_read_1 - calib_read_2);
+	adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_ABSOLUTE].dx
+						= PM8XXX_CHANNEL_ADC_625_MV;
+	rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE);
+	if (rc < 0) {
+		pr_err("%s: Configuring ADC Arbiter disable"
+					"failed\n", __func__);
+		return rc;
+	}
+	/* Ratiometric Calibration */
+	conv.amux_channel = CHANNEL_MUXOFF;
+	conv.decimation = ADC_DECIMATION_TYPE2;
+	conv.amux_ip_rsv = AMUX_RSV5;
+	conv.amux_mpp_channel = PREMUX_MPP_SCALE_0;
+	pm8xxx_adc_calib_first_adc = true;
+	rc = pm8xxx_adc_configure(&conv);
+	if (rc) {
+		pr_err("pm8xxx_adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
+					PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
+		rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
+					&data_arb_usrp_cntrl1);
+		if (rc < 0)
+			return rc;
+		usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
+					PM8XXX_ADC_CONV_TIME_MAX);
+	}
+	data_arb_usrp_cntrl1 = 0;
+
+	rc = pm8xxx_adc_read_adc_code(&calib_read_1);
+	if (rc) {
+		pr_err("pm8xxx_adc read adc failed with %d\n", rc);
+		pm8xxx_adc_calib_first_adc = false;
+		goto calib_fail;
+	}
+	pm8xxx_adc_calib_first_adc = false;
+
+	conv.amux_channel = CHANNEL_MUXOFF;
+	conv.decimation = ADC_DECIMATION_TYPE2;
+	conv.amux_ip_rsv = AMUX_RSV4;
+	conv.amux_mpp_channel = PREMUX_MPP_SCALE_0;
+	pm8xxx_adc_calib_first_adc = true;
+	rc = pm8xxx_adc_configure(&conv);
+	if (rc) {
+		pr_err("pm8xxx_adc configure failed with %d\n", rc);
+		goto calib_fail;
+	}
+
+	while (data_arb_usrp_cntrl1 != (PM8XXX_ADC_ARB_USRP_CNTRL1_EOC |
+					PM8XXX_ADC_ARB_USRP_CNTRL1_EN_ARB)) {
+		rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_USRP_CNTRL1,
+					&data_arb_usrp_cntrl1);
+		if (rc < 0)
+			return rc;
+		usleep_range(PM8XXX_ADC_CONV_TIME_MIN,
+					PM8XXX_ADC_CONV_TIME_MAX);
+	}
+	data_arb_usrp_cntrl1 = 0;
+
+	rc = pm8xxx_adc_read_adc_code(&calib_read_2);
+	if (rc) {
+		pr_err("pm8xxx_adc read adc failed with %d\n", rc);
+		pm8xxx_adc_calib_first_adc = false;
+		goto calib_fail;
+	}
+	pm8xxx_adc_calib_first_adc = false;
+
+	slope_adc = (((calib_read_1 - calib_read_2) << PM8XXX_ADC_MUL)/
+				adc_pmic->adc_prop->adc_vdd_reference);
+	offset_adc = calib_read_2 -
+			((slope_adc * adc_pmic->adc_prop->adc_vdd_reference)
+							>> PM8XXX_ADC_MUL);
+
+	adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].offset
+								= offset_adc;
+	adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].dy =
+					(calib_read_1 - calib_read_2);
+	adc_pmic->conv->chan_prop->adc_graph[ADC_CALIB_RATIOMETRIC].dx =
+					adc_pmic->adc_prop->adc_vdd_reference;
+calib_fail:
+	rc = pm8xxx_adc_arb_cntrl(0, CHANNEL_NONE);
+	if (rc < 0) {
+		pr_err("%s: Configuring ADC Arbiter disable"
+					"failed\n", __func__);
+	}
+
+	return rc;
+}
+
+uint32_t pm8xxx_adc_read(enum pm8xxx_adc_channels channel,
+				struct pm8xxx_adc_chan_result *result)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int i = 0, rc = 0, rc_fail, amux_prescaling, scale_type;
+	enum pm8xxx_adc_premux_mpp_scale_type mpp_scale;
+
+	if (!pm8xxx_adc_initialized)
+		return -ENODEV;
+
+	if (!pm8xxx_adc_calib_device_init) {
+		if (pm8xxx_adc_calib_device() == 0)
+			pm8xxx_adc_calib_device_init = true;
+	}
+
+	mutex_lock(&adc_pmic->adc_lock);
+
+	for (i = 0; i < adc_pmic->adc_num_channel; i++) {
+		if (channel == adc_pmic->adc_channel[i].channel_name)
+			break;
+	}
+
+	if (i == adc_pmic->adc_num_channel) {
+		rc = -EBADF;
+		goto fail_unlock;
+	}
+
+	if (channel < PM8XXX_CHANNEL_MPP_SCALE1_IDX) {
+		mpp_scale = PREMUX_MPP_SCALE_0;
+		adc_pmic->conv->amux_channel = channel;
+	} else if (channel >= PM8XXX_CHANNEL_MPP_SCALE1_IDX) {
+		mpp_scale = PREMUX_MPP_SCALE_1;
+		adc_pmic->conv->amux_channel = channel %
+				PM8XXX_CHANNEL_MPP_SCALE1_IDX;
+	} else if (channel >= PM8XXX_CHANNEL_MPP_SCALE3_IDX) {
+		mpp_scale = PREMUX_MPP_SCALE_1_DIV3;
+		adc_pmic->conv->amux_channel = channel %
+				PM8XXX_CHANNEL_MPP_SCALE3_IDX;
+	}
+
+	adc_pmic->conv->amux_mpp_channel = mpp_scale;
+	adc_pmic->conv->amux_ip_rsv = adc_pmic->adc_channel[i].adc_rsv;
+	adc_pmic->conv->decimation = adc_pmic->adc_channel[i].adc_decimation;
+	amux_prescaling = adc_pmic->adc_channel[i].chan_path_prescaling;
+
+	adc_pmic->conv->chan_prop->offset_gain_numerator =
+		pm8xxx_amux_scaling_ratio[amux_prescaling].num;
+	adc_pmic->conv->chan_prop->offset_gain_denominator =
+		 pm8xxx_amux_scaling_ratio[amux_prescaling].den;
+
+	rc = pm8xxx_adc_channel_power_enable(channel, true);
+	if (rc) {
+		rc = -EINVAL;
+		goto fail_unlock;
+	}
+
+	rc = pm8xxx_adc_configure(adc_pmic->conv);
+	if (rc) {
+		rc = -EINVAL;
+		goto fail;
+	}
+
+	wait_for_completion(&adc_pmic->adc_rslt_completion);
+
+	rc = pm8xxx_adc_read_adc_code(&result->adc_code);
+	if (rc) {
+		rc = -EINVAL;
+		goto fail;
+	}
+
+	scale_type = adc_pmic->adc_channel[i].adc_scale_fn;
+	if (scale_type >= ADC_SCALE_NONE) {
+		rc = -EBADF;
+		goto fail;
+	}
+
+	adc_scale_fn[scale_type].chan(result->adc_code,
+			adc_pmic->adc_prop, adc_pmic->conv->chan_prop, result);
+
+	rc = pm8xxx_adc_channel_power_enable(channel, false);
+	if (rc) {
+		rc = -EINVAL;
+		goto fail_unlock;
+	}
+
+	mutex_unlock(&adc_pmic->adc_lock);
+
+	return 0;
+fail:
+	rc_fail = pm8xxx_adc_channel_power_enable(channel, false);
+	if (rc_fail)
+		pr_err("pm8xxx adc power disable failed\n");
+fail_unlock:
+	mutex_unlock(&adc_pmic->adc_lock);
+	pr_err("pm8xxx adc error with %d\n", rc);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_read);
+
+uint32_t pm8xxx_adc_mpp_config_read(uint32_t mpp_num,
+			enum pm8xxx_adc_channels channel,
+			struct pm8xxx_adc_chan_result *result)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int rc = 0;
+
+	if (!adc_pmic->mpp_base) {
+		rc = -EINVAL;
+		pr_info("PM8xxx MPP base invalid with error %d\n", rc);
+		return rc;
+	}
+
+	if (mpp_num == PM8XXX_AMUX_MPP_8) {
+		rc = -EINVAL;
+		pr_info("PM8xxx MPP8 is already configured "
+			"to AMUX8. Use pm8xxx_adc_read() instead.\n");
+		return rc;
+	}
+
+	mutex_lock(&adc_pmic->mpp_adc_lock);
+
+	rc = pm8xxx_mpp_config(((mpp_num - 1) + adc_pmic->mpp_base),
+					&pm8xxx_adc_mpp_config);
+	if (rc < 0) {
+		pr_err("pm8xxx adc mpp config error with %d\n", rc);
+		goto fail;
+	}
+
+	usleep_range(PM8XXX_ADC_MPP_SETTLE_TIME_MIN,
+					PM8XXX_ADC_MPP_SETTLE_TIME_MAX);
+
+	rc = pm8xxx_adc_read(channel, result);
+	if (rc < 0)
+		pr_err("pm8xxx adc read error with %d\n", rc);
+
+	rc = pm8xxx_mpp_config(((mpp_num - 1) + adc_pmic->mpp_base),
+					&pm8xxx_adc_mpp_unconfig);
+	if (rc < 0)
+		pr_err("pm8xxx adc mpp config error with %d\n", rc);
+fail:
+	mutex_unlock(&adc_pmic->mpp_adc_lock);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_mpp_config_read);
+
+uint32_t pm8xxx_adc_btm_configure(struct pm8xxx_adc_arb_btm_param *btm_param)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	u8 data_btm_cool_thr0, data_btm_cool_thr1;
+	u8 data_btm_warm_thr0, data_btm_warm_thr1;
+	u8 arb_btm_cntrl1;
+	unsigned long flags = 0;
+	int rc;
+
+	if (adc_pmic == NULL) {
+		pr_err("PMIC ADC not valid\n");
+		return -EINVAL;
+	}
+
+	if ((btm_param->btm_cool_fn == NULL) &&
+		(btm_param->btm_warm_fn == NULL)) {
+		pr_err("No BTM warm/cool notification??\n");
+		return -EINVAL;
+	}
+
+	rc = pm8xxx_adc_batt_scaler(btm_param);
+	if (rc < 0) {
+		pr_err("Failed to lookup the BTM thresholds\n");
+		return rc;
+	}
+
+	spin_lock_irqsave(&adc_pmic->btm_lock, flags);
+
+	data_btm_cool_thr0 = ((btm_param->low_thr_voltage << 24) >> 24);
+	data_btm_cool_thr1 = ((btm_param->low_thr_voltage << 16) >> 24);
+	data_btm_warm_thr0 = ((btm_param->high_thr_voltage << 24) >> 24);
+	data_btm_warm_thr1 = ((btm_param->high_thr_voltage << 16) >> 24);
+
+	if (btm_param->btm_cool_fn != NULL) {
+		rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_COOL_THR0,
+							data_btm_cool_thr0);
+		if (rc < 0)
+			goto write_err;
+
+		rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_COOL_THR1,
+							data_btm_cool_thr1);
+		if (rc < 0)
+			goto write_err;
+
+		adc_pmic->batt->btm_cool_fn = btm_param->btm_cool_fn;
+	}
+
+	if (btm_param->btm_warm_fn != NULL) {
+		rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_WARM_THR0,
+							data_btm_warm_thr0);
+		if (rc < 0)
+			goto write_err;
+
+		rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_BAT_WARM_THR1,
+							data_btm_warm_thr1);
+		if (rc < 0)
+			goto write_err;
+
+		adc_pmic->batt->btm_warm_fn = btm_param->btm_warm_fn;
+	}
+
+	rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, &arb_btm_cntrl1);
+	if (rc < 0)
+		goto bail_out;
+
+	btm_param->interval &= PM8XXX_ADC_BTM_INTERVAL_SEL_MASK;
+	arb_btm_cntrl1 |=
+		btm_param->interval << PM8XXX_ADC_BTM_INTERVAL_SEL_SHIFT;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1, arb_btm_cntrl1);
+	if (rc < 0)
+		goto write_err;
+
+	spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+
+	return rc;
+bail_out:
+write_err:
+	spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+	pr_debug("%s: with error code %d\n", __func__, rc);
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_configure);
+
+static uint32_t pm8xxx_adc_btm_read(uint32_t channel)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int rc, i;
+	u8 arb_btm_dig_param, arb_btm_ana_param, arb_btm_rsv;
+	u8 arb_btm_amux_cntrl, data_arb_btm_cntrl = 0;
+	unsigned long flags;
+
+	arb_btm_amux_cntrl = channel << PM8XXX_ADC_BTM_CHANNEL_SEL;
+	arb_btm_rsv = adc_pmic->adc_channel[channel].adc_rsv;
+	arb_btm_dig_param = arb_btm_ana_param = PM8XXX_ADC_ARB_ANA_DIG;
+
+	spin_lock_irqsave(&adc_pmic->btm_lock, flags);
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_AMUX_CNTRL,
+						arb_btm_amux_cntrl);
+	if (rc < 0)
+		goto write_err;
+
+	arb_btm_rsv = PM8XXX_ADC_BTM_RSV;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_RSV, arb_btm_rsv);
+	if (rc < 0)
+		goto write_err;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_DIG_PARAM,
+						arb_btm_dig_param);
+	if (rc < 0)
+		goto write_err;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_ANA_PARAM,
+						arb_btm_ana_param);
+	if (rc < 0)
+		goto write_err;
+
+	data_arb_btm_cntrl |= PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM;
+
+	for (i = 0; i < 2; i++) {
+		rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1,
+						data_arb_btm_cntrl);
+		if (rc < 0)
+			goto write_err;
+	}
+
+	data_arb_btm_cntrl |= PM8XXX_ADC_ARB_BTM_CNTRL1_REQ
+				| PM8XXX_ADC_ARB_BTM_CNTRL1_SEL_OP_MODE;
+
+	rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1,
+					data_arb_btm_cntrl);
+	if (rc < 0)
+		goto write_err;
+
+	if (pmic_adc->batt->btm_warm_fn != NULL)
+		enable_irq(adc_pmic->btm_warm_irq);
+
+	if (pmic_adc->batt->btm_cool_fn != NULL)
+		enable_irq(adc_pmic->btm_cool_irq);
+
+write_err:
+	spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+	return rc;
+}
+
+uint32_t pm8xxx_adc_btm_start(void)
+{
+	return pm8xxx_adc_btm_read(CHANNEL_BATT_THERM);
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_start);
+
+uint32_t pm8xxx_adc_btm_end(void)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int i, rc;
+	u8 data_arb_btm_cntrl;
+	unsigned long flags;
+
+	disable_irq_nosync(adc_pmic->btm_warm_irq);
+	disable_irq_nosync(adc_pmic->btm_cool_irq);
+
+	spin_lock_irqsave(&adc_pmic->btm_lock, flags);
+	/* Set BTM registers to Disable mode */
+	rc = pm8xxx_adc_read_reg(PM8XXX_ADC_ARB_BTM_CNTRL1,
+						&data_arb_btm_cntrl);
+	if (rc < 0) {
+		spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+		return rc;
+	}
+
+	data_arb_btm_cntrl |= ~PM8XXX_ADC_ARB_BTM_CNTRL1_EN_BTM;
+	/* Write twice to the CNTRL register for the arbiter settings
+	   to take into effect */
+	for (i = 0; i < 2; i++) {
+		rc = pm8xxx_adc_write_reg(PM8XXX_ADC_ARB_BTM_CNTRL1,
+							data_arb_btm_cntrl);
+		if (rc < 0) {
+			spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+			return rc;
+		}
+	}
+
+	spin_unlock_irqrestore(&adc_pmic->btm_lock, flags);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(pm8xxx_adc_btm_end);
+
+static ssize_t pm8xxx_adc_show(struct device *dev,
+			struct device_attribute *devattr, char *buf)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	struct pm8xxx_adc_chan_result result;
+	int rc = -1;
+
+	if (attr->index < adc_pmic->adc_num_channel)
+		rc = pm8xxx_adc_read(attr->index, &result);
+
+	if (rc)
+		return 0;
+
+	return snprintf(buf, sizeof(struct pm8xxx_adc_chan_result),
+				"Result:%lld Raw:%d\n",
+				result.physical, result.adc_code);
+}
+
+static int get_adc(void *data, u64 *val)
+{
+	struct pm8xxx_adc_chan_result result;
+	int i = (int)data;
+	int rc;
+
+	rc = pm8xxx_adc_read(i, &result);
+	if (!rc)
+		pr_info("ADC value raw:%x physical:%lld\n",
+			result.adc_code, result.physical);
+	*val = result.physical;
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_adc, NULL, "%llu\n");
+
+static int get_mpp_adc(void *data, u64 *val)
+{
+	struct pm8xxx_adc_chan_result result;
+	int i = (int)data;
+	int rc;
+
+	rc = pm8xxx_adc_mpp_config_read(i,
+		ADC_MPP_1_AMUX6, &result);
+	if (!rc)
+		pr_info("ADC MPP value raw:%x physical:%lld\n",
+			result.adc_code, result.physical);
+	*val = result.physical;
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_mpp_fops, get_mpp_adc, NULL, "%llu\n");
+
+#ifdef CONFIG_DEBUG_FS
+static void create_debugfs_entries(void)
+{
+	int i = 0;
+	pmic_adc->dent = debugfs_create_dir("pm8xxx_adc", NULL);
+
+	if (IS_ERR(pmic_adc->dent)) {
+		pr_err("pmic adc debugfs dir not created\n");
+		return;
+	}
+
+	for (i = 0; i < pmic_adc->adc_num_board_channel; i++)
+		debugfs_create_file(pmic_adc->adc_channel[i].name,
+			0644, pmic_adc->dent,
+			(void *)pmic_adc->adc_channel[i].channel_name,
+			&reg_fops);
+}
+#else
+static inline void create_debugfs_entries(void)
+{
+}
+#endif
+static struct sensor_device_attribute pm8xxx_adc_attr =
+	SENSOR_ATTR(NULL, S_IRUGO, pm8xxx_adc_show, NULL, 0);
+
+static int32_t pm8xxx_adc_init_hwmon(struct platform_device *pdev)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int rc = 0, i;
+
+	for (i = 0; i < pmic_adc->adc_num_board_channel; i++) {
+		pm8xxx_adc_attr.index = adc_pmic->adc_channel[i].channel_name;
+		pm8xxx_adc_attr.dev_attr.attr.name =
+						adc_pmic->adc_channel[i].name;
+		memcpy(&adc_pmic->sens_attr[i], &pm8xxx_adc_attr,
+						sizeof(pm8xxx_adc_attr));
+		rc = device_create_file(&pdev->dev,
+				&adc_pmic->sens_attr[i].dev_attr);
+		if (rc) {
+			dev_err(&pdev->dev, "device_create_file failed for "
+					    "dev %s\n",
+					    adc_pmic->adc_channel[i].name);
+			goto hwmon_err_sens;
+		}
+	}
+
+	return 0;
+hwmon_err_sens:
+	pr_info("Init HWMON failed for pm8xxx_adc with %d\n", rc);
+	return rc;
+}
+
+#ifdef CONFIG_PM
+static int pm8xxx_adc_suspend_noirq(struct device *dev)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+
+	adc_pmic->msm_suspend_check = 1;
+
+	return 0;
+}
+
+static int pm8xxx_adc_resume_noirq(struct device *dev)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+
+	adc_pmic->msm_suspend_check = 0;
+
+	return 0;
+}
+
+static const struct dev_pm_ops pm8xxx_adc_dev_pm_ops = {
+	.suspend_noirq = pm8xxx_adc_suspend_noirq,
+	.resume_noirq = pm8xxx_adc_resume_noirq,
+};
+
+#define PM8XXX_ADC_DEV_PM_OPS	(&pm8xxx_adc_dev_pm_ops)
+#else
+#define PM8XXX_ADC_DEV_PM_OPS NULL
+#endif
+
+static int __devexit pm8xxx_adc_teardown(struct platform_device *pdev)
+{
+	struct pm8xxx_adc *adc_pmic = pmic_adc;
+	int i;
+
+	wake_lock_destroy(&adc_pmic->adc_wakelock);
+	platform_set_drvdata(pdev, NULL);
+	pmic_adc = NULL;
+	for (i = 0; i < adc_pmic->adc_num_board_channel; i++)
+		device_remove_file(adc_pmic->dev,
+				&adc_pmic->sens_attr[i].dev_attr);
+	pm8xxx_adc_initialized = false;
+
+	return 0;
+}
+
+static int __devinit pm8xxx_adc_probe(struct platform_device *pdev)
+{
+	const struct pm8xxx_adc_platform_data *pdata = pdev->dev.platform_data;
+	struct pm8xxx_adc *adc_pmic;
+	struct pm8xxx_adc_amux_properties *adc_amux_prop;
+	int rc = 0;
+
+	if (!pdata) {
+		dev_err(&pdev->dev, "no platform data?\n");
+		return -EINVAL;
+	}
+
+	adc_pmic = devm_kzalloc(&pdev->dev, sizeof(struct pm8xxx_adc) +
+			sizeof(struct pm8xxx_adc_arb_btm_param) +
+			(sizeof(struct sensor_device_attribute) *
+			pdata->adc_num_board_channel), GFP_KERNEL);
+	if (!adc_pmic) {
+		dev_err(&pdev->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	adc_amux_prop = devm_kzalloc(&pdev->dev,
+				sizeof(struct pm8xxx_adc_amux_properties) +
+				sizeof(struct pm8xxx_adc_chan_properties)
+				, GFP_KERNEL);
+	if (!adc_amux_prop) {
+		dev_err(&pdev->dev, "Unable to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	adc_pmic->dev = &pdev->dev;
+	adc_pmic->adc_prop = pdata->adc_prop;
+	adc_pmic->conv = adc_amux_prop;
+	init_completion(&adc_pmic->adc_rslt_completion);
+	adc_pmic->adc_channel = pdata->adc_channel;
+	adc_pmic->adc_num_board_channel = pdata->adc_num_board_channel;
+	adc_pmic->adc_num_channel = ADC_MPP_2_CHANNEL_NONE;
+	adc_pmic->mpp_base = pdata->adc_mpp_base;
+
+	mutex_init(&adc_pmic->adc_lock);
+	mutex_init(&adc_pmic->mpp_adc_lock);
+	spin_lock_init(&adc_pmic->btm_lock);
+
+	adc_pmic->adc_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_0);
+	if (adc_pmic->adc_irq < 0)
+		return adc_pmic->adc_irq;
+
+	rc = devm_request_irq(&pdev->dev, adc_pmic->adc_irq,
+				pm8xxx_adc_isr,
+		IRQF_TRIGGER_RISING, "pm8xxx_adc_interrupt", adc_pmic);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to request adc irq "
+						"with error %d\n", rc);
+	}
+
+	disable_irq_nosync(adc_pmic->adc_irq);
+
+	adc_pmic->btm_warm_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_1);
+	if (adc_pmic->btm_warm_irq < 0)
+		return adc_pmic->btm_warm_irq;
+
+	rc = devm_request_irq(&pdev->dev, adc_pmic->btm_warm_irq,
+				pm8xxx_btm_warm_isr,
+		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"pm8xxx_btm_warm_interrupt", adc_pmic);
+	if (rc) {
+		pr_err("btm warm irq failed %d with interrupt number %d\n",
+						rc, adc_pmic->btm_warm_irq);
+		dev_err(&pdev->dev, "failed to request btm irq\n");
+	}
+
+	disable_irq_nosync(adc_pmic->btm_warm_irq);
+
+	adc_pmic->btm_cool_irq = platform_get_irq(pdev, PM8XXX_ADC_IRQ_2);
+	if (adc_pmic->btm_cool_irq < 0)
+		return adc_pmic->btm_cool_irq;
+
+	rc = devm_request_irq(&pdev->dev, adc_pmic->btm_cool_irq,
+				pm8xxx_btm_cool_isr,
+		IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"pm8xxx_btm_cool_interrupt", adc_pmic);
+	if (rc) {
+		pr_err("btm cool irq failed with return %d and number %d\n",
+						rc, adc_pmic->btm_cool_irq);
+		dev_err(&pdev->dev, "failed to request btm irq\n");
+	}
+
+	disable_irq_nosync(adc_pmic->btm_cool_irq);
+	platform_set_drvdata(pdev, adc_pmic);
+	wake_lock_init(&adc_pmic->adc_wakelock, WAKE_LOCK_SUSPEND,
+					"pm8xxx_adc_wakelock");
+	adc_pmic->msm_suspend_check = 0;
+	pmic_adc = adc_pmic;
+
+	INIT_WORK(&adc_pmic->warm_work, pm8xxx_adc_btm_warm_scheduler_fn);
+	INIT_WORK(&adc_pmic->cool_work, pm8xxx_adc_btm_cool_scheduler_fn);
+	create_debugfs_entries();
+	pm8xxx_adc_calib_first_adc = false;
+	pm8xxx_adc_calib_device_init = false;
+	pm8xxx_adc_initialized = true;
+
+	rc = pm8xxx_adc_init_hwmon(pdev);
+	if (rc) {
+		pr_err("pm8xxx adc init hwmon failed with %d\n", rc);
+		dev_err(&pdev->dev, "failed to initialize pm8xxx hwmon adc\n");
+	}
+	adc_pmic->hwmon = hwmon_device_register(adc_pmic->dev);
+	return 0;
+}
+
+static struct platform_driver pm8xxx_adc_driver = {
+	.probe	= pm8xxx_adc_probe,
+	.remove	= __devexit_p(pm8xxx_adc_teardown),
+	.driver	= {
+		.name	= PM8XXX_ADC_DEV_NAME,
+		.owner	= THIS_MODULE,
+		.pm	= PM8XXX_ADC_DEV_PM_OPS,
+	},
+};
+
+static int __init pm8xxx_adc_init(void)
+{
+	return platform_driver_register(&pm8xxx_adc_driver);
+}
+module_init(pm8xxx_adc_init);
+
+static void __exit pm8xxx_adc_exit(void)
+{
+	platform_driver_unregister(&pm8xxx_adc_driver);
+}
+module_exit(pm8xxx_adc_exit);
+
+MODULE_ALIAS("platform:" PM8XXX_ADC_DEV_NAME);
+MODULE_DESCRIPTION("PMIC8921/8018 ADC driver");
+MODULE_LICENSE("GPL v2");