staging: comedi: pcl711: use 8253.h helper to set the timers
[linux-2.6-block.git] / drivers / staging / comedi / drivers / pcl711.c
1 /*
2    comedi/drivers/pcl711.c
3    hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112
4    and compatibles
5
6    COMEDI - Linux Control and Measurement Device Interface
7    Copyright (C) 1998 David A. Schleef <ds@schleef.org>
8    Janne Jalkanen <jalkanen@cs.hut.fi>
9    Eric Bunn <ebu@cs.hut.fi>
10
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 2 of the License, or
14    (at your option) any later version.
15
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20  */
21 /*
22 Driver: pcl711
23 Description: Advantech PCL-711 and 711b, ADLink ACL-8112
24 Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi>
25 Status: mostly complete
26 Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
27   [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
28
29 Since these boards do not have DMA or FIFOs, only immediate mode is
30 supported.
31
32 */
33
34 /*
35    Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
36    driver for the PCL-711.  I used a few ideas from his driver
37    here.  His driver also has more comments, if you are
38    interested in understanding how this driver works.
39    http://tech.buffalostate.edu/~dave/driver/
40
41    The ACL-8112 driver was hacked from the sources of the PCL-711
42    driver (the 744 chip used on the 8112 is almost the same as
43    the 711b chip, but it has more I/O channels) by
44    Janne Jalkanen (jalkanen@cs.hut.fi) and
45    Erik Bunn (ebu@cs.hut.fi).  Remerged with the PCL-711 driver
46    by ds.
47
48    [acl-8112]
49    This driver supports both TRIGNOW and TRIGCLK,
50    but does not yet support DMA transfers.  It also supports
51    both high (HG) and low (DG) versions of the card, though
52    the HG version has been untested.
53
54  */
55
56 #include <linux/module.h>
57 #include <linux/interrupt.h>
58 #include "../comedidev.h"
59
60 #include <linux/delay.h>
61
62 #include "comedi_fc.h"
63 #include "8253.h"
64
65 /*
66  * I/O port register map
67  */
68 #define PCL711_TIMER_BASE       0x00
69 #define PCL711_AI_LSB_REG       0x04
70 #define PCL711_AI_MSB_REG       0x05
71 #define PCL711_AI_MSB_DRDY      (1 << 4)
72 #define PCL711_AO_LSB_REG(x)    (0x04 + ((x) * 2))
73 #define PCL711_AO_MSB_REG(x)    (0x05 + ((x) * 2))
74 #define PCL711_DI_LO            0x06
75 #define PCL711_DI_HI            0x07
76 #define PCL711_CLRINTR          0x08
77 #define PCL711_GAIN             0x09
78 #define PCL711_MUX_REG          0x0a
79 #define PCL711_MUX_CHAN(x)      (((x) & 0xf) << 0)
80 #define PCL711_MUX_CS0          (1 << 4)
81 #define PCL711_MUX_CS1          (1 << 5)
82 #define PCL711_MUX_DIFF         (PCL711_MUX_CS0 | PCL711_MUX_CS1)
83 #define PCL711_MODE_REG         0x0b
84 #define PCL711_MODE_DEFAULT     (0 << 0)
85 #define PCL711_MODE_SOFTTRIG    (1 << 0)
86 #define PCL711_MODE_EXT         (2 << 0)
87 #define PCL711_MODE_EXT_IRQ     (3 << 0)
88 #define PCL711_MODE_PACER       (4 << 0)
89 #define PCL711_MODE_PACER_IRQ   (6 << 0)
90 #define PCL711_MODE_IRQ(x)      (((x) & 0x7) << 4)
91 #define PCL711_SOFTTRIG_REG     0x0c
92 #define PCL711_SOFTTRIG         (0 << 0)  /* any value will work */
93 #define PCL711_DO_LO            0x0d
94 #define PCL711_DO_HI            0x0e
95
96 static const struct comedi_lrange range_pcl711b_ai = {
97         5, {
98                 BIP_RANGE(5),
99                 BIP_RANGE(2.5),
100                 BIP_RANGE(1.25),
101                 BIP_RANGE(0.625),
102                 BIP_RANGE(0.3125)
103         }
104 };
105
106 static const struct comedi_lrange range_acl8112hg_ai = {
107         12, {
108                 BIP_RANGE(5),
109                 BIP_RANGE(0.5),
110                 BIP_RANGE(0.05),
111                 BIP_RANGE(0.005),
112                 UNI_RANGE(10),
113                 UNI_RANGE(1),
114                 UNI_RANGE(0.1),
115                 UNI_RANGE(0.01),
116                 BIP_RANGE(10),
117                 BIP_RANGE(1),
118                 BIP_RANGE(0.1),
119                 BIP_RANGE(0.01)
120         }
121 };
122
123 static const struct comedi_lrange range_acl8112dg_ai = {
124         9, {
125                 BIP_RANGE(5),
126                 BIP_RANGE(2.5),
127                 BIP_RANGE(1.25),
128                 BIP_RANGE(0.625),
129                 UNI_RANGE(10),
130                 UNI_RANGE(5),
131                 UNI_RANGE(2.5),
132                 UNI_RANGE(1.25),
133                 BIP_RANGE(10)
134         }
135 };
136
137 static const int i8253_osc_base = 500;  /* 2 Mhz */
138
139 struct pcl711_board {
140         const char *name;
141         int n_aichan;
142         int n_aochan;
143         int maxirq;
144         const struct comedi_lrange *ai_range_type;
145 };
146
147 static const struct pcl711_board boardtypes[] = {
148         {
149                 .name           = "pcl711",
150                 .n_aichan       = 8,
151                 .n_aochan       = 1,
152                 .ai_range_type  = &range_bipolar5,
153         }, {
154                 .name           = "pcl711b",
155                 .n_aichan       = 8,
156                 .n_aochan       = 1,
157                 .maxirq         = 7,
158                 .ai_range_type  = &range_pcl711b_ai,
159         }, {
160                 .name           = "acl8112hg",
161                 .n_aichan       = 16,
162                 .n_aochan       = 2,
163                 .maxirq         = 15,
164                 .ai_range_type  = &range_acl8112hg_ai,
165         }, {
166                 .name           = "acl8112dg",
167                 .n_aichan       = 16,
168                 .n_aochan       = 2,
169                 .maxirq         = 15,
170                 .ai_range_type  = &range_acl8112dg_ai,
171         },
172 };
173
174 struct pcl711_private {
175         int ntrig;
176         unsigned int ao_readback[2];
177         unsigned int divisor1;
178         unsigned int divisor2;
179 };
180
181 static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
182 {
183         /*
184          * The pcl711b board uses bits in the mode register to select the
185          * interrupt. The other boards supported by this driver all use
186          * jumpers on the board.
187          *
188          * Enables the interrupt when needed on the pcl711b board. These
189          * bits do nothing on the other boards.
190          */
191         if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
192                 mode |= PCL711_MODE_IRQ(dev->irq);
193
194         outb(mode, dev->iobase + PCL711_MODE_REG);
195 }
196
197 static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
198                                          struct comedi_subdevice *s)
199 {
200         unsigned int val;
201
202         val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
203         val |= inb(dev->iobase + PCL711_AI_LSB_REG);
204
205         return val & s->maxdata;
206 }
207
208 static irqreturn_t pcl711_interrupt(int irq, void *d)
209 {
210         struct comedi_device *dev = d;
211         struct pcl711_private *devpriv = dev->private;
212         struct comedi_subdevice *s = dev->read_subdev;
213         unsigned int data;
214
215         if (!dev->attached) {
216                 comedi_error(dev, "spurious interrupt");
217                 return IRQ_HANDLED;
218         }
219
220         data = pcl711_ai_get_sample(dev, s);
221
222         outb(0, dev->iobase + PCL711_CLRINTR);
223
224         /* FIXME! Nothing else sets ntrig! */
225         if (!(--devpriv->ntrig)) {
226                 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
227
228                 s->async->events |= COMEDI_CB_EOA;
229         }
230         comedi_event(dev, s);
231         return IRQ_HANDLED;
232 }
233
234 static void pcl711_set_changain(struct comedi_device *dev,
235                                 struct comedi_subdevice *s,
236                                 unsigned int chanspec)
237 {
238         unsigned int chan = CR_CHAN(chanspec);
239         unsigned int range = CR_RANGE(chanspec);
240         unsigned int aref = CR_AREF(chanspec);
241         unsigned int mux = 0;
242
243         outb(range, dev->iobase + PCL711_GAIN);
244
245         if (s->n_chan > 8) {
246                 /* Select the correct MPC508A chip */
247                 if (aref == AREF_DIFF) {
248                         chan &= 0x7;
249                         mux |= PCL711_MUX_DIFF;
250                 } else {
251                         if (chan < 8)
252                                 mux |= PCL711_MUX_CS0;
253                         else
254                                 mux |= PCL711_MUX_CS1;
255                 }
256         }
257         outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
258 }
259
260 static int pcl711_ai_wait_for_eoc(struct comedi_device *dev,
261                                   unsigned int timeout)
262 {
263         unsigned int msb;
264
265         while (timeout--) {
266                 msb = inb(dev->iobase + PCL711_AI_MSB_REG);
267                 if ((msb & PCL711_AI_MSB_DRDY) == 0)
268                         return 0;
269                 udelay(1);
270         }
271         return -ETIME;
272 }
273
274 static int pcl711_ai_insn_read(struct comedi_device *dev,
275                                struct comedi_subdevice *s,
276                                struct comedi_insn *insn,
277                                unsigned int *data)
278 {
279         int ret;
280         int i;
281
282         pcl711_set_changain(dev, s, insn->chanspec);
283
284         pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
285
286         for (i = 0; i < insn->n; i++) {
287                 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
288
289                 ret = pcl711_ai_wait_for_eoc(dev, 100);
290                 if (ret)
291                         return ret;
292
293                 data[i] = pcl711_ai_get_sample(dev, s);
294         }
295
296         return insn->n;
297 }
298
299 static int pcl711_ai_cmdtest(struct comedi_device *dev,
300                              struct comedi_subdevice *s, struct comedi_cmd *cmd)
301 {
302         struct pcl711_private *devpriv = dev->private;
303         int tmp;
304         int err = 0;
305
306         /* Step 1 : check if triggers are trivially valid */
307
308         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
309         err |= cfc_check_trigger_src(&cmd->scan_begin_src,
310                                         TRIG_TIMER | TRIG_EXT);
311         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
312         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
313         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
314
315         if (err)
316                 return 1;
317
318         /* Step 2a : make sure trigger sources are unique */
319
320         err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
321         err |= cfc_check_trigger_is_unique(cmd->stop_src);
322
323         /* Step 2b : and mutually compatible */
324
325         if (err)
326                 return 2;
327
328         /* Step 3: check if arguments are trivially valid */
329
330         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
331
332         if (cmd->scan_begin_src == TRIG_EXT) {
333                 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
334         } else {
335 #define MAX_SPEED 1000
336 #define TIMER_BASE 100
337                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
338                                                  MAX_SPEED);
339         }
340
341         err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
342         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
343
344         if (cmd->stop_src == TRIG_NONE) {
345                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
346         } else {
347                 /* ignore */
348         }
349
350         if (err)
351                 return 3;
352
353         /* step 4 */
354
355         if (cmd->scan_begin_src == TRIG_TIMER) {
356                 tmp = cmd->scan_begin_arg;
357                 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
358                                                &devpriv->divisor1,
359                                                &devpriv->divisor2,
360                                                &cmd->scan_begin_arg,
361                                                cmd->flags & TRIG_ROUND_MASK);
362                 if (tmp != cmd->scan_begin_arg)
363                         err++;
364         }
365
366         if (err)
367                 return 4;
368
369         return 0;
370 }
371
372 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
373 {
374         int timer1, timer2;
375         struct comedi_cmd *cmd = &s->async->cmd;
376
377         pcl711_set_changain(dev, s, cmd->chanlist[0]);
378
379         if (cmd->scan_begin_src == TRIG_TIMER) {
380                 timer1 = timer2 = 0;
381                 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
382                                           &cmd->scan_begin_arg,
383                                           TRIG_ROUND_NEAREST);
384
385                 i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
386                            1, timer1, I8254_MODE2 | I8254_BINARY);
387                 i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
388                            2, timer2, I8254_MODE2 | I8254_BINARY);
389
390                 /* clear pending interrupts (just in case) */
391                 outb(0, dev->iobase + PCL711_CLRINTR);
392
393                 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
394         } else {
395                 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
396         }
397
398         return 0;
399 }
400
401 static void pcl711_ao_write(struct comedi_device *dev,
402                             unsigned int chan, unsigned int val)
403 {
404         outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
405         outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
406 }
407
408 static int pcl711_ao_insn_write(struct comedi_device *dev,
409                                 struct comedi_subdevice *s,
410                                 struct comedi_insn *insn,
411                                 unsigned int *data)
412 {
413         struct pcl711_private *devpriv = dev->private;
414         unsigned int chan = CR_CHAN(insn->chanspec);
415         unsigned int val = devpriv->ao_readback[chan];
416         int i;
417
418         for (i = 0; i < insn->n; i++) {
419                 val = data[i];
420                 pcl711_ao_write(dev, chan, val);
421         }
422         devpriv->ao_readback[chan] = val;
423
424         return insn->n;
425 }
426
427 static int pcl711_ao_insn_read(struct comedi_device *dev,
428                                struct comedi_subdevice *s,
429                                struct comedi_insn *insn,
430                                unsigned int *data)
431 {
432         struct pcl711_private *devpriv = dev->private;
433         unsigned int chan = CR_CHAN(insn->chanspec);
434         int i;
435
436         for (i = 0; i < insn->n; i++)
437                 data[i] = devpriv->ao_readback[chan];
438
439         return insn->n;
440 }
441
442 /* Digital port read - Untested on 8112 */
443 static int pcl711_di_insn_bits(struct comedi_device *dev,
444                                struct comedi_subdevice *s,
445                                struct comedi_insn *insn, unsigned int *data)
446 {
447         data[1] = inb(dev->iobase + PCL711_DI_LO) |
448             (inb(dev->iobase + PCL711_DI_HI) << 8);
449
450         return insn->n;
451 }
452
453 static int pcl711_do_insn_bits(struct comedi_device *dev,
454                                struct comedi_subdevice *s,
455                                struct comedi_insn *insn,
456                                unsigned int *data)
457 {
458         unsigned int mask;
459
460         mask = comedi_dio_update_state(s, data);
461         if (mask) {
462                 if (mask & 0x00ff)
463                         outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
464                 if (mask & 0xff00)
465                         outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
466         }
467
468         data[1] = s->state;
469
470         return insn->n;
471 }
472
473 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
474 {
475         const struct pcl711_board *board = comedi_board(dev);
476         struct pcl711_private *devpriv;
477         struct comedi_subdevice *s;
478         int ret;
479
480         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
481         if (!devpriv)
482                 return -ENOMEM;
483
484         ret = comedi_request_region(dev, it->options[0], 0x10);
485         if (ret)
486                 return ret;
487
488         if (it->options[1] && it->options[1] <= board->maxirq) {
489                 ret = request_irq(it->options[1], pcl711_interrupt, 0,
490                                   dev->board_name, dev);
491                 if (ret == 0)
492                         dev->irq = it->options[1];
493         }
494
495         ret = comedi_alloc_subdevices(dev, 4);
496         if (ret)
497                 return ret;
498
499         /* Analog Input subdevice */
500         s = &dev->subdevices[0];
501         s->type         = COMEDI_SUBD_AI;
502         s->subdev_flags = SDF_READABLE | SDF_GROUND;
503         if (board->n_aichan > 8)
504                 s->subdev_flags |= SDF_DIFF;
505         s->n_chan       = board->n_aichan;
506         s->maxdata      = 0xfff;
507         s->range_table  = board->ai_range_type;
508         s->insn_read    = pcl711_ai_insn_read;
509         if (dev->irq) {
510                 dev->read_subdev = s;
511                 s->subdev_flags |= SDF_CMD_READ;
512                 s->len_chanlist = 1;
513                 s->do_cmdtest   = pcl711_ai_cmdtest;
514                 s->do_cmd       = pcl711_ai_cmd;
515         }
516
517         /* Analog Output subdevice */
518         s = &dev->subdevices[1];
519         s->type         = COMEDI_SUBD_AO;
520         s->subdev_flags = SDF_WRITABLE;
521         s->n_chan       = board->n_aochan;
522         s->maxdata      = 0xfff;
523         s->range_table  = &range_bipolar5;
524         s->insn_write   = pcl711_ao_insn_write;
525         s->insn_read    = pcl711_ao_insn_read;
526
527         /* Digital Input subdevice */
528         s = &dev->subdevices[2];
529         s->type         = COMEDI_SUBD_DI;
530         s->subdev_flags = SDF_READABLE;
531         s->n_chan       = 16;
532         s->maxdata      = 1;
533         s->range_table  = &range_digital;
534         s->insn_bits    = pcl711_di_insn_bits;
535
536         /* Digital Output subdevice */
537         s = &dev->subdevices[3];
538         s->type         = COMEDI_SUBD_DO;
539         s->subdev_flags = SDF_WRITABLE;
540         s->n_chan       = 16;
541         s->maxdata      = 1;
542         s->range_table  = &range_digital;
543         s->insn_bits    = pcl711_do_insn_bits;
544
545         /* clear DAC */
546         pcl711_ao_write(dev, 0, 0x0);
547         pcl711_ao_write(dev, 1, 0x0);
548
549         return 0;
550 }
551
552 static struct comedi_driver pcl711_driver = {
553         .driver_name    = "pcl711",
554         .module         = THIS_MODULE,
555         .attach         = pcl711_attach,
556         .detach         = comedi_legacy_detach,
557         .board_name     = &boardtypes[0].name,
558         .num_names      = ARRAY_SIZE(boardtypes),
559         .offset         = sizeof(struct pcl711_board),
560 };
561 module_comedi_driver(pcl711_driver);
562
563 MODULE_AUTHOR("Comedi http://www.comedi.org");
564 MODULE_DESCRIPTION("Comedi low-level driver");
565 MODULE_LICENSE("GPL");