icp_multi.c revision aac307f9dd5ce1fe651140a036ab4b0a0571b54a
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 ICP_MULTI_ADC_CSR	0	/* R/W: ADC command/status register */
53#define ICP_MULTI_AI		2	/* R:   Analogue input data */
54#define ICP_MULTI_DAC_CSR	4	/* R/W: DAC command/status register */
55#define ICP_MULTI_AO		6	/* R/W: Analogue output data */
56#define ICP_MULTI_DI		8	/* R/W: Digital inouts */
57#define ICP_MULTI_DO		0x0A	/* R/W: Digital outputs */
58#define ICP_MULTI_INT_EN	0x0C	/* R/W: Interrupt enable register */
59#define ICP_MULTI_INT_STAT	0x0E	/* R/W: Interrupt status register */
60#define ICP_MULTI_CNTR0		0x10	/* R/W: Counter 0 */
61#define ICP_MULTI_CNTR1		0x12	/* R/W: counter 1 */
62#define ICP_MULTI_CNTR2		0x14	/* R/W: Counter 2 */
63#define ICP_MULTI_CNTR3		0x16	/* R/W: Counter 3 */
64
65/*  Define bits from ADC command/status register */
66#define	ADC_ST		0x0001	/* Start ADC */
67#define	ADC_BSY		0x0001	/* ADC busy */
68#define ADC_BI		0x0010	/* Bipolar input range 1 = bipolar */
69#define ADC_RA		0x0020	/* Input range 0 = 5V, 1 = 10V */
70#define	ADC_DI		0x0040	/* Differential input mode 1 = differential */
71
72/*  Define bits from DAC command/status register */
73#define	DAC_ST		0x0001	/* Start DAC */
74#define DAC_BSY		0x0001	/* DAC busy */
75#define	DAC_BI		0x0010	/* Bipolar input range 1 = bipolar */
76#define	DAC_RA		0x0020	/* Input range 0 = 5V, 1 = 10V */
77
78/*  Define bits from interrupt enable/status registers */
79#define	ADC_READY	0x0001	/* A/d conversion ready interrupt */
80#define	DAC_READY	0x0002	/* D/a conversion ready interrupt */
81#define	DOUT_ERROR	0x0004	/* Digital output error interrupt */
82#define	DIN_STATUS	0x0008	/* Digital input status change interrupt */
83#define	CIE0		0x0010	/* Counter 0 overrun interrupt */
84#define	CIE1		0x0020	/* Counter 1 overrun interrupt */
85#define	CIE2		0x0040	/* Counter 2 overrun interrupt */
86#define	CIE3		0x0080	/* Counter 3 overrun interrupt */
87
88/*  Useful definitions */
89#define	Status_IRQ	0x00ff	/*  All interrupts */
90
91/*  Define analogue range */
92static const struct comedi_lrange range_analog = {
93	4, {
94		UNI_RANGE(5),
95		UNI_RANGE(10),
96		BIP_RANGE(5),
97		BIP_RANGE(10)
98	}
99};
100
101static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
102
103/*
104==============================================================================
105	Data & Structure declarations
106==============================================================================
107*/
108
109struct icp_multi_private {
110	char valid;		/*  card is usable */
111	unsigned int AdcCmdStatus;	/*  ADC Command/Status register */
112	unsigned int DacCmdStatus;	/*  DAC Command/Status register */
113	unsigned int IntEnable;	/*  Interrupt Enable register */
114	unsigned int IntStatus;	/*  Interrupt Status register */
115	unsigned int act_chanlist[32];	/*  list of scanned channel */
116	unsigned char act_chanlist_len;	/*  len of scanlist */
117	unsigned char act_chanlist_pos;	/*  actual position in MUX list */
118	unsigned int *ai_chanlist;	/*  actaul chanlist */
119	unsigned int do_data;	/*  Remember digital output data */
120};
121
122static void setup_channel_list(struct comedi_device *dev,
123			       struct comedi_subdevice *s,
124			       unsigned int *chanlist, unsigned int n_chan)
125{
126	struct icp_multi_private *devpriv = dev->private;
127	unsigned int i, range, chanprog;
128	unsigned int diff;
129
130	devpriv->act_chanlist_len = n_chan;
131	devpriv->act_chanlist_pos = 0;
132
133	for (i = 0; i < n_chan; i++) {
134		/*  Get channel */
135		chanprog = CR_CHAN(chanlist[i]);
136
137		/*  Determine if it is a differential channel (Bit 15  = 1) */
138		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
139			diff = 1;
140			chanprog &= 0x0007;
141		} else {
142			diff = 0;
143			chanprog &= 0x000f;
144		}
145
146		/*  Clear channel, range and input mode bits
147		 *  in A/D command/status register */
148		devpriv->AdcCmdStatus &= 0xf00f;
149
150		/*  Set channel number and differential mode status bit */
151		if (diff) {
152			/*  Set channel number, bits 9-11 & mode, bit 6 */
153			devpriv->AdcCmdStatus |= (chanprog << 9);
154			devpriv->AdcCmdStatus |= ADC_DI;
155		} else
156			/*  Set channel number, bits 8-11 */
157			devpriv->AdcCmdStatus |= (chanprog << 8);
158
159		/*  Get range for current channel */
160		range = range_codes_analog[CR_RANGE(chanlist[i])];
161		/*  Set range. bits 4-5 */
162		devpriv->AdcCmdStatus |= range;
163
164		/* Output channel, range, mode to ICP Multi */
165		writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
166	}
167}
168
169static int icp_multi_ai_eoc(struct comedi_device *dev,
170			    struct comedi_subdevice *s,
171			    struct comedi_insn *insn,
172			    unsigned long context)
173{
174	unsigned int status;
175
176	status = readw(dev->mmio + ICP_MULTI_ADC_CSR);
177	if ((status & ADC_BSY) == 0)
178		return 0;
179	return -EBUSY;
180}
181
182static int icp_multi_insn_read_ai(struct comedi_device *dev,
183				  struct comedi_subdevice *s,
184				  struct comedi_insn *insn,
185				  unsigned int *data)
186{
187	struct icp_multi_private *devpriv = dev->private;
188	int ret = 0;
189	int n;
190
191	/*  Disable A/D conversion ready interrupt */
192	devpriv->IntEnable &= ~ADC_READY;
193	writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
194
195	/*  Clear interrupt status */
196	devpriv->IntStatus |= ADC_READY;
197	writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
198
199	/*  Set up appropriate channel, mode and range data, for specified ch */
200	setup_channel_list(dev, s, &insn->chanspec, 1);
201
202	for (n = 0; n < insn->n; n++) {
203		/*  Set start ADC bit */
204		devpriv->AdcCmdStatus |= ADC_ST;
205		writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
206		devpriv->AdcCmdStatus &= ~ADC_ST;
207
208		udelay(1);
209
210		/*  Wait for conversion to complete, or get fed up waiting */
211		ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
212		if (ret)
213			break;
214
215		data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff;
216	}
217
218	/*  Disable interrupt */
219	devpriv->IntEnable &= ~ADC_READY;
220	writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
221
222	/*  Clear interrupt status */
223	devpriv->IntStatus |= ADC_READY;
224	writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
225
226	return ret ? ret : n;
227}
228
229static int icp_multi_ao_eoc(struct comedi_device *dev,
230			    struct comedi_subdevice *s,
231			    struct comedi_insn *insn,
232			    unsigned long context)
233{
234	unsigned int status;
235
236	status = readw(dev->mmio + ICP_MULTI_DAC_CSR);
237	if ((status & DAC_BSY) == 0)
238		return 0;
239	return -EBUSY;
240}
241
242static int icp_multi_ao_insn_write(struct comedi_device *dev,
243				   struct comedi_subdevice *s,
244				   struct comedi_insn *insn,
245				   unsigned int *data)
246{
247	struct icp_multi_private *devpriv = dev->private;
248	unsigned int chan = CR_CHAN(insn->chanspec);
249	unsigned int range = CR_RANGE(insn->chanspec);
250	int i;
251
252	/*  Disable D/A conversion ready interrupt */
253	devpriv->IntEnable &= ~DAC_READY;
254	writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
255
256	/*  Clear interrupt status */
257	devpriv->IntStatus |= DAC_READY;
258	writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
259
260	/*  Set up range and channel data */
261	/*  Bit 4 = 1 : Bipolar */
262	/*  Bit 5 = 0 : 5V */
263	/*  Bit 5 = 1 : 10V */
264	/*  Bits 8-9 : Channel number */
265	devpriv->DacCmdStatus &= 0xfccf;
266	devpriv->DacCmdStatus |= range_codes_analog[range];
267	devpriv->DacCmdStatus |= (chan << 8);
268
269	writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
270
271	for (i = 0; i < insn->n; i++) {
272		unsigned int val = data[i];
273		int ret;
274
275		/*  Wait for analogue output data register to be
276		 *  ready for new data, or get fed up waiting */
277		ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
278		if (ret) {
279			/*  Disable interrupt */
280			devpriv->IntEnable &= ~DAC_READY;
281			writew(devpriv->IntEnable,
282			       dev->mmio + ICP_MULTI_INT_EN);
283
284			/*  Clear interrupt status */
285			devpriv->IntStatus |= DAC_READY;
286			writew(devpriv->IntStatus,
287			       dev->mmio + ICP_MULTI_INT_STAT);
288
289			return ret;
290		}
291
292		writew(val, dev->mmio + ICP_MULTI_AO);
293
294		/*  Set DAC_ST bit to write the data to selected channel */
295		devpriv->DacCmdStatus |= DAC_ST;
296		writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
297		devpriv->DacCmdStatus &= ~DAC_ST;
298
299		s->readback[chan] = val;
300	}
301
302	return insn->n;
303}
304
305static int icp_multi_insn_bits_di(struct comedi_device *dev,
306				  struct comedi_subdevice *s,
307				  struct comedi_insn *insn,
308				  unsigned int *data)
309{
310	data[1] = readw(dev->mmio + ICP_MULTI_DI);
311
312	return insn->n;
313}
314
315static int icp_multi_insn_bits_do(struct comedi_device *dev,
316				  struct comedi_subdevice *s,
317				  struct comedi_insn *insn,
318				  unsigned int *data)
319{
320	if (comedi_dio_update_state(s, data))
321		writew(s->state, dev->mmio + ICP_MULTI_DO);
322
323	data[1] = readw(dev->mmio + ICP_MULTI_DI);
324
325	return insn->n;
326}
327
328static int icp_multi_insn_read_ctr(struct comedi_device *dev,
329				   struct comedi_subdevice *s,
330				   struct comedi_insn *insn, unsigned int *data)
331{
332	return 0;
333}
334
335static int icp_multi_insn_write_ctr(struct comedi_device *dev,
336				    struct comedi_subdevice *s,
337				    struct comedi_insn *insn,
338				    unsigned int *data)
339{
340	return 0;
341}
342
343static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
344{
345	struct comedi_device *dev = d;
346	int int_no;
347
348	/*  Is this interrupt from our board? */
349	int_no = readw(dev->mmio + ICP_MULTI_INT_STAT) & Status_IRQ;
350	if (!int_no)
351		/*  No, exit */
352		return IRQ_NONE;
353
354	/*  Determine which interrupt is active & handle it */
355	switch (int_no) {
356	case ADC_READY:
357		break;
358	case DAC_READY:
359		break;
360	case DOUT_ERROR:
361		break;
362	case DIN_STATUS:
363		break;
364	case CIE0:
365		break;
366	case CIE1:
367		break;
368	case CIE2:
369		break;
370	case CIE3:
371		break;
372	default:
373		break;
374
375	}
376
377	return IRQ_HANDLED;
378}
379
380#if 0
381static int check_channel_list(struct comedi_device *dev,
382			      struct comedi_subdevice *s,
383			      unsigned int *chanlist, unsigned int n_chan)
384{
385	unsigned int i;
386
387	/*  Check that we at least have one channel to check */
388	if (n_chan < 1) {
389		dev_err(dev->class_dev, "range/channel list is empty!\n");
390		return 0;
391	}
392	/*  Check all channels */
393	for (i = 0; i < n_chan; i++) {
394		/*  Check that channel number is < maximum */
395		if (CR_AREF(chanlist[i]) == AREF_DIFF) {
396			if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
397				dev_err(dev->class_dev,
398					"Incorrect differential ai ch-nr\n");
399				return 0;
400			}
401		} else {
402			if (CR_CHAN(chanlist[i]) > s->n_chan) {
403				dev_err(dev->class_dev,
404					"Incorrect ai channel number\n");
405				return 0;
406			}
407		}
408	}
409	return 1;
410}
411#endif
412
413static int icp_multi_reset(struct comedi_device *dev)
414{
415	struct icp_multi_private *devpriv = dev->private;
416	unsigned int i;
417
418	/*  Clear INT enables and requests */
419	writew(0, dev->mmio + ICP_MULTI_INT_EN);
420	writew(0x00ff, dev->mmio + ICP_MULTI_INT_STAT);
421
422	/* Set DACs to 0..5V range and 0V output */
423	for (i = 0; i < 4; i++) {
424		devpriv->DacCmdStatus &= 0xfcce;
425
426		/*  Set channel number */
427		devpriv->DacCmdStatus |= (i << 8);
428
429		/*  Output 0V */
430		writew(0, dev->mmio + ICP_MULTI_AO);
431
432		/*  Set start conversion bit */
433		devpriv->DacCmdStatus |= DAC_ST;
434
435		/*  Output to command / status register */
436		writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
437
438		/*  Delay to allow DAC time to recover */
439		udelay(1);
440	}
441
442	/* Digital outputs to 0 */
443	writew(0, dev->mmio + ICP_MULTI_DO);
444
445	return 0;
446}
447
448static int icp_multi_auto_attach(struct comedi_device *dev,
449					   unsigned long context_unused)
450{
451	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
452	struct icp_multi_private *devpriv;
453	struct comedi_subdevice *s;
454	int ret;
455
456	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
457	if (!devpriv)
458		return -ENOMEM;
459
460	ret = comedi_pci_enable(dev);
461	if (ret)
462		return ret;
463
464	dev->mmio = pci_ioremap_bar(pcidev, 2);
465	if (!dev->mmio)
466		return -ENOMEM;
467
468	ret = comedi_alloc_subdevices(dev, 5);
469	if (ret)
470		return ret;
471
472	icp_multi_reset(dev);
473
474	if (pcidev->irq) {
475		ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
476				  IRQF_SHARED, dev->board_name, dev);
477		if (ret == 0)
478			dev->irq = pcidev->irq;
479	}
480
481	s = &dev->subdevices[0];
482	dev->read_subdev = s;
483	s->type = COMEDI_SUBD_AI;
484	s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
485	s->n_chan = 16;
486	s->maxdata = 0x0fff;
487	s->len_chanlist = 16;
488	s->range_table = &range_analog;
489	s->insn_read = icp_multi_insn_read_ai;
490
491	s = &dev->subdevices[1];
492	s->type = COMEDI_SUBD_AO;
493	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
494	s->n_chan = 4;
495	s->maxdata = 0x0fff;
496	s->len_chanlist = 4;
497	s->range_table = &range_analog;
498	s->insn_write = icp_multi_ao_insn_write;
499	s->insn_read = comedi_readback_insn_read;
500
501	ret = comedi_alloc_subdev_readback(s);
502	if (ret)
503		return ret;
504
505	s = &dev->subdevices[2];
506	s->type = COMEDI_SUBD_DI;
507	s->subdev_flags = SDF_READABLE;
508	s->n_chan = 16;
509	s->maxdata = 1;
510	s->len_chanlist = 16;
511	s->range_table = &range_digital;
512	s->insn_bits = icp_multi_insn_bits_di;
513
514	s = &dev->subdevices[3];
515	s->type = COMEDI_SUBD_DO;
516	s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
517	s->n_chan = 8;
518	s->maxdata = 1;
519	s->len_chanlist = 8;
520	s->range_table = &range_digital;
521	s->insn_bits = icp_multi_insn_bits_do;
522
523	s = &dev->subdevices[4];
524	s->type = COMEDI_SUBD_COUNTER;
525	s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
526	s->n_chan = 4;
527	s->maxdata = 0xffff;
528	s->len_chanlist = 4;
529	s->state = 0;
530	s->insn_read = icp_multi_insn_read_ctr;
531	s->insn_write = icp_multi_insn_write_ctr;
532
533	devpriv->valid = 1;
534
535	return 0;
536}
537
538static void icp_multi_detach(struct comedi_device *dev)
539{
540	struct icp_multi_private *devpriv = dev->private;
541
542	if (devpriv)
543		if (devpriv->valid)
544			icp_multi_reset(dev);
545	comedi_pci_detach(dev);
546}
547
548static struct comedi_driver icp_multi_driver = {
549	.driver_name	= "icp_multi",
550	.module		= THIS_MODULE,
551	.auto_attach	= icp_multi_auto_attach,
552	.detach		= icp_multi_detach,
553};
554
555static int icp_multi_pci_probe(struct pci_dev *dev,
556			       const struct pci_device_id *id)
557{
558	return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
559}
560
561static const struct pci_device_id icp_multi_pci_table[] = {
562	{ PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
563	{ 0 }
564};
565MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
566
567static struct pci_driver icp_multi_pci_driver = {
568	.name		= "icp_multi",
569	.id_table	= icp_multi_pci_table,
570	.probe		= icp_multi_pci_probe,
571	.remove		= comedi_pci_auto_unconfig,
572};
573module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
574
575MODULE_AUTHOR("Comedi http://www.comedi.org");
576MODULE_DESCRIPTION("Comedi low-level driver");
577MODULE_LICENSE("GPL");
578