diff options
author | Balaji Rao <balajirrao@openmoko.org> | 2009-01-08 19:49:26 -0500 |
---|---|---|
committer | Samuel Ortiz <samuel@sortiz.org> | 2009-01-10 19:34:23 -0500 |
commit | 08c3e06a5eb27d43b712adef18379f8464425e71 (patch) | |
tree | 63950d3beac51f8ca4e51c4fae7959a50608f31e /drivers | |
parent | f52046b14b1e1a8a02ae48d0c69d39c5e204644f (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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/mfd/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 3 | ||||
-rw-r--r-- | drivers/mfd/pcf50633-adc.c | 277 |
3 files changed, 286 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 | ||
229 | config 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 | |||
229 | endmenu | 236 | endmenu |
230 | 237 | ||
231 | menu "Multimedia Capabilities Port drivers" | 238 | menu "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 | ||
39 | obj-$(CONFIG_PMIC_DA903X) += da903x.o | 39 | obj-$(CONFIG_PMIC_DA903X) += da903x.o |
40 | 40 | ||
41 | obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o \ No newline at end of file | 41 | obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o |
42 | obj-$(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 | |||
29 | struct 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 | |||
43 | struct 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 | |||
53 | static inline struct pcf50633_adc *__to_adc(struct pcf50633 *pcf) | ||
54 | { | ||
55 | return platform_get_drvdata(pcf->adc_pdev); | ||
56 | } | ||
57 | |||
58 | static 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 | |||
71 | static 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 | |||
89 | static int | ||
90 | adc_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 | |||
115 | static void | ||
116 | pcf50633_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 | |||
124 | int 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 | } | ||
144 | EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read); | ||
145 | |||
146 | int 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 | } | ||
166 | EXPORT_SYMBOL_GPL(pcf50633_adc_async_read); | ||
167 | |||
168 | static 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 | |||
182 | static 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 | |||
210 | static 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 | |||
230 | static 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 | |||
253 | static 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 | |||
261 | static int __init pcf50633_adc_init(void) | ||
262 | { | ||
263 | return platform_driver_register(&pcf50633_adc_driver); | ||
264 | } | ||
265 | module_init(pcf50633_adc_init); | ||
266 | |||
267 | static void __exit pcf50633_adc_exit(void) | ||
268 | { | ||
269 | platform_driver_unregister(&pcf50633_adc_driver); | ||
270 | } | ||
271 | module_exit(pcf50633_adc_exit); | ||
272 | |||
273 | MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); | ||
274 | MODULE_DESCRIPTION("PCF50633 adc driver"); | ||
275 | MODULE_LICENSE("GPL"); | ||
276 | MODULE_ALIAS("platform:pcf50633-adc"); | ||
277 | |||