/* Copyright (c) 2010-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/init.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/msm_adc.h>
#include <linux/pmic8058-xoadc.h>
#include <linux/mfd/pmic8058.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/ratelimit.h>
#include <linux/delay.h>
#include <linux/wakelock.h>

#include <mach/mpp.h>
#include <mach/msm_xo.h>

#define ADC_DRIVER_NAME			"pm8058-xoadc"

#define MAX_QUEUE_LENGTH        0X15
#define MAX_CHANNEL_PROPERTIES_QUEUE    0X7
#define MAX_QUEUE_SLOT		0x1

/* User Processor */
#define ADC_ARB_USRP_CNTRL                      0x197
	#define ADC_ARB_USRP_CNTRL_EN_ARB	BIT(0)
	#define ADC_ARB_USRP_CNTRL_RSV1		BIT(1)
	#define ADC_ARB_USRP_CNTRL_RSV2		BIT(2)
	#define ADC_ARB_USRP_CNTRL_RSV3		BIT(3)
	#define ADC_ARB_USRP_CNTRL_RSV4		BIT(4)
	#define ADC_ARB_USRP_CNTRL_RSV5		BIT(5)
	#define ADC_ARB_USRP_CNTRL_EOC		BIT(6)
	#define ADC_ARB_USRP_CNTRL_REQ		BIT(7)

#define ADC_ARB_USRP_AMUX_CNTRL         0x198
#define ADC_ARB_USRP_ANA_PARAM          0x199
#define ADC_ARB_USRP_DIG_PARAM          0x19A
#define ADC_ARB_USRP_RSV                        0x19B

#define ADC_ARB_USRP_DATA0                      0x19D
#define ADC_ARB_USRP_DATA1                      0x19C

struct pmic8058_adc {
	struct xoadc_platform_data *pdata;
	struct pm8058_chip *pm_chip;
	struct adc_properties *adc_prop;
	struct xoadc_conv_state	conv[2];
	int xoadc_queue_count;
	int adc_irq;
	struct linear_graph *adc_graph;
	struct xoadc_conv_state *conv_slot_request;
	struct xoadc_conv_state *conv_queue_list;
	struct adc_conv_slot conv_queue_elements[MAX_QUEUE_LENGTH];
	int xoadc_num;
	struct msm_xo_voter *adc_voter;
	struct wake_lock adc_wakelock;
	/* flag to warn/bug if wakelocks are taken after suspend_noirq */
	int msm_suspend_check;
};

static struct pmic8058_adc *pmic_adc[XOADC_PMIC_0 + 1];

static bool xoadc_initialized, xoadc_calib_first_adc;

DEFINE_RATELIMIT_STATE(pm8058_xoadc_msg_ratelimit,
		DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST);

static inline int pm8058_xoadc_can_print(void)
{
	return __ratelimit(&pm8058_xoadc_msg_ratelimit);
}

int32_t pm8058_xoadc_registered(void)
{
	return xoadc_initialized;
}
EXPORT_SYMBOL(pm8058_xoadc_registered);

void pm8058_xoadc_restore_slot(uint32_t adc_instance,
					struct adc_conv_slot *slot)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	struct xoadc_conv_state *slot_state = adc_pmic->conv_slot_request;

	mutex_lock(&slot_state->list_lock);
	list_add(&slot->list, &slot_state->slots);
	mutex_unlock(&slot_state->list_lock);
}
EXPORT_SYMBOL(pm8058_xoadc_restore_slot);

void pm8058_xoadc_slot_request(uint32_t adc_instance,
					struct adc_conv_slot **slot)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	struct xoadc_conv_state *slot_state = adc_pmic->conv_slot_request;

	mutex_lock(&slot_state->list_lock);

	if (!list_empty(&slot_state->slots)) {
		*slot = list_first_entry(&slot_state->slots,
				struct adc_conv_slot, list);
		list_del(&(*slot)->list);
	} else
		*slot = NULL;

	mutex_unlock(&slot_state->list_lock);
}
EXPORT_SYMBOL(pm8058_xoadc_slot_request);

