| /* 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/mfd/pm8xxx/core.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 device *dev; | 
 | 	struct xoadc_platform_data *pdata; | 
 | 	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 = pm8xxx_writeb(adc_pmic->dev->parent, ADC_ARB_USRP_CNTRL, | 
 | 							data_arb_cntrl); | 
 | 		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 = 0, data_amux_chan = 0, data_arb_rsv = 0; | 
 | 	u8 data_dig_param = 0, data_ana_param2 = 0, data_ana_param = 0; | 
 | 	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 = pm8xxx_writeb(adc_pmic->dev->parent, | 
 | 			ADC_ARB_USRP_AMUX_CNTRL, data_amux_chan); | 
 | 	if (rc < 0) { | 
 | 		pr_debug("%s: PM8058 write failed\n", __func__); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	rc = pm8xxx_writeb(adc_pmic->dev->parent, | 
 | 			ADC_ARB_USRP_RSV, data_arb_rsv); | 
 | 	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 = pm8xxx_writeb(adc_pmic->dev->parent, | 
 | 				ADC_ARB_USRP_ANA_PARAM, data_ana_param); | 
 | 	if (rc < 0) { | 
 | 		pr_debug("%s: PM8058 write failed\n", __func__); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	rc = pm8xxx_writeb(adc_pmic->dev->parent, | 
 | 			ADC_ARB_USRP_DIG_PARAM, data_dig_param); | 
 | 	if (rc < 0) { | 
 | 		pr_debug("%s: PM8058 write failed\n", __func__); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	rc = pm8xxx_writeb(adc_pmic->dev->parent, | 
 | 			ADC_ARB_USRP_ANA_PARAM, data_ana_param2); | 
 | 	if (rc < 0) { | 
 | 		pr_debug("%s: PM8058 write failed\n", __func__); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	enable_irq(adc_pmic->adc_irq); | 
 |  | 
 | 	rc = pm8xxx_writeb(adc_pmic->dev->parent, | 
 | 				ADC_ARB_USRP_CNTRL, data_arb_cntrl); | 
 | 	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 = pm8xxx_readb(adc_pmic->dev->parent, ADC_ARB_USRP_DATA0, | 
 | 							&rslt_lsb); | 
 | 	if (rc < 0) { | 
 | 		pr_debug("%s: PM8058 read failed\n", __func__); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	rc = pm8xxx_readb(adc_pmic->dev->parent, ADC_ARB_USRP_DATA1, | 
 | 							&rslt_msb); | 
 | 	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, NULL); | 
 | 	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 pmic8058_adc *adc_pmic; | 
 | 	int i, rc = 0; | 
 |  | 
 | 	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->dev = &pdev->dev; | 
 | 	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"); |