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