icp_multi.c revision e2fde58204af85f20d08e2dc97b9586d2f34497e
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 = {
95	4, {
96		UNI_RANGE(5),
97		UNI_RANGE(10),
98		BIP_RANGE(5),
99		BIP_RANGE(10)
100	}
101};
102
103static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
104
105/*
106==============================================================================
107	Data & Structure declarations
108==============================================================================
109*/
110
111struct icp_multi_private {
112	char valid;		/*  card is usable */
113	void __iomem *io_addr;		/*  Pointer to mapped io address */
114	unsigned int AdcCmdStatus;	/*  ADC Command/Status register */
115	unsigned int DacCmdStatus;	/*  DAC Command/Status register */
116	unsigned int IntEnable;	/*  Interrupt Enable register */
117	unsigned int IntStatus;	/*  Interrupt Status register */
118	unsigned int act_chanlist[32];	/*  list of scanned channel */
119	unsigned char act_chanlist_len;	/*  len of scanlist */
120	unsigned char act_chanlist_pos;	/*  actual position in MUX list */
121	unsigned int *ai_chanlist;	/*  actaul chanlist */
122	unsigned short ao_data[4];	/*  data output buffer */
123	unsigned int do_data;	/*  Remember digital output data */
124};
125
126static 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
174static int icp_multi_ai_eoc(struct comedi_device *dev,
175			    struct comedi_subdevice *s,
176			    struct comedi_insn *insn,
177			    unsigned long context)
178{
179	struct icp_multi_private *devpriv = dev->private;
180	unsigned int status;
181
182	status = readw(devpriv->io_addr + ICP_MULTI_ADC_CSR);
183	if ((status & ADC_BSY) == 0)
184		return 0;
185	return -EBUSY;
186}
187
188static int icp_multi_insn_read_ai(struct comedi_device *dev,
189				  struct comedi_subdevice *s,
190				  struct comedi_insn *insn, unsigned int *data)
191{
192	struct icp_multi_private *devpriv = dev->private;
193	int ret = 0;
194	int n;
195
196	/*  Disable A/D conversion ready interrupt */
197	devpriv->IntEnable &= ~ADC_READY;
198	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
199
200	/*  Clear interrupt status */
201	devpriv->IntStatus |= ADC_READY;
202	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
203
204	/*  Set up appropriate channel, mode and range data, for specified ch */
205	setup_channel_list(dev, s, &insn->chanspec, 1);
206
207	for (n = 0; n < insn->n; n++) {
208		/*  Set start ADC bit */
209		devpriv->AdcCmdStatus |= ADC_ST;
210		writew(devpriv->AdcCmdStatus,
211		       devpriv->io_addr + ICP_MULTI_ADC_CSR);
212		devpriv->AdcCmdStatus &= ~ADC_ST;
213
214		udelay(1);
215
216		/*  Wait for conversion to complete, or get fed up waiting */
217		ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
218		if (ret) {
219			comedi_error(dev, "A/D insn timeout");
220			/*  Clear data received */
221			data[n] = 0;
222			break;
223		}
224
225		data[n] =
226		    (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff;
227	}
228
229	/*  Disable interrupt */
230	devpriv->IntEnable &= ~ADC_READY;
231	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
232
233	/*  Clear interrupt status */
234	devpriv->IntStatus |= ADC_READY;
235	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
236
237	return ret ? ret : n;
238}
239
240static int icp_multi_ao_eoc(struct comedi_device *dev,
241			    struct comedi_subdevice *s,
242			    struct comedi_insn *insn,
243			    unsigned long context)
244{
245	struct icp_multi_private *devpriv = dev->private;
246	unsigned int status;
247
248	status = readw(devpriv->io_addr + ICP_MULTI_DAC_CSR);
249	if ((status & DAC_BSY) == 0)
250		return 0;
251	return -EBUSY;
252}
253
254static int icp_multi_insn_write_ao(struct comedi_device *dev,
255				   struct comedi_subdevice *s,
256				   struct comedi_insn *insn, unsigned int *data)
257{
258	struct icp_multi_private *devpriv = dev->private;
259	int n, chan, range;
260	int ret;
261
262	/*  Disable D/A conversion ready interrupt */
263	devpriv->IntEnable &= ~DAC_READY;
264	writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN);
265
266	/*  Clear interrupt status */
267	devpriv->IntStatus |= DAC_READY;
268	writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT);
269
270	/*  Get channel number and range */
271	chan = CR_CHAN(insn->chanspec);
272	range = CR_RANGE(insn->chanspec);
273
274	/*  Set up range and channel data */
275	/*  Bit 4 = 1 : Bipolar */
276	/*  Bit 5 = 0 : 5V */
277	/*  Bit 5 = 1 : 10V */
278	/*  Bits 8-9 : Channel number */
279	devpriv->DacCmdStatus &= 0xfccf;
280	devpriv->DacCmdStatus |= range_codes_analog[range];
281	devpriv->DacCmdStatus |= (chan << 8);
282
283	writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR);
284
285	for (n = 0; n < insn->n; n++) {
286		/*  Wait for analogue output data register to be
287		 *  ready for new data, or get fed up waiting */
288		ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
289		if (ret) {
290			comedi_error(dev, "D/A insn timeout");
291
292			/*  Disable interrupt */
293			devpriv->IntEnable &= ~DAC_READY;
294			writew(devpriv->IntEnable,
295			       devpriv->io_addr + ICP_MULTI_INT_EN);
296
297			/*  Clear interrupt status */
298			devpriv->IntStatus |= DAC_READY;
299			writew(devpriv->IntStatus,
300			       devpriv->io_addr + ICP_MULTI_INT_STAT);
301
302			/*  Clear data received */
303			devpriv->ao_data[chan] = 0;
304
305			return ret;
306		}
307
308		/*  Write data to analogue output data register */
309		writew(data[n], devpriv->io_addr + ICP_MULTI_AO);
310
311		/*  Set DAC_ST bit to write the data to selected channel */
312		devpriv->DacCmdStatus |= DAC_ST;
313		writew(devpriv->DacCmdStatus,
314		       devpriv->io_addr + ICP_MULTI_DAC_CSR);
315		devpriv->DacCmdStatus &= ~DAC_ST;
316
317		/*  Save analogue output data */
318		devpriv->ao_data[chan] = data[n];
319	}
320
321	return n;
322}
323
324static int icp_multi_insn_read_ao(struct comedi_device *dev,
325				  struct comedi_subdevice *s,
326				  struct comedi_insn *insn, unsigned int *data)
327{
328	struct icp_multi_private *devpriv = dev->private;
329	int n, chan;
330
331	/*  Get channel number */
332	chan = CR_CHAN(insn->chanspec);
333
334	/*  Read analogue outputs */
335	for (n = 0; n < insn->n; n++)
336		data[n] = devpriv->ao_data[chan];
337
338	return n;
339}
340
341static int icp_multi_insn_bits_di(struct comedi_device *dev,
342				  struct comedi_subdevice *s,
343				  struct comedi_insn *insn, unsigned int *data)
344{
345	struct icp_multi_private *devpriv = dev->private;
346
347	data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
348
349	return insn->n;
350}
351
352static int icp_multi_insn_bits_do(struct comedi_device *dev,
353				  struct comedi_subdevice *s,
354				  struct comedi_insn *insn,
355				  unsigned int *data)
356{
357	struct icp_multi_private *devpriv = dev->private;
358
359	if (comedi_dio_update_state(s, data))
360		writew(s->state, devpriv->io_addr + ICP_MULTI_DO);
361
362	data[1] = readw(devpriv->io_addr + ICP_MULTI_DI);
363
364	return insn->n;
365}
366
367static int icp_multi_insn_read_ctr(struct comedi_device *dev,
368				   struct comedi_subdevice *s,
369				   struct comedi_insn *insn, unsigned int *data)
370{
371	return 0;
372}
373
374static int icp_multi_insn_write_ctr(struct comedi_device *dev,
375				    struct comedi_subdevice *s,
376				    struct comedi_insn *insn,
377				    unsigned int *data)
378{
379	return 0;
380}
381
382static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
383{
384	struct comedi_device *dev = d;
385	struct icp_multi_private *devpriv = dev->private;
386	int int_no;
387
388	/*  Is this interrupt from our board? */
389	int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ;
390	if (!int_no)
391		/*  No, exit */
392		return IRQ_NONE;
393
394	/*  Determine which interrupt is active & handle it */
395	switch (int_no) {
396	case ADC_READY:
397		break;
398	case DAC_READY:
399		break;
400	case DOUT_ERROR:
401		break;
402	case DIN_STATUS:
403		break;
404	case CIE0:
405		break;
406	case CIE1:
407		break;
408	case CIE2:
409		break;
410	case CIE3:
411		break;
412	default:
413		break;
414
415	}
416
417	return IRQ_HANDLED;
418}
419
420#if 0
421static int check_channel_list(struct comedi_device *dev,
422			      struct comedi_subdevice *s,
423			      unsigned int *chanlist, unsigned int n_chan)
424{
425	unsigned int i;
426
427	/*  Check that we at least have one channel to check */
428	if (n_chan < 1) {
429		comedi_error(dev, "range/channel list is empty!");
430		return 0;
431	}
432	/*  Check all channels */
433	for (i = 0; i < n_chan; i++) {
434		/*  Check that channel number is < maximum */
435		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
436			if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
437				comedi_error(dev,
438					     "Incorrect differential ai ch-nr");
439				return 0;
440			}
441		} else {
442			if (CR_CHAN(chanlist[i]) > s->n_chan) {
443				comedi_error(dev,
444					     "Incorrect ai channel number");
445				return 0;
446			}
447		}
448	}
449	return 1;
450}
451#endif
452
453static int icp_multi_reset(struct comedi_device *dev)
454{
455	struct icp_multi_private *devpriv = dev->private;
456	unsigned int i;
457
458	/*  Clear INT enables and requests */
459	writew(0, devpriv->io_addr + ICP_MULTI_INT_EN);
460	writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT);
461
462	/* Set DACs to 0..5V range and 0V output */
463	for (i = 0; i < 4; i++) {
464		devpriv->DacCmdStatus &= 0xfcce;
465
466		/*  Set channel number */
467		devpriv->DacCmdStatus |= (i << 8);
468
469		/*  Output 0V */
470		writew(0, devpriv->io_addr + ICP_MULTI_AO);
471
472		/*  Set start conversion bit */
473		devpriv->DacCmdStatus |= DAC_ST;
474
475		/*  Output to command / status register */
476		writew(devpriv->DacCmdStatus,
477			devpriv->io_addr + ICP_MULTI_DAC_CSR);
478
479		/*  Delay to allow DAC time to recover */
480		udelay(1);
481	}
482
483	/* Digital outputs to 0 */
484	writew(0, devpriv->io_addr + ICP_MULTI_DO);
485
486	return 0;
487}
488
489static int icp_multi_auto_attach(struct comedi_device *dev,
490					   unsigned long context_unused)
491{
492	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
493	struct icp_multi_private *devpriv;
494	struct comedi_subdevice *s;
495	int ret;
496
497	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
498	if (!devpriv)
499		return -ENOMEM;
500
501	ret = comedi_pci_enable(dev);
502	if (ret)
503		return ret;
504
505	devpriv->io_addr = pci_ioremap_bar(pcidev, 2);
506	if (!devpriv->io_addr)
507		return -ENOMEM;
508
509	ret = comedi_alloc_subdevices(dev, 5);
510	if (ret)
511		return ret;
512
513	icp_multi_reset(dev);
514
515	if (pcidev->irq) {
516		ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
517				  IRQF_SHARED, dev->board_name, dev);
518		if (ret == 0)
519			dev->irq = pcidev->irq;
520	}
521
522	s = &dev->subdevices[0];
523	dev->read_subdev = s;
524	s->type = COMEDI_SUBD_AI;
525	s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
526	s->n_chan = 16;
527	s->maxdata = 0x0fff;
528	s->len_chanlist = 16;
529	s->range_table = &range_analog;
530	s->insn_read = icp_multi_insn_read_ai;
531
532	s = &dev->subdevices[1];
533	s->type = COMEDI_SUBD_AO;
534	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
535	s->n_chan = 4;
536	s->maxdata = 0x0fff;
537	s->len_chanlist = 4;
538	s->range_table = &range_analog;
539	s->insn_write = icp_multi_insn_write_ao;
540	s->insn_read = icp_multi_insn_read_ao;
541
542	s = &dev->subdevices[2];
543	s->type = COMEDI_SUBD_DI;
544	s->subdev_flags = SDF_READABLE;
545	s->n_chan = 16;
546	s->maxdata = 1;
547	s->len_chanlist = 16;
548	s->range_table = &range_digital;
549	s->insn_bits = icp_multi_insn_bits_di;
550
551	s = &dev->subdevices[3];
552	s->type = COMEDI_SUBD_DO;
553	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
554	s->n_chan = 8;
555	s->maxdata = 1;
556	s->len_chanlist = 8;
557	s->range_table = &range_digital;
558	s->insn_bits = icp_multi_insn_bits_do;
559
560	s = &dev->subdevices[4];
561	s->type = COMEDI_SUBD_COUNTER;
562	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
563	s->n_chan = 4;
564	s->maxdata = 0xffff;
565	s->len_chanlist = 4;
566	s->state = 0;
567	s->insn_read = icp_multi_insn_read_ctr;
568	s->insn_write = icp_multi_insn_write_ctr;
569
570	devpriv->valid = 1;
571
572	return 0;
573}
574
575static void icp_multi_detach(struct comedi_device *dev)
576{
577	struct icp_multi_private *devpriv = dev->private;
578
579	if (devpriv)
580		if (devpriv->valid)
581			icp_multi_reset(dev);
582	if (dev->irq)
583		free_irq(dev->irq, dev);
584	if (devpriv && devpriv->io_addr)
585		iounmap(devpriv->io_addr);
586	comedi_pci_disable(dev);
587}
588
589static struct comedi_driver icp_multi_driver = {
590	.driver_name	= "icp_multi",
591	.module		= THIS_MODULE,
592	.auto_attach	= icp_multi_auto_attach,
593	.detach		= icp_multi_detach,
594};
595
596static int icp_multi_pci_probe(struct pci_dev *dev,
597			       const struct pci_device_id *id)
598{
599	return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
600}
601
602static const struct pci_device_id icp_multi_pci_table[] = {
603	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) },
604	{ 0 }
605};
606MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
607
608static struct pci_driver icp_multi_pci_driver = {
609	.name		= "icp_multi",
610	.id_table	= icp_multi_pci_table,
611	.probe		= icp_multi_pci_probe,
612	.remove		= comedi_pci_auto_unconfig,
613};
614module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
615
616MODULE_AUTHOR("Comedi http://www.comedi.org");
617MODULE_DESCRIPTION("Comedi low-level driver");
618MODULE_LICENSE("GPL");
619