1 /*
2     comedi/drivers/icp_multi.c
3 
4     COMEDI - Linux Control and Measurement Device Interface
5     Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org>
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 */
17 
18 /*
19 Driver: icp_multi
20 Description: Inova ICP_MULTI
21 Author: Anne Smorthit <anne.smorthit@sfwte.ch>
22 Devices: [Inova] ICP_MULTI (icp_multi)
23 Status: works
24 
25 The driver works for analog input and output and digital input and output.
26 It does not work with interrupts or with the counters.  Currently no support
27 for DMA.
28 
29 It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
30 resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.  Input
31 ranges can be individually programmed for each channel.  Voltage or current
32 measurement is selected by jumper.
33 
34 There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
35 
36 16 x Digital Inputs, 24V
37 
38 8 x Digital Outputs, 24V, 1A
39 
40 4 x 16-bit counters
41 
42 Configuration options: not applicable, uses PCI auto config
43 */
44 
45 #include <linux/module.h>
46 #include <linux/delay.h>
47 #include <linux/interrupt.h>
48 
49 #include "../comedi_pci.h"
50 
51 #define ICP_MULTI_ADC_CSR	0	/* R/W: ADC command/status register */
52 #define ICP_MULTI_AI		2	/* R:   Analogue input data */
53 #define ICP_MULTI_DAC_CSR	4	/* R/W: DAC command/status register */
54 #define ICP_MULTI_AO		6	/* R/W: Analogue output data */
55 #define ICP_MULTI_DI		8	/* R/W: Digital inputs */
56 #define ICP_MULTI_DO		0x0A	/* R/W: Digital outputs */
57 #define ICP_MULTI_INT_EN	0x0C	/* R/W: Interrupt enable register */
58 #define ICP_MULTI_INT_STAT	0x0E	/* R/W: Interrupt status register */
59 #define ICP_MULTI_CNTR0		0x10	/* R/W: Counter 0 */
60 #define ICP_MULTI_CNTR1		0x12	/* R/W: counter 1 */
61 #define ICP_MULTI_CNTR2		0x14	/* R/W: Counter 2 */
62 #define ICP_MULTI_CNTR3		0x16	/* R/W: Counter 3 */
63 
64 /*  Define bits from ADC command/status register */
65 #define	ADC_ST		0x0001	/* Start ADC */
66 #define	ADC_BSY		0x0001	/* ADC busy */
67 #define ADC_BI		0x0010	/* Bipolar input range 1 = bipolar */
68 #define ADC_RA		0x0020	/* Input range 0 = 5V, 1 = 10V */
69 #define	ADC_DI		0x0040	/* Differential input mode 1 = differential */
70 
71 /*  Define bits from DAC command/status register */
72 #define	DAC_ST		0x0001	/* Start DAC */
73 #define DAC_BSY		0x0001	/* DAC busy */
74 #define	DAC_BI		0x0010	/* Bipolar input range 1 = bipolar */
75 #define	DAC_RA		0x0020	/* Input range 0 = 5V, 1 = 10V */
76 
77 /*  Define bits from interrupt enable/status registers */
78 #define	ADC_READY	0x0001	/* A/d conversion ready interrupt */
79 #define	DAC_READY	0x0002	/* D/a conversion ready interrupt */
80 #define	DOUT_ERROR	0x0004	/* Digital output error interrupt */
81 #define	DIN_STATUS	0x0008	/* Digital input status change interrupt */
82 #define	CIE0		0x0010	/* Counter 0 overrun interrupt */
83 #define	CIE1		0x0020	/* Counter 1 overrun interrupt */
84 #define	CIE2		0x0040	/* Counter 2 overrun interrupt */
85 #define	CIE3		0x0080	/* Counter 3 overrun interrupt */
86 
87 /*  Useful definitions */
88 #define	Status_IRQ	0x00ff	/*  All interrupts */
89 
90 /*  Define analogue range */
91 static const struct comedi_lrange range_analog = {
92 	4, {
93 		UNI_RANGE(5),
94 		UNI_RANGE(10),
95 		BIP_RANGE(5),
96 		BIP_RANGE(10)
97 	}
98 };
99 
100 static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
101 
102 /*
103 ==============================================================================
104 	Data & Structure declarations
105 ==============================================================================
106 */
107 
108 struct icp_multi_private {
109 	unsigned int AdcCmdStatus;	/*  ADC Command/Status register */
110 	unsigned int DacCmdStatus;	/*  DAC Command/Status register */
111 	unsigned int IntEnable;	/*  Interrupt Enable register */
112 	unsigned int IntStatus;	/*  Interrupt Status register */
113 	unsigned int act_chanlist[32];	/*  list of scanned channel */
114 	unsigned char act_chanlist_len;	/*  len of scanlist */
115 	unsigned char act_chanlist_pos;	/*  actual position in MUX list */
116 	unsigned int *ai_chanlist;	/*  actaul chanlist */
117 	unsigned int do_data;	/*  Remember digital output data */
118 };
119 
setup_channel_list(struct comedi_device * dev,struct comedi_subdevice * s,unsigned int * chanlist,unsigned int n_chan)120 static void setup_channel_list(struct comedi_device *dev,
121 			       struct comedi_subdevice *s,
122 			       unsigned int *chanlist, unsigned int n_chan)
123 {
124 	struct icp_multi_private *devpriv = dev->private;
125 	unsigned int i, range, chanprog;
126 	unsigned int diff;
127 
128 	devpriv->act_chanlist_len = n_chan;
129 	devpriv->act_chanlist_pos = 0;
130 
131 	for (i = 0; i < n_chan; i++) {
132 		/*  Get channel */
133 		chanprog = CR_CHAN(chanlist[i]);
134 
135 		/*  Determine if it is a differential channel (Bit 15  = 1) */
136 		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
137 			diff = 1;
138 			chanprog &= 0x0007;
139 		} else {
140 			diff = 0;
141 			chanprog &= 0x000f;
142 		}
143 
144 		/*  Clear channel, range and input mode bits
145 		 *  in A/D command/status register */
146 		devpriv->AdcCmdStatus &= 0xf00f;
147 
148 		/*  Set channel number and differential mode status bit */
149 		if (diff) {
150 			/*  Set channel number, bits 9-11 & mode, bit 6 */
151 			devpriv->AdcCmdStatus |= (chanprog << 9);
152 			devpriv->AdcCmdStatus |= ADC_DI;
153 		} else
154 			/*  Set channel number, bits 8-11 */
155 			devpriv->AdcCmdStatus |= (chanprog << 8);
156 
157 		/*  Get range for current channel */
158 		range = range_codes_analog[CR_RANGE(chanlist[i])];
159 		/*  Set range. bits 4-5 */
160 		devpriv->AdcCmdStatus |= range;
161 
162 		/* Output channel, range, mode to ICP Multi */
163 		writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
164 	}
165 }
166 
icp_multi_ai_eoc(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned long context)167 static int icp_multi_ai_eoc(struct comedi_device *dev,
168 			    struct comedi_subdevice *s,
169 			    struct comedi_insn *insn,
170 			    unsigned long context)
171 {
172 	unsigned int status;
173 
174 	status = readw(dev->mmio + ICP_MULTI_ADC_CSR);
175 	if ((status & ADC_BSY) == 0)
176 		return 0;
177 	return -EBUSY;
178 }
179 
icp_multi_insn_read_ai(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)180 static int icp_multi_insn_read_ai(struct comedi_device *dev,
181 				  struct comedi_subdevice *s,
182 				  struct comedi_insn *insn,
183 				  unsigned int *data)
184 {
185 	struct icp_multi_private *devpriv = dev->private;
186 	int ret = 0;
187 	int n;
188 
189 	/*  Disable A/D conversion ready interrupt */
190 	devpriv->IntEnable &= ~ADC_READY;
191 	writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
192 
193 	/*  Clear interrupt status */
194 	devpriv->IntStatus |= ADC_READY;
195 	writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
196 
197 	/*  Set up appropriate channel, mode and range data, for specified ch */
198 	setup_channel_list(dev, s, &insn->chanspec, 1);
199 
200 	for (n = 0; n < insn->n; n++) {
201 		/*  Set start ADC bit */
202 		devpriv->AdcCmdStatus |= ADC_ST;
203 		writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
204 		devpriv->AdcCmdStatus &= ~ADC_ST;
205 
206 		udelay(1);
207 
208 		/*  Wait for conversion to complete, or get fed up waiting */
209 		ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
210 		if (ret)
211 			break;
212 
213 		data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff;
214 	}
215 
216 	/*  Disable interrupt */
217 	devpriv->IntEnable &= ~ADC_READY;
218 	writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
219 
220 	/*  Clear interrupt status */
221 	devpriv->IntStatus |= ADC_READY;
222 	writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
223 
224 	return ret ? ret : n;
225 }
226 
icp_multi_ao_eoc(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned long context)227 static int icp_multi_ao_eoc(struct comedi_device *dev,
228 			    struct comedi_subdevice *s,
229 			    struct comedi_insn *insn,
230 			    unsigned long context)
231 {
232 	unsigned int status;
233 
234 	status = readw(dev->mmio + ICP_MULTI_DAC_CSR);
235 	if ((status & DAC_BSY) == 0)
236 		return 0;
237 	return -EBUSY;
238 }
239 
icp_multi_ao_insn_write(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)240 static int icp_multi_ao_insn_write(struct comedi_device *dev,
241 				   struct comedi_subdevice *s,
242 				   struct comedi_insn *insn,
243 				   unsigned int *data)
244 {
245 	struct icp_multi_private *devpriv = dev->private;
246 	unsigned int chan = CR_CHAN(insn->chanspec);
247 	unsigned int range = CR_RANGE(insn->chanspec);
248 	int i;
249 
250 	/*  Disable D/A conversion ready interrupt */
251 	devpriv->IntEnable &= ~DAC_READY;
252 	writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
253 
254 	/*  Clear interrupt status */
255 	devpriv->IntStatus |= DAC_READY;
256 	writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
257 
258 	/*  Set up range and channel data */
259 	/*  Bit 4 = 1 : Bipolar */
260 	/*  Bit 5 = 0 : 5V */
261 	/*  Bit 5 = 1 : 10V */
262 	/*  Bits 8-9 : Channel number */
263 	devpriv->DacCmdStatus &= 0xfccf;
264 	devpriv->DacCmdStatus |= range_codes_analog[range];
265 	devpriv->DacCmdStatus |= (chan << 8);
266 
267 	writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
268 
269 	for (i = 0; i < insn->n; i++) {
270 		unsigned int val = data[i];
271 		int ret;
272 
273 		/*  Wait for analogue output data register to be
274 		 *  ready for new data, or get fed up waiting */
275 		ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
276 		if (ret) {
277 			/*  Disable interrupt */
278 			devpriv->IntEnable &= ~DAC_READY;
279 			writew(devpriv->IntEnable,
280 			       dev->mmio + ICP_MULTI_INT_EN);
281 
282 			/*  Clear interrupt status */
283 			devpriv->IntStatus |= DAC_READY;
284 			writew(devpriv->IntStatus,
285 			       dev->mmio + ICP_MULTI_INT_STAT);
286 
287 			return ret;
288 		}
289 
290 		writew(val, dev->mmio + ICP_MULTI_AO);
291 
292 		/*  Set DAC_ST bit to write the data to selected channel */
293 		devpriv->DacCmdStatus |= DAC_ST;
294 		writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
295 		devpriv->DacCmdStatus &= ~DAC_ST;
296 
297 		s->readback[chan] = val;
298 	}
299 
300 	return insn->n;
301 }
302 
icp_multi_insn_bits_di(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)303 static int icp_multi_insn_bits_di(struct comedi_device *dev,
304 				  struct comedi_subdevice *s,
305 				  struct comedi_insn *insn,
306 				  unsigned int *data)
307 {
308 	data[1] = readw(dev->mmio + ICP_MULTI_DI);
309 
310 	return insn->n;
311 }
312 
icp_multi_insn_bits_do(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)313 static int icp_multi_insn_bits_do(struct comedi_device *dev,
314 				  struct comedi_subdevice *s,
315 				  struct comedi_insn *insn,
316 				  unsigned int *data)
317 {
318 	if (comedi_dio_update_state(s, data))
319 		writew(s->state, dev->mmio + ICP_MULTI_DO);
320 
321 	data[1] = s->state;
322 
323 	return insn->n;
324 }
325 
icp_multi_insn_read_ctr(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)326 static int icp_multi_insn_read_ctr(struct comedi_device *dev,
327 				   struct comedi_subdevice *s,
328 				   struct comedi_insn *insn, unsigned int *data)
329 {
330 	return 0;
331 }
332 
icp_multi_insn_write_ctr(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)333 static int icp_multi_insn_write_ctr(struct comedi_device *dev,
334 				    struct comedi_subdevice *s,
335 				    struct comedi_insn *insn,
336 				    unsigned int *data)
337 {
338 	return 0;
339 }
340 
interrupt_service_icp_multi(int irq,void * d)341 static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
342 {
343 	struct comedi_device *dev = d;
344 	int int_no;
345 
346 	/*  Is this interrupt from our board? */
347 	int_no = readw(dev->mmio + ICP_MULTI_INT_STAT) & Status_IRQ;
348 	if (!int_no)
349 		/*  No, exit */
350 		return IRQ_NONE;
351 
352 	/*  Determine which interrupt is active & handle it */
353 	switch (int_no) {
354 	case ADC_READY:
355 		break;
356 	case DAC_READY:
357 		break;
358 	case DOUT_ERROR:
359 		break;
360 	case DIN_STATUS:
361 		break;
362 	case CIE0:
363 		break;
364 	case CIE1:
365 		break;
366 	case CIE2:
367 		break;
368 	case CIE3:
369 		break;
370 	default:
371 		break;
372 	}
373 
374 	return IRQ_HANDLED;
375 }
376 
377 #if 0
378 static int check_channel_list(struct comedi_device *dev,
379 			      struct comedi_subdevice *s,
380 			      unsigned int *chanlist, unsigned int n_chan)
381 {
382 	unsigned int i;
383 
384 	/*  Check that we at least have one channel to check */
385 	if (n_chan < 1) {
386 		dev_err(dev->class_dev, "range/channel list is empty!\n");
387 		return 0;
388 	}
389 	/*  Check all channels */
390 	for (i = 0; i < n_chan; i++) {
391 		/*  Check that channel number is < maximum */
392 		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
393 			if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
394 				dev_err(dev->class_dev,
395 					"Incorrect differential ai ch-nr\n");
396 				return 0;
397 			}
398 		} else {
399 			if (CR_CHAN(chanlist[i]) > s->n_chan) {
400 				dev_err(dev->class_dev,
401 					"Incorrect ai channel number\n");
402 				return 0;
403 			}
404 		}
405 	}
406 	return 1;
407 }
408 #endif
409 
icp_multi_reset(struct comedi_device * dev)410 static int icp_multi_reset(struct comedi_device *dev)
411 {
412 	struct icp_multi_private *devpriv = dev->private;
413 	unsigned int i;
414 
415 	/*  Clear INT enables and requests */
416 	writew(0, dev->mmio + ICP_MULTI_INT_EN);
417 	writew(0x00ff, dev->mmio + ICP_MULTI_INT_STAT);
418 
419 	/* Set DACs to 0..5V range and 0V output */
420 	for (i = 0; i < 4; i++) {
421 		devpriv->DacCmdStatus &= 0xfcce;
422 
423 		/*  Set channel number */
424 		devpriv->DacCmdStatus |= (i << 8);
425 
426 		/*  Output 0V */
427 		writew(0, dev->mmio + ICP_MULTI_AO);
428 
429 		/*  Set start conversion bit */
430 		devpriv->DacCmdStatus |= DAC_ST;
431 
432 		/*  Output to command / status register */
433 		writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
434 
435 		/*  Delay to allow DAC time to recover */
436 		udelay(1);
437 	}
438 
439 	/* Digital outputs to 0 */
440 	writew(0, dev->mmio + ICP_MULTI_DO);
441 
442 	return 0;
443 }
444 
icp_multi_auto_attach(struct comedi_device * dev,unsigned long context_unused)445 static int icp_multi_auto_attach(struct comedi_device *dev,
446 				 unsigned long context_unused)
447 {
448 	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
449 	struct icp_multi_private *devpriv;
450 	struct comedi_subdevice *s;
451 	int ret;
452 
453 	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
454 	if (!devpriv)
455 		return -ENOMEM;
456 
457 	ret = comedi_pci_enable(dev);
458 	if (ret)
459 		return ret;
460 
461 	dev->mmio = pci_ioremap_bar(pcidev, 2);
462 	if (!dev->mmio)
463 		return -ENOMEM;
464 
465 	ret = comedi_alloc_subdevices(dev, 5);
466 	if (ret)
467 		return ret;
468 
469 	icp_multi_reset(dev);
470 
471 	if (pcidev->irq) {
472 		ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
473 				  IRQF_SHARED, dev->board_name, dev);
474 		if (ret == 0)
475 			dev->irq = pcidev->irq;
476 	}
477 
478 	s = &dev->subdevices[0];
479 	dev->read_subdev = s;
480 	s->type = COMEDI_SUBD_AI;
481 	s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
482 	s->n_chan = 16;
483 	s->maxdata = 0x0fff;
484 	s->len_chanlist = 16;
485 	s->range_table = &range_analog;
486 	s->insn_read = icp_multi_insn_read_ai;
487 
488 	s = &dev->subdevices[1];
489 	s->type = COMEDI_SUBD_AO;
490 	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
491 	s->n_chan = 4;
492 	s->maxdata = 0x0fff;
493 	s->len_chanlist = 4;
494 	s->range_table = &range_analog;
495 	s->insn_write = icp_multi_ao_insn_write;
496 
497 	ret = comedi_alloc_subdev_readback(s);
498 	if (ret)
499 		return ret;
500 
501 	s = &dev->subdevices[2];
502 	s->type = COMEDI_SUBD_DI;
503 	s->subdev_flags = SDF_READABLE;
504 	s->n_chan = 16;
505 	s->maxdata = 1;
506 	s->len_chanlist = 16;
507 	s->range_table = &range_digital;
508 	s->insn_bits = icp_multi_insn_bits_di;
509 
510 	s = &dev->subdevices[3];
511 	s->type = COMEDI_SUBD_DO;
512 	s->subdev_flags = SDF_WRITABLE;
513 	s->n_chan = 8;
514 	s->maxdata = 1;
515 	s->len_chanlist = 8;
516 	s->range_table = &range_digital;
517 	s->insn_bits = icp_multi_insn_bits_do;
518 
519 	s = &dev->subdevices[4];
520 	s->type = COMEDI_SUBD_COUNTER;
521 	s->subdev_flags = SDF_WRITABLE;
522 	s->n_chan = 4;
523 	s->maxdata = 0xffff;
524 	s->len_chanlist = 4;
525 	s->state = 0;
526 	s->insn_read = icp_multi_insn_read_ctr;
527 	s->insn_write = icp_multi_insn_write_ctr;
528 
529 	return 0;
530 }
531 
icp_multi_detach(struct comedi_device * dev)532 static void icp_multi_detach(struct comedi_device *dev)
533 {
534 	if (dev->mmio)
535 		icp_multi_reset(dev);
536 	comedi_pci_detach(dev);
537 }
538 
539 static struct comedi_driver icp_multi_driver = {
540 	.driver_name	= "icp_multi",
541 	.module		= THIS_MODULE,
542 	.auto_attach	= icp_multi_auto_attach,
543 	.detach		= icp_multi_detach,
544 };
545 
icp_multi_pci_probe(struct pci_dev * dev,const struct pci_device_id * id)546 static int icp_multi_pci_probe(struct pci_dev *dev,
547 			       const struct pci_device_id *id)
548 {
549 	return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
550 }
551 
552 static const struct pci_device_id icp_multi_pci_table[] = {
553 	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
554 	{ 0 }
555 };
556 MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
557 
558 static struct pci_driver icp_multi_pci_driver = {
559 	.name		= "icp_multi",
560 	.id_table	= icp_multi_pci_table,
561 	.probe		= icp_multi_pci_probe,
562 	.remove		= comedi_pci_auto_unconfig,
563 };
564 module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
565 
566 MODULE_AUTHOR("Comedi http://www.comedi.org");
567 MODULE_DESCRIPTION("Comedi low-level driver");
568 MODULE_LICENSE("GPL");
569