| /* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 and | 
 |  * only version 2 as published by the Free Software Foundation. | 
 |  * | 
 |  * This program is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  * GNU General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <linux/kernel.h> | 
 | #include <linux/init.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/err.h> | 
 | #include <linux/hwmon.h> | 
 | #include <linux/hwmon-sysfs.h> | 
 | #include <linux/miscdevice.h> | 
 | #include <linux/fs.h> | 
 | #include <linux/sched.h> | 
 | #include <linux/wait.h> | 
 | #include <linux/uaccess.h> | 
 | #include <linux/msm_adc.h> | 
 | #include <linux/pmic8058-xoadc.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/semaphore.h> | 
 | #include <linux/module.h> | 
 |  | 
 | #include <mach/dal.h> | 
 |  | 
 | #define MSM_ADC_DRIVER_NAME		"msm_adc" | 
 | #define MSM_ADC_MAX_FNAME		15 | 
 |  | 
 | #define MSM_ADC_DALRPC_DEVICEID		0x02000067 | 
 | #define MSM_ADC_DALRPC_PORT_NAME	"DAL00" | 
 | #define MSM_ADC_DALRPC_CPU		SMD_APPS_MODEM | 
 |  | 
 | #define MSM_ADC_DALRPC_CMD_REQ_CONV	9 | 
 | #define MSM_ADC_DALRPC_CMD_INPUT_PROP	11 | 
 |  | 
 | #define MSM_ADC_DALRC_CONV_TIMEOUT	(5 * HZ)  /* 5 seconds */ | 
 |  | 
 | #define MSM_8x25_ADC_DEV_ID		0 | 
 | #define MSM_8x25_CHAN_ID		16 | 
 |  | 
 | enum dal_error { | 
 | 	DAL_ERROR_INVALID_DEVICE_IDX = 1, | 
 | 	DAL_ERROR_INVALID_CHANNEL_IDX, | 
 | 	DAL_ERROR_NULL_POINTER, | 
 | 	DAL_ERROR_DEVICE_QUEUE_FULL, | 
 | 	DAL_ERROR_INVALID_PROPERTY_LENGTH, | 
 | 	DAL_ERROR_REMOTE_EVENT_POOL_FULL | 
 | }; | 
 |  | 
 | enum dal_result_status { | 
 | 	DAL_RESULT_STATUS_INVALID, | 
 | 	DAL_RESULT_STATUS_VALID | 
 | }; | 
 |  | 
 | struct dal_conv_state { | 
 | 	struct dal_conv_slot		context[MSM_ADC_DEV_MAX_INFLIGHT]; | 
 | 	struct list_head		slots; | 
 | 	struct mutex			list_lock; | 
 | 	struct semaphore		slot_count; | 
 | }; | 
 |  | 
 | struct adc_dev { | 
 | 	char				*name; | 
 | 	uint32_t			nchans; | 
 | 	struct dal_conv_state		conv; | 
 | 	struct dal_translation		transl; | 
 | 	struct sensor_device_attribute	*sens_attr; | 
 | 	char				**fnames; | 
 | }; | 
 |  | 
 | struct msm_adc_drv { | 
 | 	/*  Common to both XOADC and EPM  */ | 
 | 	struct platform_device		*pdev; | 
 | 	struct device			*hwmon; | 
 | 	struct miscdevice		misc; | 
 | 	/*  XOADC variables  */ | 
 | 	struct sensor_device_attribute	*sens_attr; | 
 | 	struct workqueue_struct		*wq; | 
 | 	atomic_t			online; | 
 | 	atomic_t			total_outst; | 
 | 	wait_queue_head_t		total_outst_wait; | 
 |  | 
 | 	/*  EPM variables  */ | 
 | 	void				*dev_h; | 
 | 	struct adc_dev			*devs[MSM_ADC_MAX_NUM_DEVS]; | 
 | 	struct mutex			prop_lock; | 
 | 	atomic_t			rpc_online; | 
 | 	atomic_t			rpc_total_outst; | 
 | 	wait_queue_head_t		rpc_total_outst_wait; | 
 | }; | 
 |  | 
 | static bool epm_init; | 
 | static bool epm_fluid_enabled; | 
 |  | 
 | /* Needed to support file_op interfaces */ | 
 | static struct msm_adc_drv *msm_adc_drv; | 
 |  | 
 | static bool conv_first_request; | 
 |  | 
 | static ssize_t msm_adc_show_curr(struct device *dev, | 
 | 				struct device_attribute *devattr, char *buf); | 
 |  | 
 | static int msm_rpc_adc_blocking_conversion(struct msm_adc_drv *msm_adc, | 
 | 				uint32_t chan, struct adc_chan_result *result); | 
 |  | 
 | static int msm_adc_blocking_conversion(struct msm_adc_drv *msm_adc, | 
 | 				uint32_t chan, struct adc_chan_result *result); | 
 |  | 
 | static int msm_adc_open(struct inode *inode, struct file *file) | 
 | { | 
 | 	struct msm_client_data *client; | 
 | 	struct msm_adc_drv *msm_adc = msm_adc_drv; | 
 | 	struct platform_device *pdev = msm_adc->pdev; | 
 |  | 
 | 	client = kzalloc(sizeof(struct msm_client_data), GFP_KERNEL); | 
 | 	if (!client) { | 
 | 		dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	if (!try_module_get(THIS_MODULE)) { | 
 | 		kfree(client); | 
 | 		return -EACCES; | 
 | 	} | 
 |  | 
 | 	mutex_init(&client->lock); | 
 | 	INIT_LIST_HEAD(&client->complete_list); | 
 | 	init_waitqueue_head(&client->data_wait); | 
 | 	init_waitqueue_head(&client->outst_wait); | 
 |  | 
 | 	client->online = 1; | 
 |  | 
 | 	file->private_data = client; | 
 |  | 
 | 	return nonseekable_open(inode, file); | 
 | } | 
 |  | 
 | static inline void msm_adc_restore_slot(struct dal_conv_state *conv_s, | 
 | 					struct dal_conv_slot *slot) | 
 | { | 
 | 	mutex_lock(&conv_s->list_lock); | 
 | 	list_add(&slot->list, &conv_s->slots); | 
 | 	mutex_unlock(&conv_s->list_lock); | 
 |  | 
 | 	up(&conv_s->slot_count); | 
 | } | 
 |  | 
 | static int no_pending_client_requests(struct msm_client_data *client) | 
 | { | 
 | 	mutex_lock(&client->lock); | 
 |  | 
 | 	if (client->num_outstanding == 0) { | 
 | 		mutex_unlock(&client->lock); | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&client->lock); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int data_avail(struct msm_client_data *client, uint32_t *pending) | 
 | { | 
 | 	uint32_t completed; | 
 |  | 
 | 	mutex_lock(&client->lock); | 
 | 	completed = client->num_complete; | 
 | 	mutex_unlock(&client->lock); | 
 |  | 
 | 	if (completed > 0) { | 
 | 		if (pending != NULL) | 
 | 			*pending = completed; | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int msm_adc_release(struct inode *inode, struct file *file) | 
 | { | 
 | 	struct msm_client_data *client = file->private_data; | 
 | 	struct adc_conv_slot *slot, *tmp; | 
 | 	int rc; | 
 | 	struct msm_adc_platform_data *pdata = | 
 | 					msm_adc_drv->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = pdata->channel; | 
 |  | 
 | 	module_put(THIS_MODULE); | 
 |  | 
 | 	mutex_lock(&client->lock); | 
 |  | 
 | 	/* prevent any further requests while we teardown the client */ | 
 | 	client->online = 0; | 
 |  | 
 | 	mutex_unlock(&client->lock); | 
 |  | 
 | 	/* | 
 | 	 * We may still have outstanding transactions in flight from this | 
 | 	 * client that have not completed. Make sure they're completed | 
 | 	 * before removing the client. | 
 | 	 */ | 
 | 	rc = wait_event_interruptible(client->outst_wait, | 
 | 				      no_pending_client_requests(client)); | 
 | 	if (rc) { | 
 | 		pr_err("%s: wait_event_interruptible failed rc = %d\n", | 
 | 								__func__, rc); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * All transactions have completed. Add slot resources back to the | 
 | 	 * appropriate devices. | 
 | 	 */ | 
 | 	list_for_each_entry_safe(slot, tmp, &client->complete_list, list) { | 
 | 		slot->client = NULL; | 
 | 		list_del(&slot->list); | 
 | 		channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot( | 
 | 		channel[slot->conv.result.chan].adc_dev_instance, slot); | 
 | 	} | 
 |  | 
 | 	kfree(client); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int msm_adc_translate_dal_to_hwmon(struct msm_adc_drv *msm_adc, | 
 | 					  uint32_t chan, | 
 | 					  struct adc_dev_spec *dest) | 
 | { | 
 | 	struct dal_translation *transl; | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < pdata->num_adc; i++) { | 
 | 		transl = &msm_adc->devs[i]->transl; | 
 | 		if (chan >= transl->hwmon_start && | 
 | 		    chan <= transl->hwmon_end) { | 
 | 			dest->dal.dev_idx = transl->dal_dev_idx; | 
 | 			dest->hwmon_dev_idx = transl->hwmon_dev_idx; | 
 | 			dest->dal.chan_idx = chan - transl->hwmon_start; | 
 | 			return 0; | 
 | 		} | 
 | 	} | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static int msm_adc_translate_hwmon_to_dal(struct msm_adc_drv *msm_adc, | 
 | 					  struct adc_dev_spec *source, | 
 | 					  uint32_t *chan) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 | 	struct dal_translation *transl; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < pdata->num_adc; i++) { | 
 | 		transl = &msm_adc->devs[i]->transl; | 
 | 		if (source->dal.dev_idx != transl->dal_dev_idx) | 
 | 			continue; | 
 | 		*chan = transl->hwmon_start + source->dal.chan_idx; | 
 | 		return 0; | 
 | 	} | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static int msm_adc_getinputproperties(struct msm_adc_drv *msm_adc, | 
 | 					  const char *lookup_name, | 
 | 					  struct adc_dev_spec *result) | 
 | { | 
 | 	struct device *dev = &msm_adc->pdev->dev; | 
 | 	int rc; | 
 |  | 
 | 	mutex_lock(&msm_adc->prop_lock); | 
 |  | 
 | 	rc = dalrpc_fcn_8(MSM_ADC_DALRPC_CMD_INPUT_PROP, msm_adc->dev_h, | 
 | 			  lookup_name, strlen(lookup_name) + 1, | 
 | 			  &result->dal, sizeof(struct dal_dev_spec)); | 
 | 	if (rc) { | 
 | 		dev_err(dev, "DAL getprop request failed: rc = %d\n", rc); | 
 | 		mutex_unlock(&msm_adc->prop_lock); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&msm_adc->prop_lock); | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int msm_adc_lookup(struct msm_adc_drv *msm_adc, | 
 | 			  struct msm_adc_lookup *lookup) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 | 	struct adc_dev_spec target; | 
 | 	int rc = 0, i = 0; | 
 | 	uint32_t len = 0; | 
 |  | 
 | 	len = strnlen(lookup->name, MSM_ADC_MAX_CHAN_STR); | 
 | 	while (i < pdata->num_chan_supported) { | 
 | 		if (strncmp(lookup->name, pdata->channel[i].name, len)) | 
 | 			i++; | 
 | 		else | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	if (pdata->num_chan_supported > 0 && i < pdata->num_chan_supported) { | 
 | 		lookup->chan_idx = i; | 
 | 	} else if (msm_adc->dev_h) { | 
 | 		rc = msm_adc_getinputproperties(msm_adc, lookup->name, &target); | 
 | 		if (rc) { | 
 | 			pr_err("%s: Lookup failed for %s\n", __func__, | 
 | 				lookup->name); | 
 | 			return rc; | 
 | 		} | 
 | 		rc = msm_adc_translate_hwmon_to_dal(msm_adc, &target, | 
 | 						&lookup->chan_idx); | 
 | 		if (rc) | 
 | 			pr_err("%s: Translation failed for %s\n", __func__, | 
 | 						lookup->name); | 
 | 	} else { | 
 | 		pr_err("%s: Lookup failed for %s\n", __func__, lookup->name); | 
 | 		rc = -EINVAL; | 
 | 	} | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int msm_adc_aio_conversion(struct msm_adc_drv *msm_adc, | 
 | 				  struct adc_chan_result *request, | 
 | 				  struct msm_client_data *client) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = | 
 | 					msm_adc_drv->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = &pdata->channel[request->chan]; | 
 | 	struct adc_conv_slot *slot; | 
 |  | 
 | 	/* we could block here, but only for a bounded time */ | 
 | 	channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, | 
 | 									&slot); | 
 |  | 
 | 	if (slot) { | 
 | 		atomic_inc(&msm_adc->total_outst); | 
 | 		mutex_lock(&client->lock); | 
 | 		client->num_outstanding++; | 
 | 		mutex_unlock(&client->lock); | 
 |  | 
 | 		/* indicates non blocking request to callback handler */ | 
 | 		slot->blocking = 0; | 
 | 		slot->compk = NULL;/*For kernel space usage; n/a for usr space*/ | 
 | 		slot->conv.result.chan = client->adc_chan = request->chan; | 
 | 		slot->client = client; | 
 | 		slot->adc_request = START_OF_CONV; | 
 | 		slot->chan_path = channel->chan_path_type; | 
 | 		slot->chan_adc_config = channel->adc_config_type; | 
 | 		slot->chan_adc_calib = channel->adc_calib_type; | 
 | 		queue_work(msm_adc->wq, &slot->work); | 
 | 		return 0; | 
 | 	} | 
 | 	return -EBUSY; | 
 | } | 
 |  | 
 | static int msm_adc_fluid_hw_deinit(struct msm_adc_drv *msm_adc) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 |  | 
 | 	if (!epm_init) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (pdata->gpio_config == APROC_CONFIG && | 
 | 		epm_fluid_enabled && pdata->adc_fluid_disable != NULL) { | 
 | 		pdata->adc_fluid_disable(); | 
 | 		epm_fluid_enabled = false; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int msm_adc_fluid_hw_init(struct msm_adc_drv *msm_adc) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 |  | 
 | 	if (!epm_init) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!pdata->adc_fluid_enable) | 
 | 		return -ENODEV; | 
 |  | 
 | 	printk(KERN_DEBUG "msm_adc_fluid_hw_init: Calling adc_fluid_enable.\n"); | 
 |  | 
 | 	if (pdata->gpio_config == APROC_CONFIG && !epm_fluid_enabled) { | 
 | 		pdata->adc_fluid_enable(); | 
 | 		epm_fluid_enabled = true; | 
 | 	} | 
 |  | 
 |   /* return success for now but check for errors from hw init configuration */ | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int msm_adc_poll_complete(struct msm_adc_drv *msm_adc, | 
 | 			     struct msm_client_data *client, uint32_t *pending) | 
 | { | 
 | 	int rc; | 
 |  | 
 | 	/* | 
 | 	 * Don't proceed if there there's nothing queued on this client. | 
 | 	 * We could deadlock otherwise in a single threaded scenario. | 
 | 	 */ | 
 | 	if (no_pending_client_requests(client) && !data_avail(client, pending)) | 
 | 		return -EDEADLK; | 
 |  | 
 | 	rc = wait_event_interruptible(client->data_wait, | 
 | 				data_avail(client, pending)); | 
 | 	if (rc) | 
 | 		return rc; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int msm_adc_read_result(struct msm_adc_drv *msm_adc, | 
 | 			       struct msm_client_data *client, | 
 | 			       struct adc_chan_result *result) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = pdata->channel; | 
 | 	struct adc_conv_slot *slot; | 
 | 	int rc = 0; | 
 |  | 
 | 	mutex_lock(&client->lock); | 
 |  | 
 | 	slot = list_first_entry(&client->complete_list, | 
 | 				struct adc_conv_slot, list); | 
 | 	if (!slot) { | 
 | 		mutex_unlock(&client->lock); | 
 | 		return -ENOMSG; | 
 | 	} | 
 |  | 
 | 	slot->client = NULL; | 
 | 	list_del(&slot->list); | 
 |  | 
 | 	client->num_complete--; | 
 |  | 
 | 	mutex_unlock(&client->lock); | 
 |  | 
 | 	*result = slot->conv.result; | 
 |  | 
 | 	/* restore this slot to reserve */ | 
 | 	channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot( | 
 | 		channel[slot->conv.result.chan].adc_dev_instance, slot); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static long msm_adc_ioctl(struct file *file, unsigned int cmd, | 
 | 					     unsigned long arg) | 
 | { | 
 | 	struct msm_client_data *client = file->private_data; | 
 | 	struct msm_adc_drv *msm_adc = msm_adc_drv; | 
 | 	struct platform_device *pdev = msm_adc->pdev; | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	uint32_t block_res = 0; | 
 |  | 
 | 	int rc; | 
 |  | 
 | 	switch (cmd) { | 
 | 	case MSM_ADC_REQUEST: | 
 | 		{ | 
 | 			struct adc_chan_result conv; | 
 |  | 
 | 			if (copy_from_user(&conv, (void __user *)arg, | 
 | 					sizeof(struct adc_chan_result))) | 
 | 				return -EFAULT; | 
 |  | 
 | 			if (conv.chan < pdata->num_chan_supported) { | 
 | 				rc = msm_adc_blocking_conversion(msm_adc, | 
 | 							conv.chan, &conv); | 
 | 			} else { | 
 | 				if (!msm_adc->dev_h) | 
 | 					return -EAGAIN; | 
 |  | 
 | 				rc = msm_rpc_adc_blocking_conversion(msm_adc, | 
 | 							conv.chan, &conv); | 
 | 			} | 
 | 			if (rc) { | 
 | 				dev_dbg(&pdev->dev, "BLK conversion failed\n"); | 
 | 				return rc; | 
 | 			} | 
 |  | 
 | 			if (copy_to_user((void __user *)arg, &conv, | 
 | 					sizeof(struct adc_chan_result))) | 
 | 				return -EFAULT; | 
 | 			break; | 
 | 		} | 
 | 	case MSM_ADC_AIO_REQUEST_BLOCK_RES: | 
 | 		block_res = 1; | 
 | 	case MSM_ADC_AIO_REQUEST: | 
 | 		{ | 
 | 			struct adc_chan_result conv; | 
 |  | 
 | 			if (copy_from_user(&conv, (void __user *)arg, | 
 | 					sizeof(struct adc_chan_result))) | 
 | 				return -EFAULT; | 
 |  | 
 | 			if (conv.chan >= pdata->num_chan_supported) | 
 | 				return -EINVAL; | 
 |  | 
 | 			rc = msm_adc_aio_conversion(msm_adc, &conv, client); | 
 | 			if (rc) { | 
 | 				dev_dbg(&pdev->dev, "AIO conversion failed\n"); | 
 | 				return rc; | 
 | 			} | 
 | 			if (copy_to_user((void __user *)arg, &conv, | 
 | 					sizeof(struct adc_chan_result))) | 
 | 				return -EFAULT; | 
 | 			break; | 
 | 		} | 
 | 	case MSM_ADC_AIO_POLL: | 
 | 		{ | 
 | 			uint32_t completed; | 
 |  | 
 | 			rc = msm_adc_poll_complete(msm_adc, client, &completed); | 
 | 			if (rc) { | 
 | 				dev_dbg(&pdev->dev, "poll request failed\n"); | 
 | 				return rc; | 
 | 			} | 
 |  | 
 | 			if (copy_to_user((void __user *)arg, &completed, | 
 | 					sizeof(uint32_t))) | 
 | 				return -EFAULT; | 
 |  | 
 | 			break; | 
 | 		} | 
 | 	case MSM_ADC_AIO_READ: | 
 | 		{ | 
 | 			struct adc_chan_result result; | 
 |  | 
 | 			rc = msm_adc_read_result(msm_adc, client, &result); | 
 | 			if (rc) { | 
 | 				dev_dbg(&pdev->dev, "read result failed\n"); | 
 | 				return rc; | 
 | 			} | 
 |  | 
 | 			if (copy_to_user((void __user *)arg, &result, | 
 | 					sizeof(struct adc_chan_result))) | 
 | 				return -EFAULT; | 
 | 			break; | 
 | 		} | 
 | 	case MSM_ADC_LOOKUP: | 
 | 		{ | 
 | 			struct msm_adc_lookup lookup; | 
 |  | 
 | 			if (copy_from_user(&lookup, (void __user *)arg, | 
 | 					sizeof(struct msm_adc_lookup))) | 
 | 				return -EFAULT; | 
 |  | 
 | 			rc = msm_adc_lookup(msm_adc, &lookup); | 
 | 			if (rc) { | 
 | 				dev_dbg(&pdev->dev, "No such channel: %s\n", | 
 | 						lookup.name); | 
 | 				return rc; | 
 | 			} | 
 |  | 
 | 			if (copy_to_user((void __user *)arg, &lookup, | 
 | 					sizeof(struct msm_adc_lookup))) | 
 | 				return -EFAULT; | 
 | 			break; | 
 | 		} | 
 | 	case MSM_ADC_FLUID_INIT: | 
 | 		{ | 
 | 			uint32_t result; | 
 |  | 
 | 			result = msm_adc_fluid_hw_init(msm_adc); | 
 |  | 
 | 			if (copy_to_user((void __user *)arg, &result, | 
 | 						sizeof(uint32_t)))	{ | 
 | 				printk(KERN_ERR "MSM_ADC_FLUID_INIT: " | 
 | 					"copy_to_user returned an error.\n"); | 
 | 				return -EFAULT; | 
 | 			} | 
 | 			printk(KERN_DEBUG "MSM_ADC_FLUID_INIT: Success.\n"); | 
 | 			break; | 
 | 		} | 
 | 	case MSM_ADC_FLUID_DEINIT: | 
 | 		{ | 
 | 			uint32_t result; | 
 |  | 
 | 			result = msm_adc_fluid_hw_deinit(msm_adc); | 
 |  | 
 | 			if (copy_to_user((void __user *)arg, &result, | 
 | 						sizeof(uint32_t))) | 
 | 				return -EFAULT; | 
 | 			break; | 
 | 		} | 
 | 	default: | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | const struct file_operations msm_adc_fops = { | 
 | 	.open = msm_adc_open, | 
 | 	.release = msm_adc_release, | 
 | 	.unlocked_ioctl = msm_adc_ioctl, | 
 | }; | 
 |  | 
 | static ssize_t msm_adc_show_curr(struct device *dev, | 
 | 				 struct device_attribute *devattr, char *buf) | 
 | { | 
 | 	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | 
 | 	struct msm_adc_drv *msm_adc = dev_get_drvdata(dev); | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 | 	struct adc_chan_result result; | 
 | 	int rc; | 
 |  | 
 | #ifdef CONFIG_PMIC8058_XOADC | 
 | 	rc = pm8058_xoadc_registered(); | 
 | 	if (rc <= 0) | 
 | 		return -ENODEV; | 
 | #endif | 
 | 	if (attr->index < pdata->num_chan_supported) { | 
 | 		rc = msm_adc_blocking_conversion(msm_adc, | 
 | 					attr->index, &result); | 
 | 	} else { | 
 | 		if (pdata->gpio_config == APROC_CONFIG && !epm_fluid_enabled | 
 | 					&& pdata->adc_fluid_enable != NULL) { | 
 | 			printk(KERN_DEBUG "This is to read ADC value for " | 
 | 				"Fluid EPM and init. Do it only once.\n"); | 
 | 			pdata->adc_fluid_enable(); | 
 | 			epm_fluid_enabled = true; | 
 | 		} | 
 | 		rc = msm_rpc_adc_blocking_conversion(msm_adc, | 
 | 					attr->index, &result); | 
 | 	} | 
 | 	if (rc) | 
 | 		return 0; | 
 |  | 
 | 	return sprintf(buf, "Result: %lld Raw: %d\n", result.physical, | 
 | 		result.adc_code); | 
 | } | 
 |  | 
 | static int msm_rpc_adc_blocking_conversion(struct msm_adc_drv *msm_adc, | 
 | 		uint32_t hwmon_chan, struct adc_chan_result *result) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data; | 
 | 	struct dal_conv_request params; | 
 | 	struct device *dev = &msm_adc->pdev->dev; | 
 | 	struct adc_dev *adc_dev; | 
 | 	struct dal_conv_state *conv_s; | 
 | 	struct dal_conv_slot *slot; | 
 | 	struct adc_dev_spec dest; | 
 | 	int timeout, rc = 0; | 
 |  | 
 | 	if (pdata->gpio_config == APROC_CONFIG && | 
 | 			pdata->adc_gpio_enable != NULL) | 
 | 		pdata->adc_gpio_enable(hwmon_chan-pdata->num_chan_supported); | 
 |  | 
 | 	rc = msm_adc_translate_dal_to_hwmon(msm_adc, hwmon_chan, &dest); | 
 | 	if (rc) { | 
 | 		dev_err(dev, "%s: translation from chan %u failed\n", | 
 | 							__func__, hwmon_chan); | 
 | 		if (pdata->gpio_config == APROC_CONFIG && | 
 | 				pdata->adc_gpio_disable != NULL) | 
 | 			pdata->adc_gpio_disable(hwmon_chan | 
 | 					-pdata->num_chan_supported); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	adc_dev = msm_adc->devs[dest.hwmon_dev_idx]; | 
 | 	conv_s = &adc_dev->conv; | 
 |  | 
 | 	down(&conv_s->slot_count); | 
 |  | 
 | 	mutex_lock(&conv_s->list_lock); | 
 |  | 
 | 	slot = list_first_entry(&conv_s->slots, struct dal_conv_slot, list); | 
 | 	list_del(&slot->list); | 
 | 	BUG_ON(!slot); | 
 |  | 
 | 	mutex_unlock(&conv_s->list_lock); | 
 |  | 
 | 	/* indicates blocking request to callback handler */ | 
 | 	slot->blocking = 1; | 
 |  | 
 | 	params.target.dev_idx = dest.dal.dev_idx; | 
 | 	params.target.chan_idx = dest.dal.chan_idx; | 
 | 	params.cb_h = slot->cb_h; | 
 |  | 
 | 	rc = dalrpc_fcn_8(MSM_ADC_DALRPC_CMD_REQ_CONV, msm_adc->dev_h, | 
 | 			¶ms, sizeof(params), NULL, 0); | 
 | 	if (rc) { | 
 | 		dev_err(dev, "%s: Conversion for device = %u channel = %u" | 
 | 			     " failed\n", __func__, params.target.dev_idx, | 
 | 						    params.target.chan_idx); | 
 |  | 
 | 		rc = -EIO; | 
 | 		goto blk_conv_err; | 
 | 	} | 
 |  | 
 | 	timeout = wait_for_completion_interruptible_timeout(&slot->comp, | 
 | 					      MSM_ADC_DALRC_CONV_TIMEOUT); | 
 | 	if (timeout == 0) { | 
 | 		dev_err(dev, "read for device = %u channel = %u timed out\n", | 
 | 				params.target.dev_idx, params.target.chan_idx); | 
 | 		rc = -ETIMEDOUT; | 
 | 		goto blk_conv_err; | 
 | 	} else if (timeout < 0) { | 
 | 		rc = -EINTR; | 
 | 		goto blk_conv_err; | 
 | 	} | 
 |  | 
 | 	result->physical = (int64_t)slot->result.physical; | 
 |  | 
 | 	if (slot->result.status == DAL_RESULT_STATUS_INVALID) | 
 | 		rc = -ENODATA; | 
 |  | 
 | blk_conv_err: | 
 | 	if (pdata->gpio_config == APROC_CONFIG && | 
 | 			pdata->adc_gpio_disable != NULL) | 
 | 		pdata->adc_gpio_disable(hwmon_chan-pdata->num_chan_supported); | 
 | 	msm_adc_restore_slot(conv_s, slot); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int msm_adc_blocking_conversion(struct msm_adc_drv *msm_adc, | 
 | 			uint32_t hwmon_chan, struct adc_chan_result *result) | 
 | { | 
 | 	struct adc_conv_slot *slot; | 
 | 	struct msm_adc_platform_data *pdata = | 
 | 					msm_adc_drv->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = &pdata->channel[hwmon_chan]; | 
 | 	int ret = 0; | 
 |  | 
 | 	if (conv_first_request) { | 
 | 		ret = pm8058_xoadc_calib_device(channel->adc_dev_instance); | 
 | 		if (ret) { | 
 | 			pr_err("pmic8058 xoadc calibration failed, retry\n"); | 
 | 			return ret; | 
 | 		} | 
 | 		conv_first_request = false; | 
 | 	} | 
 |  | 
 | 	channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, | 
 | 									&slot); | 
 | 	if (slot) { | 
 | 		slot->conv.result.chan = hwmon_chan; | 
 | 		/* indicates blocking request to callback handler */ | 
 | 		slot->blocking = 1; | 
 | 		slot->adc_request = START_OF_CONV; | 
 | 		slot->chan_path = channel->chan_path_type; | 
 | 		slot->chan_adc_config = channel->adc_config_type; | 
 | 		slot->chan_adc_calib = channel->adc_calib_type; | 
 | 		queue_work(msm_adc_drv->wq, &slot->work); | 
 |  | 
 | 		wait_for_completion_interruptible(&slot->comp); | 
 | 		*result = slot->conv.result; | 
 | 		channel->adc_access_fn->adc_restore_slot( | 
 | 					channel->adc_dev_instance, slot); | 
 | 		return 0; | 
 | 	} | 
 | 	return -EBUSY; | 
 | } | 
 |  | 
 | int32_t adc_channel_open(uint32_t channel, void **h) | 
 | { | 
 | 	struct msm_client_data *client; | 
 | 	struct msm_adc_drv *msm_adc = msm_adc_drv; | 
 | 	struct msm_adc_platform_data *pdata; | 
 | 	struct platform_device *pdev; | 
 | 	int i = 0; | 
 |  | 
 | 	if (!msm_adc_drv) | 
 | 		return -EFAULT; | 
 |  | 
 | #ifdef CONFIG_PMIC8058_XOADC | 
 | 	if (pm8058_xoadc_registered() <= 0) | 
 | 		return -ENODEV; | 
 | #endif | 
 | 	pdata = msm_adc->pdev->dev.platform_data; | 
 | 	pdev = msm_adc->pdev; | 
 |  | 
 | 	while (i < pdata->num_chan_supported) { | 
 | 		if (channel == pdata->channel[i].channel_name) | 
 | 			break; | 
 | 		else | 
 | 			i++; | 
 | 	} | 
 |  | 
 | 	if (i == pdata->num_chan_supported) | 
 | 		return -EBADF; /* unknown channel */ | 
 |  | 
 | 	client = kzalloc(sizeof(struct msm_client_data), GFP_KERNEL); | 
 | 	if (!client) { | 
 | 		dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	if (!try_module_get(THIS_MODULE)) { | 
 | 		kfree(client); | 
 | 		return -EACCES; | 
 | 	} | 
 |  | 
 | 	mutex_init(&client->lock); | 
 | 	INIT_LIST_HEAD(&client->complete_list); | 
 | 	init_waitqueue_head(&client->data_wait); | 
 | 	init_waitqueue_head(&client->outst_wait); | 
 |  | 
 | 	client->online = 1; | 
 | 	client->adc_chan = i; | 
 | 	*h = (void *)client; | 
 | 	return 0; | 
 | } | 
 |  | 
 | int32_t adc_channel_close(void *h) | 
 | { | 
 | 	struct msm_client_data *client = (struct msm_client_data *)h; | 
 |  | 
 | 	kfree(client); | 
 | 	return 0; | 
 | } | 
 |  | 
 | int32_t adc_channel_request_conv(void *h, struct completion *conv_complete_evt) | 
 | { | 
 | 	struct msm_client_data *client = (struct msm_client_data *)h; | 
 | 	struct msm_adc_platform_data *pdata = | 
 | 					msm_adc_drv->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = &pdata->channel[client->adc_chan]; | 
 | 	struct adc_conv_slot *slot; | 
 | 	int ret; | 
 |  | 
 | 	if (conv_first_request) { | 
 | 		ret = pm8058_xoadc_calib_device(channel->adc_dev_instance); | 
 | 		if (ret) { | 
 | 			pr_err("pmic8058 xoadc calibration failed, retry\n"); | 
 | 			return ret; | 
 | 		} | 
 | 		conv_first_request = false; | 
 | 	} | 
 |  | 
 | 	channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance, | 
 | 									&slot); | 
 |  | 
 | 	if (slot) { | 
 | 		atomic_inc(&msm_adc_drv->total_outst); | 
 | 		mutex_lock(&client->lock); | 
 | 		client->num_outstanding++; | 
 | 		mutex_unlock(&client->lock); | 
 |  | 
 | 		slot->conv.result.chan = client->adc_chan; | 
 | 		slot->blocking = 0; | 
 | 		slot->compk = conv_complete_evt; | 
 | 		slot->client = client; | 
 | 		slot->adc_request = START_OF_CONV; | 
 | 		slot->chan_path = channel->chan_path_type; | 
 | 		slot->chan_adc_config = channel->adc_config_type; | 
 | 		slot->chan_adc_calib = channel->adc_calib_type; | 
 | 		queue_work(msm_adc_drv->wq, &slot->work); | 
 | 		return 0; | 
 | 	} | 
 | 	return -EBUSY; | 
 | } | 
 |  | 
 | int32_t adc_channel_read_result(void *h, struct adc_chan_result *chan_result) | 
 | { | 
 | 	struct msm_client_data *client = (struct msm_client_data *)h; | 
 | 	struct msm_adc_platform_data *pdata = | 
 | 					msm_adc_drv->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = pdata->channel; | 
 | 	struct adc_conv_slot *slot; | 
 | 	int rc = 0; | 
 |  | 
 | 	mutex_lock(&client->lock); | 
 |  | 
 | 	slot = list_first_entry(&client->complete_list, | 
 | 				struct adc_conv_slot, list); | 
 | 	if (!slot) { | 
 | 		mutex_unlock(&client->lock); | 
 | 		return -ENOMSG; | 
 | 	} | 
 |  | 
 | 	slot->client = NULL; | 
 | 	list_del(&slot->list); | 
 |  | 
 | 	client->num_complete--; | 
 |  | 
 | 	mutex_unlock(&client->lock); | 
 |  | 
 | 	*chan_result = slot->conv.result; | 
 |  | 
 | 	/* restore this slot to reserve */ | 
 | 	channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot( | 
 | 		channel[slot->conv.result.chan].adc_dev_instance, slot); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static void msm_rpc_adc_conv_cb(void *context, u32 param, | 
 | 			    void *evt_buf, u32 len) | 
 | { | 
 | 	struct dal_adc_result *result = evt_buf; | 
 | 	struct dal_conv_slot *slot = context; | 
 | 	struct msm_adc_drv *msm_adc = msm_adc_drv; | 
 |  | 
 | 	memcpy(&slot->result, result, sizeof(slot->result)); | 
 |  | 
 | 	/* for blocking requests, signal complete */ | 
 | 	if (slot->blocking) | 
 | 		complete(&slot->comp); | 
 |  | 
 | 	/* for non-blocking requests, add slot to the client completed list */ | 
 | 	else { | 
 | 		struct msm_client_data *client = slot->client; | 
 |  | 
 | 		mutex_lock(&client->lock); | 
 |  | 
 | 		list_add(&slot->list, &client->complete_list); | 
 | 		client->num_complete++; | 
 | 		client->num_outstanding--; | 
 |  | 
 | 		/* | 
 | 		 * if the client release has been invoked and this is call | 
 | 		 * corresponds to the last request, then signal release | 
 | 		 * to complete. | 
 | 		 */ | 
 | 		if (slot->client->online == 0 && client->num_outstanding == 0) | 
 | 			wake_up_interruptible_all(&client->outst_wait); | 
 |  | 
 | 		mutex_unlock(&client->lock); | 
 |  | 
 | 		wake_up_interruptible_all(&client->data_wait); | 
 |  | 
 | 		atomic_dec(&msm_adc->total_outst); | 
 |  | 
 | 		/* verify driver remove has not been invoked */ | 
 | 		if (atomic_read(&msm_adc->online) == 0 && | 
 | 				atomic_read(&msm_adc->total_outst) == 0) | 
 | 			wake_up_interruptible_all(&msm_adc->total_outst_wait); | 
 | 	} | 
 | } | 
 |  | 
 | void msm_adc_conv_cb(void *context, u32 param, | 
 | 			    void *evt_buf, u32 len) | 
 | { | 
 | 	struct adc_conv_slot *slot = context; | 
 | 	struct msm_adc_drv *msm_adc = msm_adc_drv; | 
 |  | 
 | 	switch (slot->adc_request) { | 
 | 	case START_OF_CONV: | 
 | 		slot->adc_request = END_OF_CONV; | 
 | 	break; | 
 | 	case START_OF_CALIBRATION: | 
 | 		slot->adc_request = END_OF_CALIBRATION; | 
 | 	break; | 
 | 	case END_OF_CALIBRATION: | 
 | 	case END_OF_CONV: | 
 | 	break; | 
 | 	} | 
 | 	queue_work(msm_adc->wq, &slot->work); | 
 | } | 
 |  | 
 | static void msm_adc_teardown_device_conv(struct platform_device *pdev, | 
 | 				    struct adc_dev *adc_dev) | 
 | { | 
 | 	struct dal_conv_state *conv_s = &adc_dev->conv; | 
 | 	struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); | 
 | 	struct dal_conv_slot *slot; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < MSM_ADC_DEV_MAX_INFLIGHT; i++) { | 
 | 		slot = &conv_s->context[i]; | 
 | 		if (slot->cb_h) { | 
 | 			dalrpc_dealloc_cb(msm_adc->dev_h, slot->cb_h); | 
 | 			slot->cb_h = NULL; | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static void msm_rpc_adc_teardown_device(struct platform_device *pdev, | 
 | 				    struct adc_dev *adc_dev) | 
 | { | 
 | 	struct dal_translation *transl = &adc_dev->transl; | 
 | 	int i, num_chans = transl->hwmon_end - transl->hwmon_start + 1; | 
 |  | 
 | 	if (adc_dev->sens_attr) | 
 | 		for (i = 0; i < num_chans; i++) | 
 | 			device_remove_file(&pdev->dev, | 
 | 					&adc_dev->sens_attr[i].dev_attr); | 
 |  | 
 | 	msm_adc_teardown_device_conv(pdev, adc_dev); | 
 |  | 
 | 	kfree(adc_dev->fnames); | 
 | 	kfree(adc_dev->sens_attr); | 
 | 	kfree(adc_dev); | 
 | } | 
 |  | 
 | static void msm_rpc_adc_teardown_devices(struct platform_device *pdev) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); | 
 | 	int i, rc = 0; | 
 |  | 
 | 	for (i = 0; i < pdata->num_adc; i++) { | 
 | 		if (msm_adc->devs[i]) { | 
 | 			msm_rpc_adc_teardown_device(pdev, msm_adc->devs[i]); | 
 | 			msm_adc->devs[i] = NULL; | 
 | 		} else | 
 | 			break; | 
 | 	} | 
 |  | 
 | 	if (msm_adc->dev_h) { | 
 | 		rc = daldevice_detach(msm_adc->dev_h); | 
 | 		if (rc) | 
 | 			dev_err(&pdev->dev, "Cannot detach from dal device\n"); | 
 | 		msm_adc->dev_h = NULL; | 
 | 	} | 
 |  | 
 | } | 
 |  | 
 | static void msm_adc_teardown_device(struct platform_device *pdev, | 
 | 				    struct msm_adc_drv *msm_adc) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	int i, num_chans = pdata->num_chan_supported; | 
 |  | 
 | 	if (pdata->num_chan_supported > 0) { | 
 | 		if (msm_adc->sens_attr) | 
 | 			for (i = 0; i < num_chans; i++) | 
 | 				device_remove_file(&pdev->dev, | 
 | 					&msm_adc->sens_attr[i].dev_attr); | 
 | 		kfree(msm_adc->sens_attr); | 
 | 	} | 
 | } | 
 |  | 
 | static void msm_adc_teardown(struct platform_device *pdev) | 
 | { | 
 | 	struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); | 
 |  | 
 | 	if (!msm_adc) | 
 | 		return; | 
 |  | 
 | 	misc_deregister(&msm_adc->misc); | 
 |  | 
 | 	if (msm_adc->hwmon) | 
 | 		hwmon_device_unregister(msm_adc->hwmon); | 
 |  | 
 | 	msm_rpc_adc_teardown_devices(pdev); | 
 | 	msm_adc_teardown_device(pdev, msm_adc); | 
 |  | 
 | 	kfree(msm_adc); | 
 | 	platform_set_drvdata(pdev, NULL); | 
 | } | 
 |  | 
 | static int __devinit msm_adc_device_conv_init(struct msm_adc_drv *msm_adc, | 
 | 					      struct adc_dev *adc_dev) | 
 | { | 
 | 	struct platform_device *pdev = msm_adc->pdev; | 
 | 	struct dal_conv_state *conv_s = &adc_dev->conv; | 
 | 	struct dal_conv_slot *slot = conv_s->context; | 
 | 	int rc, i; | 
 |  | 
 | 	sema_init(&conv_s->slot_count, MSM_ADC_DEV_MAX_INFLIGHT); | 
 | 	mutex_init(&conv_s->list_lock); | 
 | 	INIT_LIST_HEAD(&conv_s->slots); | 
 |  | 
 | 	for (i = 0; i < MSM_ADC_DEV_MAX_INFLIGHT; i++) { | 
 | 		list_add(&slot->list, &conv_s->slots); | 
 | 		slot->cb_h = dalrpc_alloc_cb(msm_adc->dev_h, | 
 | 					     msm_rpc_adc_conv_cb, slot); | 
 | 		if (!slot->cb_h) { | 
 | 			dev_err(&pdev->dev, "Unable to allocate DAL callback" | 
 | 							" for slot %d\n", i); | 
 | 			rc = -ENOMEM; | 
 | 			goto dal_err_cb; | 
 | 		} | 
 | 		init_completion(&slot->comp); | 
 | 		slot->idx = i; | 
 | 		slot++; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | dal_err_cb: | 
 | 	msm_adc_teardown_device_conv(pdev, adc_dev); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static struct sensor_device_attribute msm_rpc_adc_curr_in_attr = | 
 | 	SENSOR_ATTR(NULL, S_IRUGO, msm_adc_show_curr, NULL, 0); | 
 |  | 
 | static int __devinit msm_rpc_adc_device_init_hwmon(struct platform_device *pdev, | 
 | 						struct adc_dev *adc_dev) | 
 | { | 
 | 	struct dal_translation *transl = &adc_dev->transl; | 
 | 	int i, rc, num_chans = transl->hwmon_end - transl->hwmon_start + 1; | 
 | 	const char prefix[] = "curr", postfix[] = "_input"; | 
 | 	char tmpbuf[5]; | 
 |  | 
 | 	adc_dev->fnames = kzalloc(num_chans * MSM_ADC_MAX_FNAME + | 
 | 				  num_chans * sizeof(char *), GFP_KERNEL); | 
 | 	if (!adc_dev->fnames) { | 
 | 		dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	adc_dev->sens_attr = kzalloc(num_chans * | 
 | 			    sizeof(struct sensor_device_attribute), GFP_KERNEL); | 
 | 	if (!adc_dev->sens_attr) { | 
 | 		dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 		rc = -ENOMEM; | 
 | 		goto hwmon_err_fnames; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < num_chans; i++) { | 
 | 		adc_dev->fnames[i] = (char *)adc_dev->fnames + | 
 | 			i * MSM_ADC_MAX_FNAME + num_chans * sizeof(char *); | 
 | 		strcpy(adc_dev->fnames[i], prefix); | 
 | 		sprintf(tmpbuf, "%d", transl->hwmon_start + i); | 
 | 		strcat(adc_dev->fnames[i], tmpbuf); | 
 | 		strcat(adc_dev->fnames[i], postfix); | 
 |  | 
 | 		msm_rpc_adc_curr_in_attr.index = transl->hwmon_start + i; | 
 | 		msm_rpc_adc_curr_in_attr.dev_attr.attr.name = | 
 | 					adc_dev->fnames[i]; | 
 | 		memcpy(&adc_dev->sens_attr[i], &msm_rpc_adc_curr_in_attr, | 
 | 					sizeof(msm_rpc_adc_curr_in_attr)); | 
 |  | 
 | 		rc = device_create_file(&pdev->dev, | 
 | 				&adc_dev->sens_attr[i].dev_attr); | 
 | 		if (rc) { | 
 | 			dev_err(&pdev->dev, "device_create_file failed for " | 
 | 						"dal dev %u chan %d\n", | 
 | 					    adc_dev->transl.dal_dev_idx, i); | 
 | 			goto hwmon_err_sens; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | hwmon_err_sens: | 
 | 	kfree(adc_dev->sens_attr); | 
 | hwmon_err_fnames: | 
 | 	kfree(adc_dev->fnames); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int __devinit msm_rpc_adc_device_init(struct platform_device *pdev) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); | 
 | 	struct adc_dev *adc_dev; | 
 | 	struct adc_dev_spec target; | 
 | 	int i, rc; | 
 | 	int hwmon_cntr = pdata->num_chan_supported; | 
 |  | 
 | 	for (i = 0; i < pdata->num_adc; i++) { | 
 | 		adc_dev = kzalloc(sizeof(struct adc_dev), GFP_KERNEL); | 
 | 		if (!adc_dev) { | 
 | 			dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 			rc = -ENOMEM; | 
 | 			goto dev_init_err; | 
 | 		} | 
 |  | 
 | 		msm_adc->devs[i] = adc_dev; | 
 | 		adc_dev->name = pdata->dev_names[i]; | 
 |  | 
 | 		rc = msm_adc_device_conv_init(msm_adc, adc_dev); | 
 | 		if (rc) { | 
 | 			dev_err(&pdev->dev, "DAL device[%s] failed conv init\n", | 
 | 							adc_dev->name); | 
 | 			goto dev_init_err; | 
 | 		} | 
 |  | 
 | 		if (!pdata->target_hw == MSM_8x25) { | 
 | 			/* DAL device lookup */ | 
 | 			rc = msm_adc_getinputproperties(msm_adc, adc_dev->name, | 
 | 								&target); | 
 | 			if (rc) { | 
 | 				dev_err(&pdev->dev, "No such DAL device[%s]\n", | 
 | 							adc_dev->name); | 
 | 				goto dev_init_err; | 
 | 			} | 
 |  | 
 | 			adc_dev->transl.dal_dev_idx = target.dal.dev_idx; | 
 | 			adc_dev->nchans = target.dal.chan_idx; | 
 | 		} else { | 
 | 			/* On targets prior to MSM7x30 the remote driver has | 
 | 			   only the channel list and no device id. */ | 
 | 			adc_dev->transl.dal_dev_idx = MSM_8x25_ADC_DEV_ID; | 
 | 			adc_dev->nchans = MSM_8x25_CHAN_ID; | 
 | 		} | 
 |  | 
 | 		adc_dev->transl.hwmon_dev_idx = i; | 
 | 		adc_dev->transl.hwmon_start = hwmon_cntr; | 
 | 		adc_dev->transl.hwmon_end = hwmon_cntr + adc_dev->nchans - 1; | 
 | 		hwmon_cntr += adc_dev->nchans; | 
 |  | 
 | 		rc = msm_rpc_adc_device_init_hwmon(pdev, adc_dev); | 
 | 		if (rc) | 
 | 			goto dev_init_err; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | dev_init_err: | 
 | 	msm_rpc_adc_teardown_devices(pdev); | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int __devinit msm_rpc_adc_init(struct platform_device *pdev1) | 
 | { | 
 | 	struct msm_adc_drv *msm_adc = msm_adc_drv; | 
 | 	struct platform_device *pdev = msm_adc->pdev; | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	int rc = 0; | 
 |  | 
 | 	dev_dbg(&pdev->dev, "msm_rpc_adc_init called\n"); | 
 |  | 
 | 	if (!pdata) { | 
 | 		dev_err(&pdev->dev, "no platform data?\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	mutex_init(&msm_adc->prop_lock); | 
 |  | 
 | 	rc = daldevice_attach(MSM_ADC_DALRPC_DEVICEID, | 
 | 			MSM_ADC_DALRPC_PORT_NAME, | 
 | 			MSM_ADC_DALRPC_CPU, | 
 | 			&msm_adc->dev_h); | 
 | 	if (rc) { | 
 | 		dev_err(&pdev->dev, "Cannot attach to dal device\n"); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	dev_dbg(&pdev->dev, "Attach to dal device Succeeded\n"); | 
 |  | 
 | 	rc = msm_rpc_adc_device_init(pdev); | 
 | 	if (rc) { | 
 | 		dev_err(&pdev->dev, "msm_adc_dev_init failed\n"); | 
 | 		goto err_cleanup; | 
 | 	} | 
 |  | 
 | 	init_waitqueue_head(&msm_adc->rpc_total_outst_wait); | 
 | 	atomic_set(&msm_adc->rpc_online, 1); | 
 | 	atomic_set(&msm_adc->rpc_total_outst, 0); | 
 | 	epm_init = true; | 
 | 	pr_info("msm_adc successfully registered\n"); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_cleanup: | 
 | 	msm_rpc_adc_teardown_devices(pdev); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | /* | 
 |  * Process the deferred job | 
 |  */ | 
 | void msm_adc_wq_work(struct work_struct *work) | 
 | { | 
 | 	struct adc_properties *adc_properties; | 
 | 	struct adc_conv_slot *slot = container_of(work, | 
 | 						struct adc_conv_slot, work); | 
 | 	uint32_t idx = slot->conv.result.chan; | 
 | 	struct msm_adc_platform_data *pdata = | 
 | 					msm_adc_drv->pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = &pdata->channel[idx]; | 
 | 	int32_t adc_code; | 
 |  | 
 | 	switch (slot->adc_request) { | 
 | 	case START_OF_CONV: | 
 | 			channel->adc_access_fn->adc_select_chan_and_start_conv( | 
 | 					channel->adc_dev_instance, slot); | 
 | 	break; | 
 | 	case END_OF_CONV: | 
 | 		adc_properties = channel->adc_access_fn->adc_get_properties( | 
 | 						channel->adc_dev_instance); | 
 | 		if (channel->adc_access_fn->adc_read_adc_code) | 
 | 			channel->adc_access_fn->adc_read_adc_code( | 
 | 					channel->adc_dev_instance, &adc_code); | 
 | 		if (channel->chan_processor) | 
 | 			channel->chan_processor(adc_code, adc_properties, | 
 | 				&slot->chan_properties, &slot->conv.result); | 
 | 		/* Intentionally a fall thru here.  Calibraton does not need | 
 | 		to perform channel processing, etc.  However, both | 
 | 		end of conversion and end of calibration requires the below | 
 | 		fall thru code to be executed. */ | 
 | 	case END_OF_CALIBRATION: | 
 | 		/* for blocking requests, signal complete */ | 
 | 		if (slot->blocking) | 
 | 			complete(&slot->comp); | 
 | 		else { | 
 | 			struct msm_client_data *client = slot->client; | 
 |  | 
 | 			mutex_lock(&client->lock); | 
 |  | 
 | 			if (slot->adc_request == END_OF_CONV) { | 
 | 				list_add(&slot->list, &client->complete_list); | 
 | 				client->num_complete++; | 
 | 			} | 
 | 			client->num_outstanding--; | 
 |  | 
 | 		/* | 
 | 		 * if the client release has been invoked and this is call | 
 | 		 * corresponds to the last request, then signal release | 
 | 		 * to complete. | 
 | 		 */ | 
 | 			if (slot->client->online == 0 && | 
 | 						client->num_outstanding == 0) | 
 | 				wake_up_interruptible_all(&client->outst_wait); | 
 |  | 
 | 			mutex_unlock(&client->lock); | 
 |  | 
 | 			wake_up_interruptible_all(&client->data_wait); | 
 |  | 
 | 			atomic_dec(&msm_adc_drv->total_outst); | 
 |  | 
 | 			/* verify driver remove has not been invoked */ | 
 | 			if (atomic_read(&msm_adc_drv->online) == 0 && | 
 | 				atomic_read(&msm_adc_drv->total_outst) == 0) | 
 | 				wake_up_interruptible_all( | 
 | 					&msm_adc_drv->total_outst_wait); | 
 |  | 
 | 			if (slot->compk) /* Kernel space request */ | 
 | 				complete(slot->compk); | 
 | 			if (slot->adc_request == END_OF_CALIBRATION) | 
 | 				channel->adc_access_fn->adc_restore_slot( | 
 | 					channel->adc_dev_instance, slot); | 
 | 		} | 
 | 	break; | 
 | 	case START_OF_CALIBRATION: /* code here to please code reviewers | 
 | 					to satisfy silly compiler warnings */ | 
 | 	break; | 
 | 	} | 
 | } | 
 |  | 
 | static struct sensor_device_attribute msm_adc_curr_in_attr = | 
 | 	SENSOR_ATTR(NULL, S_IRUGO, msm_adc_show_curr, NULL, 0); | 
 |  | 
 | static int __devinit msm_adc_init_hwmon(struct platform_device *pdev, | 
 | 					       struct msm_adc_drv *msm_adc) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	struct msm_adc_channels *channel = pdata->channel; | 
 | 	int i, rc, num_chans = pdata->num_chan_supported; | 
 |  | 
 | 	if (!channel) | 
 | 		return -EINVAL; | 
 |  | 
 | 	msm_adc->sens_attr = kzalloc(num_chans * | 
 | 			    sizeof(struct sensor_device_attribute), GFP_KERNEL); | 
 | 	if (!msm_adc->sens_attr) { | 
 | 		dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 		rc = -ENOMEM; | 
 | 		goto hwmon_err_sens; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < num_chans; i++) { | 
 | 		msm_adc_curr_in_attr.index = i; | 
 | 		msm_adc_curr_in_attr.dev_attr.attr.name = channel[i].name; | 
 | 		memcpy(&msm_adc->sens_attr[i], &msm_adc_curr_in_attr, | 
 | 						sizeof(msm_adc_curr_in_attr)); | 
 |  | 
 | 		rc = device_create_file(&pdev->dev, | 
 | 				&msm_adc->sens_attr[i].dev_attr); | 
 | 		if (rc) { | 
 | 			dev_err(&pdev->dev, "device_create_file failed for " | 
 | 					    "dal dev %s\n", | 
 | 					    channel[i].name); | 
 | 			goto hwmon_err_sens; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | hwmon_err_sens: | 
 | 	kfree(msm_adc->sens_attr); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static struct platform_driver msm_adc_rpcrouter_remote_driver = { | 
 | 	.probe          = msm_rpc_adc_init, | 
 | 	.driver         = { | 
 | 		.name   = MSM_ADC_DALRPC_PORT_NAME, | 
 | 		.owner  = THIS_MODULE, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __devinit msm_adc_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct msm_adc_platform_data *pdata = pdev->dev.platform_data; | 
 | 	struct msm_adc_drv *msm_adc; | 
 | 	int rc = 0; | 
 |  | 
 | 	if (!pdata) { | 
 | 		dev_err(&pdev->dev, "no platform data?\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	msm_adc = kzalloc(sizeof(struct msm_adc_drv), GFP_KERNEL); | 
 | 	if (!msm_adc) { | 
 | 		dev_err(&pdev->dev, "Unable to allocate memory\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	platform_set_drvdata(pdev, msm_adc); | 
 | 	msm_adc_drv = msm_adc; | 
 | 	msm_adc->pdev = pdev; | 
 |  | 
 | 	if (pdata->target_hw == MSM_8x60 || pdata->target_hw == FSM_9xxx) { | 
 | 		rc = msm_adc_init_hwmon(pdev, msm_adc); | 
 | 		if (rc) { | 
 | 			dev_err(&pdev->dev, "msm_adc_dev_init failed\n"); | 
 | 			goto err_cleanup; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	msm_adc->hwmon = hwmon_device_register(&pdev->dev); | 
 | 	if (IS_ERR(msm_adc->hwmon)) { | 
 | 		dev_err(&pdev->dev, "hwmon_device_register failed\n"); | 
 | 		rc = PTR_ERR(msm_adc->hwmon); | 
 | 		goto err_cleanup; | 
 | 	} | 
 |  | 
 | 	msm_adc->misc.name = MSM_ADC_DRIVER_NAME; | 
 | 	msm_adc->misc.minor = MISC_DYNAMIC_MINOR; | 
 | 	msm_adc->misc.fops = &msm_adc_fops; | 
 |  | 
 | 	if (misc_register(&msm_adc->misc)) { | 
 | 		dev_err(&pdev->dev, "Unable to register misc device!\n"); | 
 | 		goto err_cleanup; | 
 | 	} | 
 |  | 
 | 	init_waitqueue_head(&msm_adc->total_outst_wait); | 
 | 	atomic_set(&msm_adc->online, 1); | 
 | 	atomic_set(&msm_adc->total_outst, 0); | 
 |  | 
 | 	msm_adc->wq = create_singlethread_workqueue("msm_adc"); | 
 | 	if (!msm_adc->wq) | 
 | 		goto err_cleanup; | 
 |  | 
 | 	if (pdata->num_adc > 0) { | 
 | 		if (pdata->target_hw == MSM_8x60) | 
 | 			platform_driver_register( | 
 | 				&msm_adc_rpcrouter_remote_driver); | 
 | 		else | 
 | 			msm_rpc_adc_init(pdev); | 
 | 	} | 
 | 	conv_first_request = true; | 
 |  | 
 | 	pr_info("msm_adc successfully registered\n"); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_cleanup: | 
 | 	msm_adc_teardown(pdev); | 
 |  | 
 | 	return rc; | 
 | } | 
 |  | 
 | static int __devexit msm_adc_remove(struct platform_device *pdev) | 
 | { | 
 | 	int rc; | 
 |  | 
 | 	struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev); | 
 |  | 
 | 	atomic_set(&msm_adc->online, 0); | 
 |  | 
 | 	atomic_set(&msm_adc->rpc_online, 0); | 
 |  | 
 | 	misc_deregister(&msm_adc->misc); | 
 |  | 
 | 	hwmon_device_unregister(msm_adc->hwmon); | 
 | 	msm_adc->hwmon = NULL; | 
 |  | 
 | 	/* | 
 | 	 * We may still have outstanding transactions in flight that have not | 
 | 	 * completed. Make sure they're completed before tearing down. | 
 | 	 */ | 
 | 	rc = wait_event_interruptible(msm_adc->total_outst_wait, | 
 | 				      atomic_read(&msm_adc->total_outst) == 0); | 
 | 	if (rc) { | 
 | 		pr_err("%s: wait_event_interruptible failed rc = %d\n", | 
 | 								__func__, rc); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	rc = wait_event_interruptible(msm_adc->rpc_total_outst_wait, | 
 | 	      atomic_read(&msm_adc->rpc_total_outst) == 0); | 
 | 	if (rc) { | 
 | 		pr_err("%s: wait_event_interruptible failed rc = %d\n", | 
 | 								__func__, rc); | 
 | 		return rc; | 
 | 	} | 
 |  | 
 | 	msm_adc_teardown(pdev); | 
 |  | 
 | 	pr_info("msm_adc unregistered\n"); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static struct platform_driver msm_adc_driver = { | 
 | 	.probe = msm_adc_probe, | 
 | 	.remove = __devexit_p(msm_adc_remove), | 
 | 	.driver = { | 
 | 		.name = MSM_ADC_DRIVER_NAME, | 
 | 		.owner = THIS_MODULE, | 
 | 	}, | 
 | }; | 
 |  | 
 | static int __init msm_adc_init(void) | 
 | { | 
 | 	return platform_driver_register(&msm_adc_driver); | 
 | } | 
 | module_init(msm_adc_init); | 
 |  | 
 | static void __exit msm_adc_exit(void) | 
 | { | 
 | 	platform_driver_unregister(&msm_adc_driver); | 
 | } | 
 | module_exit(msm_adc_exit); | 
 |  | 
 | MODULE_DESCRIPTION("MSM ADC Driver"); | 
 | MODULE_ALIAS("platform:msm_adc"); | 
 | MODULE_LICENSE("GPL v2"); | 
 | MODULE_VERSION("0.1"); |