1 /*
2     comedi/drivers/pcl726.c
3 
4     hardware driver for Advantech cards:
5      card:   PCL-726, PCL-727, PCL-728
6      driver: pcl726,  pcl727,  pcl728
7     and for ADLink cards:
8      card:   ACL-6126, ACL-6128
9      driver: acl6126,  acl6128
10 
11     COMEDI - Linux Control and Measurement Device Interface
12     Copyright (C) 1998 David A. Schleef <ds@schleef.org>
13 
14     This program is free software; you can redistribute it and/or modify
15     it under the terms of the GNU General Public License as published by
16     the Free Software Foundation; either version 2 of the License, or
17     (at your option) any later version.
18 
19     This program is distributed in the hope that it will be useful,
20     but WITHOUT ANY WARRANTY; without even the implied warranty of
21     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22     GNU General Public License for more details.
23 
24     You should have received a copy of the GNU General Public License
25     along with this program; if not, write to the Free Software
26     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 
28 */
29 /*
30 Driver: pcl726
31 Description: Advantech PCL-726 & compatibles
32 Author: ds
33 Status: untested
34 Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728),
35   [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128)
36 
37 Interrupts are not supported.
38 
39     Options for PCL-726:
40      [0] - IO Base
41      [2]...[7] - D/A output range for channel 1-6:
42 		0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V,
43 		4: 4-20mA, 5: unknown (external reference)
44 
45     Options for PCL-727:
46      [0] - IO Base
47      [2]...[13] - D/A output range for channel 1-12:
48 		0: 0-5V, 1: 0-10V, 2: +/-5V,
49 		3: 4-20mA
50 
51     Options for PCL-728 and ACL-6128:
52      [0] - IO Base
53      [2], [3] - D/A output range for channel 1 and 2:
54 		0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V,
55 		4: 4-20mA, 5: 0-20mA
56 
57     Options for ACL-6126:
58      [0] - IO Base
59      [1] - IRQ (0=disable, 3, 5, 6, 7, 9, 10, 11, 12, 15) (currently ignored)
60      [2]...[7] - D/A output range for channel 1-6:
61 		0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V,
62 		4: 4-20mA
63 */
64 
65 /*
66     Thanks to Circuit Specialists for having programming info (!) on
67     their web page.  (http://www.cir.com/)
68 */
69 
70 #include "../comedidev.h"
71 
72 #include <linux/ioport.h>
73 
74 #undef ACL6126_IRQ		/* no interrupt support (yet) */
75 
76 #define PCL726_SIZE 16
77 #define PCL727_SIZE 32
78 #define PCL728_SIZE 8
79 
80 #define PCL726_DAC0_HI 0
81 #define PCL726_DAC0_LO 1
82 
83 #define PCL726_DO_HI 12
84 #define PCL726_DO_LO 13
85 #define PCL726_DI_HI 14
86 #define PCL726_DI_LO 15
87 
88 #define PCL727_DO_HI 24
89 #define PCL727_DO_LO 25
90 #define PCL727_DI_HI  0
91 #define PCL727_DI_LO  1
92 
93 static const struct comedi_lrange range_4_20mA = { 1, {RANGE_mA(4, 20)} };
94 static const struct comedi_lrange range_0_20mA = { 1, {RANGE_mA(0, 20)} };
95 
96 static const struct comedi_lrange *const rangelist_726[] = {
97 	&range_unipolar5, &range_unipolar10,
98 	&range_bipolar5, &range_bipolar10,
99 	&range_4_20mA, &range_unknown
100 };
101 
102 static const struct comedi_lrange *const rangelist_727[] = {
103 	&range_unipolar5, &range_unipolar10,
104 	&range_bipolar5,
105 	&range_4_20mA
106 };
107 
108 static const struct comedi_lrange *const rangelist_728[] = {
109 	&range_unipolar5, &range_unipolar10,
110 	&range_bipolar5, &range_bipolar10,
111 	&range_4_20mA, &range_0_20mA
112 };
113 
114 static int pcl726_attach(struct comedi_device *dev,
115 			 struct comedi_devconfig *it);
116 static int pcl726_detach(struct comedi_device *dev);
117 
118 struct pcl726_board {
119 
120 	const char *name;	/*  driver name */
121 	int n_aochan;		/*  num of D/A chans */
122 	int num_of_ranges;	/*  num of ranges */
123 	unsigned int IRQbits;	/*  allowed interrupts */
124 	unsigned int io_range;	/*  len of IO space */
125 	char have_dio;		/*  1=card have DI/DO ports */
126 	int di_hi;		/*  ports for DI/DO operations */
127 	int di_lo;
128 	int do_hi;
129 	int do_lo;
130 	const struct comedi_lrange *const *range_type_list;
131 	/*  list of supported ranges */
132 };
133 
134 static const struct pcl726_board boardtypes[] = {
135 	{"pcl726", 6, 6, 0x0000, PCL726_SIZE, 1,
136 	 PCL726_DI_HI, PCL726_DI_LO, PCL726_DO_HI, PCL726_DO_LO,
137 	 &rangelist_726[0],},
138 	{"pcl727", 12, 4, 0x0000, PCL727_SIZE, 1,
139 	 PCL727_DI_HI, PCL727_DI_LO, PCL727_DO_HI, PCL727_DO_LO,
140 	 &rangelist_727[0],},
141 	{"pcl728", 2, 6, 0x0000, PCL728_SIZE, 0,
142 	 0, 0, 0, 0,
143 	 &rangelist_728[0],},
144 	{"acl6126", 6, 5, 0x96e8, PCL726_SIZE, 1,
145 	 PCL726_DI_HI, PCL726_DI_LO, PCL726_DO_HI, PCL726_DO_LO,
146 	 &rangelist_726[0],},
147 	{"acl6128", 2, 6, 0x0000, PCL728_SIZE, 0,
148 	 0, 0, 0, 0,
149 	 &rangelist_728[0],},
150 };
151 
152 #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl726_board))
153 #define this_board ((const struct pcl726_board *)dev->board_ptr)
154 
155 static struct comedi_driver driver_pcl726 = {
156 	.driver_name = "pcl726",
157 	.module = THIS_MODULE,
158 	.attach = pcl726_attach,
159 	.detach = pcl726_detach,
160 	.board_name = &boardtypes[0].name,
161 	.num_names = n_boardtypes,
162 	.offset = sizeof(struct pcl726_board),
163 };
164 
driver_pcl726_init_module(void)165 static int __init driver_pcl726_init_module(void)
166 {
167 	return comedi_driver_register(&driver_pcl726);
168 }
169 
driver_pcl726_cleanup_module(void)170 static void __exit driver_pcl726_cleanup_module(void)
171 {
172 	comedi_driver_unregister(&driver_pcl726);
173 }
174 
175 module_init(driver_pcl726_init_module);
176 module_exit(driver_pcl726_cleanup_module);
177 
178 struct pcl726_private {
179 
180 	int bipolar[12];
181 	const struct comedi_lrange *rangelist[12];
182 	unsigned int ao_readback[12];
183 };
184 
185 #define devpriv ((struct pcl726_private *)dev->private)
186 
pcl726_ao_insn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)187 static int pcl726_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
188 			  struct comedi_insn *insn, unsigned int *data)
189 {
190 	int hi, lo;
191 	int n;
192 	int chan = CR_CHAN(insn->chanspec);
193 
194 	for (n = 0; n < insn->n; n++) {
195 		lo = data[n] & 0xff;
196 		hi = (data[n] >> 8) & 0xf;
197 		if (devpriv->bipolar[chan])
198 			hi ^= 0x8;
199 		/*
200 		 * the programming info did not say which order
201 		 * to write bytes.  switch the order of the next
202 		 * two lines if you get glitches.
203 		 */
204 		outb(hi, dev->iobase + PCL726_DAC0_HI + 2 * chan);
205 		outb(lo, dev->iobase + PCL726_DAC0_LO + 2 * chan);
206 		devpriv->ao_readback[chan] = data[n];
207 	}
208 
209 	return n;
210 }
211 
pcl726_ao_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)212 static int pcl726_ao_insn_read(struct comedi_device *dev,
213 			       struct comedi_subdevice *s,
214 			       struct comedi_insn *insn, unsigned int *data)
215 {
216 	int chan = CR_CHAN(insn->chanspec);
217 	int n;
218 
219 	for (n = 0; n < insn->n; n++)
220 		data[n] = devpriv->ao_readback[chan];
221 	return n;
222 }
223 
pcl726_di_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)224 static int pcl726_di_insn_bits(struct comedi_device *dev,
225 			       struct comedi_subdevice *s,
226 			       struct comedi_insn *insn, unsigned int *data)
227 {
228 	if (insn->n != 2)
229 		return -EINVAL;
230 
231 	data[1] = inb(dev->iobase + this_board->di_lo) |
232 	    (inb(dev->iobase + this_board->di_hi) << 8);
233 
234 	return 2;
235 }
236 
pcl726_do_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)237 static int pcl726_do_insn_bits(struct comedi_device *dev,
238 			       struct comedi_subdevice *s,
239 			       struct comedi_insn *insn, unsigned int *data)
240 {
241 	if (insn->n != 2)
242 		return -EINVAL;
243 
244 	if (data[0]) {
245 		s->state &= ~data[0];
246 		s->state |= data[0] & data[1];
247 	}
248 	if (data[1] & 0x00ff)
249 		outb(s->state & 0xff, dev->iobase + this_board->do_lo);
250 	if (data[1] & 0xff00)
251 		outb((s->state >> 8), dev->iobase + this_board->do_hi);
252 
253 	data[1] = s->state;
254 
255 	return 2;
256 }
257 
pcl726_attach(struct comedi_device * dev,struct comedi_devconfig * it)258 static int pcl726_attach(struct comedi_device *dev, struct comedi_devconfig *it)
259 {
260 	struct comedi_subdevice *s;
261 	unsigned long iobase;
262 	unsigned int iorange;
263 	int ret, i;
264 #ifdef ACL6126_IRQ
265 	unsigned int irq;
266 #endif
267 
268 	iobase = it->options[0];
269 	iorange = this_board->io_range;
270 	printk(KERN_WARNING "comedi%d: pcl726: board=%s, 0x%03lx ", dev->minor,
271 	       this_board->name, iobase);
272 	if (!request_region(iobase, iorange, "pcl726")) {
273 		printk(KERN_WARNING "I/O port conflict\n");
274 		return -EIO;
275 	}
276 
277 	dev->iobase = iobase;
278 
279 	dev->board_name = this_board->name;
280 
281 	ret = alloc_private(dev, sizeof(struct pcl726_private));
282 	if (ret < 0)
283 		return -ENOMEM;
284 
285 	for (i = 0; i < 12; i++) {
286 		devpriv->bipolar[i] = 0;
287 		devpriv->rangelist[i] = &range_unknown;
288 	}
289 
290 #ifdef ACL6126_IRQ
291 	irq = 0;
292 	if (boardtypes[board].IRQbits != 0) {	/* board support IRQ */
293 		irq = it->options[1];
294 		devpriv->first_chan = 2;
295 		if (irq) {	/* we want to use IRQ */
296 			if (((1 << irq) & boardtypes[board].IRQbits) == 0) {
297 				printk(KERN_WARNING
298 					", IRQ %d is out of allowed range,"
299 					" DISABLING IT", irq);
300 				irq = 0;	/* Bad IRQ */
301 			} else {
302 				if (request_irq(irq, interrupt_pcl818, 0,
303 						"pcl726", dev)) {
304 					printk(KERN_WARNING
305 						", unable to allocate IRQ %d,"
306 						" DISABLING IT", irq);
307 					irq = 0;	/* Can't use IRQ */
308 				} else {
309 					printk(", irq=%d", irq);
310 				}
311 			}
312 		}
313 	}
314 
315 	dev->irq = irq;
316 #endif
317 
318 	printk("\n");
319 
320 	ret = alloc_subdevices(dev, 3);
321 	if (ret < 0)
322 		return ret;
323 
324 	s = dev->subdevices + 0;
325 	/* ao */
326 	s->type = COMEDI_SUBD_AO;
327 	s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
328 	s->n_chan = this_board->n_aochan;
329 	s->maxdata = 0xfff;
330 	s->len_chanlist = 1;
331 	s->insn_write = pcl726_ao_insn;
332 	s->insn_read = pcl726_ao_insn_read;
333 	s->range_table_list = devpriv->rangelist;
334 	for (i = 0; i < this_board->n_aochan; i++) {
335 		int j;
336 
337 		j = it->options[2 + 1];
338 		if ((j < 0) || (j >= this_board->num_of_ranges)) {
339 			printk
340 			    ("Invalid range for channel %d! Must be 0<=%d<%d\n",
341 			     i, j, this_board->num_of_ranges - 1);
342 			j = 0;
343 		}
344 		devpriv->rangelist[i] = this_board->range_type_list[j];
345 		if (devpriv->rangelist[i]->range[0].min ==
346 		    -devpriv->rangelist[i]->range[0].max)
347 			devpriv->bipolar[i] = 1;	/* bipolar range */
348 	}
349 
350 	s = dev->subdevices + 1;
351 	/* di */
352 	if (!this_board->have_dio) {
353 		s->type = COMEDI_SUBD_UNUSED;
354 	} else {
355 		s->type = COMEDI_SUBD_DI;
356 		s->subdev_flags = SDF_READABLE | SDF_GROUND;
357 		s->n_chan = 16;
358 		s->maxdata = 1;
359 		s->len_chanlist = 1;
360 		s->insn_bits = pcl726_di_insn_bits;
361 		s->range_table = &range_digital;
362 	}
363 
364 	s = dev->subdevices + 2;
365 	/* do */
366 	if (!this_board->have_dio) {
367 		s->type = COMEDI_SUBD_UNUSED;
368 	} else {
369 		s->type = COMEDI_SUBD_DO;
370 		s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
371 		s->n_chan = 16;
372 		s->maxdata = 1;
373 		s->len_chanlist = 1;
374 		s->insn_bits = pcl726_do_insn_bits;
375 		s->range_table = &range_digital;
376 	}
377 
378 	return 0;
379 }
380 
pcl726_detach(struct comedi_device * dev)381 static int pcl726_detach(struct comedi_device *dev)
382 {
383 /* printk("comedi%d: pcl726: remove\n",dev->minor); */
384 
385 #ifdef ACL6126_IRQ
386 	if (dev->irq)
387 		free_irq(dev->irq, dev);
388 #endif
389 
390 	if (dev->iobase)
391 		release_region(dev->iobase, this_board->io_range);
392 
393 	return 0;
394 }
395 
396 MODULE_AUTHOR("Comedi http://www.comedi.org");
397 MODULE_DESCRIPTION("Comedi low-level driver");
398 MODULE_LICENSE("GPL");
399