[go: nahoru, domu]

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