]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/pcl711.c
staging: comedi: pcl711: no need to calc the timer values twice
[karo-tx-linux.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 struct pcl711_board {
138         const char *name;
139         int n_aichan;
140         int n_aochan;
141         int maxirq;
142         const struct comedi_lrange *ai_range_type;
143 };
144
145 static const struct pcl711_board boardtypes[] = {
146         {
147                 .name           = "pcl711",
148                 .n_aichan       = 8,
149                 .n_aochan       = 1,
150                 .ai_range_type  = &range_bipolar5,
151         }, {
152                 .name           = "pcl711b",
153                 .n_aichan       = 8,
154                 .n_aochan       = 1,
155                 .maxirq         = 7,
156                 .ai_range_type  = &range_pcl711b_ai,
157         }, {
158                 .name           = "acl8112hg",
159                 .n_aichan       = 16,
160                 .n_aochan       = 2,
161                 .maxirq         = 15,
162                 .ai_range_type  = &range_acl8112hg_ai,
163         }, {
164                 .name           = "acl8112dg",
165                 .n_aichan       = 16,
166                 .n_aochan       = 2,
167                 .maxirq         = 15,
168                 .ai_range_type  = &range_acl8112dg_ai,
169         },
170 };
171
172 struct pcl711_private {
173         int ntrig;
174         unsigned int ao_readback[2];
175         unsigned int divisor1;
176         unsigned int divisor2;
177 };
178
179 static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
180 {
181         /*
182          * The pcl711b board uses bits in the mode register to select the
183          * interrupt. The other boards supported by this driver all use
184          * jumpers on the board.
185          *
186          * Enables the interrupt when needed on the pcl711b board. These
187          * bits do nothing on the other boards.
188          */
189         if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
190                 mode |= PCL711_MODE_IRQ(dev->irq);
191
192         outb(mode, dev->iobase + PCL711_MODE_REG);
193 }
194
195 static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
196                                          struct comedi_subdevice *s)
197 {
198         unsigned int val;
199
200         val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
201         val |= inb(dev->iobase + PCL711_AI_LSB_REG);
202
203         return val & s->maxdata;
204 }
205
206 static irqreturn_t pcl711_interrupt(int irq, void *d)
207 {
208         struct comedi_device *dev = d;
209         struct pcl711_private *devpriv = dev->private;
210         struct comedi_subdevice *s = dev->read_subdev;
211         unsigned int data;
212
213         if (!dev->attached) {
214                 comedi_error(dev, "spurious interrupt");
215                 return IRQ_HANDLED;
216         }
217
218         data = pcl711_ai_get_sample(dev, s);
219
220         outb(0, dev->iobase + PCL711_CLRINTR);
221
222         /* FIXME! Nothing else sets ntrig! */
223         if (!(--devpriv->ntrig)) {
224                 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
225
226                 s->async->events |= COMEDI_CB_EOA;
227         }
228         comedi_event(dev, s);
229         return IRQ_HANDLED;
230 }
231
232 static void pcl711_set_changain(struct comedi_device *dev,
233                                 struct comedi_subdevice *s,
234                                 unsigned int chanspec)
235 {
236         unsigned int chan = CR_CHAN(chanspec);
237         unsigned int range = CR_RANGE(chanspec);
238         unsigned int aref = CR_AREF(chanspec);
239         unsigned int mux = 0;
240
241         outb(range, dev->iobase + PCL711_GAIN);
242
243         if (s->n_chan > 8) {
244                 /* Select the correct MPC508A chip */
245                 if (aref == AREF_DIFF) {
246                         chan &= 0x7;
247                         mux |= PCL711_MUX_DIFF;
248                 } else {
249                         if (chan < 8)
250                                 mux |= PCL711_MUX_CS0;
251                         else
252                                 mux |= PCL711_MUX_CS1;
253                 }
254         }
255         outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
256 }
257
258 static int pcl711_ai_wait_for_eoc(struct comedi_device *dev,
259                                   unsigned int timeout)
260 {
261         unsigned int msb;
262
263         while (timeout--) {
264                 msb = inb(dev->iobase + PCL711_AI_MSB_REG);
265                 if ((msb & PCL711_AI_MSB_DRDY) == 0)
266                         return 0;
267                 udelay(1);
268         }
269         return -ETIME;
270 }
271
272 static int pcl711_ai_insn_read(struct comedi_device *dev,
273                                struct comedi_subdevice *s,
274                                struct comedi_insn *insn,
275                                unsigned int *data)
276 {
277         int ret;
278         int i;
279
280         pcl711_set_changain(dev, s, insn->chanspec);
281
282         pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
283
284         for (i = 0; i < insn->n; i++) {
285                 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
286
287                 ret = pcl711_ai_wait_for_eoc(dev, 100);
288                 if (ret)
289                         return ret;
290
291                 data[i] = pcl711_ai_get_sample(dev, s);
292         }
293
294         return insn->n;
295 }
296
297 static int pcl711_ai_cmdtest(struct comedi_device *dev,
298                              struct comedi_subdevice *s, struct comedi_cmd *cmd)
299 {
300         struct pcl711_private *devpriv = dev->private;
301         int tmp;
302         int err = 0;
303
304         /* Step 1 : check if triggers are trivially valid */
305
306         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
307         err |= cfc_check_trigger_src(&cmd->scan_begin_src,
308                                         TRIG_TIMER | TRIG_EXT);
309         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
310         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
311         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
312
313         if (err)
314                 return 1;
315
316         /* Step 2a : make sure trigger sources are unique */
317
318         err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
319         err |= cfc_check_trigger_is_unique(cmd->stop_src);
320
321         /* Step 2b : and mutually compatible */
322
323         if (err)
324                 return 2;
325
326         /* Step 3: check if arguments are trivially valid */
327
328         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
329
330         if (cmd->scan_begin_src == TRIG_EXT) {
331                 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
332         } else {
333 #define MAX_SPEED 1000
334                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
335                                                  MAX_SPEED);
336         }
337
338         err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
339         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
340
341         if (cmd->stop_src == TRIG_NONE) {
342                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
343         } else {
344                 /* ignore */
345         }
346
347         if (err)
348                 return 3;
349
350         /* step 4 */
351
352         if (cmd->scan_begin_src == TRIG_TIMER) {
353                 tmp = cmd->scan_begin_arg;
354                 i8253_cascade_ns_to_timer(500,          /* 2 Mhz */
355                                           &devpriv->divisor1,
356                                           &devpriv->divisor2,
357                                           &cmd->scan_begin_arg,
358                                           cmd->flags & TRIG_ROUND_MASK);
359                 if (tmp != cmd->scan_begin_arg)
360                         err++;
361         }
362
363         if (err)
364                 return 4;
365
366         return 0;
367 }
368
369 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
370 {
371         struct pcl711_private *devpriv = dev->private;
372         struct comedi_cmd *cmd = &s->async->cmd;
373
374         pcl711_set_changain(dev, s, cmd->chanlist[0]);
375
376         if (cmd->scan_begin_src == TRIG_TIMER) {
377                 i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
378                            1, devpriv->divisor1, I8254_MODE2 | I8254_BINARY);
379                 i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
380                            2, devpriv->divisor2, I8254_MODE2 | I8254_BINARY);
381
382                 /* clear pending interrupts (just in case) */
383                 outb(0, dev->iobase + PCL711_CLRINTR);
384
385                 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
386         } else {
387                 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
388         }
389
390         return 0;
391 }
392
393 static void pcl711_ao_write(struct comedi_device *dev,
394                             unsigned int chan, unsigned int val)
395 {
396         outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
397         outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
398 }
399
400 static int pcl711_ao_insn_write(struct comedi_device *dev,
401                                 struct comedi_subdevice *s,
402                                 struct comedi_insn *insn,
403                                 unsigned int *data)
404 {
405         struct pcl711_private *devpriv = dev->private;
406         unsigned int chan = CR_CHAN(insn->chanspec);
407         unsigned int val = devpriv->ao_readback[chan];
408         int i;
409
410         for (i = 0; i < insn->n; i++) {
411                 val = data[i];
412                 pcl711_ao_write(dev, chan, val);
413         }
414         devpriv->ao_readback[chan] = val;
415
416         return insn->n;
417 }
418
419 static int pcl711_ao_insn_read(struct comedi_device *dev,
420                                struct comedi_subdevice *s,
421                                struct comedi_insn *insn,
422                                unsigned int *data)
423 {
424         struct pcl711_private *devpriv = dev->private;
425         unsigned int chan = CR_CHAN(insn->chanspec);
426         int i;
427
428         for (i = 0; i < insn->n; i++)
429                 data[i] = devpriv->ao_readback[chan];
430
431         return insn->n;
432 }
433
434 /* Digital port read - Untested on 8112 */
435 static int pcl711_di_insn_bits(struct comedi_device *dev,
436                                struct comedi_subdevice *s,
437                                struct comedi_insn *insn, unsigned int *data)
438 {
439         data[1] = inb(dev->iobase + PCL711_DI_LO) |
440             (inb(dev->iobase + PCL711_DI_HI) << 8);
441
442         return insn->n;
443 }
444
445 static int pcl711_do_insn_bits(struct comedi_device *dev,
446                                struct comedi_subdevice *s,
447                                struct comedi_insn *insn,
448                                unsigned int *data)
449 {
450         unsigned int mask;
451
452         mask = comedi_dio_update_state(s, data);
453         if (mask) {
454                 if (mask & 0x00ff)
455                         outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
456                 if (mask & 0xff00)
457                         outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
458         }
459
460         data[1] = s->state;
461
462         return insn->n;
463 }
464
465 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
466 {
467         const struct pcl711_board *board = comedi_board(dev);
468         struct pcl711_private *devpriv;
469         struct comedi_subdevice *s;
470         int ret;
471
472         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
473         if (!devpriv)
474                 return -ENOMEM;
475
476         ret = comedi_request_region(dev, it->options[0], 0x10);
477         if (ret)
478                 return ret;
479
480         if (it->options[1] && it->options[1] <= board->maxirq) {
481                 ret = request_irq(it->options[1], pcl711_interrupt, 0,
482                                   dev->board_name, dev);
483                 if (ret == 0)
484                         dev->irq = it->options[1];
485         }
486
487         ret = comedi_alloc_subdevices(dev, 4);
488         if (ret)
489                 return ret;
490
491         /* Analog Input subdevice */
492         s = &dev->subdevices[0];
493         s->type         = COMEDI_SUBD_AI;
494         s->subdev_flags = SDF_READABLE | SDF_GROUND;
495         if (board->n_aichan > 8)
496                 s->subdev_flags |= SDF_DIFF;
497         s->n_chan       = board->n_aichan;
498         s->maxdata      = 0xfff;
499         s->range_table  = board->ai_range_type;
500         s->insn_read    = pcl711_ai_insn_read;
501         if (dev->irq) {
502                 dev->read_subdev = s;
503                 s->subdev_flags |= SDF_CMD_READ;
504                 s->len_chanlist = 1;
505                 s->do_cmdtest   = pcl711_ai_cmdtest;
506                 s->do_cmd       = pcl711_ai_cmd;
507         }
508
509         /* Analog Output subdevice */
510         s = &dev->subdevices[1];
511         s->type         = COMEDI_SUBD_AO;
512         s->subdev_flags = SDF_WRITABLE;
513         s->n_chan       = board->n_aochan;
514         s->maxdata      = 0xfff;
515         s->range_table  = &range_bipolar5;
516         s->insn_write   = pcl711_ao_insn_write;
517         s->insn_read    = pcl711_ao_insn_read;
518
519         /* Digital Input subdevice */
520         s = &dev->subdevices[2];
521         s->type         = COMEDI_SUBD_DI;
522         s->subdev_flags = SDF_READABLE;
523         s->n_chan       = 16;
524         s->maxdata      = 1;
525         s->range_table  = &range_digital;
526         s->insn_bits    = pcl711_di_insn_bits;
527
528         /* Digital Output subdevice */
529         s = &dev->subdevices[3];
530         s->type         = COMEDI_SUBD_DO;
531         s->subdev_flags = SDF_WRITABLE;
532         s->n_chan       = 16;
533         s->maxdata      = 1;
534         s->range_table  = &range_digital;
535         s->insn_bits    = pcl711_do_insn_bits;
536
537         /* clear DAC */
538         pcl711_ao_write(dev, 0, 0x0);
539         pcl711_ao_write(dev, 1, 0x0);
540
541         return 0;
542 }
543
544 static struct comedi_driver pcl711_driver = {
545         .driver_name    = "pcl711",
546         .module         = THIS_MODULE,
547         .attach         = pcl711_attach,
548         .detach         = comedi_legacy_detach,
549         .board_name     = &boardtypes[0].name,
550         .num_names      = ARRAY_SIZE(boardtypes),
551         .offset         = sizeof(struct pcl711_board),
552 };
553 module_comedi_driver(pcl711_driver);
554
555 MODULE_AUTHOR("Comedi http://www.comedi.org");
556 MODULE_DESCRIPTION("Comedi low-level driver");
557 MODULE_LICENSE("GPL");