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