]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/icp_multi.c
Merge remote-tracking branch 'logfs/master'
[karo-tx-linux.git] / drivers / staging / comedi / drivers / icp_multi.c
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/pci.h>
47 #include <linux/delay.h>
48 #include <linux/interrupt.h>
49
50 #include "../comedidev.h"
51
52 #define PCI_DEVICE_ID_ICP_MULTI 0x8000
53
54 #define ICP_MULTI_ADC_CSR       0       /* R/W: ADC command/status register */
55 #define ICP_MULTI_AI            2       /* R:   Analogue input data */
56 #define ICP_MULTI_DAC_CSR       4       /* R/W: DAC command/status register */
57 #define ICP_MULTI_AO            6       /* R/W: Analogue output data */
58 #define ICP_MULTI_DI            8       /* R/W: Digital inouts */
59 #define ICP_MULTI_DO            0x0A    /* R/W: Digital outputs */
60 #define ICP_MULTI_INT_EN        0x0C    /* R/W: Interrupt enable register */
61 #define ICP_MULTI_INT_STAT      0x0E    /* R/W: Interrupt status register */
62 #define ICP_MULTI_CNTR0         0x10    /* R/W: Counter 0 */
63 #define ICP_MULTI_CNTR1         0x12    /* R/W: counter 1 */
64 #define ICP_MULTI_CNTR2         0x14    /* R/W: Counter 2 */
65 #define ICP_MULTI_CNTR3         0x16    /* R/W: Counter 3 */
66
67 /*  Define bits from ADC command/status register */
68 #define ADC_ST          0x0001  /* Start ADC */
69 #define ADC_BSY         0x0001  /* ADC busy */
70 #define ADC_BI          0x0010  /* Bipolar input range 1 = bipolar */
71 #define ADC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
72 #define ADC_DI          0x0040  /* Differential input mode 1 = differential */
73
74 /*  Define bits from DAC command/status register */
75 #define DAC_ST          0x0001  /* Start DAC */
76 #define DAC_BSY         0x0001  /* DAC busy */
77 #define DAC_BI          0x0010  /* Bipolar input range 1 = bipolar */
78 #define DAC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
79
80 /*  Define bits from interrupt enable/status registers */
81 #define ADC_READY       0x0001  /* A/d conversion ready interrupt */
82 #define DAC_READY       0x0002  /* D/a conversion ready interrupt */
83 #define DOUT_ERROR      0x0004  /* Digital output error interrupt */
84 #define DIN_STATUS      0x0008  /* Digital input status change interrupt */
85 #define CIE0            0x0010  /* Counter 0 overrun interrupt */
86 #define CIE1            0x0020  /* Counter 1 overrun interrupt */
87 #define CIE2            0x0040  /* Counter 2 overrun interrupt */
88 #define CIE3            0x0080  /* Counter 3 overrun interrupt */
89
90 /*  Useful definitions */
91 #define Status_IRQ      0x00ff  /*  All interrupts */
92
93 /*  Define analogue range */
94 static const struct comedi_lrange range_analog = { 4, {
95                                                        UNI_RANGE(5),
96                                                        UNI_RANGE(10),
97                                                        BIP_RANGE(5),
98                                                        BIP_RANGE(10)
99                                                        }
100 };
101
102 static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
103
104 /*
105 ==============================================================================
106         Data & Structure declarations
107 ==============================================================================
108 */
109
110 struct icp_multi_private {
111         char valid;             /*  card is usable */
112         void __iomem *io_addr;          /*  Pointer to mapped io address */
113         unsigned int AdcCmdStatus;      /*  ADC Command/Status register */
114         unsigned int DacCmdStatus;      /*  DAC Command/Status register */
115         unsigned int IntEnable; /*  Interrupt Enable register */
116         unsigned int IntStatus; /*  Interrupt Status register */
117         unsigned int act_chanlist[32];  /*  list of scanned channel */
118         unsigned char act_chanlist_len; /*  len of scanlist */
119         unsigned char act_chanlist_pos; /*  actual position in MUX list */
120         unsigned int *ai_chanlist;      /*  actaul chanlist */
121         short *ai_data;         /*  data buffer */
122         short ao_data[4];       /*  data output buffer */
123         short di_data;          /*  Digital input data */
124         unsigned int do_data;   /*  Remember digital output data */
125 };
126
127 static void setup_channel_list(struct comedi_device *dev,
128                                struct comedi_subdevice *s,
129                                unsigned int *chanlist, unsigned int n_chan)
130 {
131         struct icp_multi_private *devpriv = dev->private;
132         unsigned int i, range, chanprog;
133         unsigned int diff;
134
135         devpriv->act_chanlist_len = n_chan;
136         devpriv->act_chanlist_pos = 0;
137
138         for (i = 0; i < n_chan; i++) {
139                 /*  Get channel */
140                 chanprog = CR_CHAN(chanlist[i]);
141
142                 /*  Determine if it is a differential channel (Bit 15  = 1) */
143                 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
144                         diff = 1;
145                         chanprog &= 0x0007;
146                 } else {
147                         diff = 0;
148                         chanprog &= 0x000f;
149                 }
150
151                 /*  Clear channel, range and input mode bits
152                  *  in A/D command/status register */
153                 devpriv->AdcCmdStatus &= 0xf00f;
154
155                 /*  Set channel number and differential mode status bit */
156                 if (diff) {
157                         /*  Set channel number, bits 9-11 & mode, bit 6 */
158                         devpriv->AdcCmdStatus |= (chanprog << 9);
159                         devpriv->AdcCmdStatus |= ADC_DI;
160                 } else
161                         /*  Set channel number, bits 8-11 */
162                         devpriv->AdcCmdStatus |= (chanprog << 8);
163
164                 /*  Get range for current channel */
165                 range = range_codes_analog[CR_RANGE(chanlist[i])];
166                 /*  Set range. bits 4-5 */
167                 devpriv->AdcCmdStatus |= range;
168
169                 /* Output channel, range, mode to ICP Multi */
170                 writew(devpriv->AdcCmdStatus,
171                        devpriv->io_addr + ICP_MULTI_ADC_CSR);
172         }
173 }
174
175 static int icp_multi_insn_read_ai(struct comedi_device *dev,
176                                   struct comedi_subdevice *s,
177                                   struct comedi_insn *insn, unsigned int *data)
178 {
179         struct icp_multi_private *devpriv = dev->private;
180         int n, timeout;
181
182         /*  Disable A/D conversion ready interrupt */
183         devpriv->IntEnable &= ~ADC_READY;
184         writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
185
186         /*  Clear interrupt status */
187         devpriv->IntStatus |= ADC_READY;
188         writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
189
190         /*  Set up appropriate channel, mode and range data, for specified ch */
191         setup_channel_list(dev, s, &insn->chanspec, 1);
192
193         for (n = 0; n < insn->n; n++) {
194                 /*  Set start ADC bit */
195                 devpriv->AdcCmdStatus |= ADC_ST;
196                 writew(devpriv->AdcCmdStatus,
197                        devpriv->io_addr + ICP_MULTI_ADC_CSR);
198                 devpriv->AdcCmdStatus &= ~ADC_ST;
199
200                 udelay(1);
201
202                 /*  Wait for conversion to complete, or get fed up waiting */
203                 timeout = 100;
204                 while (timeout--) {
205                         if (!(readw(devpriv->io_addr +
206                                     ICP_MULTI_ADC_CSR) & ADC_BSY))
207                                 goto conv_finish;
208
209                         udelay(1);
210                 }
211
212                 /*  If we reach here, a timeout has occurred */
213                 comedi_error(dev, "A/D insn timeout");
214
215                 /*  Disable interrupt */
216                 devpriv->IntEnable &= ~ADC_READY;
217                 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
218
219                 /*  Clear interrupt status */
220                 devpriv->IntStatus |= ADC_READY;
221                 writew(devpriv->IntStatus,
222                        devpriv->io_addr + ICP_MULTI_INT_STAT);
223
224                 /*  Clear data received */
225                 data[n] = 0;
226
227                 return -ETIME;
228
229 conv_finish:
230                 data[n] =
231                     (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
232         }
233
234         /*  Disable interrupt */
235         devpriv->IntEnable &= ~ADC_READY;
236         writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
237
238         /*  Clear interrupt status */
239         devpriv->IntStatus |= ADC_READY;
240         writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
241
242         return n;
243 }
244
245 static int icp_multi_insn_write_ao(struct comedi_device *dev,
246                                    struct comedi_subdevice *s,
247                                    struct comedi_insn *insn, unsigned int *data)
248 {
249         struct icp_multi_private *devpriv = dev->private;
250         int n, chan, range, timeout;
251
252         /*  Disable D/A conversion ready interrupt */
253         devpriv->IntEnable &= ~DAC_READY;
254         writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
255
256         /*  Clear interrupt status */
257         devpriv->IntStatus |= DAC_READY;
258         writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
259
260         /*  Get channel number and range */
261         chan = CR_CHAN(insn->chanspec);
262         range = CR_RANGE(insn->chanspec);
263
264         /*  Set up range and channel data */
265         /*  Bit 4 = 1 : Bipolar */
266         /*  Bit 5 = 0 : 5V */
267         /*  Bit 5 = 1 : 10V */
268         /*  Bits 8-9 : Channel number */
269         devpriv->DacCmdStatus &= 0xfccf;
270         devpriv->DacCmdStatus |= range_codes_analog[range];
271         devpriv->DacCmdStatus |= (chan << 8);
272
273         writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
274
275         for (n = 0; n < insn->n; n++) {
276                 /*  Wait for analogue output data register to be
277                  *  ready for new data, or get fed up waiting */
278                 timeout = 100;
279                 while (timeout--) {
280                         if (!(readw(devpriv->io_addr +
281                                     ICP_MULTI_DAC_CSR) & DAC_BSY))
282                                 goto dac_ready;
283
284                         udelay(1);
285                 }
286
287                 /*  If we reach here, a timeout has occurred */
288                 comedi_error(dev, "D/A insn timeout");
289
290                 /*  Disable interrupt */
291                 devpriv->IntEnable &= ~DAC_READY;
292                 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
293
294                 /*  Clear interrupt status */
295                 devpriv->IntStatus |= DAC_READY;
296                 writew(devpriv->IntStatus,
297                        devpriv->io_addr + ICP_MULTI_INT_STAT);
298
299                 /*  Clear data received */
300                 devpriv->ao_data[chan] = 0;
301
302                 return -ETIME;
303
304 dac_ready:
305                 /*  Write data to analogue output data register */
306                 writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
307
308                 /*  Set DAC_ST bit to write the data to selected channel */
309                 devpriv->DacCmdStatus |= DAC_ST;
310                 writew(devpriv->DacCmdStatus,
311                        devpriv->io_addr + ICP_MULTI_DAC_CSR);
312                 devpriv->DacCmdStatus &= ~DAC_ST;
313
314                 /*  Save analogue output data */
315                 devpriv->ao_data[chan] = data[n];
316         }
317
318         return n;
319 }
320
321 static int icp_multi_insn_read_ao(struct comedi_device *dev,
322                                   struct comedi_subdevice *s,
323                                   struct comedi_insn *insn, unsigned int *data)
324 {
325         struct icp_multi_private *devpriv = dev->private;
326         int n, chan;
327
328         /*  Get channel number */
329         chan = CR_CHAN(insn->chanspec);
330
331         /*  Read analogue outputs */
332         for (n = 0; n < insn->n; n++)
333                 data[n] = devpriv->ao_data[chan];
334
335         return n;
336 }
337
338 static int icp_multi_insn_bits_di(struct comedi_device *dev,
339                                   struct comedi_subdevice *s,
340                                   struct comedi_insn *insn, unsigned int *data)
341 {
342         struct icp_multi_private *devpriv = dev->private;
343
344         data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
345
346         return insn->n;
347 }
348
349 static int icp_multi_insn_bits_do(struct comedi_device *dev,
350                                   struct comedi_subdevice *s,
351                                   struct comedi_insn *insn, unsigned int *data)
352 {
353         struct icp_multi_private *devpriv = dev->private;
354
355         if (data[0]) {
356                 s->state &= ~data[0];
357                 s->state |= (data[0] & data[1]);
358
359                 printk(KERN_DEBUG "Digital outputs = %4x \n", s->state);
360
361                 writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
362         }
363
364         data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
365
366         return insn->n;
367 }
368
369 static int icp_multi_insn_read_ctr(struct comedi_device *dev,
370                                    struct comedi_subdevice *s,
371                                    struct comedi_insn *insn, unsigned int *data)
372 {
373         return 0;
374 }
375
376 static int icp_multi_insn_write_ctr(struct comedi_device *dev,
377                                     struct comedi_subdevice *s,
378                                     struct comedi_insn *insn,
379                                     unsigned int *data)
380 {
381         return 0;
382 }
383
384 static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
385 {
386         struct comedi_device *dev = d;
387         struct icp_multi_private *devpriv = dev->private;
388         int int_no;
389
390         /*  Is this interrupt from our board? */
391         int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
392         if (!int_no)
393                 /*  No, exit */
394                 return IRQ_NONE;
395
396         /*  Determine which interrupt is active & handle it */
397         switch (int_no) {
398         case ADC_READY:
399                 break;
400         case DAC_READY:
401                 break;
402         case DOUT_ERROR:
403                 break;
404         case DIN_STATUS:
405                 break;
406         case CIE0:
407                 break;
408         case CIE1:
409                 break;
410         case CIE2:
411                 break;
412         case CIE3:
413                 break;
414         default:
415                 break;
416
417         }
418
419         return IRQ_HANDLED;
420 }
421
422 #if 0
423 static int check_channel_list(struct comedi_device *dev,
424                               struct comedi_subdevice *s,
425                               unsigned int *chanlist, unsigned int n_chan)
426 {
427         unsigned int i;
428
429         /*  Check that we at least have one channel to check */
430         if (n_chan < 1) {
431                 comedi_error(dev, "range/channel list is empty!");
432                 return 0;
433         }
434         /*  Check all channels */
435         for (i = 0; i < n_chan; i++) {
436                 /*  Check that channel number is < maximum */
437                 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
438                         if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
439                                 comedi_error(dev,
440                                              "Incorrect differential ai ch-nr");
441                                 return 0;
442                         }
443                 } else {
444                         if (CR_CHAN(chanlist[i]) > s->n_chan) {
445                                 comedi_error(dev,
446                                              "Incorrect ai channel number");
447                                 return 0;
448                         }
449                 }
450         }
451         return 1;
452 }
453 #endif
454
455 static int icp_multi_reset(struct comedi_device *dev)
456 {
457         struct icp_multi_private *devpriv = dev->private;
458         unsigned int i;
459
460         /*  Clear INT enables and requests */
461         writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
462         writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
463
464         /* Set DACs to 0..5V range and 0V output */
465         for (i = 0; i < 4; i++) {
466                 devpriv->DacCmdStatus &= 0xfcce;
467
468                 /*  Set channel number */
469                 devpriv->DacCmdStatus |= (i << 8);
470
471                 /*  Output 0V */
472                 writew(0, devpriv->io_addr + ICP_MULTI_AO);
473
474                 /*  Set start conversion bit */
475                 devpriv->DacCmdStatus |= DAC_ST;
476
477                 /*  Output to command / status register */
478                 writew(devpriv->DacCmdStatus,
479                         devpriv->io_addr + ICP_MULTI_DAC_CSR);
480
481                 /*  Delay to allow DAC time to recover */
482                 udelay(1);
483         }
484
485         /* Digital outputs to 0 */
486         writew(0, devpriv->io_addr + ICP_MULTI_DO);
487
488         return 0;
489 }
490
491 static int icp_multi_auto_attach(struct comedi_device *dev,
492                                            unsigned long context_unused)
493 {
494         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
495         struct icp_multi_private *devpriv;
496         struct comedi_subdevice *s;
497         int ret;
498
499         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
500         if (!devpriv)
501                 return -ENOMEM;
502
503         ret = comedi_pci_enable(dev);
504         if (ret)
505                 return ret;
506
507         devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
508         if (!devpriv->io_addr)
509                 return -ENOMEM;
510
511         ret = comedi_alloc_subdevices(dev, 5);
512         if (ret)
513                 return ret;
514
515         icp_multi_reset(dev);
516
517         if (pcidev->irq) {
518                 ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
519                                   IRQF_SHARED, dev->board_name, dev);
520                 if (ret == 0)
521                         dev->irq = pcidev->irq;
522         }
523
524         s = &dev->subdevices[0];
525         dev->read_subdev = s;
526         s->type = COMEDI_SUBD_AI;
527         s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
528         s->n_chan = 16;
529         s->maxdata = 0x0fff;
530         s->len_chanlist = 16;
531         s->range_table = &range_analog;
532         s->insn_read = icp_multi_insn_read_ai;
533
534         s = &dev->subdevices[1];
535         s->type = COMEDI_SUBD_AO;
536         s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
537         s->n_chan = 4;
538         s->maxdata = 0x0fff;
539         s->len_chanlist = 4;
540         s->range_table = &range_analog;
541         s->insn_write = icp_multi_insn_write_ao;
542         s->insn_read = icp_multi_insn_read_ao;
543
544         s = &dev->subdevices[2];
545         s->type = COMEDI_SUBD_DI;
546         s->subdev_flags = SDF_READABLE;
547         s->n_chan = 16;
548         s->maxdata = 1;
549         s->len_chanlist = 16;
550         s->range_table = &range_digital;
551         s->io_bits = 0;
552         s->insn_bits = icp_multi_insn_bits_di;
553
554         s = &dev->subdevices[3];
555         s->type = COMEDI_SUBD_DO;
556         s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
557         s->n_chan = 8;
558         s->maxdata = 1;
559         s->len_chanlist = 8;
560         s->range_table = &range_digital;
561         s->io_bits = 0xff;
562         s->state = 0;
563         s->insn_bits = icp_multi_insn_bits_do;
564
565         s = &dev->subdevices[4];
566         s->type = COMEDI_SUBD_COUNTER;
567         s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
568         s->n_chan = 4;
569         s->maxdata = 0xffff;
570         s->len_chanlist = 4;
571         s->state = 0;
572         s->insn_read = icp_multi_insn_read_ctr;
573         s->insn_write = icp_multi_insn_write_ctr;
574
575         devpriv->valid = 1;
576
577         dev_info(dev->class_dev, "%s attached, irq %sabled\n",
578                 dev->board_name, dev->irq ? "en" : "dis");
579
580         return 0;
581 }
582
583 static void icp_multi_detach(struct comedi_device *dev)
584 {
585         struct icp_multi_private *devpriv = dev->private;
586
587         if (devpriv)
588                 if (devpriv->valid)
589                         icp_multi_reset(dev);
590         if (dev->irq)
591                 free_irq(dev->irq, dev);
592         if (devpriv && devpriv->io_addr)
593                 iounmap(devpriv->io_addr);
594         comedi_pci_disable(dev);
595 }
596
597 static struct comedi_driver icp_multi_driver = {
598         .driver_name    = "icp_multi",
599         .module         = THIS_MODULE,
600         .auto_attach    = icp_multi_auto_attach,
601         .detach         = icp_multi_detach,
602 };
603
604 static int icp_multi_pci_probe(struct pci_dev *dev,
605                                const struct pci_device_id *id)
606 {
607         return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
608 }
609
610 static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = {
611         { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
612         { 0 }
613 };
614 MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
615
616 static struct pci_driver icp_multi_pci_driver = {
617         .name           = "icp_multi",
618         .id_table       = icp_multi_pci_table,
619         .probe          = icp_multi_pci_probe,
620         .remove         = comedi_pci_auto_unconfig,
621 };
622 module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
623
624 MODULE_AUTHOR("Comedi http://www.comedi.org");
625 MODULE_DESCRIPTION("Comedi low-level driver");
626 MODULE_LICENSE("GPL");