static int32_t pm8058_xoadc_arb_cntrl(uint32_t arb_cntrl,
				uint32_t adc_instance, uint32_t channel)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	int i, rc;
	u8 data_arb_cntrl;

	data_arb_cntrl = ADC_ARB_USRP_CNTRL_EOC |
			ADC_ARB_USRP_CNTRL_RSV5 |
			ADC_ARB_USRP_CNTRL_RSV4;

	if (arb_cntrl) {
		if (adc_pmic->msm_suspend_check)
			pr_err("XOADC request being made after suspend irq "
				 "with channel id:%d\n", channel);
		data_arb_cntrl |= ADC_ARB_USRP_CNTRL_EN_ARB;
		msm_xo_mode_vote(adc_pmic->adc_voter, MSM_XO_MODE_ON);
		adc_pmic->pdata->xoadc_mpp_config();
		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 = pm8058_write(adc_pmic->pm_chip, ADC_ARB_USRP_CNTRL,
					&data_arb_cntrl, 1);
		if (rc < 0) {
			pr_debug("%s: PM8058 write failed\n", __func__);
			return rc;
		}
	}

	if (!arb_cntrl) {
		msm_xo_mode_vote(adc_pmic->adc_voter, MSM_XO_MODE_OFF);
		wake_unlock(&adc_pmic->adc_wakelock);
	}

	return 0;
}

static int32_t pm8058_xoadc_configure(uint32_t adc_instance,
					struct adc_conv_slot *slot)
{

	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	u8 data_arb_cntrl, data_amux_chan, data_arb_rsv, data_ana_param;
	u8 data_dig_param, data_ana_param2;
	int rc;

	rc = pm8058_xoadc_arb_cntrl(1, adc_instance, slot->chan_path);
	if (rc < 0) {
		pr_debug("%s: Configuring ADC Arbiter"
				"enable failed\n", __func__);
		return rc;
	}

	switch (slot->chan_path) {

	case CHAN_PATH_TYPE1:
		data_amux_chan = CHANNEL_VCOIN << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 2;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE2:
		data_amux_chan = CHANNEL_VBAT << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 3;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE3:
		data_amux_chan = CHANNEL_VCHG << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 10;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE4:
		data_amux_chan = CHANNEL_CHG_MONITOR << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE5:
		data_amux_chan = CHANNEL_VPH_PWR << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 3;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE6:
		data_amux_chan = CHANNEL_MPP5 << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[1];
		break;

	case CHAN_PATH_TYPE7:
		data_amux_chan = CHANNEL_MPP6 << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE8:
		data_amux_chan = CHANNEL_MPP7 << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 2;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE9:
		data_amux_chan = CHANNEL_MPP8 << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 2;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE10:
		data_amux_chan = CHANNEL_MPP9 << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 3;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE11:
		data_amux_chan = CHANNEL_USB_VBUS << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 3;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE12:
		data_amux_chan = CHANNEL_DIE_TEMP << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE13:
		data_amux_chan = CHANNEL_125V << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE14:
		data_amux_chan = CHANNEL_INTERNAL_2 << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;

	case CHAN_PATH_TYPE_NONE:
		data_amux_chan = CHANNEL_MUXOFF << 4;
		data_arb_rsv = 0x10;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[1];
		break;

	case CHAN_PATH_TYPE15:
		data_amux_chan = CHANNEL_INTERNAL << 4;
		data_arb_rsv = 0x20;
		slot->chan_properties.gain_numerator = 1;
		slot->chan_properties.gain_denominator = 1;
		slot->chan_properties.adc_graph = &adc_pmic->adc_graph[0];
		break;
	}

	rc = pm8058_write(adc_pmic->pm_chip,
			ADC_ARB_USRP_AMUX_CNTRL, &data_amux_chan, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 write failed\n", __func__);
		return rc;
	}

	rc = pm8058_write(adc_pmic->pm_chip,
			ADC_ARB_USRP_RSV, &data_arb_rsv, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 write failed\n", __func__);
		return rc;
	}

	/* Set default clock rate to 2.4 MHz XO ADC clock digital */
	switch (slot->chan_adc_config) {

	case ADC_CONFIG_TYPE1:
		data_ana_param = 0xFE;
		data_dig_param = 0x23;
		data_ana_param2 = 0xFF;
		/* AMUX register data to start the ADC conversion */
		data_arb_cntrl = 0xF1;
		break;

	case ADC_CONFIG_TYPE2:
		data_ana_param = 0xFE;
		data_dig_param = 0x03;
		data_ana_param2 = 0xFF;
		/* AMUX register data to start the ADC conversion */
		data_arb_cntrl = 0xF1;
		break;
	}

