blob: 2224128cc60367afe7580fd9962b1d5822734b36 [file] [log] [blame]
Maurus Cuelenaere3929e1e2010-01-14 00:30:31 +01001/* arch/arm/plat-samsung/adc.c
Ben Dooks28ab44c5b2008-12-18 14:20:04 +00002 *
3 * Copyright (c) 2008 Simtec Electronics
4 * http://armlinux.simtec.co.uk/
5 * Ben Dooks <ben@simtec.co.uk>, <ben-linux@fluff.org>
6 *
Maurus Cuelenaere3929e1e2010-01-14 00:30:31 +01007 * Samsung ADC device core
Ben Dooks28ab44c5b2008-12-18 14:20:04 +00008 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License.
12*/
13
14#include <linux/module.h>
15#include <linux/kernel.h>
16#include <linux/platform_device.h>
Alexey Dobriyand43c36d2009-10-07 17:09:06 +040017#include <linux/sched.h>
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000018#include <linux/list.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090019#include <linux/slab.h>
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000020#include <linux/err.h>
21#include <linux/clk.h>
22#include <linux/interrupt.h>
23#include <linux/io.h>
MyungJoo Hamf4629042011-07-20 21:08:17 +090024#include <linux/regulator/consumer.h>
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000025
26#include <plat/regs-adc.h>
27#include <plat/adc.h>
28
29/* This driver is designed to control the usage of the ADC block between
30 * the touchscreen and any other drivers that may need to use it, such as
31 * the hwmon driver.
32 *
33 * Priority will be given to the touchscreen driver, but as this itself is
34 * rate limited it should not starve other requests which are processed in
35 * order that they are received.
36 *
37 * Each user registers to get a client block which uniquely identifies it
38 * and stores information such as the necessary functions to callback when
39 * action is required.
40 */
41
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +010042enum s3c_cpu_type {
43 TYPE_S3C24XX,
44 TYPE_S3C64XX
45};
46
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000047struct s3c_adc_client {
48 struct platform_device *pdev;
49 struct list_head pend;
Ben Dookse170adc2009-07-18 10:12:27 +010050 wait_queue_head_t *wait;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000051
52 unsigned int nr_samples;
Ben Dookse170adc2009-07-18 10:12:27 +010053 int result;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000054 unsigned char is_ts;
55 unsigned char channel;
56
Ben Dookse170adc2009-07-18 10:12:27 +010057 void (*select_cb)(struct s3c_adc_client *c, unsigned selected);
58 void (*convert_cb)(struct s3c_adc_client *c,
59 unsigned val1, unsigned val2,
Nelson Castillo3f7ea462009-05-08 08:10:12 -050060 unsigned *samples_left);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000061};
62
63struct adc_device {
64 struct platform_device *pdev;
65 struct platform_device *owner;
66 struct clk *clk;
67 struct s3c_adc_client *cur;
68 struct s3c_adc_client *ts_pend;
69 void __iomem *regs;
Ben Dooks1f1f5842010-05-10 13:31:36 +090070 spinlock_t lock;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000071
72 unsigned int prescale;
73
74 int irq;
MyungJoo Hamf4629042011-07-20 21:08:17 +090075 struct regulator *vdd;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000076};
77
78static struct adc_device *adc_dev;
79
Ben Dooks1f1f5842010-05-10 13:31:36 +090080static LIST_HEAD(adc_pending); /* protected by adc_device.lock */
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000081
82#define adc_dbg(_adc, msg...) dev_dbg(&(_adc)->pdev->dev, msg)
83
84static inline void s3c_adc_convert(struct adc_device *adc)
85{
86 unsigned con = readl(adc->regs + S3C2410_ADCCON);
87
88 con |= S3C2410_ADCCON_ENABLE_START;
89 writel(con, adc->regs + S3C2410_ADCCON);
90}
91
92static inline void s3c_adc_select(struct adc_device *adc,
93 struct s3c_adc_client *client)
94{
95 unsigned con = readl(adc->regs + S3C2410_ADCCON);
96
Ben Dookse170adc2009-07-18 10:12:27 +010097 client->select_cb(client, 1);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +000098
99 con &= ~S3C2410_ADCCON_MUXMASK;
100 con &= ~S3C2410_ADCCON_STDBM;
101 con &= ~S3C2410_ADCCON_STARTMASK;
102
103 if (!client->is_ts)
104 con |= S3C2410_ADCCON_SELMUX(client->channel);
105
106 writel(con, adc->regs + S3C2410_ADCCON);
107}
108
109static void s3c_adc_dbgshow(struct adc_device *adc)
110{
111 adc_dbg(adc, "CON=%08x, TSC=%08x, DLY=%08x\n",
112 readl(adc->regs + S3C2410_ADCCON),
113 readl(adc->regs + S3C2410_ADCTSC),
114 readl(adc->regs + S3C2410_ADCDLY));
115}
116
Ben Dooksf8c8ac82009-04-17 12:36:49 +0100117static void s3c_adc_try(struct adc_device *adc)
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000118{
119 struct s3c_adc_client *next = adc->ts_pend;
120
121 if (!next && !list_empty(&adc_pending)) {
122 next = list_first_entry(&adc_pending,
123 struct s3c_adc_client, pend);
124 list_del(&next->pend);
125 } else
126 adc->ts_pend = NULL;
127
128 if (next) {
129 adc_dbg(adc, "new client is %p\n", next);
130 adc->cur = next;
131 s3c_adc_select(adc, next);
132 s3c_adc_convert(adc);
133 s3c_adc_dbgshow(adc);
134 }
135}
136
137int s3c_adc_start(struct s3c_adc_client *client,
138 unsigned int channel, unsigned int nr_samples)
139{
140 struct adc_device *adc = adc_dev;
141 unsigned long flags;
142
143 if (!adc) {
144 printk(KERN_ERR "%s: failed to find adc\n", __func__);
145 return -EINVAL;
146 }
147
148 if (client->is_ts && adc->ts_pend)
149 return -EAGAIN;
150
Ben Dooks1f1f5842010-05-10 13:31:36 +0900151 spin_lock_irqsave(&adc->lock, flags);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000152
153 client->channel = channel;
154 client->nr_samples = nr_samples;
155
156 if (client->is_ts)
157 adc->ts_pend = client;
158 else
159 list_add_tail(&client->pend, &adc_pending);
160
161 if (!adc->cur)
162 s3c_adc_try(adc);
Ben Dooks1f1f5842010-05-10 13:31:36 +0900163
164 spin_unlock_irqrestore(&adc->lock, flags);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000165
166 return 0;
167}
168EXPORT_SYMBOL_GPL(s3c_adc_start);
169
Ben Dookse170adc2009-07-18 10:12:27 +0100170static void s3c_convert_done(struct s3c_adc_client *client,
171 unsigned v, unsigned u, unsigned *left)
172{
173 client->result = v;
174 wake_up(client->wait);
175}
176
177int s3c_adc_read(struct s3c_adc_client *client, unsigned int ch)
178{
179 DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wake);
180 int ret;
181
182 client->convert_cb = s3c_convert_done;
183 client->wait = &wake;
184 client->result = -1;
185
186 ret = s3c_adc_start(client, ch, 1);
187 if (ret < 0)
188 goto err;
189
190 ret = wait_event_timeout(wake, client->result >= 0, HZ / 2);
191 if (client->result < 0) {
192 ret = -ETIMEDOUT;
193 goto err;
194 }
195
196 client->convert_cb = NULL;
197 return client->result;
198
199err:
200 return ret;
201}
Ryan Mallond3bf3952009-10-14 09:18:30 +1300202EXPORT_SYMBOL_GPL(s3c_adc_read);
Ben Dookse170adc2009-07-18 10:12:27 +0100203
204static void s3c_adc_default_select(struct s3c_adc_client *client,
205 unsigned select)
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000206{
207}
208
209struct s3c_adc_client *s3c_adc_register(struct platform_device *pdev,
Ben Dookse170adc2009-07-18 10:12:27 +0100210 void (*select)(struct s3c_adc_client *client,
211 unsigned int selected),
212 void (*conv)(struct s3c_adc_client *client,
213 unsigned d0, unsigned d1,
Nelson Castillo3f7ea462009-05-08 08:10:12 -0500214 unsigned *samples_left),
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000215 unsigned int is_ts)
216{
217 struct s3c_adc_client *client;
218
219 WARN_ON(!pdev);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000220
221 if (!select)
222 select = s3c_adc_default_select;
223
Ben Dookse170adc2009-07-18 10:12:27 +0100224 if (!pdev)
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000225 return ERR_PTR(-EINVAL);
226
227 client = kzalloc(sizeof(struct s3c_adc_client), GFP_KERNEL);
228 if (!client) {
229 dev_err(&pdev->dev, "no memory for adc client\n");
230 return ERR_PTR(-ENOMEM);
231 }
232
233 client->pdev = pdev;
234 client->is_ts = is_ts;
235 client->select_cb = select;
236 client->convert_cb = conv;
237
238 return client;
239}
240EXPORT_SYMBOL_GPL(s3c_adc_register);
241
242void s3c_adc_release(struct s3c_adc_client *client)
243{
Ben Dooks1f1f5842010-05-10 13:31:36 +0900244 unsigned long flags;
245
246 spin_lock_irqsave(&adc_dev->lock, flags);
247
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000248 /* We should really check that nothing is in progress. */
Ramax Lo0c3ee072009-04-14 23:56:18 +0800249 if (adc_dev->cur == client)
250 adc_dev->cur = NULL;
251 if (adc_dev->ts_pend == client)
252 adc_dev->ts_pend = NULL;
253 else {
254 struct list_head *p, *n;
255 struct s3c_adc_client *tmp;
256
257 list_for_each_safe(p, n, &adc_pending) {
258 tmp = list_entry(p, struct s3c_adc_client, pend);
259 if (tmp == client)
260 list_del(&tmp->pend);
261 }
262 }
263
264 if (adc_dev->cur == NULL)
265 s3c_adc_try(adc_dev);
Ben Dooks1f1f5842010-05-10 13:31:36 +0900266
267 spin_unlock_irqrestore(&adc_dev->lock, flags);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000268 kfree(client);
269}
270EXPORT_SYMBOL_GPL(s3c_adc_release);
271
272static irqreturn_t s3c_adc_irq(int irq, void *pw)
273{
274 struct adc_device *adc = pw;
275 struct s3c_adc_client *client = adc->cur;
Maurus Cuelenaere91492b42010-01-30 18:01:48 +0100276 enum s3c_cpu_type cpu = platform_get_device_id(adc->pdev)->driver_data;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000277 unsigned data0, data1;
278
279 if (!client) {
280 dev_warn(&adc->pdev->dev, "%s: no adc pending\n", __func__);
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +0100281 goto exit;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000282 }
283
284 data0 = readl(adc->regs + S3C2410_ADCDAT0);
285 data1 = readl(adc->regs + S3C2410_ADCDAT1);
286 adc_dbg(adc, "read %d: 0x%04x, 0x%04x\n", client->nr_samples, data0, data1);
287
Nelson Castillo3f7ea462009-05-08 08:10:12 -0500288 client->nr_samples--;
Ben Dookse170adc2009-07-18 10:12:27 +0100289
Maurus Cuelenaere91492b42010-01-30 18:01:48 +0100290 if (cpu == TYPE_S3C64XX) {
291 /* S3C64XX ADC resolution is 12-bit */
292 data0 &= 0xfff;
293 data1 &= 0xfff;
294 } else {
295 data0 &= 0x3ff;
296 data1 &= 0x3ff;
297 }
298
Ben Dookse170adc2009-07-18 10:12:27 +0100299 if (client->convert_cb)
Maurus Cuelenaere91492b42010-01-30 18:01:48 +0100300 (client->convert_cb)(client, data0, data1, &client->nr_samples);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000301
Nelson Castillo3f7ea462009-05-08 08:10:12 -0500302 if (client->nr_samples > 0) {
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000303 /* fire another conversion for this */
304
Ben Dookse170adc2009-07-18 10:12:27 +0100305 client->select_cb(client, 1);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000306 s3c_adc_convert(adc);
307 } else {
Ben Dooks1f1f5842010-05-10 13:31:36 +0900308 spin_lock(&adc->lock);
Ben Dookse170adc2009-07-18 10:12:27 +0100309 (client->select_cb)(client, 0);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000310 adc->cur = NULL;
311
312 s3c_adc_try(adc);
Ben Dooks1f1f5842010-05-10 13:31:36 +0900313 spin_unlock(&adc->lock);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000314 }
315
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +0100316exit:
Maurus Cuelenaere91492b42010-01-30 18:01:48 +0100317 if (cpu == TYPE_S3C64XX) {
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +0100318 /* Clear ADC interrupt */
319 writel(0, adc->regs + S3C64XX_ADCCLRINT);
320 }
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000321 return IRQ_HANDLED;
322}
323
324static int s3c_adc_probe(struct platform_device *pdev)
325{
326 struct device *dev = &pdev->dev;
327 struct adc_device *adc;
328 struct resource *regs;
329 int ret;
Maurus Cuelenaere91492b42010-01-30 18:01:48 +0100330 unsigned tmp;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000331
332 adc = kzalloc(sizeof(struct adc_device), GFP_KERNEL);
333 if (adc == NULL) {
334 dev_err(dev, "failed to allocate adc_device\n");
335 return -ENOMEM;
336 }
337
Ben Dooks1f1f5842010-05-10 13:31:36 +0900338 spin_lock_init(&adc->lock);
339
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000340 adc->pdev = pdev;
341 adc->prescale = S3C2410_ADCCON_PRSCVL(49);
342
MyungJoo Hamf4629042011-07-20 21:08:17 +0900343 adc->vdd = regulator_get(dev, "vdd");
344 if (IS_ERR(adc->vdd)) {
345 dev_err(dev, "operating without regulator \"vdd\" .\n");
346 ret = PTR_ERR(adc->vdd);
347 goto err_alloc;
348 }
349
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000350 adc->irq = platform_get_irq(pdev, 1);
351 if (adc->irq <= 0) {
352 dev_err(dev, "failed to get adc irq\n");
353 ret = -ENOENT;
MyungJoo Hamf4629042011-07-20 21:08:17 +0900354 goto err_reg;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000355 }
356
357 ret = request_irq(adc->irq, s3c_adc_irq, 0, dev_name(dev), adc);
358 if (ret < 0) {
359 dev_err(dev, "failed to attach adc irq\n");
MyungJoo Hamf4629042011-07-20 21:08:17 +0900360 goto err_reg;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000361 }
362
363 adc->clk = clk_get(dev, "adc");
364 if (IS_ERR(adc->clk)) {
365 dev_err(dev, "failed to get adc clock\n");
366 ret = PTR_ERR(adc->clk);
367 goto err_irq;
368 }
369
370 regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
371 if (!regs) {
372 dev_err(dev, "failed to find registers\n");
373 ret = -ENXIO;
374 goto err_clk;
375 }
376
377 adc->regs = ioremap(regs->start, resource_size(regs));
378 if (!adc->regs) {
379 dev_err(dev, "failed to map registers\n");
380 ret = -ENXIO;
381 goto err_clk;
382 }
383
MyungJoo Hamf4629042011-07-20 21:08:17 +0900384 ret = regulator_enable(adc->vdd);
385 if (ret)
386 goto err_ioremap;
387
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000388 clk_enable(adc->clk);
389
Maurus Cuelenaere91492b42010-01-30 18:01:48 +0100390 tmp = adc->prescale | S3C2410_ADCCON_PRSCEN;
391 if (platform_get_device_id(pdev)->driver_data == TYPE_S3C64XX) {
392 /* Enable 12-bit ADC resolution */
393 tmp |= S3C64XX_ADCCON_RESSEL;
394 }
395 writel(tmp, adc->regs + S3C2410_ADCCON);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000396
397 dev_info(dev, "attached adc driver\n");
398
399 platform_set_drvdata(pdev, adc);
400 adc_dev = adc;
401
402 return 0;
403
MyungJoo Hamf4629042011-07-20 21:08:17 +0900404 err_ioremap:
405 iounmap(adc->regs);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000406 err_clk:
407 clk_put(adc->clk);
408
409 err_irq:
410 free_irq(adc->irq, adc);
MyungJoo Hamf4629042011-07-20 21:08:17 +0900411 err_reg:
412 regulator_put(adc->vdd);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000413 err_alloc:
414 kfree(adc);
415 return ret;
416}
417
Uwe Kleine-Königad4e22f2009-11-24 22:07:10 +0100418static int __devexit s3c_adc_remove(struct platform_device *pdev)
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000419{
420 struct adc_device *adc = platform_get_drvdata(pdev);
421
422 iounmap(adc->regs);
423 free_irq(adc->irq, adc);
424 clk_disable(adc->clk);
MyungJoo Hamf4629042011-07-20 21:08:17 +0900425 regulator_disable(adc->vdd);
426 regulator_put(adc->vdd);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000427 clk_put(adc->clk);
428 kfree(adc);
429
430 return 0;
431}
432
433#ifdef CONFIG_PM
434static int s3c_adc_suspend(struct platform_device *pdev, pm_message_t state)
435{
436 struct adc_device *adc = platform_get_drvdata(pdev);
Ben Dooks1f1f5842010-05-10 13:31:36 +0900437 unsigned long flags;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000438 u32 con;
439
Ben Dooks1f1f5842010-05-10 13:31:36 +0900440 spin_lock_irqsave(&adc->lock, flags);
441
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000442 con = readl(adc->regs + S3C2410_ADCCON);
443 con |= S3C2410_ADCCON_STDBM;
444 writel(con, adc->regs + S3C2410_ADCCON);
445
Vasily Khoruzhicka0af8b32010-02-18 18:32:29 +0200446 disable_irq(adc->irq);
Ben Dooks1f1f5842010-05-10 13:31:36 +0900447 spin_unlock_irqrestore(&adc->lock, flags);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000448 clk_disable(adc->clk);
MyungJoo Hamf4629042011-07-20 21:08:17 +0900449 regulator_disable(adc->vdd);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000450
451 return 0;
452}
453
454static int s3c_adc_resume(struct platform_device *pdev)
455{
456 struct adc_device *adc = platform_get_drvdata(pdev);
MyungJoo Hamf4629042011-07-20 21:08:17 +0900457 int ret;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000458
MyungJoo Hamf4629042011-07-20 21:08:17 +0900459 ret = regulator_enable(adc->vdd);
460 if (ret)
461 return ret;
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000462 clk_enable(adc->clk);
Vasily Khoruzhicka0af8b32010-02-18 18:32:29 +0200463 enable_irq(adc->irq);
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000464
465 writel(adc->prescale | S3C2410_ADCCON_PRSCEN,
466 adc->regs + S3C2410_ADCCON);
467
468 return 0;
469}
470
471#else
472#define s3c_adc_suspend NULL
473#define s3c_adc_resume NULL
474#endif
475
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +0100476static struct platform_device_id s3c_adc_driver_ids[] = {
477 {
478 .name = "s3c24xx-adc",
479 .driver_data = TYPE_S3C24XX,
480 }, {
481 .name = "s3c64xx-adc",
482 .driver_data = TYPE_S3C64XX,
483 },
484 { }
485};
486MODULE_DEVICE_TABLE(platform, s3c_adc_driver_ids);
487
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000488static struct platform_driver s3c_adc_driver = {
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +0100489 .id_table = s3c_adc_driver_ids,
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000490 .driver = {
Maurus Cuelenaerebcedfa92010-01-14 00:30:34 +0100491 .name = "s3c-adc",
Ben Dooks28ab44c5b2008-12-18 14:20:04 +0000492 .owner = THIS_MODULE,
493 },
494 .probe = s3c_adc_probe,
495 .remove = __devexit_p(s3c_adc_remove),
496 .suspend = s3c_adc_suspend,
497 .resume = s3c_adc_resume,
498};
499
500static int __init adc_init(void)
501{
502 int ret;
503
504 ret = platform_driver_register(&s3c_adc_driver);
505 if (ret)
506 printk(KERN_ERR "%s: failed to add adc driver\n", __func__);
507
508 return ret;
509}
510
MyungJoo Hamf4629042011-07-20 21:08:17 +0900511module_init(adc_init);