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