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