Merge branch 'for-4.0-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj...
[linux-2.6-block.git] / drivers / staging / comedi / drivers / pcl711.c
1 /*
2  * pcl711.c
3  * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles
4  * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
5  *                    Janne Jalkanen <jalkanen@cs.hut.fi>
6  *                    Eric Bunn <ebu@cs.hut.fi>
7  *
8  * COMEDI - Linux Control and Measurement Device Interface
9  * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  */
21
22 /*
23  * Driver: pcl711
24  * Description: Advantech PCL-711 and 711b, ADLink ACL-8112
25  * Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
26  *   [ADLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
27  * Author: David A. Schleef <ds@schleef.org>
28  *         Janne Jalkanen <jalkanen@cs.hut.fi>
29  *         Eric Bunn <ebu@cs.hut.fi>
30  * Updated:
31  * Status: mostly complete
32  *
33  * Configuration Options:
34  *   [0] - I/O port base
35  *   [1] - IRQ, optional
36  */
37
38 #include <linux/module.h>
39 #include <linux/delay.h>
40 #include <linux/interrupt.h>
41
42 #include "../comedidev.h"
43
44 #include "comedi_fc.h"
45 #include "8253.h"
46
47 /*
48  * I/O port register map
49  */
50 #define PCL711_TIMER_BASE       0x00
51 #define PCL711_AI_LSB_REG       0x04
52 #define PCL711_AI_MSB_REG       0x05
53 #define PCL711_AI_MSB_DRDY      (1 << 4)
54 #define PCL711_AO_LSB_REG(x)    (0x04 + ((x) * 2))
55 #define PCL711_AO_MSB_REG(x)    (0x05 + ((x) * 2))
56 #define PCL711_DI_LSB_REG       0x06
57 #define PCL711_DI_MSB_REG       0x07
58 #define PCL711_INT_STAT_REG     0x08
59 #define PCL711_INT_STAT_CLR     (0 << 0)  /* any value will work */
60 #define PCL711_AI_GAIN_REG      0x09
61 #define PCL711_AI_GAIN(x)       (((x) & 0xf) << 0)
62 #define PCL711_MUX_REG          0x0a
63 #define PCL711_MUX_CHAN(x)      (((x) & 0xf) << 0)
64 #define PCL711_MUX_CS0          (1 << 4)
65 #define PCL711_MUX_CS1          (1 << 5)
66 #define PCL711_MUX_DIFF         (PCL711_MUX_CS0 | PCL711_MUX_CS1)
67 #define PCL711_MODE_REG         0x0b
68 #define PCL711_MODE_DEFAULT     (0 << 0)
69 #define PCL711_MODE_SOFTTRIG    (1 << 0)
70 #define PCL711_MODE_EXT         (2 << 0)
71 #define PCL711_MODE_EXT_IRQ     (3 << 0)
72 #define PCL711_MODE_PACER       (4 << 0)
73 #define PCL711_MODE_PACER_IRQ   (6 << 0)
74 #define PCL711_MODE_IRQ(x)      (((x) & 0x7) << 4)
75 #define PCL711_SOFTTRIG_REG     0x0c
76 #define PCL711_SOFTTRIG         (0 << 0)  /* any value will work */
77 #define PCL711_DO_LSB_REG       0x0d
78 #define PCL711_DO_MSB_REG       0x0e
79
80 static const struct comedi_lrange range_pcl711b_ai = {
81         5, {
82                 BIP_RANGE(5),
83                 BIP_RANGE(2.5),
84                 BIP_RANGE(1.25),
85                 BIP_RANGE(0.625),
86                 BIP_RANGE(0.3125)
87         }
88 };
89
90 static const struct comedi_lrange range_acl8112hg_ai = {
91         12, {
92                 BIP_RANGE(5),
93                 BIP_RANGE(0.5),
94                 BIP_RANGE(0.05),
95                 BIP_RANGE(0.005),
96                 UNI_RANGE(10),
97                 UNI_RANGE(1),
98                 UNI_RANGE(0.1),
99                 UNI_RANGE(0.01),
100                 BIP_RANGE(10),
101                 BIP_RANGE(1),
102                 BIP_RANGE(0.1),
103                 BIP_RANGE(0.01)
104         }
105 };
106
107 static const struct comedi_lrange range_acl8112dg_ai = {
108         9, {
109                 BIP_RANGE(5),
110                 BIP_RANGE(2.5),
111                 BIP_RANGE(1.25),
112                 BIP_RANGE(0.625),
113                 UNI_RANGE(10),
114                 UNI_RANGE(5),
115                 UNI_RANGE(2.5),
116                 UNI_RANGE(1.25),
117                 BIP_RANGE(10)
118         }
119 };
120
121 struct pcl711_board {
122         const char *name;
123         int n_aichan;
124         int n_aochan;
125         int maxirq;
126         const struct comedi_lrange *ai_range_type;
127 };
128
129 static const struct pcl711_board boardtypes[] = {
130         {
131                 .name           = "pcl711",
132                 .n_aichan       = 8,
133                 .n_aochan       = 1,
134                 .ai_range_type  = &range_bipolar5,
135         }, {
136                 .name           = "pcl711b",
137                 .n_aichan       = 8,
138                 .n_aochan       = 1,
139                 .maxirq         = 7,
140                 .ai_range_type  = &range_pcl711b_ai,
141         }, {
142                 .name           = "acl8112hg",
143                 .n_aichan       = 16,
144                 .n_aochan       = 2,
145                 .maxirq         = 15,
146                 .ai_range_type  = &range_acl8112hg_ai,
147         }, {
148                 .name           = "acl8112dg",
149                 .n_aichan       = 16,
150                 .n_aochan       = 2,
151                 .maxirq         = 15,
152                 .ai_range_type  = &range_acl8112dg_ai,
153         },
154 };
155
156 struct pcl711_private {
157         unsigned int divisor1;
158         unsigned int divisor2;
159 };
160
161 static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
162 {
163         /*
164          * The pcl711b board uses bits in the mode register to select the
165          * interrupt. The other boards supported by this driver all use
166          * jumpers on the board.
167          *
168          * Enables the interrupt when needed on the pcl711b board. These
169          * bits do nothing on the other boards.
170          */
171         if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
172                 mode |= PCL711_MODE_IRQ(dev->irq);
173
174         outb(mode, dev->iobase + PCL711_MODE_REG);
175 }
176
177 static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
178                                          struct comedi_subdevice *s)
179 {
180         unsigned int val;
181
182         val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
183         val |= inb(dev->iobase + PCL711_AI_LSB_REG);
184
185         return val & s->maxdata;
186 }
187
188 static int pcl711_ai_cancel(struct comedi_device *dev,
189                             struct comedi_subdevice *s)
190 {
191         outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
192         pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
193         return 0;
194 }
195
196 static irqreturn_t pcl711_interrupt(int irq, void *d)
197 {
198         struct comedi_device *dev = d;
199         struct comedi_subdevice *s = dev->read_subdev;
200         struct comedi_cmd *cmd = &s->async->cmd;
201         unsigned int data;
202
203         if (!dev->attached) {
204                 dev_err(dev->class_dev, "spurious interrupt\n");
205                 return IRQ_HANDLED;
206         }
207
208         data = pcl711_ai_get_sample(dev, s);
209
210         outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
211
212         comedi_buf_write_samples(s, &data, 1);
213
214         if (cmd->stop_src == TRIG_COUNT &&
215             s->async->scans_done >= cmd->stop_arg)
216                 s->async->events |= COMEDI_CB_EOA;
217
218         comedi_handle_events(dev, s);
219
220         return IRQ_HANDLED;
221 }
222
223 static void pcl711_set_changain(struct comedi_device *dev,
224                                 struct comedi_subdevice *s,
225                                 unsigned int chanspec)
226 {
227         unsigned int chan = CR_CHAN(chanspec);
228         unsigned int range = CR_RANGE(chanspec);
229         unsigned int aref = CR_AREF(chanspec);
230         unsigned int mux = 0;
231
232         outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
233
234         if (s->n_chan > 8) {
235                 /* Select the correct MPC508A chip */
236                 if (aref == AREF_DIFF) {
237                         chan &= 0x7;
238                         mux |= PCL711_MUX_DIFF;
239                 } else {
240                         if (chan < 8)
241                                 mux |= PCL711_MUX_CS0;
242                         else
243                                 mux |= PCL711_MUX_CS1;
244                 }
245         }
246         outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
247 }
248
249 static int pcl711_ai_eoc(struct comedi_device *dev,
250                          struct comedi_subdevice *s,
251                          struct comedi_insn *insn,
252                          unsigned long context)
253 {
254         unsigned int status;
255
256         status = inb(dev->iobase + PCL711_AI_MSB_REG);
257         if ((status & PCL711_AI_MSB_DRDY) == 0)
258                 return 0;
259         return -EBUSY;
260 }
261
262 static int pcl711_ai_insn_read(struct comedi_device *dev,
263                                struct comedi_subdevice *s,
264                                struct comedi_insn *insn,
265                                unsigned int *data)
266 {
267         int ret;
268         int i;
269
270         pcl711_set_changain(dev, s, insn->chanspec);
271
272         pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
273
274         for (i = 0; i < insn->n; i++) {
275                 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
276
277                 ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
278                 if (ret)
279                         return ret;
280
281                 data[i] = pcl711_ai_get_sample(dev, s);
282         }
283
284         return insn->n;
285 }
286
287 static int pcl711_ai_cmdtest(struct comedi_device *dev,
288                              struct comedi_subdevice *s, struct comedi_cmd *cmd)
289 {
290         struct pcl711_private *devpriv = dev->private;
291         int err = 0;
292         unsigned int arg;
293
294         /* Step 1 : check if triggers are trivially valid */
295
296         err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
297         err |= cfc_check_trigger_src(&cmd->scan_begin_src,
298                                         TRIG_TIMER | TRIG_EXT);
299         err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
300         err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
301         err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
302
303         if (err)
304                 return 1;
305
306         /* Step 2a : make sure trigger sources are unique */
307
308         err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
309         err |= cfc_check_trigger_is_unique(cmd->stop_src);
310
311         /* Step 2b : and mutually compatible */
312
313         if (err)
314                 return 2;
315
316         /* Step 3: check if arguments are trivially valid */
317
318         err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
319
320         if (cmd->scan_begin_src == TRIG_EXT) {
321                 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
322         } else {
323 #define MAX_SPEED 1000
324                 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
325                                                  MAX_SPEED);
326         }
327
328         err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
329         err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
330
331         if (cmd->stop_src == TRIG_COUNT)
332                 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
333         else    /* TRIG_NONE */
334                 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
335
336         if (err)
337                 return 3;
338
339         /* step 4 */
340
341         if (cmd->scan_begin_src == TRIG_TIMER) {
342                 arg = cmd->scan_begin_arg;
343                 i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
344                                           &devpriv->divisor1,
345                                           &devpriv->divisor2,
346                                           &arg, cmd->flags);
347                 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
348         }
349
350         if (err)
351                 return 4;
352
353         return 0;
354 }
355
356 static void pcl711_ai_load_counters(struct comedi_device *dev)
357 {
358         struct pcl711_private *devpriv = dev->private;
359         unsigned long timer_base = dev->iobase + PCL711_TIMER_BASE;
360
361         i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
362         i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
363
364         i8254_write(timer_base, 0, 1, devpriv->divisor1);
365         i8254_write(timer_base, 0, 2, devpriv->divisor2);
366 }
367
368 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
369 {
370         struct comedi_cmd *cmd = &s->async->cmd;
371
372         pcl711_set_changain(dev, s, cmd->chanlist[0]);
373
374         if (cmd->scan_begin_src == TRIG_TIMER) {
375                 pcl711_ai_load_counters(dev);
376                 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
377                 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
378         } else {
379                 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
380         }
381
382         return 0;
383 }
384
385 static void pcl711_ao_write(struct comedi_device *dev,
386                             unsigned int chan, unsigned int val)
387 {
388         outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
389         outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
390 }
391
392 static int pcl711_ao_insn_write(struct comedi_device *dev,
393                                 struct comedi_subdevice *s,
394                                 struct comedi_insn *insn,
395                                 unsigned int *data)
396 {
397         unsigned int chan = CR_CHAN(insn->chanspec);
398         unsigned int val = s->readback[chan];
399         int i;
400
401         for (i = 0; i < insn->n; i++) {
402                 val = data[i];
403                 pcl711_ao_write(dev, chan, val);
404         }
405         s->readback[chan] = val;
406
407         return insn->n;
408 }
409
410 static int pcl711_di_insn_bits(struct comedi_device *dev,
411                                struct comedi_subdevice *s,
412                                struct comedi_insn *insn,
413                                unsigned int *data)
414 {
415         unsigned int val;
416
417         val = inb(dev->iobase + PCL711_DI_LSB_REG);
418         val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
419
420         data[1] = val;
421
422         return insn->n;
423 }
424
425 static int pcl711_do_insn_bits(struct comedi_device *dev,
426                                struct comedi_subdevice *s,
427                                struct comedi_insn *insn,
428                                unsigned int *data)
429 {
430         unsigned int mask;
431
432         mask = comedi_dio_update_state(s, data);
433         if (mask) {
434                 if (mask & 0x00ff)
435                         outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
436                 if (mask & 0xff00)
437                         outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
438         }
439
440         data[1] = s->state;
441
442         return insn->n;
443 }
444
445 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
446 {
447         const struct pcl711_board *board = dev->board_ptr;
448         struct pcl711_private *devpriv;
449         struct comedi_subdevice *s;
450         int ret;
451
452         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
453         if (!devpriv)
454                 return -ENOMEM;
455
456         ret = comedi_request_region(dev, it->options[0], 0x10);
457         if (ret)
458                 return ret;
459
460         if (it->options[1] && it->options[1] <= board->maxirq) {
461                 ret = request_irq(it->options[1], pcl711_interrupt, 0,
462                                   dev->board_name, dev);
463                 if (ret == 0)
464                         dev->irq = it->options[1];
465         }
466
467         ret = comedi_alloc_subdevices(dev, 4);
468         if (ret)
469                 return ret;
470
471         /* Analog Input subdevice */
472         s = &dev->subdevices[0];
473         s->type         = COMEDI_SUBD_AI;
474         s->subdev_flags = SDF_READABLE | SDF_GROUND;
475         if (board->n_aichan > 8)
476                 s->subdev_flags |= SDF_DIFF;
477         s->n_chan       = board->n_aichan;
478         s->maxdata      = 0xfff;
479         s->range_table  = board->ai_range_type;
480         s->insn_read    = pcl711_ai_insn_read;
481         if (dev->irq) {
482                 dev->read_subdev = s;
483                 s->subdev_flags |= SDF_CMD_READ;
484                 s->len_chanlist = 1;
485                 s->do_cmdtest   = pcl711_ai_cmdtest;
486                 s->do_cmd       = pcl711_ai_cmd;
487                 s->cancel       = pcl711_ai_cancel;
488         }
489
490         /* Analog Output subdevice */
491         s = &dev->subdevices[1];
492         s->type         = COMEDI_SUBD_AO;
493         s->subdev_flags = SDF_WRITABLE;
494         s->n_chan       = board->n_aochan;
495         s->maxdata      = 0xfff;
496         s->range_table  = &range_bipolar5;
497         s->insn_write   = pcl711_ao_insn_write;
498
499         ret = comedi_alloc_subdev_readback(s);
500         if (ret)
501                 return ret;
502
503         /* Digital Input subdevice */
504         s = &dev->subdevices[2];
505         s->type         = COMEDI_SUBD_DI;
506         s->subdev_flags = SDF_READABLE;
507         s->n_chan       = 16;
508         s->maxdata      = 1;
509         s->range_table  = &range_digital;
510         s->insn_bits    = pcl711_di_insn_bits;
511
512         /* Digital Output subdevice */
513         s = &dev->subdevices[3];
514         s->type         = COMEDI_SUBD_DO;
515         s->subdev_flags = SDF_WRITABLE;
516         s->n_chan       = 16;
517         s->maxdata      = 1;
518         s->range_table  = &range_digital;
519         s->insn_bits    = pcl711_do_insn_bits;
520
521         /* clear DAC */
522         pcl711_ao_write(dev, 0, 0x0);
523         pcl711_ao_write(dev, 1, 0x0);
524
525         return 0;
526 }
527
528 static struct comedi_driver pcl711_driver = {
529         .driver_name    = "pcl711",
530         .module         = THIS_MODULE,
531         .attach         = pcl711_attach,
532         .detach         = comedi_legacy_detach,
533         .board_name     = &boardtypes[0].name,
534         .num_names      = ARRAY_SIZE(boardtypes),
535         .offset         = sizeof(struct pcl711_board),
536 };
537 module_comedi_driver(pcl711_driver);
538
539 MODULE_AUTHOR("Comedi http://www.comedi.org");
540 MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
541 MODULE_LICENSE("GPL");