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