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