	rc = pm8058_write(adc_pmic->pm_chip,
				ADC_ARB_USRP_ANA_PARAM, &data_ana_param, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 write failed\n", __func__);
		return rc;
	}

	rc = pm8058_write(adc_pmic->pm_chip,
				ADC_ARB_USRP_DIG_PARAM, &data_dig_param, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 write failed\n", __func__);
		return rc;
	}

	rc = pm8058_write(adc_pmic->pm_chip,
				ADC_ARB_USRP_ANA_PARAM, &data_ana_param2, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 write failed\n", __func__);
		return rc;
	}

	enable_irq(adc_pmic->adc_irq);

	rc = pm8058_write(adc_pmic->pm_chip,
				ADC_ARB_USRP_CNTRL, &data_arb_cntrl, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 write failed\n", __func__);
		return rc;
	}

	return 0;
}

int32_t pm8058_xoadc_select_chan_and_start_conv(uint32_t adc_instance,
					struct adc_conv_slot *slot)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	struct xoadc_conv_state *slot_state = adc_pmic->conv_queue_list;

	if (!xoadc_initialized)
		return -ENODEV;

	mutex_lock(&slot_state->list_lock);
	list_add_tail(&slot->list, &slot_state->slots);
	if (adc_pmic->xoadc_queue_count == 0) {
		if (adc_pmic->pdata->xoadc_vreg_set != NULL)
			adc_pmic->pdata->xoadc_vreg_set(1);
		pm8058_xoadc_configure(adc_instance, slot);
	}
	adc_pmic->xoadc_queue_count++;
	mutex_unlock(&slot_state->list_lock);

	return 0;
}
EXPORT_SYMBOL(pm8058_xoadc_select_chan_and_start_conv);

static int32_t pm8058_xoadc_dequeue_slot_request(uint32_t adc_instance,
				struct adc_conv_slot **slot)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	struct xoadc_conv_state *slot_state = adc_pmic->conv_queue_list;
	int rc = 0;

	mutex_lock(&slot_state->list_lock);
	if (adc_pmic->xoadc_queue_count > 0 &&
			!list_empty(&slot_state->slots)) {
		*slot = list_first_entry(&slot_state->slots,
			struct adc_conv_slot, list);
		list_del(&(*slot)->list);
	} else
		rc = -EINVAL;
	mutex_unlock(&slot_state->list_lock);

	if (rc < 0) {
		if (pm8058_xoadc_can_print())
			pr_err("Pmic 8058 xoadc spurious interrupt detected\n");
		return rc;
	}

	return 0;
}

int32_t pm8058_xoadc_read_adc_code(uint32_t adc_instance, int32_t *data)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	struct xoadc_conv_state *slot_state = adc_pmic->conv_queue_list;
	uint8_t rslt_lsb, rslt_msb;
	struct adc_conv_slot *slot;
	int32_t rc, max_ideal_adc_code = 1 << adc_pmic->adc_prop->bitresolution;

	if (!xoadc_initialized)
		return -ENODEV;

	rc = pm8058_read(adc_pmic->pm_chip, ADC_ARB_USRP_DATA0, &rslt_lsb, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 read failed\n", __func__);
		return rc;
	}

	rc = pm8058_read(adc_pmic->pm_chip, ADC_ARB_USRP_DATA1, &rslt_msb, 1);
	if (rc < 0) {
		pr_debug("%s: PM8058 read failed\n", __func__);
		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;
	/* Return if this is a calibration run since there
	 * is no need to check requests in the waiting queue */
	if (xoadc_calib_first_adc)
		return 0;

	mutex_lock(&slot_state->list_lock);
	adc_pmic->xoadc_queue_count--;
	if (adc_pmic->xoadc_queue_count > 0) {
		slot = list_first_entry(&slot_state->slots,
				struct adc_conv_slot, list);
		pm8058_xoadc_configure(adc_instance, slot);
	}
	mutex_unlock(&slot_state->list_lock);

	mutex_lock(&slot_state->list_lock);
	/* Default value for switching off the arbiter after reading
	   the ADC value. Bit 0 set to 0. */
	if (adc_pmic->xoadc_queue_count == 0) {
		rc = pm8058_xoadc_arb_cntrl(0, adc_instance, CHANNEL_MUXOFF);
		if (rc < 0) {
			pr_debug("%s: Configuring ADC Arbiter disable"
						"failed\n", __func__);
			return rc;
		}
		if (adc_pmic->pdata->xoadc_vreg_set != NULL)
			adc_pmic->pdata->xoadc_vreg_set(0);
	}
	mutex_unlock(&slot_state->list_lock);

	return 0;
}
EXPORT_SYMBOL(pm8058_xoadc_read_adc_code);

