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