aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBalaji Rao <balajirrao@openmoko.org>2009-01-08 19:49:26 -0500
committerSamuel Ortiz <samuel@sortiz.org>2009-01-10 19:34:23 -0500
commit08c3e06a5eb27d43b712adef18379f8464425e71 (patch)
tree63950d3beac51f8ca4e51c4fae7959a50608f31e
parentf52046b14b1e1a8a02ae48d0c69d39c5e204644f (diff)
mfd: PCF50633 adc driver
This patch adds basic support for the PCF50633 ADC. The subtractive mode is not supported yet. Since we don't have adc subsystem, it currently lives in drivers/mfd. Signed-off-by: Balaji Rao <balajirrao@openmoko.org> Cc: Andy Green <andy@openmoko.com> Acked-by: Jonathan Cameron <jonathan.cameron@gmail.com> Signed-off-by: Samuel Ortiz <sameo@openedhand.com>
-rw-r--r--drivers/mfd/Kconfig7
-rw-r--r--drivers/mfd/Makefile3
-rw-r--r--drivers/mfd/pcf50633-adc.c277
-rw-r--r--include/linux/mfd/pcf50633/adc.h72
4 files changed, 358 insertions, 1 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 3ac32e45b03b..2fa3504e165a 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -226,6 +226,13 @@ config MFD_PCF50633
226 facilities, and registers devices for the various functions 226 facilities, and registers devices for the various functions
227 so that function-specific drivers can bind to them. 227 so that function-specific drivers can bind to them.
228 228
229config PCF50633_ADC
230 tristate "Support for NXP PCF50633 ADC"
231 depends on MFD_PCF50633
232 help
233 Say yes here if you want to include support for ADC in the
234 NXP PCF50633 chip.
235
229endmenu 236endmenu
230 237
231menu "Multimedia Capabilities Port drivers" 238menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 23d4d10981b3..138f9c4d3d7b 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -38,4 +38,5 @@ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o
38 38
39obj-$(CONFIG_PMIC_DA903X) += da903x.o 39obj-$(CONFIG_PMIC_DA903X) += da903x.o
40 40
41obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o \ No newline at end of file 41obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
42obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o \ No newline at end of file
diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c
new file mode 100644
index 000000000000..c2d05becfa97
--- /dev/null
+++ b/drivers/mfd/pcf50633-adc.c
@@ -0,0 +1,277 @@
1/* NXP PCF50633 ADC Driver
2 *
3 * (C) 2006-2008 by Openmoko, Inc.
4 * Author: Balaji Rao <balajirrao@openmoko.org>
5 * All rights reserved.
6 *
7 * Broken down from monstrous PCF50633 driver mainly by
8 * Harald Welte, Andy Green and Werner Almesberger
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version.
14 *
15 * NOTE: This driver does not yet support subtractive ADC mode, which means
16 * you can do only one measurement per read request.
17 */
18
19#include <linux/kernel.h>
20#include <linux/module.h>
21#include <linux/init.h>
22#include <linux/device.h>
23#include <linux/platform_device.h>
24#include <linux/completion.h>
25
26#include <linux/mfd/pcf50633/core.h>
27#include <linux/mfd/pcf50633/adc.h>
28
29struct pcf50633_adc_request {
30 int mux;
31 int avg;
32 int result;
33 void (*callback)(struct pcf50633 *, void *, int);
34 void *callback_param;
35
36 /* Used in case of sync requests */
37 struct completion completion;
38
39};
40
41#define PCF50633_MAX_ADC_FIFO_DEPTH 8
42
43struct pcf50633_adc {
44 struct pcf50633 *pcf;
45
46 /* Private stuff */
47 struct pcf50633_adc_request *queue[PCF50633_MAX_ADC_FIFO_DEPTH];
48 int queue_head;
49 int queue_tail;
50 struct mutex queue_mutex;
51};
52
53static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf)
54{
55 return platform_get_drvdata(pcf->adc_pdev);
56}
57
58static void adc_setup(struct pcf50633 *pcf, int channel, int avg)
59{
60 channel &= PCF50633_ADCC1_ADCMUX_MASK;
61
62 /* kill ratiometric, but enable ACCSW biasing */
63 pcf50633_reg_write(pcf, PCF50633_REG_ADCC2, 0x00);
64 pcf50633_reg_write(pcf, PCF50633_REG_ADCC3, 0x01);
65
66 /* start ADC conversion on selected channel */
67 pcf50633_reg_write(pcf, PCF50633_REG_ADCC1, channel | avg |
68 PCF50633_ADCC1_ADCSTART | PCF50633_ADCC1_RES_10BIT);
69}
70
71static void trigger_next_adc_job_if_any(struct pcf50633 *pcf)
72{
73 struct pcf50633_adc *adc = __to_adc(pcf);
74 int head;
75
76 mutex_lock(&adc->queue_mutex);
77
78 head = adc->queue_head;
79
80 if (!adc->queue[head]) {
81 mutex_unlock(&adc->queue_mutex);
82 return;
83 }
84 mutex_unlock(&adc->queue_mutex);
85
86 adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg);
87}
88
89static int
90adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
91{
92 struct pcf50633_adc *adc = __to_adc(pcf);
93 int head, tail;
94
95 mutex_lock(&adc->queue_mutex);
96
97 head = adc->queue_head;
98 tail = adc->queue_tail;
99
100 if (adc->queue[tail]) {
101 mutex_unlock(&adc->queue_mutex);
102 return -EBUSY;
103 }
104
105 adc->queue[tail] = req;
106 adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
107
108 mutex_unlock(&adc->queue_mutex);
109
110 trigger_next_adc_job_if_any(pcf);
111
112 return 0;
113}
114
115static void
116pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result)
117{
118 struct pcf50633_adc_request *req = param;
119
120 req->result = result;
121 complete(&req->completion);
122}
123
124int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
125{
126 struct pcf50633_adc_request *req;
127
128 /* req is freed when the result is ready, in interrupt handler */
129 req = kzalloc(sizeof(*req), GFP_KERNEL);
130 if (!req)
131 return -ENOMEM;
132
133 req->mux = mux;
134 req->avg = avg;
135 req->callback = pcf50633_adc_sync_read_callback;
136 req->callback_param = req;
137
138 init_completion(&req->completion);
139 adc_enqueue_request(pcf, req);
140 wait_for_completion(&req->completion);
141
142 return req->result;
143}
144EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
145
146int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
147 void (*callback)(struct pcf50633 *, void *, int),
148 void *callback_param)
149{
150 struct pcf50633_adc_request *req;
151
152 /* req is freed when the result is ready, in interrupt handler */
153 req = kmalloc(sizeof(*req), GFP_KERNEL);
154 if (!req)
155 return -ENOMEM;
156
157 req->mux = mux;
158 req->avg = avg;
159 req->callback = callback;
160 req->callback_param = callback_param;
161
162 adc_enqueue_request(pcf, req);
163
164 return 0;
165}
166EXPORT_SYMBOL_GPL(pcf50633_adc_async_read);
167
168static int adc_result(struct pcf50633 *pcf)
169{
170 u8 adcs1, adcs3;
171 u16 result;
172
173 adcs1 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS1);
174 adcs3 = pcf50633_reg_read(pcf, PCF50633_REG_ADCS3);
175 result = (adcs1 << 2) | (adcs3 & PCF50633_ADCS3_ADCDAT1L_MASK);
176
177 dev_dbg(pcf->dev, "adc result = %d\n", result);
178
179 return result;
180}
181
182static void pcf50633_adc_irq(int irq, void *data)
183{
184 struct pcf50633_adc *adc = data;
185 struct pcf50633 *pcf = adc->pcf;
186 struct pcf50633_adc_request *req;
187 int head;
188
189 mutex_lock(&adc->queue_mutex);
190 head = adc->queue_head;
191
192 req = adc->queue[head];
193 if (WARN_ON(!req)) {
194 dev_err(pcf->dev, "pcf50633-adc irq: ADC queue empty!\n");
195 mutex_unlock(&adc->queue_mutex);
196 return;
197 }
198 adc->queue[head] = NULL;
199 adc->queue_head = (head + 1) &
200 (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
201
202 mutex_unlock(&adc->queue_mutex);
203
204 req->callback(pcf, req->callback_param, adc_result(pcf));
205 kfree(req);
206
207 trigger_next_adc_job_if_any(pcf);
208}
209
210static int __devinit pcf50633_adc_probe(struct platform_device *pdev)
211{
212 struct pcf50633_subdev_pdata *pdata = pdev->dev.platform_data;
213 struct pcf50633_adc *adc;
214
215 adc = kzalloc(sizeof(*adc), GFP_KERNEL);
216 if (!adc)
217 return -ENOMEM;
218
219 adc->pcf = pdata->pcf;
220 platform_set_drvdata(pdev, adc);
221
222 pcf50633_register_irq(pdata->pcf, PCF50633_IRQ_ADCRDY,
223 pcf50633_adc_irq, adc);
224
225 mutex_init(&adc->queue_mutex);
226
227 return 0;
228}
229
230static int __devexit pcf50633_adc_remove(struct platform_device *pdev)
231{
232 struct pcf50633_adc *adc = platform_get_drvdata(pdev);
233 int i, head;
234
235 pcf50633_free_irq(adc->pcf, PCF50633_IRQ_ADCRDY);
236
237 mutex_lock(&adc->queue_mutex);
238 head = adc->queue_head;
239
240 if (WARN_ON(adc->queue[head]))
241 dev_err(adc->pcf->dev,
242 "adc driver removed with request pending\n");
243
244 for (i = 0; i < PCF50633_MAX_ADC_FIFO_DEPTH; i++)
245 kfree(adc->queue[i]);
246
247 mutex_unlock(&adc->queue_mutex);
248 kfree(adc);
249
250 return 0;
251}
252
253static struct platform_driver pcf50633_adc_driver = {
254 .driver = {
255 .name = "pcf50633-adc",
256 },
257 .probe = pcf50633_adc_probe,
258 .remove = __devexit_p(pcf50633_adc_remove),
259};
260
261static int __init pcf50633_adc_init(void)
262{
263 return platform_driver_register(&pcf50633_adc_driver);
264}
265module_init(pcf50633_adc_init);
266
267static void __exit pcf50633_adc_exit(void)
268{
269 platform_driver_unregister(&pcf50633_adc_driver);
270}
271module_exit(pcf50633_adc_exit);
272
273MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
274MODULE_DESCRIPTION("PCF50633 adc driver");
275MODULE_LICENSE("GPL");
276MODULE_ALIAS("platform:pcf50633-adc");
277
diff --git a/include/linux/mfd/pcf50633/adc.h b/include/linux/mfd/pcf50633/adc.h
new file mode 100644
index 000000000000..56669b4183ad
--- /dev/null
+++ b/include/linux/mfd/pcf50633/adc.h
@@ -0,0 +1,72 @@
1/*
2 * adc.h -- Driver for NXP PCF50633 ADC
3 *
4 * (C) 2006-2008 by Openmoko, Inc.
5 * All rights reserved.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version.
11 */
12
13#ifndef __LINUX_MFD_PCF50633_ADC_H
14#define __LINUX_MFD_PCF50633_ADC_H
15
16#include <linux/mfd/pcf50633/core.h>
17#include <linux/platform_device.h>
18
19/* ADC Registers */
20#define PCF50633_REG_ADCC3 0x52
21#define PCF50633_REG_ADCC2 0x53
22#define PCF50633_REG_ADCC1 0x54
23#define PCF50633_REG_ADCS1 0x55
24#define PCF50633_REG_ADCS2 0x56
25#define PCF50633_REG_ADCS3 0x57
26
27#define PCF50633_ADCC1_ADCSTART 0x01
28#define PCF50633_ADCC1_RES_10BIT 0x02
29#define PCF50633_ADCC1_AVERAGE_NO 0x00
30#define PCF50633_ADCC1_AVERAGE_4 0x04
31#define PCF50633_ADCC1_AVERAGE_8 0x08
32#define PCF50633_ADCC1_AVERAGE_16 0x0c
33#define PCF50633_ADCC1_MUX_BATSNS_RES 0x00
34#define PCF50633_ADCC1_MUX_BATSNS_SUBTR 0x10
35#define PCF50633_ADCC1_MUX_ADCIN2_RES 0x20
36#define PCF50633_ADCC1_MUX_ADCIN2_SUBTR 0x30
37#define PCF50633_ADCC1_MUX_BATTEMP 0x60
38#define PCF50633_ADCC1_MUX_ADCIN1 0x70
39#define PCF50633_ADCC1_AVERAGE_MASK 0x0c
40#define PCF50633_ADCC1_ADCMUX_MASK 0xf0
41
42#define PCF50633_ADCC2_RATIO_NONE 0x00
43#define PCF50633_ADCC2_RATIO_BATTEMP 0x01
44#define PCF50633_ADCC2_RATIO_ADCIN1 0x02
45#define PCF50633_ADCC2_RATIO_BOTH 0x03
46#define PCF50633_ADCC2_RATIOSETTL_100US 0x04
47
48#define PCF50633_ADCC3_ACCSW_EN 0x01
49#define PCF50633_ADCC3_NTCSW_EN 0x04
50#define PCF50633_ADCC3_RES_DIV_TWO 0x10
51#define PCF50633_ADCC3_RES_DIV_THREE 0x00
52
53#define PCF50633_ADCS3_REF_NTCSW 0x00
54#define PCF50633_ADCS3_REF_ACCSW 0x10
55#define PCF50633_ADCS3_REF_2V0 0x20
56#define PCF50633_ADCS3_REF_VISA 0x30
57#define PCF50633_ADCS3_REF_2V0_2 0x70
58#define PCF50633_ADCS3_ADCRDY 0x80
59
60#define PCF50633_ADCS3_ADCDAT1L_MASK 0x03
61#define PCF50633_ADCS3_ADCDAT2L_MASK 0x0c
62#define PCF50633_ADCS3_ADCDAT2L_SHIFT 2
63#define PCF50633_ASCS3_REF_MASK 0x70
64
65extern int
66pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
67 void (*callback)(struct pcf50633 *, void *, int),
68 void *callback_param);
69extern int
70pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg);
71
72#endif /* __LINUX_PCF50633_ADC_H */