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