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