static irqreturn_t pm8058_xoadc(int irq, void *dev_id)
{
	struct pmic8058_adc *xoadc_8058 = dev_id;
	struct adc_conv_slot *slot = NULL;
	int rc;

	disable_irq_nosync(xoadc_8058->adc_irq);

	if (xoadc_calib_first_adc)
		return IRQ_HANDLED;

	rc = pm8058_xoadc_dequeue_slot_request(xoadc_8058->xoadc_num, &slot);

	if (rc < 0)
		return IRQ_NONE;

	if (rc == 0)
		msm_adc_conv_cb(slot, 0, NULL, 0);

	return IRQ_HANDLED;
}

struct adc_properties *pm8058_xoadc_get_properties(uint32_t dev_instance)
{
	struct pmic8058_adc *xoadc_8058 = pmic_adc[dev_instance];

	return xoadc_8058->adc_prop;
}
EXPORT_SYMBOL(pm8058_xoadc_get_properties);

int32_t pm8058_xoadc_calib_device(uint32_t adc_instance)
{
	struct pmic8058_adc *adc_pmic = pmic_adc[adc_instance];
	struct adc_conv_slot *slot;
	int rc, offset_xoadc, slope_xoadc, calib_read_1, calib_read_2;

	if (adc_pmic->pdata->xoadc_vreg_set != NULL)
		adc_pmic->pdata->xoadc_vreg_set(1);

	pm8058_xoadc_slot_request(adc_instance, &slot);
	if (slot) {
		slot->chan_path = CHAN_PATH_TYPE13;
		slot->chan_adc_config = ADC_CONFIG_TYPE2;
		slot->chan_adc_calib = ADC_CONFIG_TYPE2;
		xoadc_calib_first_adc = true;
		rc = pm8058_xoadc_configure(adc_instance, slot);
		if (rc) {
			pr_err("pm8058_xoadc configure failed\n");
			goto fail;
		}
	} else {
		rc = -EINVAL;
		goto fail;
	}

	msleep(3);

	rc = pm8058_xoadc_read_adc_code(adc_instance, &calib_read_1);
	if (rc) {
		pr_err("pm8058_xoadc read adc failed\n");
		xoadc_calib_first_adc = false;
		goto fail;
	}
	xoadc_calib_first_adc = false;

	pm8058_xoadc_slot_request(adc_instance, &slot);
	if (slot) {
		slot->chan_path = CHAN_PATH_TYPE15;
		slot->chan_adc_config = ADC_CONFIG_TYPE2;
		slot->chan_adc_calib = ADC_CONFIG_TYPE2;
		xoadc_calib_first_adc = true;
		rc = pm8058_xoadc_configure(adc_instance, slot);
		if (rc) {
			pr_err("pm8058_xoadc configure failed\n");
			goto fail;
		}
	} else {
		rc = -EINVAL;
		goto fail;
	}

	msleep(3);

	rc = pm8058_xoadc_read_adc_code(adc_instance, &calib_read_2);
	if (rc) {
		pr_err("pm8058_xoadc read adc failed\n");
		xoadc_calib_first_adc = false;
		goto fail;
	}
	xoadc_calib_first_adc = false;

	pm8058_xoadc_restore_slot(adc_instance, slot);

	slope_xoadc = (((calib_read_1 - calib_read_2) << 10)/
					CHANNEL_ADC_625_MV);
	offset_xoadc = calib_read_2 -
			((slope_xoadc * CHANNEL_ADC_625_MV) >> 10);

	printk(KERN_INFO"pmic8058_xoadc:The offset for AMUX calibration"
						"was %d\n", offset_xoadc);

	adc_pmic->adc_graph[0].offset = offset_xoadc;
	adc_pmic->adc_graph[0].dy = (calib_read_1 - calib_read_2);
	adc_pmic->adc_graph[0].dx = CHANNEL_ADC_625_MV;

	/* Retain ideal calibration settings for therm readings */
	adc_pmic->adc_graph[1].offset = 0 ;
	adc_pmic->adc_graph[1].dy = (1 << 15) - 1;
	adc_pmic->adc_graph[1].dx = 2200;

	if (adc_pmic->pdata->xoadc_vreg_set != NULL)
		adc_pmic->pdata->xoadc_vreg_set(0);

	return 0;
fail:
	if (adc_pmic->pdata->xoadc_vreg_set != NULL)
		adc_pmic->pdata->xoadc_vreg_set(0);

	return rc;
}
EXPORT_SYMBOL(pm8058_xoadc_calib_device);

