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