aboutsummaryrefslogtreecommitdiff
path: root/drivers/mfd/pcf50606-adc.c
diff options
context:
space:
mode:
authorBalaji Rao <balajirrao@openmoko.org>2009-01-28 19:30:42 +0000
committerAndy Green <agreen@octopus.localdomain>2009-01-28 19:30:42 +0000
commit9d1fd9e7b19d66a35ee4e760b30a3b9a168c20a2 (patch)
tree48f4ebb86706de06434d8ba582f58b5a7c9ecb51 /drivers/mfd/pcf50606-adc.c
parent11afa2f75c68eb2c984ea43a92b8e453b8b70e49 (diff)
Subject: pcf50606_rebase_changes,patch
X-Git-Url: http://git.openmoko.org/?p=kernel.git;a=commitdiff_plain;h=938eddf17625cce0307f7612a3ea2560384e2384 pcf50606_rebase_changes,patch This patch brings into andy-tracking all changes related to pcf50606 from old balaji-tracking.
Diffstat (limited to 'drivers/mfd/pcf50606-adc.c')
-rw-r--r--drivers/mfd/pcf50606-adc.c279
1 files changed, 279 insertions, 0 deletions
diff --git a/drivers/mfd/pcf50606-adc.c b/drivers/mfd/pcf50606-adc.c
new file mode 100644
index 00000000000..fe0336b9753
--- /dev/null
+++ b/drivers/mfd/pcf50606-adc.c
@@ -0,0 +1,279 @@
+/* Philips PCF50606 ADC Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50606 driver mainly by
+ * Harald Welte, Andy Green, Werner Almesberger and Matt Hsu
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * NOTE: This driver does not yet support subtractive ADC mode, which means
+ * you can do only one measurement per read request.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/completion.h>
+
+#include <linux/mfd/pcf50606/core.h>
+#include <linux/mfd/pcf50606/adc.h>
+
+struct pcf50606_adc_request {
+ int mux;
+ int result;
+ void (*callback)(struct pcf50606 *, void *, int);
+ void *callback_param;
+
+ /* Used in case of sync requests */
+ struct completion completion;
+
+};
+
+#define PCF50606_MAX_ADC_FIFO_DEPTH 8
+
+struct pcf50606_adc {
+ struct pcf50606 *pcf;
+
+ /* Private stuff */
+ struct pcf50606_adc_request *queue[PCF50606_MAX_ADC_FIFO_DEPTH];
+ int queue_head;
+ int queue_tail;
+ struct mutex queue_mutex;
+};
+
+static inline struct pcf50606_adc *__to_adc(struct pcf50606 *pcf)
+{
+ return platform_get_drvdata(pcf->adc_pdev);
+}
+
+static void adc_setup(struct pcf50606 *pcf, int channel)
+{
+ channel &= PCF50606_ADCC2_ADCMUX_MASK;
+
+ /* start ADC conversion of selected channel */
+ pcf50606_reg_write(pcf, PCF50606_REG_ADCC2, channel |
+ PCF50606_ADCC2_ADCSTART | PCF50606_ADCC2_RES_10BIT);
+
+}
+
+static void trigger_next_adc_job_if_any(struct pcf50606 *pcf)
+{
+ struct pcf50606_adc *adc = __to_adc(pcf);
+ int head, tail;
+
+ mutex_lock(&adc->queue_mutex);
+
+ head = adc->queue_head;
+ tail = adc->queue_tail;
+
+ if (!adc->queue[head])
+ goto out;
+
+ adc_setup(pcf, adc->queue[head]->mux);
+out:
+ mutex_unlock(&adc->queue_mutex);
+}
+
+static int
+adc_enqueue_request(struct pcf50606 *pcf, struct pcf50606_adc_request *req)
+{
+ struct pcf50606_adc *adc = __to_adc(pcf);
+ int head, tail;
+
+ mutex_lock(&adc->queue_mutex);
+ head = adc->queue_head;
+ tail = adc->queue_tail;
+
+ if (adc->queue[tail]) {
+ mutex_unlock(&adc->queue_mutex);
+ return -EBUSY;
+ }
+
+ adc->queue[tail] = req;
+
+ adc->queue_tail =
+ (tail + 1) & (PCF50606_MAX_ADC_FIFO_DEPTH - 1);
+
+ mutex_unlock(&adc->queue_mutex);
+
+ trigger_next_adc_job_if_any(pcf);
+
+ return 0;
+}
+
+static void
+pcf50606_adc_sync_read_callback(struct pcf50606 *pcf, void *param, int result)
+{
+ struct pcf50606_adc_request *req;
+
+ /*We know here that the passed param is an adc_request object */
+ req = (struct pcf50606_adc_request *)param;
+
+ req->result = result;
+ complete(&req->completion);
+}
+
+int pcf50606_adc_sync_read(struct pcf50606 *pcf, int mux)
+{
+
+ struct pcf50606_adc_request *req;
+ int result;
+
+ /* req is freed when the result is ready, in irq handler*/
+ req = kzalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->mux = mux;
+ req->callback = pcf50606_adc_sync_read_callback;
+ req->callback_param = req;
+ init_completion(&req->completion);
+
+ adc_enqueue_request(pcf, req);
+
+ if (wait_for_completion_timeout(&req->completion, 5 * HZ) == 5 * HZ) {
+ dev_err(pcf->dev, "ADC read timed out \n");
+ }
+
+ result = req->result;
+
+ return result;
+}
+EXPORT_SYMBOL_GPL(pcf50606_adc_sync_read);
+
+int pcf50606_adc_async_read(struct pcf50606 *pcf, int mux,
+ void (*callback)(struct pcf50606 *, void *, int),
+ void *callback_param)
+{
+ struct pcf50606_adc_request *req;
+
+ /* req is freed when the result is ready, in pcf50606_work*/
+ req = kmalloc(sizeof(*req), GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->mux = mux;
+ req->callback = callback;
+ req->callback_param = callback_param;
+
+ adc_enqueue_request(pcf, req);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pcf50606_adc_async_read);
+
+static int adc_result(struct pcf50606 *pcf)
+{
+ u16 ret = (pcf50606_reg_read(pcf, PCF50606_REG_ADCS1) << 2) |
+ (pcf50606_reg_read(pcf, PCF50606_REG_ADCS2) & 0x03);
+
+ dev_info(pcf->dev, "adc result = %d\n", ret);
+
+ return ret;
+}
+
+static void pcf50606_adc_irq(int irq, void *data)
+{
+ struct pcf50606_adc *adc = data;
+ struct pcf50606 *pcf = adc->pcf;
+ struct pcf50606_adc_request *req;
+ int head;
+
+ mutex_lock(&adc->queue_mutex);
+ head = adc->queue_head;
+
+ req = adc->queue[head];
+ if (WARN_ON(!req)) {
+ dev_err(pcf->dev, "pcf50606-adc irq: ADC queue empty!\n");
+ mutex_unlock(&adc->queue_mutex);
+ return;
+ }
+
+ adc->queue[head] = NULL;
+ adc->queue_head = (head + 1) &
+ (PCF50606_MAX_ADC_FIFO_DEPTH - 1);
+
+ mutex_unlock(&adc->queue_mutex);
+
+ req->callback(pcf, req->callback_param, adc_result(pcf));
+ kfree(req);
+
+ trigger_next_adc_job_if_any(pcf);
+}
+
+static int __devinit pcf50606_adc_probe(struct platform_device *pdev)
+{
+ struct pcf50606_subdev_pdata *pdata = pdev->dev.platform_data;
+ struct pcf50606_adc *adc;
+
+ adc = kzalloc(sizeof(*adc), GFP_KERNEL);
+ if (!adc)
+ return -ENOMEM;
+
+ adc->pcf = pdata->pcf;
+ platform_set_drvdata(pdev, adc);
+
+ pcf50606_register_irq(pdata->pcf, PCF50606_IRQ_ADCRDY,
+ pcf50606_adc_irq, adc);
+
+ mutex_init(&adc->queue_mutex);
+
+ return 0;
+}
+
+static int __devexit pcf50606_adc_remove(struct platform_device *pdev)
+{
+ struct pcf50606_adc *adc = platform_get_drvdata(pdev);
+ int i, head;
+
+ pcf50606_free_irq(adc->pcf, PCF50606_IRQ_ADCRDY);
+
+ mutex_lock(&adc->queue_mutex);
+ head = adc->queue_head;
+
+ if (WARN_ON(adc->queue[head]))
+ dev_err(adc->pcf->dev,
+ "adc driver removed with request pending\n");
+
+ for (i = 0; i < PCF50606_MAX_ADC_FIFO_DEPTH; i++)
+ kfree(adc->queue[i]);
+
+ mutex_unlock(&adc->queue_mutex);
+ kfree(adc);
+
+ return 0;
+}
+
+struct platform_driver pcf50606_adc_driver = {
+ .driver = {
+ .name = "pcf50606-adc",
+ },
+ .probe = pcf50606_adc_probe,
+ .remove = __devexit_p(pcf50606_adc_remove),
+};
+
+static int __init pcf50606_adc_init(void)
+{
+ return platform_driver_register(&pcf50606_adc_driver);
+}
+module_init(pcf50606_adc_init);
+
+static void __exit pcf50606_adc_exit(void)
+{
+ platform_driver_unregister(&pcf50606_adc_driver);
+}
+module_exit(pcf50606_adc_exit);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50606 adc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50606-adc");
+