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