icp_multi.c revision 41e043fcfa2236bb2c4a8335eb09f4c8cee224b3
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/*
19Driver: icp_multi
20Description: Inova ICP_MULTI
21Author: Anne Smorthit <anne.smorthit@sfwte.ch>
22Devices: [Inova] ICP_MULTI (icp_multi)
23Status: works
24
25The driver works for analog input and output and digital input and output.
26It does not work with interrupts or with the counters.  Currently no support
27for DMA.
28
29It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
30resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.  Input
31ranges can be individually programmed for each channel.  Voltage or current
32measurement is selected by jumper.
33
34There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
35
3616 x Digital Inputs, 24V
37
388 x Digital Outputs, 24V, 1A
39
404 x 16-bit counters
41
42Configuration 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 */
94static 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
102static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
103
104/*
105==============================================================================
106	Data & Structure declarations
107==============================================================================
108*/
109
110struct 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	unsigned short ao_data[4];	/*  data output buffer */
122	unsigned int do_data;	/*  Remember digital output data */
123};
124
125static void setup_channel_list(struct comedi_device *dev,
126			       struct comedi_subdevice *s,
127			       unsigned int *chanlist, unsigned int n_chan)
128{
129	struct icp_multi_private *devpriv = dev->private;
130	unsigned int i, range, chanprog;
131	unsigned int diff;
132
133	devpriv->act_chanlist_len = n_chan;
134	devpriv->act_chanlist_pos = 0;
135
136	for (i = 0; i < n_chan; i++) {
137		/*  Get channel */
138		chanprog = CR_CHAN(chanlist[i]);
139
140		/*  Determine if it is a differential channel (Bit 15  = 1) */
141		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
142			diff = 1;
143			chanprog &= 0x0007;
144		} else {
145			diff = 0;
146			chanprog &= 0x000f;
147		}
148
149		/*  Clear channel, range and input mode bits
150		 *  in A/D command/status register */
151		devpriv->AdcCmdStatus &= 0xf00f;
152
153		/*  Set channel number and differential mode status bit */
154		if (diff) {
155			/*  Set channel number, bits 9-11 & mode, bit 6 */
156			devpriv->AdcCmdStatus |= (chanprog << 9);
157			devpriv->AdcCmdStatus |= ADC_DI;
158		} else
159			/*  Set channel number, bits 8-11 */
160			devpriv->AdcCmdStatus |= (chanprog << 8);
161
162		/*  Get range for current channel */
163		range = range_codes_analog[CR_RANGE(chanlist[i])];
164		/*  Set range. bits 4-5 */
165		devpriv->AdcCmdStatus |= range;
166
167		/* Output channel, range, mode to ICP Multi */
168		writew(devpriv->AdcCmdStatus,
169		       devpriv->io_addr + ICP_MULTI_ADC_CSR);
170	}
171}
172
173static int icp_multi_insn_read_ai(struct comedi_device *dev,
174				  struct comedi_subdevice *s,
175				  struct comedi_insn *insn, unsigned int *data)
176{
177	struct icp_multi_private *devpriv = dev->private;
178	int n, timeout;
179
180	/*  Disable A/D conversion ready interrupt */
181	devpriv->IntEnable &= ~ADC_READY;
182	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
183
184	/*  Clear interrupt status */
185	devpriv->IntStatus |= ADC_READY;
186	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
187
188	/*  Set up appropriate channel, mode and range data, for specified ch */
189	setup_channel_list(dev, s, &insn->chanspec, 1);
190
191	for (n = 0; n < insn->n; n++) {
192		/*  Set start ADC bit */
193		devpriv->AdcCmdStatus |= ADC_ST;
194		writew(devpriv->AdcCmdStatus,
195		       devpriv->io_addr + ICP_MULTI_ADC_CSR);
196		devpriv->AdcCmdStatus &= ~ADC_ST;
197
198		udelay(1);
199
200		/*  Wait for conversion to complete, or get fed up waiting */
201		timeout = 100;
202		while (timeout--) {
203			if (!(readw(devpriv->io_addr +
204				    ICP_MULTI_ADC_CSR) & ADC_BSY))
205				goto conv_finish;
206
207			udelay(1);
208		}
209
210		/*  If we reach here, a timeout has occurred */
211		comedi_error(dev, "A/D insn timeout");
212
213		/*  Disable interrupt */
214		devpriv->IntEnable &= ~ADC_READY;
215		writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
216
217		/*  Clear interrupt status */
218		devpriv->IntStatus |= ADC_READY;
219		writew(devpriv->IntStatus,
220		       devpriv->io_addr + ICP_MULTI_INT_STAT);
221
222		/*  Clear data received */
223		data[n] = 0;
224
225		return -ETIME;
226
227conv_finish:
228		data[n] =
229		    (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
230	}
231
232	/*  Disable interrupt */
233	devpriv->IntEnable &= ~ADC_READY;
234	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
235
236	/*  Clear interrupt status */
237	devpriv->IntStatus |= ADC_READY;
238	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
239
240	return n;
241}
242
243static int icp_multi_insn_write_ao(struct comedi_device *dev,
244				   struct comedi_subdevice *s,
245				   struct comedi_insn *insn, unsigned int *data)
246{
247	struct icp_multi_private *devpriv = dev->private;
248	int n, chan, range, timeout;
249
250	/*  Disable D/A conversion ready interrupt */
251	devpriv->IntEnable &= ~DAC_READY;
252	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
253
254	/*  Clear interrupt status */
255	devpriv->IntStatus |= DAC_READY;
256	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
257
258	/*  Get channel number and range */
259	chan = CR_CHAN(insn->chanspec);
260	range = CR_RANGE(insn->chanspec);
261
262	/*  Set up range and channel data */
263	/*  Bit 4 = 1 : Bipolar */
264	/*  Bit 5 = 0 : 5V */
265	/*  Bit 5 = 1 : 10V */
266	/*  Bits 8-9 : Channel number */
267	devpriv->DacCmdStatus &= 0xfccf;
268	devpriv->DacCmdStatus |= range_codes_analog[range];
269	devpriv->DacCmdStatus |= (chan << 8);
270
271	writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
272
273	for (n = 0; n < insn->n; n++) {
274		/*  Wait for analogue output data register to be
275		 *  ready for new data, or get fed up waiting */
276		timeout = 100;
277		while (timeout--) {
278			if (!(readw(devpriv->io_addr +
279				    ICP_MULTI_DAC_CSR) & DAC_BSY))
280				goto dac_ready;
281
282			udelay(1);
283		}
284
285		/*  If we reach here, a timeout has occurred */
286		comedi_error(dev, "D/A insn timeout");
287
288		/*  Disable interrupt */
289		devpriv->IntEnable &= ~DAC_READY;
290		writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
291
292		/*  Clear interrupt status */
293		devpriv->IntStatus |= DAC_READY;
294		writew(devpriv->IntStatus,
295		       devpriv->io_addr + ICP_MULTI_INT_STAT);
296
297		/*  Clear data received */
298		devpriv->ao_data[chan] = 0;
299
300		return -ETIME;
301
302dac_ready:
303		/*  Write data to analogue output data register */
304		writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
305
306		/*  Set DAC_ST bit to write the data to selected channel */
307		devpriv->DacCmdStatus |= DAC_ST;
308		writew(devpriv->DacCmdStatus,
309		       devpriv->io_addr + ICP_MULTI_DAC_CSR);
310		devpriv->DacCmdStatus &= ~DAC_ST;
311
312		/*  Save analogue output data */
313		devpriv->ao_data[chan] = data[n];
314	}
315
316	return n;
317}
318
319static int icp_multi_insn_read_ao(struct comedi_device *dev,
320				  struct comedi_subdevice *s,
321				  struct comedi_insn *insn, unsigned int *data)
322{
323	struct icp_multi_private *devpriv = dev->private;
324	int n, chan;
325
326	/*  Get channel number */
327	chan = CR_CHAN(insn->chanspec);
328
329	/*  Read analogue outputs */
330	for (n = 0; n < insn->n; n++)
331		data[n] = devpriv->ao_data[chan];
332
333	return n;
334}
335
336static int icp_multi_insn_bits_di(struct comedi_device *dev,
337				  struct comedi_subdevice *s,
338				  struct comedi_insn *insn, unsigned int *data)
339{
340	struct icp_multi_private *devpriv = dev->private;
341
342	data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
343
344	return insn->n;
345}
346
347static int icp_multi_insn_bits_do(struct comedi_device *dev,
348				  struct comedi_subdevice *s,
349				  struct comedi_insn *insn,
350				  unsigned int *data)
351{
352	struct icp_multi_private *devpriv = dev->private;
353
354	if (comedi_dio_update_state(s, data))
355		writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
356
357	data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
358
359	return insn->n;
360}
361
362static int icp_multi_insn_read_ctr(struct comedi_device *dev,
363				   struct comedi_subdevice *s,
364				   struct comedi_insn *insn, unsigned int *data)
365{
366	return 0;
367}
368
369static int icp_multi_insn_write_ctr(struct comedi_device *dev,
370				    struct comedi_subdevice *s,
371				    struct comedi_insn *insn,
372				    unsigned int *data)
373{
374	return 0;
375}
376
377static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
378{
379	struct comedi_device *dev = d;
380	struct icp_multi_private *devpriv = dev->private;
381	int int_no;
382
383	/*  Is this interrupt from our board? */
384	int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
385	if (!int_no)
386		/*  No, exit */
387		return IRQ_NONE;
388
389	/*  Determine which interrupt is active & handle it */
390	switch (int_no) {
391	case ADC_READY:
392		break;
393	case DAC_READY:
394		break;
395	case DOUT_ERROR:
396		break;
397	case DIN_STATUS:
398		break;
399	case CIE0:
400		break;
401	case CIE1:
402		break;
403	case CIE2:
404		break;
405	case CIE3:
406		break;
407	default:
408		break;
409
410	}
411
412	return IRQ_HANDLED;
413}
414
415#if 0
416static int check_channel_list(struct comedi_device *dev,
417			      struct comedi_subdevice *s,
418			      unsigned int *chanlist, unsigned int n_chan)
419{
420	unsigned int i;
421
422	/*  Check that we at least have one channel to check */
423	if (n_chan < 1) {
424		comedi_error(dev, "range/channel list is empty!");
425		return 0;
426	}
427	/*  Check all channels */
428	for (i = 0; i < n_chan; i++) {
429		/*  Check that channel number is < maximum */
430		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
431			if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
432				comedi_error(dev,
433					     "Incorrect differential ai ch-nr");
434				return 0;
435			}
436		} else {
437			if (CR_CHAN(chanlist[i]) > s->n_chan) {
438				comedi_error(dev,
439					     "Incorrect ai channel number");
440				return 0;
441			}
442		}
443	}
444	return 1;
445}
446#endif
447
448static int icp_multi_reset(struct comedi_device *dev)
449{
450	struct icp_multi_private *devpriv = dev->private;
451	unsigned int i;
452
453	/*  Clear INT enables and requests */
454	writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
455	writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
456
457	/* Set DACs to 0..5V range and 0V output */
458	for (i = 0; i < 4; i++) {
459		devpriv->DacCmdStatus &= 0xfcce;
460
461		/*  Set channel number */
462		devpriv->DacCmdStatus |= (i << 8);
463
464		/*  Output 0V */
465		writew(0, devpriv->io_addr + ICP_MULTI_AO);
466
467		/*  Set start conversion bit */
468		devpriv->DacCmdStatus |= DAC_ST;
469
470		/*  Output to command / status register */
471		writew(devpriv->DacCmdStatus,
472			devpriv->io_addr + ICP_MULTI_DAC_CSR);
473
474		/*  Delay to allow DAC time to recover */
475		udelay(1);
476	}
477
478	/* Digital outputs to 0 */
479	writew(0, devpriv->io_addr + ICP_MULTI_DO);
480
481	return 0;
482}
483
484static int icp_multi_auto_attach(struct comedi_device *dev,
485					   unsigned long context_unused)
486{
487	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
488	struct icp_multi_private *devpriv;
489	struct comedi_subdevice *s;
490	int ret;
491
492	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
493	if (!devpriv)
494		return -ENOMEM;
495
496	ret = comedi_pci_enable(dev);
497	if (ret)
498		return ret;
499
500	devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
501	if (!devpriv->io_addr)
502		return -ENOMEM;
503
504	ret = comedi_alloc_subdevices(dev, 5);
505	if (ret)
506		return ret;
507
508	icp_multi_reset(dev);
509
510	if (pcidev->irq) {
511		ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
512				  IRQF_SHARED, dev->board_name, dev);
513		if (ret == 0)
514			dev->irq = pcidev->irq;
515	}
516
517	s = &dev->subdevices[0];
518	dev->read_subdev = s;
519	s->type = COMEDI_SUBD_AI;
520	s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
521	s->n_chan = 16;
522	s->maxdata = 0x0fff;
523	s->len_chanlist = 16;
524	s->range_table = &range_analog;
525	s->insn_read = icp_multi_insn_read_ai;
526
527	s = &dev->subdevices[1];
528	s->type = COMEDI_SUBD_AO;
529	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
530	s->n_chan = 4;
531	s->maxdata = 0x0fff;
532	s->len_chanlist = 4;
533	s->range_table = &range_analog;
534	s->insn_write = icp_multi_insn_write_ao;
535	s->insn_read = icp_multi_insn_read_ao;
536
537	s = &dev->subdevices[2];
538	s->type = COMEDI_SUBD_DI;
539	s->subdev_flags = SDF_READABLE;
540	s->n_chan = 16;
541	s->maxdata = 1;
542	s->len_chanlist = 16;
543	s->range_table = &range_digital;
544	s->insn_bits = icp_multi_insn_bits_di;
545
546	s = &dev->subdevices[3];
547	s->type = COMEDI_SUBD_DO;
548	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
549	s->n_chan = 8;
550	s->maxdata = 1;
551	s->len_chanlist = 8;
552	s->range_table = &range_digital;
553	s->insn_bits = icp_multi_insn_bits_do;
554
555	s = &dev->subdevices[4];
556	s->type = COMEDI_SUBD_COUNTER;
557	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
558	s->n_chan = 4;
559	s->maxdata = 0xffff;
560	s->len_chanlist = 4;
561	s->state = 0;
562	s->insn_read = icp_multi_insn_read_ctr;
563	s->insn_write = icp_multi_insn_write_ctr;
564
565	devpriv->valid = 1;
566
567	dev_info(dev->class_dev, "%s attached, irq %sabled\n",
568		dev->board_name, dev->irq ? "en" : "dis");
569
570	return 0;
571}
572
573static void icp_multi_detach(struct comedi_device *dev)
574{
575	struct icp_multi_private *devpriv = dev->private;
576
577	if (devpriv)
578		if (devpriv->valid)
579			icp_multi_reset(dev);
580	if (dev->irq)
581		free_irq(dev->irq, dev);
582	if (devpriv && devpriv->io_addr)
583		iounmap(devpriv->io_addr);
584	comedi_pci_disable(dev);
585}
586
587static struct comedi_driver icp_multi_driver = {
588	.driver_name	= "icp_multi",
589	.module		= THIS_MODULE,
590	.auto_attach	= icp_multi_auto_attach,
591	.detach		= icp_multi_detach,
592};
593
594static int icp_multi_pci_probe(struct pci_dev *dev,
595			       const struct pci_device_id *id)
596{
597	return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
598}
599
600static const struct pci_device_id icp_multi_pci_table[] = {
601	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
602	{ 0 }
603};
604MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
605
606static struct pci_driver icp_multi_pci_driver = {
607	.name		= "icp_multi",
608	.id_table	= icp_multi_pci_table,
609	.probe		= icp_multi_pci_probe,
610	.remove		= comedi_pci_auto_unconfig,
611};
612module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
613
614MODULE_AUTHOR("Comedi http://www.comedi.org");
615MODULE_DESCRIPTION("Comedi low-level driver");
616MODULE_LICENSE("GPL");
617