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