]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/icp_multi.c
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth...
[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/pci.h>
46 #include <linux/delay.h>
47 #include <linux/interrupt.h>
48
49 #include "../comedidev.h"
50
51 #define PCI_DEVICE_ID_ICP_MULTI 0x8000
52
53 #define ICP_MULTI_ADC_CSR       0       /* R/W: ADC command/status register */
54 #define ICP_MULTI_AI            2       /* R:   Analogue input data */
55 #define ICP_MULTI_DAC_CSR       4       /* R/W: DAC command/status register */
56 #define ICP_MULTI_AO            6       /* R/W: Analogue output data */
57 #define ICP_MULTI_DI            8       /* R/W: Digital inouts */
58 #define ICP_MULTI_DO            0x0A    /* R/W: Digital outputs */
59 #define ICP_MULTI_INT_EN        0x0C    /* R/W: Interrupt enable register */
60 #define ICP_MULTI_INT_STAT      0x0E    /* R/W: Interrupt status register */
61 #define ICP_MULTI_CNTR0         0x10    /* R/W: Counter 0 */
62 #define ICP_MULTI_CNTR1         0x12    /* R/W: counter 1 */
63 #define ICP_MULTI_CNTR2         0x14    /* R/W: Counter 2 */
64 #define ICP_MULTI_CNTR3         0x16    /* R/W: Counter 3 */
65
66 /*  Define bits from ADC command/status register */
67 #define ADC_ST          0x0001  /* Start ADC */
68 #define ADC_BSY         0x0001  /* ADC busy */
69 #define ADC_BI          0x0010  /* Bipolar input range 1 = bipolar */
70 #define ADC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
71 #define ADC_DI          0x0040  /* Differential input mode 1 = differential */
72
73 /*  Define bits from DAC command/status register */
74 #define DAC_ST          0x0001  /* Start DAC */
75 #define DAC_BSY         0x0001  /* DAC busy */
76 #define DAC_BI          0x0010  /* Bipolar input range 1 = bipolar */
77 #define DAC_RA          0x0020  /* Input range 0 = 5V, 1 = 10V */
78
79 /*  Define bits from interrupt enable/status registers */
80 #define ADC_READY       0x0001  /* A/d conversion ready interrupt */
81 #define DAC_READY       0x0002  /* D/a conversion ready interrupt */
82 #define DOUT_ERROR      0x0004  /* Digital output error interrupt */
83 #define DIN_STATUS      0x0008  /* Digital input status change interrupt */
84 #define CIE0            0x0010  /* Counter 0 overrun interrupt */
85 #define CIE1            0x0020  /* Counter 1 overrun interrupt */
86 #define CIE2            0x0040  /* Counter 2 overrun interrupt */
87 #define CIE3            0x0080  /* Counter 3 overrun interrupt */
88
89 /*  Useful definitions */
90 #define Status_IRQ      0x00ff  /*  All interrupts */
91
92 /*  Define analogue range */
93 static const struct comedi_lrange range_analog = { 4, {
94                                                        UNI_RANGE(5),
95                                                        UNI_RANGE(10),
96                                                        BIP_RANGE(5),
97                                                        BIP_RANGE(10)
98                                                        }
99 };
100
101 static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
102
103 /*
104 ==============================================================================
105         Data & Structure declarations
106 ==============================================================================
107 */
108
109 struct icp_multi_private {
110         char valid;             /*  card is usable */
111         void __iomem *io_addr;          /*  Pointer to mapped io address */
112         unsigned int AdcCmdStatus;      /*  ADC Command/Status register */
113         unsigned int DacCmdStatus;      /*  DAC Command/Status register */
114         unsigned int IntEnable; /*  Interrupt Enable register */
115         unsigned int IntStatus; /*  Interrupt Status register */
116         unsigned int act_chanlist[32];  /*  list of scanned channel */
117         unsigned char act_chanlist_len; /*  len of scanlist */
118         unsigned char act_chanlist_pos; /*  actual position in MUX list */
119         unsigned int *ai_chanlist;      /*  actaul chanlist */
120         short *ai_data;         /*  data buffer */
121         short ao_data[4];       /*  data output buffer */
122         short di_data;          /*  Digital input data */
123         unsigned int do_data;   /*  Remember digital output data */
124 };
125
126 static void setup_channel_list(struct comedi_device *dev,
127                                struct comedi_subdevice *s,
128                                unsigned int *chanlist, unsigned int n_chan)
129 {
130         struct icp_multi_private *devpriv = dev->private;
131         unsigned int i, range, chanprog;
132         unsigned int diff;
133
134         devpriv->act_chanlist_len = n_chan;
135         devpriv->act_chanlist_pos = 0;
136
137         for (i = 0; i < n_chan; i++) {
138                 /*  Get channel */
139                 chanprog = CR_CHAN(chanlist[i]);
140
141                 /*  Determine if it is a differential channel (Bit 15  = 1) */
142                 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
143                         diff = 1;
144                         chanprog &= 0x0007;
145                 } else {
146                         diff = 0;
147                         chanprog &= 0x000f;
148                 }
149
150                 /*  Clear channel, range and input mode bits
151                  *  in A/D command/status register */
152                 devpriv->AdcCmdStatus &= 0xf00f;
153
154                 /*  Set channel number and differential mode status bit */
155                 if (diff) {
156                         /*  Set channel number, bits 9-11 & mode, bit 6 */
157                         devpriv->AdcCmdStatus |= (chanprog << 9);
158                         devpriv->AdcCmdStatus |= ADC_DI;
159                 } else
160                         /*  Set channel number, bits 8-11 */
161                         devpriv->AdcCmdStatus |= (chanprog << 8);
162
163                 /*  Get range for current channel */
164                 range = range_codes_analog[CR_RANGE(chanlist[i])];
165                 /*  Set range. bits 4-5 */
166                 devpriv->AdcCmdStatus |= range;
167
168                 /* Output channel, range, mode to ICP Multi */
169                 writew(devpriv->AdcCmdStatus,
170                        devpriv->io_addr + ICP_MULTI_ADC_CSR);
171         }
172 }
173
174 static int icp_multi_insn_read_ai(struct comedi_device *dev,
175                                   struct comedi_subdevice *s,
176                                   struct comedi_insn *insn, unsigned int *data)
177 {
178         struct icp_multi_private *devpriv = dev->private;
179         int n, timeout;
180
181         /*  Disable A/D conversion ready interrupt */
182         devpriv->IntEnable &= ~ADC_READY;
183         writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
184
185         /*  Clear interrupt status */
186         devpriv->IntStatus |= ADC_READY;
187         writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
188
189         /*  Set up appropriate channel, mode and range data, for specified ch */
190         setup_channel_list(dev, s, &insn->chanspec, 1);
191
192         for (n = 0; n < insn->n; n++) {
193                 /*  Set start ADC bit */
194                 devpriv->AdcCmdStatus |= ADC_ST;
195                 writew(devpriv->AdcCmdStatus,
196                        devpriv->io_addr + ICP_MULTI_ADC_CSR);
197                 devpriv->AdcCmdStatus &= ~ADC_ST;
198
199                 udelay(1);
200
201                 /*  Wait for conversion to complete, or get fed up waiting */
202                 timeout = 100;
203                 while (timeout--) {
204                         if (!(readw(devpriv->io_addr +
205                                     ICP_MULTI_ADC_CSR) & ADC_BSY))
206                                 goto conv_finish;
207
208                         udelay(1);
209                 }
210
211                 /*  If we reach here, a timeout has occurred */
212                 comedi_error(dev, "A/D insn timeout");
213
214                 /*  Disable interrupt */
215                 devpriv->IntEnable &= ~ADC_READY;
216                 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
217
218                 /*  Clear interrupt status */
219                 devpriv->IntStatus |= ADC_READY;
220                 writew(devpriv->IntStatus,
221                        devpriv->io_addr + ICP_MULTI_INT_STAT);
222
223                 /*  Clear data received */
224                 data[n] = 0;
225
226                 return -ETIME;
227
228 conv_finish:
229                 data[n] =
230                     (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
231         }
232
233         /*  Disable interrupt */
234         devpriv->IntEnable &= ~ADC_READY;
235         writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
236
237         /*  Clear interrupt status */
238         devpriv->IntStatus |= ADC_READY;
239         writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
240
241         return n;
242 }
243
244 static int icp_multi_insn_write_ao(struct comedi_device *dev,
245                                    struct comedi_subdevice *s,
246                                    struct comedi_insn *insn, unsigned int *data)
247 {
248         struct icp_multi_private *devpriv = dev->private;
249         int n, chan, range, timeout;
250
251         /*  Disable D/A conversion ready interrupt */
252         devpriv->IntEnable &= ~DAC_READY;
253         writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
254
255         /*  Clear interrupt status */
256         devpriv->IntStatus |= DAC_READY;
257         writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
258
259         /*  Get channel number and range */
260         chan = CR_CHAN(insn->chanspec);
261         range = CR_RANGE(insn->chanspec);
262
263         /*  Set up range and channel data */
264         /*  Bit 4 = 1 : Bipolar */
265         /*  Bit 5 = 0 : 5V */
266         /*  Bit 5 = 1 : 10V */
267         /*  Bits 8-9 : Channel number */
268         devpriv->DacCmdStatus &= 0xfccf;
269         devpriv->DacCmdStatus |= range_codes_analog[range];
270         devpriv->DacCmdStatus |= (chan << 8);
271
272         writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
273
274         for (n = 0; n < insn->n; n++) {
275                 /*  Wait for analogue output data register to be
276                  *  ready for new data, or get fed up waiting */
277                 timeout = 100;
278                 while (timeout--) {
279                         if (!(readw(devpriv->io_addr +
280                                     ICP_MULTI_DAC_CSR) & DAC_BSY))
281                                 goto dac_ready;
282
283                         udelay(1);
284                 }
285
286                 /*  If we reach here, a timeout has occurred */
287                 comedi_error(dev, "D/A insn timeout");
288
289                 /*  Disable interrupt */
290                 devpriv->IntEnable &= ~DAC_READY;
291                 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
292
293                 /*  Clear interrupt status */
294                 devpriv->IntStatus |= DAC_READY;
295                 writew(devpriv->IntStatus,
296                        devpriv->io_addr + ICP_MULTI_INT_STAT);
297
298                 /*  Clear data received */
299                 devpriv->ao_data[chan] = 0;
300
301                 return -ETIME;
302
303 dac_ready:
304                 /*  Write data to analogue output data register */
305                 writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
306
307                 /*  Set DAC_ST bit to write the data to selected channel */
308                 devpriv->DacCmdStatus |= DAC_ST;
309                 writew(devpriv->DacCmdStatus,
310                        devpriv->io_addr + ICP_MULTI_DAC_CSR);
311                 devpriv->DacCmdStatus &= ~DAC_ST;
312
313                 /*  Save analogue output data */
314                 devpriv->ao_data[chan] = data[n];
315         }
316
317         return n;
318 }
319
320 static int icp_multi_insn_read_ao(struct comedi_device *dev,
321                                   struct comedi_subdevice *s,
322                                   struct comedi_insn *insn, unsigned int *data)
323 {
324         struct icp_multi_private *devpriv = dev->private;
325         int n, chan;
326
327         /*  Get channel number */
328         chan = CR_CHAN(insn->chanspec);
329
330         /*  Read analogue outputs */
331         for (n = 0; n < insn->n; n++)
332                 data[n] = devpriv->ao_data[chan];
333
334         return n;
335 }
336
337 static int icp_multi_insn_bits_di(struct comedi_device *dev,
338                                   struct comedi_subdevice *s,
339                                   struct comedi_insn *insn, unsigned int *data)
340 {
341         struct icp_multi_private *devpriv = dev->private;
342
343         data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
344
345         return insn->n;
346 }
347
348 static int icp_multi_insn_bits_do(struct comedi_device *dev,
349                                   struct comedi_subdevice *s,
350                                   struct comedi_insn *insn, unsigned int *data)
351 {
352         struct icp_multi_private *devpriv = dev->private;
353
354         if (data[0]) {
355                 s->state &= ~data[0];
356                 s->state |= (data[0] & data[1]);
357
358                 printk(KERN_DEBUG "Digital outputs = %4x \n", s->state);
359
360                 writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
361         }
362
363         data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
364
365         return insn->n;
366 }
367
368 static int icp_multi_insn_read_ctr(struct comedi_device *dev,
369                                    struct comedi_subdevice *s,
370                                    struct comedi_insn *insn, unsigned int *data)
371 {
372         return 0;
373 }
374
375 static int icp_multi_insn_write_ctr(struct comedi_device *dev,
376                                     struct comedi_subdevice *s,
377                                     struct comedi_insn *insn,
378                                     unsigned int *data)
379 {
380         return 0;
381 }
382
383 static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
384 {
385         struct comedi_device *dev = d;
386         struct icp_multi_private *devpriv = dev->private;
387         int int_no;
388
389         /*  Is this interrupt from our board? */
390         int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
391         if (!int_no)
392                 /*  No, exit */
393                 return IRQ_NONE;
394
395         /*  Determine which interrupt is active & handle it */
396         switch (int_no) {
397         case ADC_READY:
398                 break;
399         case DAC_READY:
400                 break;
401         case DOUT_ERROR:
402                 break;
403         case DIN_STATUS:
404                 break;
405         case CIE0:
406                 break;
407         case CIE1:
408                 break;
409         case CIE2:
410                 break;
411         case CIE3:
412                 break;
413         default:
414                 break;
415
416         }
417
418         return IRQ_HANDLED;
419 }
420
421 #if 0
422 static int check_channel_list(struct comedi_device *dev,
423                               struct comedi_subdevice *s,
424                               unsigned int *chanlist, unsigned int n_chan)
425 {
426         unsigned int i;
427
428         /*  Check that we at least have one channel to check */
429         if (n_chan < 1) {
430                 comedi_error(dev, "range/channel list is empty!");
431                 return 0;
432         }
433         /*  Check all channels */
434         for (i = 0; i < n_chan; i++) {
435                 /*  Check that channel number is < maximum */
436                 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
437                         if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
438                                 comedi_error(dev,
439                                              "Incorrect differential ai ch-nr");
440                                 return 0;
441                         }
442                 } else {
443                         if (CR_CHAN(chanlist[i]) > s->n_chan) {
444                                 comedi_error(dev,
445                                              "Incorrect ai channel number");
446                                 return 0;
447                         }
448                 }
449         }
450         return 1;
451 }
452 #endif
453
454 static int icp_multi_reset(struct comedi_device *dev)
455 {
456         struct icp_multi_private *devpriv = dev->private;
457         unsigned int i;
458
459         /*  Clear INT enables and requests */
460         writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
461         writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
462
463         /* Set DACs to 0..5V range and 0V output */
464         for (i = 0; i < 4; i++) {
465                 devpriv->DacCmdStatus &= 0xfcce;
466
467                 /*  Set channel number */
468                 devpriv->DacCmdStatus |= (i << 8);
469
470                 /*  Output 0V */
471                 writew(0, devpriv->io_addr + ICP_MULTI_AO);
472
473                 /*  Set start conversion bit */
474                 devpriv->DacCmdStatus |= DAC_ST;
475
476                 /*  Output to command / status register */
477                 writew(devpriv->DacCmdStatus,
478                         devpriv->io_addr + ICP_MULTI_DAC_CSR);
479
480                 /*  Delay to allow DAC time to recover */
481                 udelay(1);
482         }
483
484         /* Digital outputs to 0 */
485         writew(0, devpriv->io_addr + ICP_MULTI_DO);
486
487         return 0;
488 }
489
490 static int icp_multi_auto_attach(struct comedi_device *dev,
491                                            unsigned long context_unused)
492 {
493         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
494         struct icp_multi_private *devpriv;
495         struct comedi_subdevice *s;
496         int ret;
497
498         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
499         if (!devpriv)
500                 return -ENOMEM;
501         dev->private = devpriv;
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");