int32_t pm8058_xoadc_calibrate(uint32_t dev_instance,
				struct adc_conv_slot *slot, int *calib_status)
{
	*calib_status = CALIB_NOT_REQUIRED;

	return 0;
}
EXPORT_SYMBOL(pm8058_xoadc_calibrate);

#ifdef CONFIG_PM
static int pm8058_xoadc_suspend_noirq(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct pmic8058_adc *adc_pmic = platform_get_drvdata(pdev);

	adc_pmic->msm_suspend_check = 1;

	return 0;
}

static int pm8058_xoadc_resume_noirq(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct pmic8058_adc *adc_pmic = platform_get_drvdata(pdev);

	adc_pmic->msm_suspend_check = 0;

	return 0;
}

static const struct dev_pm_ops pm8058_xoadc_dev_pm_ops = {
	.suspend_noirq = pm8058_xoadc_suspend_noirq,
	.resume_noirq = pm8058_xoadc_resume_noirq,
};

#define PM8058_XOADC_DEV_PM_OPS	(&pm8058_xoadc_dev_pm_ops)
#else
#define PM8058_XOADC_DEV_PM_OPS NULL
#endif

static int __devexit pm8058_xoadc_teardown(struct platform_device *pdev)
{
	struct pmic8058_adc *adc_pmic = platform_get_drvdata(pdev);

	if (adc_pmic->pdata->xoadc_vreg_shutdown != NULL)
		adc_pmic->pdata->xoadc_vreg_shutdown();

	wake_lock_destroy(&adc_pmic->adc_wakelock);
	msm_xo_put(adc_pmic->adc_voter);
	platform_set_drvdata(pdev, adc_pmic->pm_chip);
	device_init_wakeup(&pdev->dev, 0);
	kfree(adc_pmic);
	xoadc_initialized = false;

	return 0;
}

