icp_multi.c revision 7fc465b106b40a598d83a0c98d0e8c2a1b4653ff
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    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/*
24Driver: icp_multi
25Description: Inova ICP_MULTI
26Author: Anne Smorthit <anne.smorthit@sfwte.ch>
27Devices: [Inova] ICP_MULTI (icp_multi)
28Status: works
29
30The driver works for analog input and output and digital input and output.
31It does not work with interrupts or with the counters.  Currently no support
32for DMA.
33
34It has 16 single-ended or 8 differential Analogue Input channels with 12-bit
35resolution.  Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA.  Input
36ranges can be individually programmed for each channel.  Voltage or current
37measurement is selected by jumper.
38
39There are 4 x 12-bit Analogue Outputs.  Ranges : 5V, 10V, +/-5V, +/-10V
40
4116 x Digital Inputs, 24V
42
438 x Digital Outputs, 24V, 1A
44
454 x 16-bit counters
46
47Configuration 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 */
100static 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
108static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
109
110/*
111==============================================================================
112	Data & Structure declarations
113==============================================================================
114*/
115
116struct icp_multi_private {
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
133static 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
181static 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 +
212				    ICP_MULTI_ADC_CSR) & ADC_BSY))
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
235conv_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
251static 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 +
287				    ICP_MULTI_DAC_CSR) & DAC_BSY))
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
310dac_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
327static 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
344static 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
355static 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
375static 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
382static 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
390static 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
429static 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
461static 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
497static 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;
502	resource_size_t iobase;
503	int ret;
504
505	dev->board_name = dev->driver->driver_name;
506
507	devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
508	if (!devpriv)
509		return -ENOMEM;
510	dev->private = devpriv;
511
512	ret = comedi_pci_enable(pcidev, dev->board_name);
513	if (ret)
514		return ret;
515	iobase = pci_resource_start(pcidev, 2);
516	dev->iobase = iobase;
517
518	devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE);
519	if (!devpriv->io_addr)
520		return -ENOMEM;
521
522	ret = comedi_alloc_subdevices(dev, 5);
523	if (ret)
524		return ret;
525
526	icp_multi_reset(dev);
527
528	if (pcidev->irq) {
529		ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
530				  IRQF_SHARED, dev->board_name, dev);
531		if (ret == 0)
532			dev->irq = pcidev->irq;
533	}
534
535	s = &dev->subdevices[0];
536	dev->read_subdev = s;
537	s->type = COMEDI_SUBD_AI;
538	s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
539	s->n_chan = 16;
540	s->maxdata = 0x0fff;
541	s->len_chanlist = 16;
542	s->range_table = &range_analog;
543	s->insn_read = icp_multi_insn_read_ai;
544
545	s = &dev->subdevices[1];
546	s->type = COMEDI_SUBD_AO;
547	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
548	s->n_chan = 4;
549	s->maxdata = 0x0fff;
550	s->len_chanlist = 4;
551	s->range_table = &range_analog;
552	s->insn_write = icp_multi_insn_write_ao;
553	s->insn_read = icp_multi_insn_read_ao;
554
555	s = &dev->subdevices[2];
556	s->type = COMEDI_SUBD_DI;
557	s->subdev_flags = SDF_READABLE;
558	s->n_chan = 16;
559	s->maxdata = 1;
560	s->len_chanlist = 16;
561	s->range_table = &range_digital;
562	s->io_bits = 0;
563	s->insn_bits = icp_multi_insn_bits_di;
564
565	s = &dev->subdevices[3];
566	s->type = COMEDI_SUBD_DO;
567	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
568	s->n_chan = 8;
569	s->maxdata = 1;
570	s->len_chanlist = 8;
571	s->range_table = &range_digital;
572	s->io_bits = 0xff;
573	s->state = 0;
574	s->insn_bits = icp_multi_insn_bits_do;
575
576	s = &dev->subdevices[4];
577	s->type = COMEDI_SUBD_COUNTER;
578	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
579	s->n_chan = 4;
580	s->maxdata = 0xffff;
581	s->len_chanlist = 4;
582	s->state = 0;
583	s->insn_read = icp_multi_insn_read_ctr;
584	s->insn_write = icp_multi_insn_write_ctr;
585
586	devpriv->valid = 1;
587
588	dev_info(dev->class_dev, "%s attached, irq %sabled\n",
589		dev->board_name, dev->irq ? "en" : "dis");
590
591	return 0;
592}
593
594static void icp_multi_detach(struct comedi_device *dev)
595{
596	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
597	struct icp_multi_private *devpriv = dev->private;
598
599	if (devpriv)
600		if (devpriv->valid)
601			icp_multi_reset(dev);
602	if (dev->irq)
603		free_irq(dev->irq, dev);
604	if (devpriv && devpriv->io_addr)
605		iounmap(devpriv->io_addr);
606	if (pcidev) {
607		if (dev->iobase)
608			comedi_pci_disable(pcidev);
609	}
610}
611
612static struct comedi_driver icp_multi_driver = {
613	.driver_name	= "icp_multi",
614	.module		= THIS_MODULE,
615	.attach_pci	= icp_multi_attach_pci,
616	.detach		= icp_multi_detach,
617};
618
619static int __devinit icp_multi_pci_probe(struct pci_dev *dev,
620					   const struct pci_device_id *ent)
621{
622	return comedi_pci_auto_config(dev, &icp_multi_driver);
623}
624
625static void __devexit icp_multi_pci_remove(struct pci_dev *dev)
626{
627	comedi_pci_auto_unconfig(dev);
628}
629
630static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = {
631	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
632	{ 0 }
633};
634MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
635
636static struct pci_driver icp_multi_pci_driver = {
637	.name		= "icp_multi",
638	.id_table	= icp_multi_pci_table,
639	.probe		= icp_multi_pci_probe,
640	.remove		= __devexit_p(icp_multi_pci_remove),
641};
642module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
643
644MODULE_AUTHOR("Comedi http://www.comedi.org");
645MODULE_DESCRIPTION("Comedi low-level driver");
646MODULE_LICENSE("GPL");
647