staging: comedi: drivers: use comedi_fc.h cmdtest helpers
[pandora-kernel.git] / drivers / staging / comedi / drivers / cb_das16_cs.c
1 /*
2     comedi/drivers/das16cs.c
3     Driver for Computer Boards PC-CARD DAS16/16.
4
5     COMEDI - Linux Control and Measurement Device Interface
6     Copyright (C) 2000, 2001, 2002 David A. Schleef <ds@schleef.org>
7
8     This program is free software; you can redistribute it and/or modify
9     it under the terms of the GNU General Public License as published by
10     the Free Software Foundation; either version 2 of the License, or
11     (at your option) any later version.
12
13     This program is distributed in the hope that it will be useful,
14     but WITHOUT ANY WARRANTY; without even the implied warranty of
15     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16     GNU General Public License for more details.
17
18     You should have received a copy of the GNU General Public License
19     along with this program; if not, write to the Free Software
20     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22     PCMCIA support code for this driver is adapted from the dummy_cs.c
23     driver of the Linux PCMCIA Card Services package.
24
25     The initial developer of the original code is David A. Hinds
26     <dahinds@users.sourceforge.net>.  Portions created by David A. Hinds
27     are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.
28
29 */
30 /*
31 Driver: cb_das16_cs
32 Description: Computer Boards PC-CARD DAS16/16
33 Devices: [ComputerBoards] PC-CARD DAS16/16 (cb_das16_cs), PC-CARD DAS16/16-AO
34 Author: ds
35 Updated: Mon, 04 Nov 2002 20:04:21 -0800
36 Status: experimental
37
38
39 */
40
41 #include <linux/interrupt.h>
42 #include <linux/slab.h>
43 #include "../comedidev.h"
44 #include <linux/delay.h>
45
46 #include <pcmcia/cistpl.h>
47 #include <pcmcia/ds.h>
48
49 #include "comedi_fc.h"
50 #include "8253.h"
51
52 #define DAS16CS_SIZE                    18
53
54 #define DAS16CS_ADC_DATA                0
55 #define DAS16CS_DIO_MUX                 2
56 #define DAS16CS_MISC1                   4
57 #define DAS16CS_MISC2                   6
58 #define DAS16CS_CTR0                    8
59 #define DAS16CS_CTR1                    10
60 #define DAS16CS_CTR2                    12
61 #define DAS16CS_CTR_CONTROL             14
62 #define DAS16CS_DIO                     16
63
64 struct das16cs_board {
65         const char *name;
66         int device_id;
67         int n_ao_chans;
68 };
69
70 static const struct das16cs_board das16cs_boards[] = {
71         {
72                 .name           = "PC-CARD DAS16/16-AO",
73                 .device_id      = 0x0039,
74                 .n_ao_chans     = 2,
75         }, {
76                 .name           = "PCM-DAS16s/16",
77                 .device_id      = 0x4009,
78                 .n_ao_chans     = 0,
79         }, {
80                 .name           = "PC-CARD DAS16/16",
81                 .device_id      = 0x0000,       /* unknown */
82                 .n_ao_chans     = 0,
83         },
84 };
85
86 struct das16cs_private {
87         unsigned int ao_readback[2];
88         unsigned short status1;
89         unsigned short status2;
90 };
91
92 static struct pcmcia_device *cur_dev;
93
94 static const struct comedi_lrange das16cs_ai_range = {
95         4, {
96                 BIP_RANGE(10),
97                 BIP_RANGE(5),
98                 BIP_RANGE(2.5),
99                 BIP_RANGE(1.25),
100         }
101 };
102
103 static irqreturn_t das16cs_interrupt(int irq, void *d)
104 {
105         /* struct comedi_device *dev = d; */
106         return IRQ_HANDLED;
107 }
108
109 static int das16cs_ai_rinsn(struct comedi_device *dev,
110                             struct comedi_subdevice *s,
111                             struct comedi_insn *insn, unsigned int *data)
112 {
113         struct das16cs_private *devpriv = dev->private;
114         int chan = CR_CHAN(insn->chanspec);
115         int range = CR_RANGE(insn->chanspec);
116         int aref = CR_AREF(insn->chanspec);
117         int i;
118         int to;
119
120         outw(chan, dev->iobase + DAS16CS_DIO_MUX);
121
122         devpriv->status1 &= ~0xf320;
123         devpriv->status1 |= (aref == AREF_DIFF) ? 0 : 0x0020;
124         outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
125
126         devpriv->status2 &= ~0xff00;
127         switch (range) {
128         case 0:
129                 devpriv->status2 |= 0x800;
130                 break;
131         case 1:
132                 devpriv->status2 |= 0x000;
133                 break;
134         case 2:
135                 devpriv->status2 |= 0x100;
136                 break;
137         case 3:
138                 devpriv->status2 |= 0x200;
139                 break;
140         }
141         outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
142
143         for (i = 0; i < insn->n; i++) {
144                 outw(0, dev->iobase + DAS16CS_ADC_DATA);
145
146 #define TIMEOUT 1000
147                 for (to = 0; to < TIMEOUT; to++) {
148                         if (inw(dev->iobase + DAS16CS_MISC1) & 0x0080)
149                                 break;
150                 }
151                 if (to == TIMEOUT) {
152                         dev_dbg(dev->class_dev, "cb_das16_cs: ai timeout\n");
153                         return -ETIME;
154                 }
155                 data[i] = inw(dev->iobase + DAS16CS_ADC_DATA);
156         }
157
158         return i;
159 }
160
161 static int das16cs_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
162 {
163         return -EINVAL;
164 }
165
166 static int das16cs_ai_cmdtest(struct comedi_device *dev,
167                               struct comedi_subdevice *s,
168                               struct comedi_cmd *cmd)
169 {
170         int err = 0;
171         int tmp;
172
173         /* Step 1 : check if triggers are trivially valid */
174
175         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
176         err |= cfc_check_trigger_src(&cmd->scan_begin_src,
177                                         TRIG_TIMER | TRIG_EXT);
178         err |= cfc_check_trigger_src(&cmd->convert_src,
179                                         TRIG_TIMER | TRIG_EXT);
180         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
181         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
182
183         if (err)
184                 return 1;
185
186         /* Step 2a : make sure trigger sources are unique */
187
188         err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
189         err |= cfc_check_trigger_is_unique(cmd->convert_src);
190         err |= cfc_check_trigger_is_unique(cmd->stop_src);
191
192         /* Step 2b : and mutually compatible */
193
194         if (err)
195                 return 2;
196
197         /* step 3: make sure arguments are trivially compatible */
198
199         if (cmd->start_arg != 0) {
200                 cmd->start_arg = 0;
201                 err++;
202         }
203 #define MAX_SPEED       10000   /* in nanoseconds */
204 #define MIN_SPEED       1000000000      /* in nanoseconds */
205
206         if (cmd->scan_begin_src == TRIG_TIMER) {
207                 if (cmd->scan_begin_arg < MAX_SPEED) {
208                         cmd->scan_begin_arg = MAX_SPEED;
209                         err++;
210                 }
211                 if (cmd->scan_begin_arg > MIN_SPEED) {
212                         cmd->scan_begin_arg = MIN_SPEED;
213                         err++;
214                 }
215         } else {
216                 /* external trigger */
217                 /* should be level/edge, hi/lo specification here */
218                 /* should specify multiple external triggers */
219                 if (cmd->scan_begin_arg > 9) {
220                         cmd->scan_begin_arg = 9;
221                         err++;
222                 }
223         }
224         if (cmd->convert_src == TRIG_TIMER) {
225                 if (cmd->convert_arg < MAX_SPEED) {
226                         cmd->convert_arg = MAX_SPEED;
227                         err++;
228                 }
229                 if (cmd->convert_arg > MIN_SPEED) {
230                         cmd->convert_arg = MIN_SPEED;
231                         err++;
232                 }
233         } else {
234                 /* external trigger */
235                 /* see above */
236                 if (cmd->convert_arg > 9) {
237                         cmd->convert_arg = 9;
238                         err++;
239                 }
240         }
241
242         if (cmd->scan_end_arg != cmd->chanlist_len) {
243                 cmd->scan_end_arg = cmd->chanlist_len;
244                 err++;
245         }
246         if (cmd->stop_src == TRIG_COUNT) {
247                 if (cmd->stop_arg > 0x00ffffff) {
248                         cmd->stop_arg = 0x00ffffff;
249                         err++;
250                 }
251         } else {
252                 /* TRIG_NONE */
253                 if (cmd->stop_arg != 0) {
254                         cmd->stop_arg = 0;
255                         err++;
256                 }
257         }
258
259         if (err)
260                 return 3;
261
262         /* step 4: fix up any arguments */
263
264         if (cmd->scan_begin_src == TRIG_TIMER) {
265                 unsigned int div1 = 0, div2 = 0;
266
267                 tmp = cmd->scan_begin_arg;
268                 i8253_cascade_ns_to_timer(100, &div1, &div2,
269                                           &cmd->scan_begin_arg,
270                                           cmd->flags & TRIG_ROUND_MASK);
271                 if (tmp != cmd->scan_begin_arg)
272                         err++;
273         }
274         if (cmd->convert_src == TRIG_TIMER) {
275                 unsigned int div1 = 0, div2 = 0;
276
277                 tmp = cmd->convert_arg;
278                 i8253_cascade_ns_to_timer(100, &div1, &div2,
279                                           &cmd->scan_begin_arg,
280                                           cmd->flags & TRIG_ROUND_MASK);
281                 if (tmp != cmd->convert_arg)
282                         err++;
283                 if (cmd->scan_begin_src == TRIG_TIMER &&
284                     cmd->scan_begin_arg <
285                     cmd->convert_arg * cmd->scan_end_arg) {
286                         cmd->scan_begin_arg =
287                             cmd->convert_arg * cmd->scan_end_arg;
288                         err++;
289                 }
290         }
291
292         if (err)
293                 return 4;
294
295         return 0;
296 }
297
298 static int das16cs_ao_winsn(struct comedi_device *dev,
299                             struct comedi_subdevice *s,
300                             struct comedi_insn *insn, unsigned int *data)
301 {
302         struct das16cs_private *devpriv = dev->private;
303         int i;
304         int chan = CR_CHAN(insn->chanspec);
305         unsigned short status1;
306         unsigned short d;
307         int bit;
308
309         for (i = 0; i < insn->n; i++) {
310                 devpriv->ao_readback[chan] = data[i];
311                 d = data[i];
312
313                 outw(devpriv->status1, dev->iobase + DAS16CS_MISC1);
314                 udelay(1);
315
316                 status1 = devpriv->status1 & ~0xf;
317                 if (chan)
318                         status1 |= 0x0001;
319                 else
320                         status1 |= 0x0008;
321
322                 outw(status1, dev->iobase + DAS16CS_MISC1);
323                 udelay(1);
324
325                 for (bit = 15; bit >= 0; bit--) {
326                         int b = (d >> bit) & 0x1;
327                         b <<= 1;
328                         outw(status1 | b | 0x0000, dev->iobase + DAS16CS_MISC1);
329                         udelay(1);
330                         outw(status1 | b | 0x0004, dev->iobase + DAS16CS_MISC1);
331                         udelay(1);
332                 }
333                 /*
334                  * Make both DAC0CS and DAC1CS high to load
335                  * the new data and update analog the output
336                  */
337                 outw(status1 | 0x9, dev->iobase + DAS16CS_MISC1);
338         }
339
340         return i;
341 }
342
343 static int das16cs_ao_rinsn(struct comedi_device *dev,
344                             struct comedi_subdevice *s,
345                             struct comedi_insn *insn, unsigned int *data)
346 {
347         struct das16cs_private *devpriv = dev->private;
348         int i;
349         int chan = CR_CHAN(insn->chanspec);
350
351         for (i = 0; i < insn->n; i++)
352                 data[i] = devpriv->ao_readback[chan];
353
354         return i;
355 }
356
357 static int das16cs_dio_insn_bits(struct comedi_device *dev,
358                                  struct comedi_subdevice *s,
359                                  struct comedi_insn *insn, unsigned int *data)
360 {
361         if (data[0]) {
362                 s->state &= ~data[0];
363                 s->state |= data[0] & data[1];
364
365                 outw(s->state, dev->iobase + DAS16CS_DIO);
366         }
367
368         data[1] = inw(dev->iobase + DAS16CS_DIO);
369
370         return insn->n;
371 }
372
373 static int das16cs_dio_insn_config(struct comedi_device *dev,
374                                    struct comedi_subdevice *s,
375                                    struct comedi_insn *insn, unsigned int *data)
376 {
377         struct das16cs_private *devpriv = dev->private;
378         int chan = CR_CHAN(insn->chanspec);
379         int bits;
380
381         if (chan < 4)
382                 bits = 0x0f;
383         else
384                 bits = 0xf0;
385
386         switch (data[0]) {
387         case INSN_CONFIG_DIO_OUTPUT:
388                 s->io_bits |= bits;
389                 break;
390         case INSN_CONFIG_DIO_INPUT:
391                 s->io_bits &= bits;
392                 break;
393         case INSN_CONFIG_DIO_QUERY:
394                 data[1] =
395                     (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
396                 return insn->n;
397                 break;
398         default:
399                 return -EINVAL;
400                 break;
401         }
402
403         devpriv->status2 &= ~0x00c0;
404         devpriv->status2 |= (s->io_bits & 0xf0) ? 0x0080 : 0;
405         devpriv->status2 |= (s->io_bits & 0x0f) ? 0x0040 : 0;
406
407         outw(devpriv->status2, dev->iobase + DAS16CS_MISC2);
408
409         return insn->n;
410 }
411
412 static const struct das16cs_board *das16cs_probe(struct comedi_device *dev,
413                                                  struct pcmcia_device *link)
414 {
415         int i;
416
417         for (i = 0; i < ARRAY_SIZE(das16cs_boards); i++) {
418                 if (das16cs_boards[i].device_id == link->card_id)
419                         return das16cs_boards + i;
420         }
421
422         dev_dbg(dev->class_dev, "unknown board!\n");
423
424         return NULL;
425 }
426
427 static int das16cs_attach(struct comedi_device *dev,
428                           struct comedi_devconfig *it)
429 {
430         const struct das16cs_board *thisboard;
431         struct pcmcia_device *link;
432         struct comedi_subdevice *s;
433         int ret;
434
435         link = cur_dev;         /* XXX hack */
436         if (!link)
437                 return -EIO;
438
439         dev->board_ptr = das16cs_probe(dev, link);
440         if (!dev->board_ptr)
441                 return -EIO;
442         thisboard = comedi_board(dev);
443
444         dev->board_name = thisboard->name;
445
446         dev->iobase = link->resource[0]->start;
447
448         ret = request_irq(link->irq, das16cs_interrupt,
449                           IRQF_SHARED, "cb_das16_cs", dev);
450         if (ret < 0)
451                 return ret;
452         dev->irq = link->irq;
453
454         if (alloc_private(dev, sizeof(struct das16cs_private)) < 0)
455                 return -ENOMEM;
456
457         ret = comedi_alloc_subdevices(dev, 3);
458         if (ret)
459                 return ret;
460
461         s = &dev->subdevices[0];
462         dev->read_subdev = s;
463         /* analog input subdevice */
464         s->type         = COMEDI_SUBD_AI;
465         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ;
466         s->n_chan       = 16;
467         s->maxdata      = 0xffff;
468         s->range_table  = &das16cs_ai_range;
469         s->len_chanlist = 16;
470         s->insn_read    = das16cs_ai_rinsn;
471         s->do_cmd       = das16cs_ai_cmd;
472         s->do_cmdtest   = das16cs_ai_cmdtest;
473
474         s = &dev->subdevices[1];
475         /* analog output subdevice */
476         if (thisboard->n_ao_chans) {
477                 s->type         = COMEDI_SUBD_AO;
478                 s->subdev_flags = SDF_WRITABLE;
479                 s->n_chan       = thisboard->n_ao_chans;
480                 s->maxdata      = 0xffff;
481                 s->range_table  = &range_bipolar10;
482                 s->insn_write   = &das16cs_ao_winsn;
483                 s->insn_read    = &das16cs_ao_rinsn;
484         } else {
485                 s->type         = COMEDI_SUBD_UNUSED;
486         }
487
488         s = &dev->subdevices[2];
489         /* digital i/o subdevice */
490         s->type         = COMEDI_SUBD_DIO;
491         s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
492         s->n_chan       = 8;
493         s->maxdata      = 1;
494         s->range_table  = &range_digital;
495         s->insn_bits    = das16cs_dio_insn_bits;
496         s->insn_config  = das16cs_dio_insn_config;
497
498         dev_info(dev->class_dev, "%s: %s, I/O base=0x%04lx, irq=%u\n",
499                 dev->driver->driver_name, dev->board_name,
500                 dev->iobase, dev->irq);
501
502         return 0;
503 }
504
505 static void das16cs_detach(struct comedi_device *dev)
506 {
507         if (dev->irq)
508                 free_irq(dev->irq, dev);
509 }
510
511 static struct comedi_driver driver_das16cs = {
512         .driver_name    = "cb_das16_cs",
513         .module         = THIS_MODULE,
514         .attach         = das16cs_attach,
515         .detach         = das16cs_detach,
516 };
517
518 static int das16cs_pcmcia_config_loop(struct pcmcia_device *p_dev,
519                                 void *priv_data)
520 {
521         if (p_dev->config_index == 0)
522                 return -EINVAL;
523
524         return pcmcia_request_io(p_dev);
525 }
526
527 static int das16cs_pcmcia_attach(struct pcmcia_device *link)
528 {
529         int ret;
530
531         /* Do we need to allocate an interrupt? */
532         link->config_flags |= CONF_ENABLE_IRQ | CONF_AUTO_SET_IO;
533
534         ret = pcmcia_loop_config(link, das16cs_pcmcia_config_loop, NULL);
535         if (ret)
536                 goto failed;
537
538         if (!link->irq)
539                 goto failed;
540
541         ret = pcmcia_enable_device(link);
542         if (ret)
543                 goto failed;
544
545         cur_dev = link;
546         return 0;
547
548 failed:
549         pcmcia_disable_device(link);
550         return ret;
551 }
552
553 static void das16cs_pcmcia_detach(struct pcmcia_device *link)
554 {
555         pcmcia_disable_device(link);
556         cur_dev = NULL;
557 }
558
559 static const struct pcmcia_device_id das16cs_id_table[] = {
560         PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0039),
561         PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x4009),
562         PCMCIA_DEVICE_NULL
563 };
564 MODULE_DEVICE_TABLE(pcmcia, das16cs_id_table);
565
566 static struct pcmcia_driver das16cs_driver = {
567         .name           = "cb_das16_cs",
568         .owner          = THIS_MODULE,
569         .probe          = das16cs_pcmcia_attach,
570         .remove         = das16cs_pcmcia_detach,
571         .id_table       = das16cs_id_table,
572 };
573
574 static int __init das16cs_init(void)
575 {
576         int ret;
577
578         ret = comedi_driver_register(&driver_das16cs);
579         if (ret < 0)
580                 return ret;
581
582         ret = pcmcia_register_driver(&das16cs_driver);
583         if (ret < 0) {
584                 comedi_driver_unregister(&driver_das16cs);
585                 return ret;
586         }
587
588         return 0;
589 }
590 module_init(das16cs_init);
591
592 static void __exit das16cs_exit(void)
593 {
594         pcmcia_unregister_driver(&das16cs_driver);
595         comedi_driver_unregister(&driver_das16cs);
596 }
597 module_exit(das16cs_exit);
598
599 MODULE_AUTHOR("David A. Schleef <ds@schleef.org>");
600 MODULE_DESCRIPTION("Comedi driver for Computer Boards PC-CARD DAS16/16");
601 MODULE_LICENSE("GPL");