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