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