pcl816.c revision 7114a28011f9d5f3d981731ad341177c21f9d948
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	outb(CR_RANGE(insn->chanspec), dev->iobase + PCL816_RANGE);	/* select gain */
268
269	for (n = 0; n < insn->n; n++) {
270
271		outb(0, dev->iobase + PCL816_AD_LO);	/* start conversion */
272
273		timeout = 100;
274		while (timeout--) {
275			if (!(inb(dev->iobase + PCL816_STATUS) &
276			      PCL816_STATUS_DRDY_MASK)) {
277				/*  return read value */
278				data[n] =
279				    ((inb(dev->iobase +
280					  PCL816_AD_HI) << 8) |
281				     (inb(dev->iobase + PCL816_AD_LO)));
282
283				outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT (conversion end) flag */
284				break;
285			}
286			udelay(1);
287		}
288		/*  Return timeout error */
289		if (!timeout) {
290			comedi_error(dev, "A/D insn timeout\n");
291			data[0] = 0;
292			outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT (conversion end) flag */
293			return -EIO;
294		}
295
296	}
297	return n;
298}
299
300/*
301==============================================================================
302   analog input interrupt mode 1 & 3, 818 cards
303   one sample per interrupt version
304*/
305static irqreturn_t interrupt_pcl816_ai_mode13_int(int irq, void *d)
306{
307	struct comedi_device *dev = d;
308	struct comedi_subdevice *s = dev->subdevices + 0;
309	int low, hi;
310	int timeout = 50;	/* wait max 50us */
311
312	while (timeout--) {
313		if (!(inb(dev->iobase + PCL816_STATUS) &
314		      PCL816_STATUS_DRDY_MASK))
315			break;
316		udelay(1);
317	}
318	if (!timeout) {		/*  timeout, bail error */
319		outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
320		comedi_error(dev, "A/D mode1/3 IRQ without DRDY!");
321		pcl816_ai_cancel(dev, s);
322		s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
323		comedi_event(dev, s);
324		return IRQ_HANDLED;
325
326	}
327
328	/*  get the sample */
329	low = inb(dev->iobase + PCL816_AD_LO);
330	hi = inb(dev->iobase + PCL816_AD_HI);
331
332	comedi_buf_put(s->async, (hi << 8) | low);
333
334	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
335
336	if (++devpriv->ai_act_chanlist_pos >= devpriv->ai_act_chanlist_len)
337		devpriv->ai_act_chanlist_pos = 0;
338
339	s->async->cur_chan++;
340	if (s->async->cur_chan >= devpriv->ai_n_chan) {
341		s->async->cur_chan = 0;
342		devpriv->ai_act_scan++;
343	}
344
345	if (!devpriv->ai_neverending)
346		if (devpriv->ai_act_scan >= devpriv->ai_scans) {	/* all data sampled */
347			/* all data sampled */
348			pcl816_ai_cancel(dev, s);
349			s->async->events |= COMEDI_CB_EOA;
350		}
351	comedi_event(dev, s);
352	return IRQ_HANDLED;
353}
354
355/*
356==============================================================================
357   analog input dma mode 1 & 3, 816 cards
358*/
359static void transfer_from_dma_buf(struct comedi_device *dev,
360				  struct comedi_subdevice *s, short *ptr,
361				  unsigned int bufptr, unsigned int len)
362{
363	int i;
364
365	s->async->events = 0;
366
367	for (i = 0; i < len; i++) {
368
369		comedi_buf_put(s->async, ptr[bufptr++]);
370
371		if (++devpriv->ai_act_chanlist_pos >=
372		    devpriv->ai_act_chanlist_len) {
373			devpriv->ai_act_chanlist_pos = 0;
374		}
375
376		s->async->cur_chan++;
377		if (s->async->cur_chan >= devpriv->ai_n_chan) {
378			s->async->cur_chan = 0;
379			devpriv->ai_act_scan++;
380		}
381
382		if (!devpriv->ai_neverending)
383			if (devpriv->ai_act_scan >= devpriv->ai_scans) {	/*  all data sampled */
384				pcl816_ai_cancel(dev, s);
385				s->async->events |= COMEDI_CB_EOA;
386				s->async->events |= COMEDI_CB_BLOCK;
387				break;
388			}
389	}
390
391	comedi_event(dev, s);
392}
393
394static irqreturn_t interrupt_pcl816_ai_mode13_dma(int irq, void *d)
395{
396	struct comedi_device *dev = d;
397	struct comedi_subdevice *s = dev->subdevices + 0;
398	int len, bufptr, this_dma_buf;
399	unsigned long dma_flags;
400	short *ptr;
401
402	disable_dma(devpriv->dma);
403	this_dma_buf = devpriv->next_dma_buf;
404
405	if ((devpriv->dma_runs_to_end > -1) || devpriv->ai_neverending) {	/*  switch dma bufs */
406
407		devpriv->next_dma_buf = 1 - devpriv->next_dma_buf;
408		set_dma_mode(devpriv->dma, DMA_MODE_READ);
409		dma_flags = claim_dma_lock();
410/* clear_dma_ff (devpriv->dma); */
411		set_dma_addr(devpriv->dma,
412			     devpriv->hwdmaptr[devpriv->next_dma_buf]);
413		if (devpriv->dma_runs_to_end) {
414			set_dma_count(devpriv->dma,
415				      devpriv->hwdmasize[devpriv->
416							 next_dma_buf]);
417		} else {
418			set_dma_count(devpriv->dma, devpriv->last_dma_run);
419		}
420		release_dma_lock(dma_flags);
421		enable_dma(devpriv->dma);
422	}
423
424	devpriv->dma_runs_to_end--;
425	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
426
427	ptr = (short *)devpriv->dmabuf[this_dma_buf];
428
429	len = (devpriv->hwdmasize[0] >> 1) - devpriv->ai_poll_ptr;
430	bufptr = devpriv->ai_poll_ptr;
431	devpriv->ai_poll_ptr = 0;
432
433	transfer_from_dma_buf(dev, s, ptr, bufptr, len);
434	return IRQ_HANDLED;
435}
436
437/*
438==============================================================================
439    INT procedure
440*/
441static irqreturn_t interrupt_pcl816(int irq, void *d)
442{
443	struct comedi_device *dev = d;
444	DPRINTK("<I>");
445
446	if (!dev->attached) {
447		comedi_error(dev, "premature interrupt");
448		return IRQ_HANDLED;
449	}
450
451	switch (devpriv->int816_mode) {
452	case INT_TYPE_AI1_DMA:
453	case INT_TYPE_AI3_DMA:
454		return interrupt_pcl816_ai_mode13_dma(irq, d);
455	case INT_TYPE_AI1_INT:
456	case INT_TYPE_AI3_INT:
457		return interrupt_pcl816_ai_mode13_int(irq, d);
458	}
459
460	outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
461	if ((!dev->irq) | (!devpriv->irq_free) | (!devpriv->irq_blocked) |
462	    (!devpriv->int816_mode)) {
463		if (devpriv->irq_was_now_closed) {
464			devpriv->irq_was_now_closed = 0;
465			/*  comedi_error(dev,"last IRQ.."); */
466			return IRQ_HANDLED;
467		}
468		comedi_error(dev, "bad IRQ!");
469		return IRQ_NONE;
470	}
471	comedi_error(dev, "IRQ from unknown source!");
472	return IRQ_NONE;
473}
474
475/*
476==============================================================================
477   COMMAND MODE
478*/
479static void pcl816_cmdtest_out(int e, struct comedi_cmd *cmd)
480{
481	printk("pcl816 e=%d startsrc=%x scansrc=%x convsrc=%x\n", e,
482	       cmd->start_src, cmd->scan_begin_src, cmd->convert_src);
483	printk("pcl816 e=%d startarg=%d scanarg=%d convarg=%d\n", e,
484	       cmd->start_arg, cmd->scan_begin_arg, cmd->convert_arg);
485	printk("pcl816 e=%d stopsrc=%x scanend=%x\n", e, cmd->stop_src,
486	       cmd->scan_end_src);
487	printk("pcl816 e=%d stoparg=%d scanendarg=%d chanlistlen=%d\n", e,
488	       cmd->stop_arg, cmd->scan_end_arg, cmd->chanlist_len);
489}
490
491/*
492==============================================================================
493*/
494static int pcl816_ai_cmdtest(struct comedi_device *dev,
495			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
496{
497	int err = 0;
498	int tmp, divisor1 = 0, divisor2 = 0;
499
500	DEBUG(printk("pcl816 pcl812_ai_cmdtest\n"); pcl816_cmdtest_out(-1, cmd);
501	    );
502
503	/* step 1: make sure trigger sources are trivially valid */
504	tmp = cmd->start_src;
505	cmd->start_src &= TRIG_NOW;
506	if (!cmd->start_src || tmp != cmd->start_src)
507		err++;
508
509	tmp = cmd->scan_begin_src;
510	cmd->scan_begin_src &= TRIG_FOLLOW;
511	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
512		err++;
513
514	tmp = cmd->convert_src;
515	cmd->convert_src &= TRIG_EXT | TRIG_TIMER;
516	if (!cmd->convert_src || tmp != cmd->convert_src)
517		err++;
518
519	tmp = cmd->scan_end_src;
520	cmd->scan_end_src &= TRIG_COUNT;
521	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
522		err++;
523
524	tmp = cmd->stop_src;
525	cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
526	if (!cmd->stop_src || tmp != cmd->stop_src)
527		err++;
528
529	if (err) {
530		return 1;
531	}
532
533	/* step 2: make sure trigger sources are unique and mutually compatible */
534
535	if (cmd->start_src != TRIG_NOW) {
536		cmd->start_src = TRIG_NOW;
537		err++;
538	}
539
540	if (cmd->scan_begin_src != TRIG_FOLLOW) {
541		cmd->scan_begin_src = TRIG_FOLLOW;
542		err++;
543	}
544
545	if (cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_TIMER) {
546		cmd->convert_src = TRIG_TIMER;
547		err++;
548	}
549
550	if (cmd->scan_end_src != TRIG_COUNT) {
551		cmd->scan_end_src = TRIG_COUNT;
552		err++;
553	}
554
555	if (cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_COUNT)
556		err++;
557
558	if (err) {
559		return 2;
560	}
561
562	/* step 3: make sure arguments are trivially compatible */
563	if (cmd->start_arg != 0) {
564		cmd->start_arg = 0;
565		err++;
566	}
567
568	if (cmd->scan_begin_arg != 0) {
569		cmd->scan_begin_arg = 0;
570		err++;
571	}
572	if (cmd->convert_src == TRIG_TIMER) {
573		if (cmd->convert_arg < this_board->ai_ns_min) {
574			cmd->convert_arg = this_board->ai_ns_min;
575			err++;
576		}
577	} else {		/* TRIG_EXT */
578		if (cmd->convert_arg != 0) {
579			cmd->convert_arg = 0;
580			err++;
581		}
582	}
583
584	if (cmd->scan_end_arg != cmd->chanlist_len) {
585		cmd->scan_end_arg = cmd->chanlist_len;
586		err++;
587	}
588	if (cmd->stop_src == TRIG_COUNT) {
589		if (!cmd->stop_arg) {
590			cmd->stop_arg = 1;
591			err++;
592		}
593	} else {		/* TRIG_NONE */
594		if (cmd->stop_arg != 0) {
595			cmd->stop_arg = 0;
596			err++;
597		}
598	}
599
600	if (err) {
601		return 3;
602	}
603
604	/* step 4: fix up any arguments */
605	if (cmd->convert_src == TRIG_TIMER) {
606		tmp = cmd->convert_arg;
607		i8253_cascade_ns_to_timer(this_board->i8254_osc_base,
608					  &divisor1, &divisor2,
609					  &cmd->convert_arg,
610					  cmd->flags & TRIG_ROUND_MASK);
611		if (cmd->convert_arg < this_board->ai_ns_min)
612			cmd->convert_arg = this_board->ai_ns_min;
613		if (tmp != cmd->convert_arg)
614			err++;
615	}
616
617	if (err) {
618		return 4;
619	}
620
621	/* step 5: complain about special chanlist considerations */
622
623	if (cmd->chanlist) {
624		if (!check_channel_list(dev, s, cmd->chanlist,
625					cmd->chanlist_len))
626			return 5;	/*  incorrect channels list */
627	}
628
629	return 0;
630}
631
632static int pcl816_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
633{
634	unsigned int divisor1 = 0, divisor2 = 0, dma_flags, bytes, dmairq;
635	struct comedi_cmd *cmd = &s->async->cmd;
636	unsigned int seglen;
637
638	if (cmd->start_src != TRIG_NOW)
639		return -EINVAL;
640	if (cmd->scan_begin_src != TRIG_FOLLOW)
641		return -EINVAL;
642	if (cmd->scan_end_src != TRIG_COUNT)
643		return -EINVAL;
644	if (cmd->scan_end_arg != cmd->chanlist_len)
645		return -EINVAL;
646/* if(cmd->chanlist_len>MAX_CHANLIST_LEN) return -EINVAL; */
647	if (devpriv->irq_blocked)
648		return -EBUSY;
649
650	if (cmd->convert_src == TRIG_TIMER) {
651		if (cmd->convert_arg < this_board->ai_ns_min)
652			cmd->convert_arg = this_board->ai_ns_min;
653
654		i8253_cascade_ns_to_timer(this_board->i8254_osc_base, &divisor1,
655					  &divisor2, &cmd->convert_arg,
656					  cmd->flags & TRIG_ROUND_MASK);
657		if (divisor1 == 1) {	/*  PCL816 crash if any divisor is set to 1 */
658			divisor1 = 2;
659			divisor2 /= 2;
660		}
661		if (divisor2 == 1) {
662			divisor2 = 2;
663			divisor1 /= 2;
664		}
665	}
666
667	start_pacer(dev, -1, 0, 0);	/*  stop pacer */
668
669	seglen = check_channel_list(dev, s, cmd->chanlist, cmd->chanlist_len);
670	if (seglen < 1)
671		return -EINVAL;
672	setup_channel_list(dev, s, cmd->chanlist, seglen);
673	udelay(1);
674
675	devpriv->ai_n_chan = cmd->chanlist_len;
676	devpriv->ai_act_scan = 0;
677	s->async->cur_chan = 0;
678	devpriv->irq_blocked = 1;
679	devpriv->ai_poll_ptr = 0;
680	devpriv->irq_was_now_closed = 0;
681
682	if (cmd->stop_src == TRIG_COUNT) {
683		devpriv->ai_scans = cmd->stop_arg;
684		devpriv->ai_neverending = 0;
685	} else {
686		devpriv->ai_scans = 0;
687		devpriv->ai_neverending = 1;
688	}
689
690	if ((cmd->flags & TRIG_WAKE_EOS)) {	/*  don't we want wake up every scan? */
691		printk("pl816: You wankt WAKE_EOS but I dont want handle it");
692		/*               devpriv->ai_eos=1; */
693		/* if (devpriv->ai_n_chan==1) */
694		/*       devpriv->dma=0; // DMA is useless for this situation */
695	}
696
697	if (devpriv->dma) {
698		bytes = devpriv->hwdmasize[0];
699		if (!devpriv->ai_neverending) {
700			bytes = s->async->cmd.chanlist_len * s->async->cmd.chanlist_len * sizeof(short);	/*  how many */
701			devpriv->dma_runs_to_end = bytes / devpriv->hwdmasize[0];	/*  how many DMA pages we must fill */
702			devpriv->last_dma_run = bytes % devpriv->hwdmasize[0];	/* on last dma transfer must be moved */
703			devpriv->dma_runs_to_end--;
704			if (devpriv->dma_runs_to_end >= 0)
705				bytes = devpriv->hwdmasize[0];
706		} else
707			devpriv->dma_runs_to_end = -1;
708
709		devpriv->next_dma_buf = 0;
710		set_dma_mode(devpriv->dma, DMA_MODE_READ);
711		dma_flags = claim_dma_lock();
712		clear_dma_ff(devpriv->dma);
713		set_dma_addr(devpriv->dma, devpriv->hwdmaptr[0]);
714		set_dma_count(devpriv->dma, bytes);
715		release_dma_lock(dma_flags);
716		enable_dma(devpriv->dma);
717	}
718
719	start_pacer(dev, 1, divisor1, divisor2);
720	dmairq = ((devpriv->dma & 0x3) << 4) | (dev->irq & 0x7);
721
722	switch (cmd->convert_src) {
723	case TRIG_TIMER:
724		devpriv->int816_mode = INT_TYPE_AI1_DMA;
725		outb(0x32, dev->iobase + PCL816_CONTROL);	/*  Pacer+IRQ+DMA */
726		outb(dmairq, dev->iobase + PCL816_STATUS);	/*  write irq and DMA to card */
727		break;
728
729	default:
730		devpriv->int816_mode = INT_TYPE_AI3_DMA;
731		outb(0x34, dev->iobase + PCL816_CONTROL);	/*  Ext trig+IRQ+DMA */
732		outb(dmairq, dev->iobase + PCL816_STATUS);	/*  write irq to card */
733		break;
734	}
735
736	DPRINTK("pcl816 END: pcl812_ai_cmd()\n");
737	return 0;
738}
739
740static int pcl816_ai_poll(struct comedi_device *dev, struct comedi_subdevice *s)
741{
742	unsigned long flags;
743	unsigned int top1, top2, i;
744
745	if (!devpriv->dma)
746		return 0;	/*  poll is valid only for DMA transfer */
747
748	spin_lock_irqsave(&dev->spinlock, flags);
749
750	for (i = 0; i < 20; i++) {
751		top1 = get_dma_residue(devpriv->dma);	/*  where is now DMA */
752		top2 = get_dma_residue(devpriv->dma);
753		if (top1 == top2)
754			break;
755	}
756	if (top1 != top2) {
757		spin_unlock_irqrestore(&dev->spinlock, flags);
758		return 0;
759	}
760
761	top1 = devpriv->hwdmasize[0] - top1;	/*  where is now DMA in buffer */
762	top1 >>= 1;		/*  sample position */
763	top2 = top1 - devpriv->ai_poll_ptr;
764	if (top2 < 1) {		/*  no new samples */
765		spin_unlock_irqrestore(&dev->spinlock, flags);
766		return 0;
767	}
768
769	transfer_from_dma_buf(dev, s,
770			      (short *)devpriv->dmabuf[devpriv->next_dma_buf],
771			      devpriv->ai_poll_ptr, top2);
772
773	devpriv->ai_poll_ptr = top1;	/*  new buffer position */
774	spin_unlock_irqrestore(&dev->spinlock, flags);
775
776	return s->async->buf_write_count - s->async->buf_read_count;
777}
778
779/*
780==============================================================================
781 cancel any mode 1-4 AI
782*/
783static int pcl816_ai_cancel(struct comedi_device *dev,
784			    struct comedi_subdevice *s)
785{
786/* DEBUG(printk("pcl816_ai_cancel()\n");) */
787
788	if (devpriv->irq_blocked > 0) {
789		switch (devpriv->int816_mode) {
790#ifdef unused
791		case INT_TYPE_AI1_DMA_RTC:
792		case INT_TYPE_AI3_DMA_RTC:
793			set_rtc_irq_bit(0);	/*  stop RTC */
794			del_timer(&devpriv->rtc_irq_timer);
795#endif
796		case INT_TYPE_AI1_DMA:
797		case INT_TYPE_AI3_DMA:
798			disable_dma(devpriv->dma);
799		case INT_TYPE_AI1_INT:
800		case INT_TYPE_AI3_INT:
801			outb(inb(dev->iobase + PCL816_CONTROL) & 0x73, dev->iobase + PCL816_CONTROL);	/* Stop A/D */
802			udelay(1);
803			outb(0, dev->iobase + PCL816_CONTROL);	/* Stop A/D */
804			outb(0xb0, dev->iobase + PCL816_CTRCTL);	/* Stop pacer */
805			outb(0x70, dev->iobase + PCL816_CTRCTL);
806			outb(0, dev->iobase + PCL816_AD_LO);
807			inb(dev->iobase + PCL816_AD_LO);
808			inb(dev->iobase + PCL816_AD_HI);
809			outb(0, dev->iobase + PCL816_CLRINT);	/* clear INT request */
810			outb(0, dev->iobase + PCL816_CONTROL);	/* Stop A/D */
811			devpriv->irq_blocked = 0;
812			devpriv->irq_was_now_closed = devpriv->int816_mode;
813			devpriv->int816_mode = 0;
814			devpriv->last_int_sub = s;
815/* s->busy = 0; */
816			break;
817		}
818	}
819
820	DEBUG(printk("comedi: pcl816_ai_cancel() successful\n");)
821	    return 0;
822}
823
824/*
825==============================================================================
826 chech for PCL816
827*/
828static int pcl816_check(unsigned long iobase)
829{
830	outb(0x00, iobase + PCL816_MUX);
831	udelay(1);
832	if (inb(iobase + PCL816_MUX) != 0x00)
833		return 1;	/* there isn't card */
834	outb(0x55, iobase + PCL816_MUX);
835	udelay(1);
836	if (inb(iobase + PCL816_MUX) != 0x55)
837		return 1;	/* there isn't card */
838	outb(0x00, iobase + PCL816_MUX);
839	udelay(1);
840	outb(0x18, iobase + PCL816_CONTROL);
841	udelay(1);
842	if (inb(iobase + PCL816_CONTROL) != 0x18)
843		return 1;	/* there isn't card */
844	return 0;		/*  ok, card exist */
845}
846
847/*
848==============================================================================
849 reset whole PCL-816 cards
850*/
851static void pcl816_reset(struct comedi_device *dev)
852{
853/* outb (0, dev->iobase + PCL818_DA_LO);         DAC=0V */
854/* outb (0, dev->iobase + PCL818_DA_HI); */
855/* udelay (1); */
856/* outb (0, dev->iobase + PCL818_DO_HI);        DO=$0000 */
857/* outb (0, dev->iobase + PCL818_DO_LO); */
858/* udelay (1); */
859	outb(0, dev->iobase + PCL816_CONTROL);
860	outb(0, dev->iobase + PCL816_MUX);
861	outb(0, dev->iobase + PCL816_CLRINT);
862	outb(0xb0, dev->iobase + PCL816_CTRCTL);	/* Stop pacer */
863	outb(0x70, dev->iobase + PCL816_CTRCTL);
864	outb(0x30, dev->iobase + PCL816_CTRCTL);
865	outb(0, dev->iobase + PCL816_RANGE);
866}
867
868/*
869==============================================================================
870 Start/stop pacer onboard pacer
871*/
872static void
873start_pacer(struct comedi_device *dev, int mode, unsigned int divisor1,
874	    unsigned int divisor2)
875{
876	outb(0x32, dev->iobase + PCL816_CTRCTL);
877	outb(0xff, dev->iobase + PCL816_CTR0);
878	outb(0x00, dev->iobase + PCL816_CTR0);
879	udelay(1);
880	outb(0xb4, dev->iobase + PCL816_CTRCTL);	/*  set counter 2 as mode 3 */
881	outb(0x74, dev->iobase + PCL816_CTRCTL);	/*  set counter 1 as mode 3 */
882	udelay(1);
883
884	if (mode == 1) {
885		DPRINTK("mode %d, divisor1 %d, divisor2 %d\n", mode, divisor1,
886			divisor2);
887		outb(divisor2 & 0xff, dev->iobase + PCL816_CTR2);
888		outb((divisor2 >> 8) & 0xff, dev->iobase + PCL816_CTR2);
889		outb(divisor1 & 0xff, dev->iobase + PCL816_CTR1);
890		outb((divisor1 >> 8) & 0xff, dev->iobase + PCL816_CTR1);
891	}
892
893	/* clear pending interrupts (just in case) */
894/* outb(0, dev->iobase + PCL816_CLRINT); */
895}
896
897/*
898==============================================================================
899 Check if channel list from user is builded correctly
900 If it's ok, then return non-zero length of repeated segment of channel list
901*/
902static int
903check_channel_list(struct comedi_device *dev,
904		   struct comedi_subdevice *s, unsigned int *chanlist,
905		   unsigned int chanlen)
906{
907	unsigned int chansegment[16];
908	unsigned int i, nowmustbechan, seglen, segpos;
909
910	/*  correct channel and range number check itself comedi/range.c */
911	if (chanlen < 1) {
912		comedi_error(dev, "range/channel list is empty!");
913		return 0;
914	}
915
916	if (chanlen > 1) {
917		chansegment[0] = chanlist[0];	/*  first channel is everytime ok */
918		for (i = 1, seglen = 1; i < chanlen; i++, seglen++) {
919			/*  build part of chanlist */
920			DEBUG(printk("%d. %d %d\n", i, CR_CHAN(chanlist[i]),
921				     CR_RANGE(chanlist[i]));)
922			    if (chanlist[0] == chanlist[i])
923				break;	/*  we detect loop, this must by finish */
924			nowmustbechan =
925			    (CR_CHAN(chansegment[i - 1]) + 1) % chanlen;
926			if (nowmustbechan != CR_CHAN(chanlist[i])) {
927				/*  channel list isn't continous :-( */
928				printk
929				    ("comedi%d: pcl816: channel list must be continous! chanlist[%i]=%d but must be %d or %d!\n",
930				     dev->minor, i, CR_CHAN(chanlist[i]),
931				     nowmustbechan, CR_CHAN(chanlist[0]));
932				return 0;
933			}
934			chansegment[i] = chanlist[i];	/*  well, this is next correct channel in list */
935		}
936
937		for (i = 0, segpos = 0; i < chanlen; i++) {	/*  check whole chanlist */
938			DEBUG(printk("%d %d=%d %d\n",
939				     CR_CHAN(chansegment[i % seglen]),
940				     CR_RANGE(chansegment[i % seglen]),
941				     CR_CHAN(chanlist[i]),
942				     CR_RANGE(chanlist[i]));)
943			    if (chanlist[i] != chansegment[i % seglen]) {
944				printk
945				    ("comedi%d: pcl816: bad channel or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
946				     dev->minor, i, CR_CHAN(chansegment[i]),
947				     CR_RANGE(chansegment[i]),
948				     CR_AREF(chansegment[i]),
949				     CR_CHAN(chanlist[i % seglen]),
950				     CR_RANGE(chanlist[i % seglen]),
951				     CR_AREF(chansegment[i % seglen]));
952				return 0;	/*  chan/gain list is strange */
953			}
954		}
955	} else {
956		seglen = 1;
957	}
958
959	return seglen;	/*  we can serve this with MUX logic */
960}
961
962/*
963==============================================================================
964 Program scan/gain logic with channel list.
965*/
966static void
967setup_channel_list(struct comedi_device *dev,
968		   struct comedi_subdevice *s, unsigned int *chanlist,
969		   unsigned int seglen)
970{
971	unsigned int i;
972
973	devpriv->ai_act_chanlist_len = seglen;
974	devpriv->ai_act_chanlist_pos = 0;
975
976	for (i = 0; i < seglen; i++) {	/*  store range list to card */
977		devpriv->ai_act_chanlist[i] = CR_CHAN(chanlist[i]);
978		outb(CR_CHAN(chanlist[0]) & 0xf, dev->iobase + PCL816_MUX);
979		outb(CR_RANGE(chanlist[0]), dev->iobase + PCL816_RANGE);	/* select gain */
980	}
981
982	udelay(1);
983
984	outb(devpriv->ai_act_chanlist[0] | (devpriv->ai_act_chanlist[seglen - 1] << 4), dev->iobase + PCL816_MUX);	/* select channel interval to scan */
985}
986
987#ifdef unused
988/*
989==============================================================================
990  Enable(1)/disable(0) periodic interrupts from RTC
991*/
992static int set_rtc_irq_bit(unsigned char bit)
993{
994	unsigned char val;
995	unsigned long flags;
996
997	if (bit == 1) {
998		RTC_timer_lock++;
999		if (RTC_timer_lock > 1)
1000			return 0;
1001	} else {
1002		RTC_timer_lock--;
1003		if (RTC_timer_lock < 0)
1004			RTC_timer_lock = 0;
1005		if (RTC_timer_lock > 0)
1006			return 0;
1007	}
1008
1009	save_flags(flags);
1010	cli();
1011	val = CMOS_READ(RTC_CONTROL);
1012	if (bit) {
1013		val |= RTC_PIE;
1014	} else {
1015		val &= ~RTC_PIE;
1016	}
1017	CMOS_WRITE(val, RTC_CONTROL);
1018	CMOS_READ(RTC_INTR_FLAGS);
1019	restore_flags(flags);
1020	return 0;
1021}
1022#endif
1023
1024/*
1025==============================================================================
1026  Free any resources that we have claimed
1027*/
1028static void free_resources(struct comedi_device *dev)
1029{
1030	/* printk("free_resource()\n"); */
1031	if (dev->private) {
1032		pcl816_ai_cancel(dev, devpriv->sub_ai);
1033		pcl816_reset(dev);
1034		if (devpriv->dma)
1035			free_dma(devpriv->dma);
1036		if (devpriv->dmabuf[0])
1037			free_pages(devpriv->dmabuf[0], devpriv->dmapages[0]);
1038		if (devpriv->dmabuf[1])
1039			free_pages(devpriv->dmabuf[1], devpriv->dmapages[1]);
1040#ifdef unused
1041		if (devpriv->rtc_irq)
1042			free_irq(devpriv->rtc_irq, dev);
1043		if ((devpriv->dma_rtc) && (RTC_lock == 1)) {
1044			if (devpriv->rtc_iobase)
1045				release_region(devpriv->rtc_iobase,
1046					       devpriv->rtc_iosize);
1047		}
1048#endif
1049	}
1050
1051	if (dev->irq)
1052		free_irq(dev->irq, dev);
1053	if (dev->iobase)
1054		release_region(dev->iobase, this_board->io_range);
1055	/* printk("free_resource() end\n"); */
1056}
1057
1058/*
1059==============================================================================
1060
1061   Initialization
1062
1063*/
1064static int pcl816_attach(struct comedi_device *dev, struct comedi_devconfig *it)
1065{
1066	int ret;
1067	unsigned long iobase;
1068	unsigned int irq, dma;
1069	unsigned long pages;
1070	/* int i; */
1071	struct comedi_subdevice *s;
1072
1073	/* claim our I/O space */
1074	iobase = it->options[0];
1075	printk("comedi%d: pcl816:  board=%s, ioport=0x%03lx", dev->minor,
1076	       this_board->name, iobase);
1077
1078	if (!request_region(iobase, this_board->io_range, "pcl816")) {
1079		printk("I/O port conflict\n");
1080		return -EIO;
1081	}
1082
1083	dev->iobase = iobase;
1084
1085	if (pcl816_check(iobase)) {
1086		printk(", I cann't detect board. FAIL!\n");
1087		return -EIO;
1088	}
1089
1090	ret = alloc_private(dev, sizeof(struct pcl816_private));
1091	if (ret < 0)
1092		return ret;	/* Can't alloc mem */
1093
1094	/* set up some name stuff */
1095	dev->board_name = this_board->name;
1096
1097	/* grab our IRQ */
1098	irq = 0;
1099	if (this_board->IRQbits != 0) {	/* board support IRQ */
1100		irq = it->options[1];
1101		if (irq) {	/* we want to use IRQ */
1102			if (((1 << irq) & this_board->IRQbits) == 0) {
1103				printk
1104				    (", IRQ %u is out of allowed range, DISABLING IT",
1105				     irq);
1106				irq = 0;	/* Bad IRQ */
1107			} else {
1108				if (request_irq
1109				    (irq, interrupt_pcl816, 0, "pcl816", dev)) {
1110					printk
1111					    (", unable to allocate IRQ %u, DISABLING IT",
1112					     irq);
1113					irq = 0;	/* Can't use IRQ */
1114				} else {
1115					printk(", irq=%u", irq);
1116				}
1117			}
1118		}
1119	}
1120
1121	dev->irq = irq;
1122	if (irq) {
1123		devpriv->irq_free = 1;
1124	} /* 1=we have allocated irq */
1125	else {
1126		devpriv->irq_free = 0;
1127	}
1128	devpriv->irq_blocked = 0;	/* number of subdevice which use IRQ */
1129	devpriv->int816_mode = 0;	/* mode of irq */
1130
1131#ifdef unused
1132	/* grab RTC for DMA operations */
1133	devpriv->dma_rtc = 0;
1134	if (it->options[2] > 0) {	/*  we want to use DMA */
1135		if (RTC_lock == 0) {
1136			if (!request_region(RTC_PORT(0), RTC_IO_EXTENT,
1137					    "pcl816 (RTC)"))
1138				goto no_rtc;
1139		}
1140		devpriv->rtc_iobase = RTC_PORT(0);
1141		devpriv->rtc_iosize = RTC_IO_EXTENT;
1142		RTC_lock++;
1143#ifdef UNTESTED_CODE
1144		if (!request_irq(RTC_IRQ, interrupt_pcl816_ai_mode13_dma_rtc, 0,
1145				 "pcl816 DMA (RTC)", dev)) {
1146			devpriv->dma_rtc = 1;
1147			devpriv->rtc_irq = RTC_IRQ;
1148			printk(", dma_irq=%u", devpriv->rtc_irq);
1149		} else {
1150			RTC_lock--;
1151			if (RTC_lock == 0) {
1152				if (devpriv->rtc_iobase)
1153					release_region(devpriv->rtc_iobase,
1154						       devpriv->rtc_iosize);
1155			}
1156			devpriv->rtc_iobase = 0;
1157			devpriv->rtc_iosize = 0;
1158		}
1159#else
1160		printk("pcl816: RTC code missing");
1161#endif
1162
1163	}
1164
1165no_rtc:
1166#endif
1167	/* grab our DMA */
1168	dma = 0;
1169	devpriv->dma = dma;
1170	if ((devpriv->irq_free == 0) && (devpriv->dma_rtc == 0))
1171		goto no_dma;	/* if we haven't IRQ, we can't use DMA */
1172
1173	if (this_board->DMAbits != 0) {	/* board support DMA */
1174		dma = it->options[2];
1175		if (dma < 1)
1176			goto no_dma;	/* DMA disabled */
1177
1178		if (((1 << dma) & this_board->DMAbits) == 0) {
1179			printk(", DMA is out of allowed range, FAIL!\n");
1180			return -EINVAL;	/* Bad DMA */
1181		}
1182		ret = request_dma(dma, "pcl816");
1183		if (ret) {
1184			printk(", unable to allocate DMA %u, FAIL!\n", dma);
1185			return -EBUSY;	/* DMA isn't free */
1186		}
1187
1188		devpriv->dma = dma;
1189		printk(", dma=%u", dma);
1190		pages = 2;	/* we need 16KB */
1191		devpriv->dmabuf[0] = __get_dma_pages(GFP_KERNEL, pages);
1192
1193		if (!devpriv->dmabuf[0]) {
1194			printk(", unable to allocate DMA buffer, FAIL!\n");
1195			/* maybe experiment with try_to_free_pages() will help .... */
1196			return -EBUSY;	/* no buffer :-( */
1197		}
1198		devpriv->dmapages[0] = pages;
1199		devpriv->hwdmaptr[0] = virt_to_bus((void *)devpriv->dmabuf[0]);
1200		devpriv->hwdmasize[0] = (1 << pages) * PAGE_SIZE;
1201		/* printk("%d %d %ld, ",devpriv->dmapages[0],devpriv->hwdmasize[0],PAGE_SIZE); */
1202
1203		if (devpriv->dma_rtc == 0) {	/*  we must do duble buff :-( */
1204			devpriv->dmabuf[1] = __get_dma_pages(GFP_KERNEL, pages);
1205			if (!devpriv->dmabuf[1]) {
1206				printk
1207				    (", unable to allocate DMA buffer, FAIL!\n");
1208				return -EBUSY;
1209			}
1210			devpriv->dmapages[1] = pages;
1211			devpriv->hwdmaptr[1] =
1212			    virt_to_bus((void *)devpriv->dmabuf[1]);
1213			devpriv->hwdmasize[1] = (1 << pages) * PAGE_SIZE;
1214		}
1215	}
1216
1217no_dma:
1218
1219/*  if (this_board->n_aochan > 0)
1220    subdevs[1] = COMEDI_SUBD_AO;
1221  if (this_board->n_dichan > 0)
1222    subdevs[2] = COMEDI_SUBD_DI;
1223  if (this_board->n_dochan > 0)
1224    subdevs[3] = COMEDI_SUBD_DO;
1225*/
1226
1227	ret = alloc_subdevices(dev, 1);
1228	if (ret < 0)
1229		return ret;
1230
1231	s = dev->subdevices + 0;
1232	if (this_board->n_aichan > 0) {
1233		s->type = COMEDI_SUBD_AI;
1234		devpriv->sub_ai = s;
1235		dev->read_subdev = s;
1236		s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
1237		s->n_chan = this_board->n_aichan;
1238		s->subdev_flags |= SDF_DIFF;
1239		/* printk (", %dchans DIFF DAC - %d", s->n_chan, i); */
1240		s->maxdata = this_board->ai_maxdata;
1241		s->len_chanlist = this_board->ai_chanlist;
1242		s->range_table = this_board->ai_range_type;
1243		s->cancel = pcl816_ai_cancel;
1244		s->do_cmdtest = pcl816_ai_cmdtest;
1245		s->do_cmd = pcl816_ai_cmd;
1246		s->poll = pcl816_ai_poll;
1247		s->insn_read = pcl816_ai_insn_read;
1248	} else {
1249		s->type = COMEDI_SUBD_UNUSED;
1250	}
1251
1252#if 0
1253case COMEDI_SUBD_AO:
1254	s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
1255	s->n_chan = this_board->n_aochan;
1256	s->maxdata = this_board->ao_maxdata;
1257	s->len_chanlist = this_board->ao_chanlist;
1258	s->range_table = this_board->ao_range_type;
1259	break;
1260
1261case COMEDI_SUBD_DI:
1262	s->subdev_flags = SDF_READABLE;
1263	s->n_chan = this_board->n_dichan;
1264	s->maxdata = 1;
1265	s->len_chanlist = this_board->n_dichan;
1266	s->range_table = &range_digital;
1267	break;
1268
1269case COMEDI_SUBD_DO:
1270	s->subdev_flags = SDF_WRITABLE;
1271	s->n_chan = this_board->n_dochan;
1272	s->maxdata = 1;
1273	s->len_chanlist = this_board->n_dochan;
1274	s->range_table = &range_digital;
1275	break;
1276#endif
1277
1278	pcl816_reset(dev);
1279
1280	printk("\n");
1281
1282	return 0;
1283}
1284
1285/*
1286==============================================================================
1287  Removes device
1288 */
1289static int pcl816_detach(struct comedi_device *dev)
1290{
1291	DEBUG(printk("comedi%d: pcl816: remove\n", dev->minor);)
1292	    free_resources(dev);
1293#ifdef unused
1294	if (devpriv->dma_rtc)
1295		RTC_lock--;
1296#endif
1297	return 0;
1298}
1299
1300MODULE_AUTHOR("Comedi http://www.comedi.org");
1301MODULE_DESCRIPTION("Comedi low-level driver");
1302MODULE_LICENSE("GPL");
1303