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