static int __devinit pm8058_xoadc_probe(struct platform_device *pdev)
{
	struct xoadc_platform_data *pdata = pdev->dev.platform_data;
	struct pm8058_chip *pm_chip;
	struct pmic8058_adc *adc_pmic;
	int i, rc = 0;

	pm_chip = dev_get_drvdata(pdev->dev.parent);
	if (pm_chip == NULL) {
		dev_err(&pdev->dev, "no parent data passed in\n");
		return -EFAULT;
	}

	if (!pdata) {
		dev_err(&pdev->dev, "no platform data?\n");
		return -EINVAL;
	}

	adc_pmic = kzalloc(sizeof(struct pmic8058_adc), GFP_KERNEL);
	if (!adc_pmic) {
		dev_err(&pdev->dev, "Unable to allocate memory\n");
		return -ENOMEM;
	}

	adc_pmic->pm_chip = pm_chip;
	adc_pmic->adc_prop = pdata->xoadc_prop;
	adc_pmic->xoadc_num = pdata->xoadc_num;
	adc_pmic->xoadc_queue_count = 0;

	platform_set_drvdata(pdev, adc_pmic);

	if (adc_pmic->xoadc_num > XOADC_PMIC_0) {
		dev_err(&pdev->dev, "ADC device not supported\n");
		rc = -EINVAL;
		goto err_cleanup;
	}

	adc_pmic->pdata = pdata;
	adc_pmic->adc_graph = kzalloc(sizeof(struct linear_graph)
			* MAX_CHANNEL_PROPERTIES_QUEUE, GFP_KERNEL);
	if (!adc_pmic->adc_graph) {
		dev_err(&pdev->dev, "Unable to allocate memory\n");
		rc = -ENOMEM;
		goto err_cleanup;
	}

	/* Will be replaced by individual channel calibration */
	for (i = 0; i < MAX_CHANNEL_PROPERTIES_QUEUE; i++) {
		adc_pmic->adc_graph[i].offset = 0 ;
		adc_pmic->adc_graph[i].dy = (1 << 15) - 1;
		adc_pmic->adc_graph[i].dx = 2200;
	}

	if (pdata->xoadc_mpp_config != NULL)
		pdata->xoadc_mpp_config();

	adc_pmic->conv_slot_request = &adc_pmic->conv[0];
	adc_pmic->conv_slot_request->context =
		&adc_pmic->conv_queue_elements[0];

	mutex_init(&adc_pmic->conv_slot_request->list_lock);
	INIT_LIST_HEAD(&adc_pmic->conv_slot_request->slots);

	/* tie each slot and initwork them */
	for (i = 0; i < MAX_QUEUE_LENGTH; i++) {
		list_add(&adc_pmic->conv_slot_request->context[i].list,
					&adc_pmic->conv_slot_request->slots);
		INIT_WORK(&adc_pmic->conv_slot_request->context[i].work,
							msm_adc_wq_work);
		init_completion(&adc_pmic->conv_slot_request->context[i].comp);
		adc_pmic->conv_slot_request->context[i].idx = i;
	}

	adc_pmic->conv_queue_list = &adc_pmic->conv[1];

	mutex_init(&adc_pmic->conv_queue_list->list_lock);
	INIT_LIST_HEAD(&adc_pmic->conv_queue_list->slots);

	adc_pmic->adc_irq = platform_get_irq(pdev, 0);
	if (adc_pmic->adc_irq < 0) {
		rc = -ENXIO;
		goto err_cleanup;
	}

	rc = request_threaded_irq(adc_pmic->adc_irq,
				NULL, pm8058_xoadc,
		IRQF_TRIGGER_RISING, "pm8058_adc_interrupt", adc_pmic);
	if (rc) {
		dev_err(&pdev->dev, "failed to request adc irq\n");
		goto err_cleanup;
	}

	disable_irq(adc_pmic->adc_irq);

	device_init_wakeup(&pdev->dev, pdata->xoadc_wakeup);

	if (adc_pmic->adc_voter == NULL) {
		adc_pmic->adc_voter = msm_xo_get(MSM_XO_TCXO_D1,
							"pmic8058_xoadc");
		if (IS_ERR(adc_pmic->adc_voter)) {
			dev_err(&pdev->dev, "Failed to get XO vote\n");
			goto err_cleanup;
		}
	}

	wake_lock_init(&adc_pmic->adc_wakelock, WAKE_LOCK_SUSPEND,
					"pmic8058_xoadc_wakelock");

	pmic_adc[adc_pmic->xoadc_num] = adc_pmic;

	if (pdata->xoadc_vreg_setup != NULL)
		pdata->xoadc_vreg_setup();

	xoadc_initialized = true;
	xoadc_calib_first_adc = false;

	return 0;

err_cleanup:
	pm8058_xoadc_teardown(pdev);

	return rc;
}

static struct platform_driver pm8058_xoadc_driver = {
	.probe = pm8058_xoadc_probe,
	.remove = __devexit_p(pm8058_xoadc_teardown),
	.driver = {
		.name = "pm8058-xoadc",
		.owner = THIS_MODULE,
		.pm = PM8058_XOADC_DEV_PM_OPS,
	},
};

static int __init pm8058_xoadc_init(void)
{
	return platform_driver_register(&pm8058_xoadc_driver);
}
module_init(pm8058_xoadc_init);

static void __exit pm8058_xoadc_exit(void)
{
	platform_driver_unregister(&pm8058_xoadc_driver);
}
module_exit(pm8058_xoadc_exit);

MODULE_ALIAS("platform:pmic8058_xoadc");
MODULE_DESCRIPTION("PMIC8058 XOADC driver");
MODULE_LICENSE("GPL v2");
