Merge commit 'AU_LINUX_ANDROID_ICS.04.00.04.00.126' into msm-3.4
AU_LINUX_ANDROID_ICS.04.00.04.00.126 from msm-3.0.
First parent is from google/android-3.4.
* commit 'AU_LINUX_ANDROID_ICS.04.00.04.00.126': (8712 commits)
PRNG: Device tree entry for qrng device.
vidc:1080p: Set video core timeout value for Thumbnail mode
msm: sps: improve the debugging support in SPS driver
board-8064 msm: Overlap secure and non secure video firmware heaps.
msm: clock: Add handoff ops for 7x30 and copper XO clocks
msm_fb: display: Wait for external vsync before DTV IOMMU unmap
msm: Fix ciruclar dependency in debug UART settings
msm: gdsc: Add GDSC regulator driver for msm-copper
defconfig: Enable Mobicore Driver.
mobicore: Add mobicore driver.
mobicore: rename variable to lower case.
mobicore: rename folder.
mobicore: add makefiles
mobicore: initial import of kernel driver
ASoC: msm: Add SLIMBUS_2_RX CPU DAI
board-8064-gpio: Update FUNC for EPM SPI CS
msm_fb: display: Remove chicken bit config during video playback
mmc: msm_sdcc: enable the sanitize capability
msm-fb: display: lm2 writeback support on mpq platfroms
msm_fb: display: Disable LVDS phy & pll during panel off
...
Signed-off-by: Steve Muckle <smuckle@codeaurora.org>
diff --git a/drivers/hwmon/msm_adc.c b/drivers/hwmon/msm_adc.c
new file mode 100644
index 0000000..018ce79
--- /dev/null
+++ b/drivers/hwmon/msm_adc.c
@@ -0,0 +1,1533 @@
+/* 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");