pcl816.c revision 13de4f000ec491b16a820e4ed59de2c98b7807be
1/*
2   comedi/drivers/pcl816.c
3
4   Author:  Juan Grigera <juan@grigera.com.ar>
5            based on pcl818 by Michal Dobes <dobes@tesnet.cz> and bits of pcl812
6
7   hardware driver for Advantech cards:
8    card:   PCL-816, PCL814B
9    driver: pcl816
10*/
11/*
12Driver: pcl816
13Description: Advantech PCL-816 cards, PCL-814
14Author: Juan Grigera <juan@grigera.com.ar>
15Devices: [Advantech] PCL-816 (pcl816), PCL-814B (pcl814b)
16Status: works
17Updated: Tue,  2 Apr 2002 23:15:21 -0800
18
19PCL 816 and 814B have 16 SE/DIFF ADCs, 16 DACs, 16 DI and 16 DO.
20Differences are at resolution (16 vs 12 bits).
21
22The driver support AI command mode, other subdevices not written.
23
24Analog output and digital input and output are not supported.
25
26Configuration Options:
27  [0] - IO Base
28  [1] - IRQ	(0=disable, 2, 3, 4, 5, 6, 7)
29  [2] - DMA	(0=disable, 1, 3)
30  [3] - 0, 10=10MHz clock for 8254
31            1= 1MHz clock for 8254
32
33*/
34
35#include "../comedidev.h"
36
37#include <linux/ioport.h>
38#include <linux/mc146818rtc.h>
39#include <linux/delay.h>
40#include <asm/dma.h>
41
42#include "8253.h"
43
44#define DEBUG(x) x
45
46/* boards constants */
47/* IO space len */
48#define PCLx1x_RANGE 16
49
50/* #define outb(x,y)  printk("OUTB(%x, 200+%d)\n", x,y-0x200); outb(x,y) */
51
52/* INTEL 8254 counters */
53#define PCL816_CTR0 4
54#define PCL816_CTR1 5
55#define PCL816_CTR2 6
56/* R: counter read-back register W: counter control */
57#define PCL816_CTRCTL 7
58
59/* R: A/D high byte W: A/D range control */
60#define PCL816_RANGE 9
61/* W: clear INT request */
62#define PCL816_CLRINT 10
63/* R: next mux scan channel W: mux scan channel & range control pointer */
64#define PCL816_MUX 11
65/* R/W: operation control register */
66#define PCL816_CONTROL 12
67
68/* R: return status byte  W: set DMA/IRQ */
69#define PCL816_STATUS 13
70#define PCL816_STATUS_DRDY_MASK 0x80
71
72/* R: low byte of A/D W: soft A/D trigger */
73#define PCL816_AD_LO 8
74/* R: high byte of A/D W: A/D range control */
75#define PCL816_AD_HI 9
76
77/* type of interrupt handler */
78#define INT_TYPE_AI1_INT 1
79#define INT_TYPE_AI1_DMA 2
80#define INT_TYPE_AI3_INT 4
81#define INT_TYPE_AI3_DMA 5
82#ifdef unused
83#define INT_TYPE_AI1_DMA_RTC 9
84#define INT_TYPE_AI3_DMA_RTC 10
85
86/* RTC stuff... */
87#define RTC_IRQ 	8
88#define RTC_IO_EXTENT	0x10
89#endif
90
91#define MAGIC_DMA_WORD 0x5a5a
92
93static const struct comedi_lrange range_pcl816 = { 8, {
94						       BIP_RANGE(10),
95						       BIP_RANGE(5),
96						       BIP_RANGE(2.5),
97						       BIP_RANGE(1.25),
98						       UNI_RANGE(10),
99						       UNI_RANGE(5),
100						       UNI_RANGE(2.5),
101						       UNI_RANGE(1.25),
102						       }
103};
104
105struct pcl816_board {
106
107	const char *name;	/*  board name */
108	int n_ranges;		/*  len of range list */
109	int n_aichan;		/*  num of A/D chans in diferencial mode */
110	unsigned int ai_ns_min;	/*  minimal alllowed delay between samples (in ns) */
111	int n_aochan;		/*  num of D/A chans */
112	int n_dichan;		/*  num of DI chans */
113	int n_dochan;		/*  num of DO chans */
114	const struct comedi_lrange *ai_range_type;	/*  default A/D rangelist */
115	const struct comedi_lrange *ao_range_type;	/*  default D/A rangelist */
116	unsigned int io_range;	/*  len of IO space */
117	unsigned int IRQbits;	/*  allowed interrupts */
118	unsigned int DMAbits;	/*  allowed DMA chans */
119	int ai_maxdata;		/*  maxdata for A/D */
120	int ao_maxdata;		/*  maxdata for D/A */
121	int ai_chanlist;	/*  allowed len of channel list A/D */
122	int ao_chanlist;	/*  allowed len of channel list D/A */
123	int i8254_osc_base;	/*  1/frequency of on board oscilator in ns */
124};
125
126static const struct pcl816_board boardtypes[] = {
127	{"pcl816", 8, 16, 10000, 1, 16, 16, &range_pcl816,
128	 &range_pcl816, PCLx1x_RANGE,
129	 0x00fc,		/*  IRQ mask */
130	 0x0a,			/*  DMA mask */
131	 0xffff,		/*  16-bit card */
132	 0xffff,		/*  D/A maxdata */
133	 1024,
134	 1,			/*  ao chan list */
135	 100},
136	{"pcl814b", 8, 16, 10000, 1, 16, 16, &range_pcl816,
137	 &range_pcl816, PCLx1x_RANGE,
138	 0x00fc,
139	 0x0a,
140	 0x3fff,		/* 14 bit card */
141	 0x3fff,
142	 1024,
143	 1,
144	 100},
145};
146
147#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl816_board))
148#define devpriv ((struct pcl816_private *)dev->private)
149#define this_board ((const struct pcl816_board *)dev->board_ptr)
150
151static int pcl816_attach(struct comedi_device *dev,
152			 struct comedi_devconfig *it);
153static int pcl816_detach(struct comedi_device *dev);
154
155#ifdef unused
156static int RTC_lock = 0;	/* RTC lock */
157static int RTC_timer_lock = 0;	/* RTC int lock */
158#endif
159
160static struct comedi_driver driver_pcl816 = {
161	.driver_name = "pcl816",
162	.module = THIS_MODULE,
163	.attach = pcl816_attach,
164	.detach = pcl816_detach,
165	.board_name = &boardtypes[0].name,
166	.num_names = n_boardtypes,
167	.offset = sizeof(struct pcl816_board),
168};
169
170COMEDI_INITCLEANUP(driver_pcl816);
171
172struct pcl816_private {
173
174	unsigned int dma;	/*  used DMA, 0=don't use DMA */
175	int dma_rtc;		/*  1=RTC used with DMA, 0=no RTC alloc */
176#ifdef unused
177	unsigned long rtc_iobase;	/*  RTC port region */
178	unsigned int rtc_iosize;
179	unsigned int rtc_irq;
180#endif
181	unsigned long dmabuf[2];	/*  pointers to begin of DMA buffers */
182	unsigned int dmapages[2];	/*  len of DMA buffers in PAGE_SIZEs */
183	unsigned int hwdmaptr[2];	/*  hardware address of DMA buffers */
184	unsigned int hwdmasize[2];	/*  len of DMA buffers in Bytes */
185	unsigned int dmasamplsize;	/*  size in samples hwdmasize[0]/2 */
186	unsigned int last_top_dma;	/*  DMA pointer in last RTC int */
187	int next_dma_buf;	/*  which DMA buffer will be used next round */
188	long dma_runs_to_end;	/*  how many we must permorm DMA transfer to end of record */
189	unsigned long last_dma_run;	/*  how many bytes we must transfer on last DMA page */
190
191	unsigned int ai_scans;	/*  len of scanlist */
192	unsigned char ai_neverending;	/*  if=1, then we do neverending record (you must use cancel()) */
193	int irq_free;		/*  1=have allocated IRQ */
194	int irq_blocked;	/*  1=IRQ now uses any subdev */
195#ifdef unused
196	int rtc_irq_blocked;	/*  1=we now do AI with DMA&RTC */
197#endif
198	int irq_was_now_closed;	/*  when IRQ finish, there's stored int816_mode for last interrupt */
199	int int816_mode;	/*  who now uses IRQ - 1=AI1 int, 2=AI1 dma, 3=AI3 int, 4AI3 dma */
200	struct comedi_subdevice *last_int_sub;	/*  ptr to subdevice which now finish */
201	int ai_act_scan;	/*  how many scans we finished */
202	unsigned int ai_act_chanlist[16];	/*  MUX setting for actual AI operations */
203	unsigned int ai_act_chanlist_len;	/*  how long is actual MUX list */
204	unsigned int ai_act_chanlist_pos;	/*  actual position in MUX list */
205	unsigned int ai_n_chan;		/*  how many channels per scan */
206	unsigned int ai_poll_ptr;	/*  how many sampes transfer poll */
207	struct comedi_subdevice *sub_ai;	/*  ptr to AI subdevice */
208#ifdef unused
209	struct timer_list rtc_irq_timer;	/*  timer for RTC sanity check */
210	unsigned long rtc_freq;	/*  RTC int freq */
211#endif
212};
213
214/*
215==============================================================================
216*/
217static int check_and_setup_channel_list(struct comedi_device *dev,
218					struct comedi_subdevice *s,
219					unsigned int *chanlist, int chanlen);
220static int pcl816_ai_cancel(struct comedi_device *dev,
221			    struct comedi_subdevice *s);
222static void start_pacer(struct comedi_device *dev, int mode,
223			unsigned int divisor1, unsigned int divisor2);
224#ifdef unused
225static int set_rtc_irq_bit(unsigned char bit);
226#endif
227
228static int pcl816_ai_cmdtest(struct comedi_device *dev,
229			     struct comedi_subdevice *s,
230			     struct comedi_cmd *cmd);
231static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s);
232
233/*
234==============================================================================
235   ANALOG INPUT MODE0, 816 cards, slow version
236*/
237static int pcl816_ai_insn_read(struct comedi_device *dev,
238			       struct comedi_subdevice *s,
239			       struct comedi_insn *insn, unsigned int *data)
240{
241	int n;
242	int timeout;
243
244	DPRINTK("mode 0 analog input\n");
245	/*  software trigger, DMA and INT off */
246	outb(0, dev->iobase + PCL816_CONTROL);
247	/*  clear INT (conversion end) flag */
248	outb(0, dev->iobase + PCL816_CLRINT);
249
250	/*  Set the input channel */
251	outb(CR_CHAN(insn->chanspec) & 0xf, dev->iobase + PCL816_MUX);
252	outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE);	/* select gain */
253
254	for (n = 0; n < insn->n; n++) {
255
256		outb(0, dev->iobase + PCL816_AD_LO);	/* start conversion */
257
258		timeout = 100;
259		while (timeout--) {
260			if (!(inb(dev->iobase + PCL816_STATUS) &
261			      PCL816_STATUS_DRDY_MASK)) {
262				/*  return read value */
263				data[n] =
264				    ((inb(dev->iobase +
265					  PCL816_AD_HI) << 8) |
266				     (inb(dev->iobase + PCL816_AD_LO)));
267
268				outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT (conversion end) flag */
269				break;
270			}
271			udelay(1);
272		}
273		/*  Return timeout error */
274		if (!timeout) {
275			comedi_error(dev, "A/D insn timeout\n");
276			data[0] = 0;
277			outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT (conversion end) flag */
278			return -EIO;
279		}
280
281	}
282	return n;
283}
284
285/*
286==============================================================================
287   analog input interrupt mode 1 & 3, 818 cards
288   one sample per interrupt version
289*/
290static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d)
291{
292	struct comedi_device *dev = d;
293	struct comedi_subdevice *s = dev->subdevices + 0;
294	int low, hi;
295	int timeout = 50;	/* wait max 50us */
296
297	while (timeout--) {
298		if (!(inb(dev->iobase + PCL816_STATUS) &
299		      PCL816_STATUS_DRDY_MASK))
300			break;
301		udelay(1);
302	}
303	if (!timeout) {		/*  timeout, bail error */
304		outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
305		comedi_error(dev, "A/D mode1/3 IRQ without DRDY!");
306		pcl816_ai_cancel(dev, s);
307		s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
308		comedi_event(dev, s);
309		return IRQ_HANDLED;
310
311	}
312
313	/*  get the sample */
314	low = inb(dev->iobase + PCL816_AD_LO);
315	hi = inb(dev->iobase + PCL816_AD_HI);
316
317	comedi_buf_put(s->async, (hi << 8) | low);
318
319	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
320
321	if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len)
322		devpriv->ai_act_chanlist_pos = 0;
323
324	s->async->cur_chan++;
325	if (s->async->cur_chan >= devpriv->ai_n_chan) {
326		s->async->cur_chan = 0;
327		devpriv->ai_act_scan++;
328	}
329
330	if (!devpriv->ai_neverending)
331		if (devpriv->ai_act_scan >= devpriv->ai_scans) {	/* all data sampled */
332			/* all data sampled */
333			pcl816_ai_cancel(dev, s);
334			s->async->events |= COMEDI_CB_EOA;
335		}
336	comedi_event(dev, s);
337	return IRQ_HANDLED;
338}
339
340/*
341==============================================================================
342   analog input dma mode 1 & 3, 816 cards
343*/
344static void transfer_from_dma_buf(struct comedi_device *dev,
345				  struct comedi_subdevice *s, short *ptr,
346				  unsigned int bufptr, unsigned int len)
347{
348	int i;
349
350	s->async->events = 0;
351
352	for (i = 0; i < len; i++) {
353
354		comedi_buf_put(s->async, ptr[bufptr++]);
355
356		if (++devpriv->ai_act_chanlist_pos >=
357		    devpriv->ai_act_chanlist_len) {
358			devpriv->ai_act_chanlist_pos = 0;
359		}
360
361		s->async->cur_chan++;
362		if (s->async->cur_chan >= devpriv->ai_n_chan) {
363			s->async->cur_chan = 0;
364			devpriv->ai_act_scan++;
365		}
366
367		if (!devpriv->ai_neverending)
368			if (devpriv->ai_act_scan >= devpriv->ai_scans) {	/*  all data sampled */
369				pcl816_ai_cancel(dev, s);
370				s->async->events |= COMEDI_CB_EOA;
371				s->async->events |= COMEDI_CB_BLOCK;
372				break;
373			}
374	}
375
376	comedi_event(dev, s);
377}
378
379static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d)
380{
381	struct comedi_device *dev = d;
382	struct comedi_subdevice *s = dev->subdevices + 0;
383	int len, bufptr, this_dma_buf;
384	unsigned long dma_flags;
385	short *ptr;
386
387	disable_dma(devpriv->dma);
388	this_dma_buf = devpriv->next_dma_buf;
389
390	if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) {	/*  switch dma bufs */
391
392		devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
393		set_dma_mode(devpriv->dma, DMA_MODE_READ);
394		dma_flags = claim_dma_lock();
395/* clear_dma_ff (devpriv->dma); */
396		set_dma_addr(devpriv->dma,
397			     devpriv->hwdmaptr[devpriv->next_dma_buf]);
398		if (devpriv->dma_runs_to_end) {
399			set_dma_count(devpriv->dma,
400				      devpriv->hwdmasize[devpriv->
401							 next_dma_buf]);
402		} else {
403			set_dma_count(devpriv->dma, devpriv->last_dma_run);
404		}
405		release_dma_lock(dma_flags);
406		enable_dma(devpriv->dma);
407	}
408
409	devpriv->dma_runs_to_end--;
410	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
411
412	ptr = (short *)devpriv->dmabuf[this_dma_buf];
413
414	len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr;
415	bufptr = devpriv->ai_poll_ptr;
416	devpriv->ai_poll_ptr = 0;
417
418	transfer_from_dma_buf(dev, s, ptr, bufptr, len);
419	return IRQ_HANDLED;
420}
421
422/*
423==============================================================================
424    INT procedure
425*/
426static irqreturn_t interrupt_pcl816(int irq, void *d)
427{
428	struct comedi_device *dev = d;
429	DPRINTK("<I>");
430
431	if (!dev->attached) {
432		comedi_error(dev, "premature interrupt");
433		return IRQ_HANDLED;
434	}
435
436	switch (devpriv->int816_mode) {
437	case INT_TYPE_AI1_DMA:
438	case INT_TYPE_AI3_DMA:
439		return interrupt_pcl816_ai_mode13_dma(irq, d);
440	case INT_TYPE_AI1_INT:
441	case INT_TYPE_AI3_INT:
442		return interrupt_pcl816_ai_mode13_int(irq, d);
443	}
444
445	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
446	if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) |
447	    (!devpriv->int816_mode)) {
448		if (devpriv->irq_was_now_closed) {
449			devpriv->irq_was_now_closed = 0;
450			/*  comedi_error(dev,"last IRQ.."); */
451			return IRQ_HANDLED;
452		}
453		comedi_error(dev, "bad IRQ!");
454		return IRQ_NONE;
455	}
456	comedi_error(dev, "IRQ from unknown source!");
457	return IRQ_NONE;
458}
459
460/*
461==============================================================================
462   COMMAND MODE
463*/
464static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd)
465{
466	printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,
467	       cmd->start_src, cmd->scan_begin_src, cmd->convert_src);
468	printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e,
469	       cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg);
470	printk("pcl816 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src,
471	       cmd->scan_end_src);
472	printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e,
473	       cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len);
474}
475
476/*
477==============================================================================
478*/
479static int pcl816_ai_cmdtest(struct comedi_device *dev,
480			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
481{
482	int err = 0;
483	int tmp, divisor1 = 0, divisor2 = 0;
484
485	DEBUG(printk("pcl816 pcl812_ai_cmdtest\n"); pcl816_cmdtest_out(-1, cmd);
486	    );
487
488	/* step 1: make sure trigger sources are trivially valid */
489	tmp = cmd->start_src;
490	cmd->start_src &= TRIG_NOW;
491	if (!cmd->start_src || tmp != cmd->start_src)
492		err++;
493
494	tmp = cmd->scan_begin_src;
495	cmd->scan_begin_src &= TRIG_FOLLOW;
496	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
497		err++;
498
499	tmp = cmd->convert_src;
500	cmd->convert_src &= TRIG_EXT | TRIG_TIMER;
501	if (!cmd->convert_src || tmp != cmd->convert_src)
502		err++;
503
504	tmp = cmd->scan_end_src;
505	cmd->scan_end_src &= TRIG_COUNT;
506	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
507		err++;
508
509	tmp = cmd->stop_src;
510	cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
511	if (!cmd->stop_src || tmp != cmd->stop_src)
512		err++;
513
514	if (err) {
515		return 1;
516	}
517
518	/* step 2: make sure trigger sources are unique and mutually compatible */
519
520	if (cmd->start_src != TRIG_NOW) {
521		cmd->start_src = TRIG_NOW;
522		err++;
523	}
524
525	if (cmd->scan_begin_src != TRIG_FOLLOW) {
526		cmd->scan_begin_src = TRIG_FOLLOW;
527		err++;
528	}
529
530	if (cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_TIMER) {
531		cmd->convert_src = TRIG_TIMER;
532		err++;
533	}
534
535	if (cmd->scan_end_src != TRIG_COUNT) {
536		cmd->scan_end_src = TRIG_COUNT;
537		err++;
538	}
539
540	if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT)
541		err++;
542
543	if (err) {
544		return 2;
545	}
546
547	/* step 3: make sure arguments are trivially compatible */
548	if (cmd->start_arg != 0) {
549		cmd->start_arg = 0;
550		err++;
551	}
552
553	if (cmd->scan_begin_arg != 0) {
554		cmd->scan_begin_arg = 0;
555		err++;
556	}
557	if (cmd->convert_src == TRIG_TIMER) {
558		if (cmd->convert_arg < this_board->ai_ns_min) {
559			cmd->convert_arg = this_board->ai_ns_min;
560			err++;
561		}
562	} else {		/* TRIG_EXT */
563		if (cmd->convert_arg != 0) {
564			cmd->convert_arg = 0;
565			err++;
566		}
567	}
568
569	if (!cmd->chanlist_len) {
570		cmd->chanlist_len = 1;
571		err++;
572	}
573	if (cmd->chanlist_len > this_board->n_aichan) {
574		cmd->chanlist_len = this_board->n_aichan;
575		err++;
576	}
577	if (cmd->scan_end_arg != cmd->chanlist_len) {
578		cmd->scan_end_arg = cmd->chanlist_len;
579		err++;
580	}
581	if (cmd->stop_src == TRIG_COUNT) {
582		if (!cmd->stop_arg) {
583			cmd->stop_arg = 1;
584			err++;
585		}
586	} else {		/* TRIG_NONE */
587		if (cmd->stop_arg != 0) {
588			cmd->stop_arg = 0;
589			err++;
590		}
591	}
592
593	if (err) {
594		return 3;
595	}
596
597	/* step 4: fix up any arguments */
598	if (cmd->convert_src == TRIG_TIMER) {
599		tmp = cmd->convert_arg;
600		i8253_cascade_ns_to_timer(this_board->i8254_osc_base,
601					  &divisor1, &divisor2,
602					  &cmd->convert_arg,
603					  cmd->flags & TRIG_ROUND_MASK);
604		if (cmd->convert_arg < this_board->ai_ns_min)
605			cmd->convert_arg = this_board->ai_ns_min;
606		if (tmp != cmd->convert_arg)
607			err++;
608	}
609
610	if (err) {
611		return 4;
612	}
613
614	return 0;
615}
616
617static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
618{
619	unsigned int divisor1 = 0, divisor2 = 0, dma_flags, bytes, dmairq;
620	struct comedi_cmd *cmd = &s->async->cmd;
621
622	if (cmd->start_src != TRIG_NOW)
623		return -EINVAL;
624	if (cmd->scan_begin_src != TRIG_FOLLOW)
625		return -EINVAL;
626	if (cmd->scan_end_src != TRIG_COUNT)
627		return -EINVAL;
628	if (cmd->scan_end_arg != cmd->chanlist_len)
629		return -EINVAL;
630/* if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL; */
631	if (devpriv->irq_blocked)
632		return -EBUSY;
633
634	if (cmd->convert_src == TRIG_TIMER) {
635		if (cmd->convert_arg < this_board->ai_ns_min)
636			cmd->convert_arg = this_board->ai_ns_min;
637
638		i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1,
639					  &divisor2, &cmd->convert_arg,
640					  cmd->flags & TRIG_ROUND_MASK);
641		if (divisor1 == 1) {	/*  PCL816 crash if any divisor is set to 1 */
642			divisor1 = 2;
643			divisor2 /= 2;
644		}
645		if (divisor2 == 1) {
646			divisor2 = 2;
647			divisor1 /= 2;
648		}
649	}
650
651	start_pacer(dev, -1, 0, 0);	/*  stop pacer */
652
653	if (!check_and_setup_channel_list(dev, s, cmd->chanlist,
654					  cmd->chanlist_len))
655		return -EINVAL;
656	udelay(1);
657
658	devpriv->ai_n_chan = cmd->chanlist_len;
659	devpriv->ai_act_scan = 0;
660	s->async->cur_chan = 0;
661	devpriv->irq_blocked = 1;
662	devpriv->ai_poll_ptr = 0;
663	devpriv->irq_was_now_closed = 0;
664
665	if (cmd->stop_src == TRIG_COUNT) {
666		devpriv->ai_scans = cmd->stop_arg;
667		devpriv->ai_neverending = 0;
668	} else {
669		devpriv->ai_scans = 0;
670		devpriv->ai_neverending = 1;
671	}
672
673	if ((cmd->flags & TRIG_WAKE_EOS)) {	/*  don't we want wake up every scan? */
674		printk("pl816: You wankt WAKE_EOS but I dont want handle it");
675		/*               devpriv->ai_eos=1; */
676		/* if (devpriv->ai_n_chan==1) */
677		/*       devpriv->dma=0; // DMA is useless for this situation */
678	}
679
680	if (devpriv->dma) {
681		bytes = devpriv->hwdmasize[0];
682		if (!devpriv->ai_neverending) {
683			bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof(short);	/*  how many */
684			devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0];	/*  how many DMA pages we must fill */
685			devpriv->last_dma_run = bytes % devpriv->hwdmasize[0];	/* on last dma transfer must be moved */
686			devpriv->dma_runs_to_end--;
687			if (devpriv->dma_runs_to_end >= 0)
688				bytes = devpriv->hwdmasize[0];
689		} else
690			devpriv->dma_runs_to_end = -1;
691
692		devpriv->next_dma_buf = 0;
693		set_dma_mode(devpriv->dma, DMA_MODE_READ);
694		dma_flags = claim_dma_lock();
695		clear_dma_ff(devpriv->dma);
696		set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]);
697		set_dma_count(devpriv->dma, bytes);
698		release_dma_lock(dma_flags);
699		enable_dma(devpriv->dma);
700	}
701
702	start_pacer(dev, 1, divisor1, divisor2);
703	dmairq = ((devpriv->dma & 0x3) << 4) | (dev->irq & 0x7);
704
705	switch (cmd->convert_src) {
706	case TRIG_TIMER:
707		devpriv->int816_mode = INT_TYPE_AI1_DMA;
708		outb(0x32, dev->iobase + PCL816_CONTROL);	/*  Pacer+IRQ+DMA */
709		outb(dmairq, dev->iobase + PCL816_STATUS);	/*  write irq and DMA to card */
710		break;
711
712	default:
713		devpriv->int816_mode = INT_TYPE_AI3_DMA;
714		outb(0x34, dev->iobase + PCL816_CONTROL);	/*  Ext trig+IRQ+DMA */
715		outb(dmairq, dev->iobase + PCL816_STATUS);	/*  write irq to card */
716		break;
717	}
718
719	DPRINTK("pcl816 END: pcl812_ai_cmd()\n");
720	return 0;
721}
722
723static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
724{
725	unsigned long flags;
726	unsigned int top1, top2, i;
727
728	if (!devpriv->dma)
729		return 0;	/*  poll is valid only for DMA transfer */
730
731	spin_lock_irqsave(&dev->spinlock, flags);
732
733	for (i = 0; i < 20; i++) {
734		top1 = get_dma_residue(devpriv->dma);	/*  where is now DMA */
735		top2 = get_dma_residue(devpriv->dma);
736		if (top1 == top2)
737			break;
738	}
739	if (top1 != top2) {
740		spin_unlock_irqrestore(&dev->spinlock, flags);
741		return 0;
742	}
743
744	top1 = devpriv->hwdmasize[0] - top1;	/*  where is now DMA in buffer */
745	top1 >>= 1;		/*  sample position */
746	top2 = top1 - devpriv->ai_poll_ptr;
747	if (top2 < 1) {		/*  no new samples */
748		spin_unlock_irqrestore(&dev->spinlock, flags);
749		return 0;
750	}
751
752	transfer_from_dma_buf(dev, s,
753			      (short *)devpriv->dmabuf[devpriv->next_dma_buf],
754			      devpriv->ai_poll_ptr, top2);
755
756	devpriv->ai_poll_ptr = top1;	/*  new buffer position */
757	spin_unlock_irqrestore(&dev->spinlock, flags);
758
759	return s->async->buf_write_count - s->async->buf_read_count;
760}
761
762/*
763==============================================================================
764 cancel any mode 1-4 AI
765*/
766static int pcl816_ai_cancel(struct comedi_device *dev,
767			    struct comedi_subdevice *s)
768{
769/* DEBUG(printk("pcl816_ai_cancel()\n");) */
770
771	if (devpriv->irq_blocked > 0) {
772		switch (devpriv->int816_mode) {
773#ifdef unused
774		case INT_TYPE_AI1_DMA_RTC:
775		case INT_TYPE_AI3_DMA_RTC:
776			set_rtc_irq_bit(0);	/*  stop RTC */
777			del_timer(&devpriv->rtc_irq_timer);
778#endif
779		case INT_TYPE_AI1_DMA:
780		case INT_TYPE_AI3_DMA:
781			disable_dma(devpriv->dma);
782		case INT_TYPE_AI1_INT:
783		case INT_TYPE_AI3_INT:
784			outb(inb(dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL);	/* Stop A/D */
785			udelay(1);
786			outb(0, dev->iobase + PCL816_CONTROL);	/* Stop A/D */
787			outb(0xb0, dev->iobase + PCL816_CTRCTL);	/* Stop pacer */
788			outb(0x70, dev->iobase + PCL816_CTRCTL);
789			outb(0, dev->iobase + PCL816_AD_LO);
790			inb(dev->iobase + PCL816_AD_LO);
791			inb(dev->iobase + PCL816_AD_HI);
792			outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
793			outb(0, dev->iobase + PCL816_CONTROL);	/* Stop A/D */
794			devpriv->irq_blocked = 0;
795			devpriv->irq_was_now_closed = devpriv->int816_mode;
796			devpriv->int816_mode = 0;
797			devpriv->last_int_sub = s;
798/* s->busy = 0; */
799			break;
800		}
801	}
802
803	DEBUG(printk("comedi: pcl816_ai_cancel() successful\n");)
804	    return 0;
805}
806
807/*
808==============================================================================
809 chech for PCL816
810*/
811static int pcl816_check(unsigned long iobase)
812{
813	outb(0x00, iobase + PCL816_MUX);
814	udelay(1);
815	if (inb(iobase + PCL816_MUX) != 0x00)
816		return 1;	/* there isn't card */
817	outb(0x55, iobase + PCL816_MUX);
818	udelay(1);
819	if (inb(iobase + PCL816_MUX) != 0x55)
820		return 1;	/* there isn't card */
821	outb(0x00, iobase + PCL816_MUX);
822	udelay(1);
823	outb(0x18, iobase + PCL816_CONTROL);
824	udelay(1);
825	if (inb(iobase + PCL816_CONTROL) != 0x18)
826		return 1;	/* there isn't card */
827	return 0;		/*  ok, card exist */
828}
829
830/*
831==============================================================================
832 reset whole PCL-816 cards
833*/
834static void pcl816_reset(struct comedi_device *dev)
835{
836/* outb (0, dev->iobase + PCL818_DA_LO);         DAC=0V */
837/* outb (0, dev->iobase + PCL818_DA_HI); */
838/* udelay (1); */
839/* outb (0, dev->iobase + PCL818_DO_HI);        DO=$0000 */
840/* outb (0, dev->iobase + PCL818_DO_LO); */
841/* udelay (1); */
842	outb(0, dev->iobase + PCL816_CONTROL);
843	outb(0, dev->iobase + PCL816_MUX);
844	outb(0, dev->iobase + PCL816_CLRINT);
845	outb(0xb0, dev->iobase + PCL816_CTRCTL);	/* Stop pacer */
846	outb(0x70, dev->iobase + PCL816_CTRCTL);
847	outb(0x30, dev->iobase + PCL816_CTRCTL);
848	outb(0, dev->iobase + PCL816_RANGE);
849}
850
851/*
852==============================================================================
853 Start/stop pacer onboard pacer
854*/
855static void
856start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1,
857	    unsigned int divisor2)
858{
859	outb(0x32, dev->iobase + PCL816_CTRCTL);
860	outb(0xff, dev->iobase + PCL816_CTR0);
861	outb(0x00, dev->iobase + PCL816_CTR0);
862	udelay(1);
863	outb(0xb4, dev->iobase + PCL816_CTRCTL);	/*  set counter 2 as mode 3 */
864	outb(0x74, dev->iobase + PCL816_CTRCTL);	/*  set counter 1 as mode 3 */
865	udelay(1);
866
867	if (mode == 1) {
868		DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1,
869			divisor2);
870		outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2);
871		outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2);
872		outb(divisor1 & 0xff, dev->iobase + PCL816_CTR1);
873		outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1);
874	}
875
876	/* clear pending interrupts (just in case) */
877/* outb(0, dev->iobase + PCL816_CLRINT); */
878}
879
880/*
881==============================================================================
882 Check if channel list from user is builded correctly
883 If it's ok, then program scan/gain logic
884*/
885static int
886check_and_setup_channel_list(struct comedi_device *dev,
887			     struct comedi_subdevice *s, unsigned int *chanlist,
888			     int chanlen)
889{
890	unsigned int chansegment[16];
891	unsigned int i, nowmustbechan, seglen, segpos;
892
893	/*  correct channel and range number check itself comedi/range.c */
894	if (chanlen < 1) {
895		comedi_error(dev, "range/channel list is empty!");
896		return 0;
897	}
898
899	if (chanlen > 1) {
900		chansegment[0] = chanlist[0];	/*  first channel is everytime ok */
901		for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
902			/*  build part of chanlist */
903			DEBUG(printk("%d. %d %d\n", i, CR_CHAN(chanlist[i]),
904				     CR_RANGE(chanlist[i]));)
905			    if (chanlist[0] == chanlist[i])
906				break;	/*  we detect loop, this must by finish */
907			nowmustbechan =
908			    (CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
909			if (nowmustbechan != CR_CHAN(chanlist[i])) {
910				/*  channel list isn't continous :-( */
911				printk
912				    ("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n",
913				     dev->minor, i, CR_CHAN(chanlist[i]),
914				     nowmustbechan, CR_CHAN(chanlist[0]));
915				return 0;
916			}
917			chansegment[i] = chanlist[i];	/*  well, this is next correct channel in list */
918		}
919
920		for (i = 0, segpos = 0; i < chanlen; i++) {	/*  check whole chanlist */
921			DEBUG(printk("%d %d=%d %d\n",
922				     CR_CHAN(chansegment[i % seglen]),
923				     CR_RANGE(chansegment[i % seglen]),
924				     CR_CHAN(chanlist[i]),
925				     CR_RANGE(chanlist[i]));)
926			    if (chanlist[i] != chansegment[i % seglen]) {
927				printk
928				    ("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
929				     dev->minor, i, CR_CHAN(chansegment[i]),
930				     CR_RANGE(chansegment[i]),
931				     CR_AREF(chansegment[i]),
932				     CR_CHAN(chanlist[i % seglen]),
933				     CR_RANGE(chanlist[i % seglen]),
934				     CR_AREF(chansegment[i % seglen]));
935				return 0;	/*  chan/gain list is strange */
936			}
937		}
938	} else {
939		seglen = 1;
940	}
941
942	devpriv->ai_act_chanlist_len = seglen;
943	devpriv->ai_act_chanlist_pos = 0;
944
945	for (i = 0; i < seglen; i++) {	/*  store range list to card */
946		devpriv->ai_act_chanlist[i] = CR_CHAN(chanlist[i]);
947		outb(CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX);
948		outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE);	/* select gain */
949	}
950
951	udelay(1);
952
953	outb(devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4), dev->iobase + PCL816_MUX);	/* select channel interval to scan */
954
955	return 1;		/*  we can serve this with MUX logic */
956}
957
958#ifdef unused
959/*
960==============================================================================
961  Enable(1)/disable(0) periodic interrupts from RTC
962*/
963static int set_rtc_irq_bit(unsigned char bit)
964{
965	unsigned char val;
966	unsigned long flags;
967
968	if (bit == 1) {
969		RTC_timer_lock++;
970		if (RTC_timer_lock > 1)
971			return 0;
972	} else {
973		RTC_timer_lock--;
974		if (RTC_timer_lock < 0)
975			RTC_timer_lock = 0;
976		if (RTC_timer_lock > 0)
977			return 0;
978	}
979
980	save_flags(flags);
981	cli();
982	val = CMOS_READ(RTC_CONTROL);
983	if (bit) {
984		val |= RTC_PIE;
985	} else {
986		val &= ~RTC_PIE;
987	}
988	CMOS_WRITE(val, RTC_CONTROL);
989	CMOS_READ(RTC_INTR_FLAGS);
990	restore_flags(flags);
991	return 0;
992}
993#endif
994
995/*
996==============================================================================
997  Free any resources that we have claimed
998*/
999static void free_resources(struct comedi_device *dev)
1000{
1001	/* printk("free_resource()\n"); */
1002	if (dev->private) {
1003		pcl816_ai_cancel(dev, devpriv->sub_ai);
1004		pcl816_reset(dev);
1005		if (devpriv->dma)
1006			free_dma(devpriv->dma);
1007		if (devpriv->dmabuf[0])
1008			free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]);
1009		if (devpriv->dmabuf[1])
1010			free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]);
1011#ifdef unused
1012		if (devpriv->rtc_irq)
1013			free_irq(devpriv->rtc_irq, dev);
1014		if ((devpriv->dma_rtc) && (RTC_lock == 1)) {
1015			if (devpriv->rtc_iobase)
1016				release_region(devpriv->rtc_iobase,
1017					       devpriv->rtc_iosize);
1018		}
1019#endif
1020	}
1021
1022	if (dev->irq)
1023		free_irq(dev->irq, dev);
1024	if (dev->iobase)
1025		release_region(dev->iobase, this_board->io_range);
1026	/* printk("free_resource() end\n"); */
1027}
1028
1029/*
1030==============================================================================
1031
1032   Initialization
1033
1034*/
1035static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
1036{
1037	int ret;
1038	unsigned long iobase;
1039	unsigned int irq, dma;
1040	unsigned long pages;
1041	/* int i; */
1042	struct comedi_subdevice *s;
1043
1044	/* claim our I/O space */
1045	iobase = it->options[0];
1046	printk("comedi%d: pcl816:  board=%s, ioport=0x%03lx", dev->minor,
1047	       this_board->name, iobase);
1048
1049	if (!request_region(iobase, this_board->io_range, "pcl816")) {
1050		printk("I/O port conflict\n");
1051		return -EIO;
1052	}
1053
1054	dev->iobase = iobase;
1055
1056	if (pcl816_check(iobase)) {
1057		printk(", I cann't detect board. FAIL!\n");
1058		return -EIO;
1059	}
1060
1061	ret = alloc_private(dev, sizeof(struct pcl816_private));
1062	if (ret < 0)
1063		return ret;	/* Can't alloc mem */
1064
1065	/* set up some name stuff */
1066	dev->board_name = this_board->name;
1067
1068	/* grab our IRQ */
1069	irq = 0;
1070	if (this_board->IRQbits != 0) {	/* board support IRQ */
1071		irq = it->options[1];
1072		if (irq) {	/* we want to use IRQ */
1073			if (((1 << irq) & this_board->IRQbits) == 0) {
1074				printk
1075				    (", IRQ %u is out of allowed range, DISABLING IT",
1076				     irq);
1077				irq = 0;	/* Bad IRQ */
1078			} else {
1079				if (request_irq
1080				    (irq, interrupt_pcl816, 0, "pcl816", dev)) {
1081					printk
1082					    (", unable to allocate IRQ %u, DISABLING IT",
1083					     irq);
1084					irq = 0;	/* Can't use IRQ */
1085				} else {
1086					printk(", irq=%u", irq);
1087				}
1088			}
1089		}
1090	}
1091
1092	dev->irq = irq;
1093	if (irq) {
1094		devpriv->irq_free = 1;
1095	} /* 1=we have allocated irq */
1096	else {
1097		devpriv->irq_free = 0;
1098	}
1099	devpriv->irq_blocked = 0;	/* number of subdevice which use IRQ */
1100	devpriv->int816_mode = 0;	/* mode of irq */
1101
1102#ifdef unused
1103	/* grab RTC for DMA operations */
1104	devpriv->dma_rtc = 0;
1105	if (it->options[2] > 0) {	/*  we want to use DMA */
1106		if (RTC_lock == 0) {
1107			if (!request_region(RTC_PORT(0), RTC_IO_EXTENT,
1108					    "pcl816 (RTC)"))
1109				goto no_rtc;
1110		}
1111		devpriv->rtc_iobase = RTC_PORT(0);
1112		devpriv->rtc_iosize = RTC_IO_EXTENT;
1113		RTC_lock++;
1114#ifdef UNTESTED_CODE
1115		if (!request_irq(RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc, 0,
1116				 "pcl816 DMA (RTC)", dev)) {
1117			devpriv->dma_rtc = 1;
1118			devpriv->rtc_irq = RTC_IRQ;
1119			printk(", dma_irq=%u", devpriv->rtc_irq);
1120		} else {
1121			RTC_lock--;
1122			if (RTC_lock == 0) {
1123				if (devpriv->rtc_iobase)
1124					release_region(devpriv->rtc_iobase,
1125						       devpriv->rtc_iosize);
1126			}
1127			devpriv->rtc_iobase = 0;
1128			devpriv->rtc_iosize = 0;
1129		}
1130#else
1131		printk("pcl816: RTC code missing");
1132#endif
1133
1134	}
1135
1136no_rtc:
1137#endif
1138	/* grab our DMA */
1139	dma = 0;
1140	devpriv->dma = dma;
1141	if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0))
1142		goto no_dma;	/* if we haven't IRQ, we can't use DMA */
1143
1144	if (this_board->DMAbits != 0) {	/* board support DMA */
1145		dma = it->options[2];
1146		if (dma < 1)
1147			goto no_dma;	/* DMA disabled */
1148
1149		if (((1 << dma) & this_board->DMAbits) == 0) {
1150			printk(", DMA is out of allowed range, FAIL!\n");
1151			return -EINVAL;	/* Bad DMA */
1152		}
1153		ret = request_dma(dma, "pcl816");
1154		if (ret) {
1155			printk(", unable to allocate DMA %u, FAIL!\n", dma);
1156			return -EBUSY;	/* DMA isn't free */
1157		}
1158
1159		devpriv->dma = dma;
1160		printk(", dma=%u", dma);
1161		pages = 2;	/* we need 16KB */
1162		devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages);
1163
1164		if (!devpriv->dmabuf[0]) {
1165			printk(", unable to allocate DMA buffer, FAIL!\n");
1166			/* maybe experiment with try_to_free_pages() will help .... */
1167			return -EBUSY;	/* no buffer :-( */
1168		}
1169		devpriv->dmapages[0] = pages;
1170		devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]);
1171		devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE;
1172		/* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */
1173
1174		if (devpriv->dma_rtc == 0) {	/*  we must do duble buff :-( */
1175			devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages);
1176			if (!devpriv->dmabuf[1]) {
1177				printk
1178				    (", unable to allocate DMA buffer, FAIL!\n");
1179				return -EBUSY;
1180			}
1181			devpriv->dmapages[1] = pages;
1182			devpriv->hwdmaptr[1] =
1183			    virt_to_bus((void *)devpriv->dmabuf[1]);
1184			devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE;
1185		}
1186	}
1187
1188no_dma:
1189
1190/*  if (this_board->n_aochan > 0)
1191    subdevs[1] = COMEDI_SUBD_AO;
1192  if (this_board->n_dichan > 0)
1193    subdevs[2] = COMEDI_SUBD_DI;
1194  if (this_board->n_dochan > 0)
1195    subdevs[3] = COMEDI_SUBD_DO;
1196*/
1197
1198	ret = alloc_subdevices(dev, 1);
1199	if (ret < 0)
1200		return ret;
1201
1202	s = dev->subdevices + 0;
1203	if (this_board->n_aichan > 0) {
1204		s->type = COMEDI_SUBD_AI;
1205		devpriv->sub_ai = s;
1206		dev->read_subdev = s;
1207		s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
1208		s->n_chan = this_board->n_aichan;
1209		s->subdev_flags |= SDF_DIFF;
1210		/* printk (", %dchans DIFF DAC - %d", s->n_chan, i); */
1211		s->maxdata = this_board->ai_maxdata;
1212		s->len_chanlist = this_board->ai_chanlist;
1213		s->range_table = this_board->ai_range_type;
1214		s->cancel = pcl816_ai_cancel;
1215		s->do_cmdtest = pcl816_ai_cmdtest;
1216		s->do_cmd = pcl816_ai_cmd;
1217		s->poll = pcl816_ai_poll;
1218		s->insn_read = pcl816_ai_insn_read;
1219	} else {
1220		s->type = COMEDI_SUBD_UNUSED;
1221	}
1222
1223#if 0
1224case COMEDI_SUBD_AO:
1225	s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
1226	s->n_chan = this_board->n_aochan;
1227	s->maxdata = this_board->ao_maxdata;
1228	s->len_chanlist = this_board->ao_chanlist;
1229	s->range_table = this_board->ao_range_type;
1230	break;
1231
1232case COMEDI_SUBD_DI:
1233	s->subdev_flags = SDF_READABLE;
1234	s->n_chan = this_board->n_dichan;
1235	s->maxdata = 1;
1236	s->len_chanlist = this_board->n_dichan;
1237	s->range_table = &range_digital;
1238	break;
1239
1240case COMEDI_SUBD_DO:
1241	s->subdev_flags = SDF_WRITABLE;
1242	s->n_chan = this_board->n_dochan;
1243	s->maxdata = 1;
1244	s->len_chanlist = this_board->n_dochan;
1245	s->range_table = &range_digital;
1246	break;
1247#endif
1248
1249	pcl816_reset(dev);
1250
1251	printk("\n");
1252
1253	return 0;
1254}
1255
1256/*
1257==============================================================================
1258  Removes device
1259 */
1260static int pcl816_detach(struct comedi_device *dev)
1261{
1262	DEBUG(printk("comedi%d: pcl816: remove\n", dev->minor);)
1263	    free_resources(dev);
1264#ifdef unused
1265	if (devpriv->dma_rtc)
1266		RTC_lock--;
1267#endif
1268	return 0;
1269}
1270