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