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