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