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