Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
pcl711.c
Go to the documentation of this file.
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 <[email protected]>
8  Janne Jalkanen <[email protected]>
9  Eric Bunn <[email protected]>
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  You should have received a copy of the GNU General Public License
22  along with this program; if not, write to the Free Software
23  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 
25  */
26 /*
27 Driver: pcl711
28 Description: Advantech PCL-711 and 711b, ADLink ACL-8112
29 Author: ds, Janne Jalkanen <[email protected]>, Eric Bunn <[email protected]>
30 Status: mostly complete
31 Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
32  [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
33 
34 Since these boards do not have DMA or FIFOs, only immediate mode is
35 supported.
36 
37 */
38 
39 /*
40  Dave Andruczyk <[email protected]> also wrote a
41  driver for the PCL-711. I used a few ideas from his driver
42  here. His driver also has more comments, if you are
43  interested in understanding how this driver works.
44  http://tech.buffalostate.edu/~dave/driver/
45 
46  The ACL-8112 driver was hacked from the sources of the PCL-711
47  driver (the 744 chip used on the 8112 is almost the same as
48  the 711b chip, but it has more I/O channels) by
49  Janne Jalkanen ([email protected]) and
50  Erik Bunn ([email protected]). Remerged with the PCL-711 driver
51  by ds.
52 
53  [acl-8112]
54  This driver supports both TRIGNOW and TRIGCLK,
55  but does not yet support DMA transfers. It also supports
56  both high (HG) and low (DG) versions of the card, though
57  the HG version has been untested.
58 
59  */
60 
61 #include <linux/interrupt.h>
62 #include "../comedidev.h"
63 
64 #include <linux/ioport.h>
65 #include <linux/delay.h>
66 
67 #include "comedi_fc.h"
68 #include "8253.h"
69 
70 #define PCL711_SIZE 16
71 
72 #define PCL711_CTR0 0
73 #define PCL711_CTR1 1
74 #define PCL711_CTR2 2
75 #define PCL711_CTRCTL 3
76 #define PCL711_AD_LO 4
77 #define PCL711_DA0_LO 4
78 #define PCL711_AD_HI 5
79 #define PCL711_DA0_HI 5
80 #define PCL711_DI_LO 6
81 #define PCL711_DA1_LO 6
82 #define PCL711_DI_HI 7
83 #define PCL711_DA1_HI 7
84 #define PCL711_CLRINTR 8
85 #define PCL711_GAIN 9
86 #define PCL711_MUX 10
87 #define PCL711_MODE 11
88 #define PCL711_SOFTTRIG 12
89 #define PCL711_DO_LO 13
90 #define PCL711_DO_HI 14
91 
92 static const struct comedi_lrange range_pcl711b_ai = { 5, {
93  BIP_RANGE(5),
94  BIP_RANGE(2.5),
95  BIP_RANGE(1.25),
96  BIP_RANGE(0.625),
97  BIP_RANGE(0.3125)
98  }
99 };
100 
101 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
102  BIP_RANGE(5),
103  BIP_RANGE(0.5),
104  BIP_RANGE(0.05),
105  BIP_RANGE(0.005),
106  UNI_RANGE(10),
107  UNI_RANGE(1),
108  UNI_RANGE(0.1),
109  UNI_RANGE(0.01),
110  BIP_RANGE(10),
111  BIP_RANGE(1),
112  BIP_RANGE(0.1),
113  BIP_RANGE(0.01)
114  }
115 };
116 
117 static const struct comedi_lrange range_acl8112dg_ai = { 9, {
118  BIP_RANGE(5),
119  BIP_RANGE(2.5),
120  BIP_RANGE(1.25),
121  BIP_RANGE(0.625),
122  UNI_RANGE(10),
123  UNI_RANGE(5),
124  UNI_RANGE(2.5),
125  UNI_RANGE(1.25),
126  BIP_RANGE(10)
127  }
128 };
129 
130 /*
131  * flags
132  */
133 
134 #define PCL711_TIMEOUT 100
135 #define PCL711_DRDY 0x10
136 
137 static const int i8253_osc_base = 500; /* 2 Mhz */
138 
139 struct pcl711_board {
140 
141  const char *name;
143  int is_8112;
144  int is_dg;
145  int n_ranges;
146  int n_aichan;
147  int n_aochan;
148  int maxirq;
150 };
151 
153 
154  int board;
155  int adchan;
156  int ntrig;
157  int aip[8];
158  int mode;
159  unsigned int ao_readback[2];
160  unsigned int divisor1;
161  unsigned int divisor2;
162 };
163 
164 #define devpriv ((struct pcl711_private *)dev->private)
165 
166 static irqreturn_t pcl711_interrupt(int irq, void *d)
167 {
168  int lo, hi;
169  int data;
170  struct comedi_device *dev = d;
171  const struct pcl711_board *board = comedi_board(dev);
172  struct comedi_subdevice *s = &dev->subdevices[0];
173 
174  if (!dev->attached) {
175  comedi_error(dev, "spurious interrupt");
176  return IRQ_HANDLED;
177  }
178 
179  hi = inb(dev->iobase + PCL711_AD_HI);
180  lo = inb(dev->iobase + PCL711_AD_LO);
181  outb(0, dev->iobase + PCL711_CLRINTR);
182 
183  data = (hi << 8) | lo;
184 
185  /* FIXME! Nothing else sets ntrig! */
186  if (!(--devpriv->ntrig)) {
187  if (board->is_8112)
188  outb(1, dev->iobase + PCL711_MODE);
189  else
190  outb(0, dev->iobase + PCL711_MODE);
191 
192  s->async->events |= COMEDI_CB_EOA;
193  }
194  comedi_event(dev, s);
195  return IRQ_HANDLED;
196 }
197 
198 static void pcl711_set_changain(struct comedi_device *dev, int chan)
199 {
200  const struct pcl711_board *board = comedi_board(dev);
201  int chan_register;
202 
203  outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
204 
205  chan_register = CR_CHAN(chan);
206 
207  if (board->is_8112) {
208 
209  /*
210  * Set the correct channel. The two channel banks are switched
211  * using the mask value.
212  * NB: To use differential channels, you should use
213  * mask = 0x30, but I haven't written the support for this
214  * yet. /JJ
215  */
216 
217  if (chan_register >= 8)
218  chan_register = 0x20 | (chan_register & 0x7);
219  else
220  chan_register |= 0x10;
221  } else {
222  outb(chan_register, dev->iobase + PCL711_MUX);
223  }
224 }
225 
226 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
227  struct comedi_insn *insn, unsigned int *data)
228 {
229  const struct pcl711_board *board = comedi_board(dev);
230  int i, n;
231  int hi, lo;
232 
233  pcl711_set_changain(dev, insn->chanspec);
234 
235  for (n = 0; n < insn->n; n++) {
236  /*
237  * Write the correct mode (software polling) and start polling
238  * by writing to the trigger register
239  */
240  outb(1, dev->iobase + PCL711_MODE);
241 
242  if (!board->is_8112)
243  outb(0, dev->iobase + PCL711_SOFTTRIG);
244 
245  i = PCL711_TIMEOUT;
246  while (--i) {
247  hi = inb(dev->iobase + PCL711_AD_HI);
248  if (!(hi & PCL711_DRDY))
249  goto ok;
250  udelay(1);
251  }
252  printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
253  return -ETIME;
254 
255 ok:
256  lo = inb(dev->iobase + PCL711_AD_LO);
257 
258  data[n] = ((hi & 0xf) << 8) | lo;
259  }
260 
261  return n;
262 }
263 
264 static int pcl711_ai_cmdtest(struct comedi_device *dev,
265  struct comedi_subdevice *s, struct comedi_cmd *cmd)
266 {
267  int tmp;
268  int err = 0;
269 
270  /* Step 1 : check if triggers are trivially valid */
271 
272  err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
273  err |= cfc_check_trigger_src(&cmd->scan_begin_src,
274  TRIG_TIMER | TRIG_EXT);
275  err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
276  err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
277  err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
278 
279  if (err)
280  return 1;
281 
282  /* Step 2a : make sure trigger sources are unique */
283 
284  err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
285  err |= cfc_check_trigger_is_unique(cmd->stop_src);
286 
287  /* Step 2b : and mutually compatible */
288 
289  if (err)
290  return 2;
291 
292  /* step 3 */
293 
294  if (cmd->start_arg != 0) {
295  cmd->start_arg = 0;
296  err++;
297  }
298  if (cmd->scan_begin_src == TRIG_EXT) {
299  if (cmd->scan_begin_arg != 0) {
300  cmd->scan_begin_arg = 0;
301  err++;
302  }
303  } else {
304 #define MAX_SPEED 1000
305 #define TIMER_BASE 100
306  if (cmd->scan_begin_arg < MAX_SPEED) {
307  cmd->scan_begin_arg = MAX_SPEED;
308  err++;
309  }
310  }
311  if (cmd->convert_arg != 0) {
312  cmd->convert_arg = 0;
313  err++;
314  }
315  if (cmd->scan_end_arg != cmd->chanlist_len) {
316  cmd->scan_end_arg = cmd->chanlist_len;
317  err++;
318  }
319  if (cmd->stop_src == TRIG_NONE) {
320  if (cmd->stop_arg != 0) {
321  cmd->stop_arg = 0;
322  err++;
323  }
324  } else {
325  /* ignore */
326  }
327 
328  if (err)
329  return 3;
330 
331  /* step 4 */
332 
333  if (cmd->scan_begin_src == TRIG_TIMER) {
334  tmp = cmd->scan_begin_arg;
335  i8253_cascade_ns_to_timer_2div(TIMER_BASE,
336  &devpriv->divisor1,
337  &devpriv->divisor2,
338  &cmd->scan_begin_arg,
339  cmd->flags & TRIG_ROUND_MASK);
340  if (tmp != cmd->scan_begin_arg)
341  err++;
342  }
343 
344  if (err)
345  return 4;
346 
347  return 0;
348 }
349 
350 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
351 {
352  int timer1, timer2;
353  struct comedi_cmd *cmd = &s->async->cmd;
354 
355  pcl711_set_changain(dev, cmd->chanlist[0]);
356 
357  if (cmd->scan_begin_src == TRIG_TIMER) {
358  /*
359  * Set timers
360  * timer chip is an 8253, with timers 1 and 2
361  * cascaded
362  * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
363  * Mode 2 = Rate generator
364  *
365  * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
366  */
367 
368  timer1 = timer2 = 0;
369  i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
370  &cmd->scan_begin_arg,
372 
373  outb(0x74, dev->iobase + PCL711_CTRCTL);
374  outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
375  outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
376  outb(0xb4, dev->iobase + PCL711_CTRCTL);
377  outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
378  outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
379 
380  /* clear pending interrupts (just in case) */
381  outb(0, dev->iobase + PCL711_CLRINTR);
382 
383  /*
384  * Set mode to IRQ transfer
385  */
386  outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
387  } else {
388  /* external trigger */
389  outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
390  }
391 
392  return 0;
393 }
394 
395 /*
396  analog output
397 */
398 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
399  struct comedi_insn *insn, unsigned int *data)
400 {
401  int n;
402  int chan = CR_CHAN(insn->chanspec);
403 
404  for (n = 0; n < insn->n; n++) {
405  outb((data[n] & 0xff),
406  dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
407  outb((data[n] >> 8),
408  dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
409 
410  devpriv->ao_readback[chan] = data[n];
411  }
412 
413  return n;
414 }
415 
416 static int pcl711_ao_insn_read(struct comedi_device *dev,
417  struct comedi_subdevice *s,
418  struct comedi_insn *insn, unsigned int *data)
419 {
420  int n;
421  int chan = CR_CHAN(insn->chanspec);
422 
423  for (n = 0; n < insn->n; n++)
424  data[n] = devpriv->ao_readback[chan];
425 
426  return n;
427 
428 }
429 
430 /* Digital port read - Untested on 8112 */
431 static int pcl711_di_insn_bits(struct comedi_device *dev,
432  struct comedi_subdevice *s,
433  struct comedi_insn *insn, unsigned int *data)
434 {
435  data[1] = inb(dev->iobase + PCL711_DI_LO) |
436  (inb(dev->iobase + PCL711_DI_HI) << 8);
437 
438  return insn->n;
439 }
440 
441 /* Digital port write - Untested on 8112 */
442 static int pcl711_do_insn_bits(struct comedi_device *dev,
443  struct comedi_subdevice *s,
444  struct comedi_insn *insn, unsigned int *data)
445 {
446  if (data[0]) {
447  s->state &= ~data[0];
448  s->state |= data[0] & data[1];
449  }
450  if (data[0] & 0x00ff)
451  outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
452  if (data[0] & 0xff00)
453  outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
454 
455  data[1] = s->state;
456 
457  return insn->n;
458 }
459 
460 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
461 {
462  const struct pcl711_board *board = comedi_board(dev);
463  int ret;
464  unsigned long iobase;
465  unsigned int irq;
466  struct comedi_subdevice *s;
467 
468  /* claim our I/O space */
469 
470  iobase = it->options[0];
471  printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
472  if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
473  printk("I/O port conflict\n");
474  return -EIO;
475  }
476  dev->iobase = iobase;
477 
478  /* there should be a sanity check here */
479 
480  dev->board_name = board->name;
481 
482  /* grab our IRQ */
483  irq = it->options[1];
484  if (irq > board->maxirq) {
485  printk(KERN_ERR "irq out of range\n");
486  return -EINVAL;
487  }
488  if (irq) {
489  if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
490  printk(KERN_ERR "unable to allocate irq %u\n", irq);
491  return -EINVAL;
492  } else {
493  printk(KERN_INFO "( irq = %u )\n", irq);
494  }
495  }
496  dev->irq = irq;
497 
498  ret = comedi_alloc_subdevices(dev, 4);
499  if (ret)
500  return ret;
501 
502  ret = alloc_private(dev, sizeof(struct pcl711_private));
503  if (ret < 0)
504  return ret;
505 
506  s = &dev->subdevices[0];
507  /* AI subdevice */
508  s->type = COMEDI_SUBD_AI;
510  s->n_chan = board->n_aichan;
511  s->maxdata = 0xfff;
512  s->len_chanlist = 1;
513  s->range_table = board->ai_range_type;
514  s->insn_read = pcl711_ai_insn;
515  if (irq) {
516  dev->read_subdev = s;
518  s->do_cmdtest = pcl711_ai_cmdtest;
519  s->do_cmd = pcl711_ai_cmd;
520  }
521 
522  s = &dev->subdevices[1];
523  /* AO subdevice */
524  s->type = COMEDI_SUBD_AO;
526  s->n_chan = board->n_aochan;
527  s->maxdata = 0xfff;
528  s->len_chanlist = 1;
530  s->insn_write = pcl711_ao_insn;
531  s->insn_read = pcl711_ao_insn_read;
532 
533  s = &dev->subdevices[2];
534  /* 16-bit digital input */
535  s->type = COMEDI_SUBD_DI;
537  s->n_chan = 16;
538  s->maxdata = 1;
539  s->len_chanlist = 16;
541  s->insn_bits = pcl711_di_insn_bits;
542 
543  s = &dev->subdevices[3];
544  /* 16-bit digital out */
545  s->type = COMEDI_SUBD_DO;
547  s->n_chan = 16;
548  s->maxdata = 1;
549  s->len_chanlist = 16;
551  s->state = 0;
552  s->insn_bits = pcl711_do_insn_bits;
553 
554  /*
555  this is the "base value" for the mode register, which is
556  used for the irq on the PCL711
557  */
558  if (board->is_pcl711b)
559  devpriv->mode = (dev->irq << 4);
560 
561  /* clear DAC */
562  outb(0, dev->iobase + PCL711_DA0_LO);
563  outb(0, dev->iobase + PCL711_DA0_HI);
564  outb(0, dev->iobase + PCL711_DA1_LO);
565  outb(0, dev->iobase + PCL711_DA1_HI);
566 
567  printk(KERN_INFO "\n");
568 
569  return 0;
570 }
571 
572 static void pcl711_detach(struct comedi_device *dev)
573 {
574  if (dev->irq)
575  free_irq(dev->irq, dev);
576  if (dev->iobase)
578 }
579 
580 static const struct pcl711_board boardtypes[] = {
581  { "pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5 },
582  { "pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai },
583  { "acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai },
584  { "acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai },
585 };
586 
587 static struct comedi_driver pcl711_driver = {
588  .driver_name = "pcl711",
589  .module = THIS_MODULE,
590  .attach = pcl711_attach,
591  .detach = pcl711_detach,
592  .board_name = &boardtypes[0].name,
593  .num_names = ARRAY_SIZE(boardtypes),
594  .offset = sizeof(struct pcl711_board),
595 };
596 module_comedi_driver(pcl711_driver);
597 
598 MODULE_AUTHOR("Comedi http://www.comedi.org");
599 MODULE_DESCRIPTION("Comedi low-level driver");
600 MODULE_LICENSE("GPL");