Merge branch 'sched-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[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 #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");
This page took 0.057094 seconds and 6 git commands to generate.