staging: comedi: pcl726: tidy up the comedi_lrange code
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / pcl726.c
1 /*
2     comedi/drivers/pcl726.c
3
4     hardware driver for Advantech cards:
5      card:   PCL-726, PCL-727, PCL-728
6      driver: pcl726,  pcl727,  pcl728
7     and for ADLink cards:
8      card:   ACL-6126, ACL-6128
9      driver: acl6126,  acl6128
10
11     COMEDI - Linux Control and Measurement Device Interface
12     Copyright (C) 1998 David A. Schleef <ds@schleef.org>
13
14     This program is free software; you can redistribute it and/or modify
15     it under the terms of the GNU General Public License as published by
16     the Free Software Foundation; either version 2 of the License, or
17     (at your option) any later version.
18
19     This program is distributed in the hope that it will be useful,
20     but WITHOUT ANY WARRANTY; without even the implied warranty of
21     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22     GNU General Public License for more details.
23 */
24 /*
25 Driver: pcl726
26 Description: Advantech PCL-726 & compatibles
27 Author: ds
28 Status: untested
29 Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728),
30   [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128)
31
32 Interrupts are not supported.
33
34     Options for PCL-726:
35      [0] - IO Base
36      [2]...[7] - D/A output range for channel 1-6:
37                 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V,
38                 4: 4-20mA, 5: unknown (external reference)
39
40     Options for PCL-727:
41      [0] - IO Base
42      [2]...[13] - D/A output range for channel 1-12:
43                 0: 0-5V, 1: 0-10V, 2: +/-5V,
44                 3: 4-20mA
45
46     Options for PCL-728 and ACL-6128:
47      [0] - IO Base
48      [2], [3] - D/A output range for channel 1 and 2:
49                 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V,
50                 4: 4-20mA, 5: 0-20mA
51
52     Options for ACL-6126:
53      [0] - IO Base
54      [1] - IRQ (0=disable, 3, 5, 6, 7, 9, 10, 11, 12, 15) (currently ignored)
55      [2]...[7] - D/A output range for channel 1-6:
56                 0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V,
57                 4: 4-20mA
58 */
59
60 /*
61     Thanks to Circuit Specialists for having programming info (!) on
62     their web page.  (http://www.cir.com/)
63 */
64
65 #include <linux/module.h>
66 #include <linux/interrupt.h>
67
68 #include "../comedidev.h"
69
70 #define PCL726_SIZE 16
71 #define PCL727_SIZE 32
72 #define PCL728_SIZE 8
73
74 #define PCL726_AO_MSB_REG(x)    (0x00 + ((x) * 2))
75 #define PCL726_AO_LSB_REG(x)    (0x01 + ((x) * 2))
76 #define PCL726_DO_MSB_REG       0x0c
77 #define PCL726_DO_LSB_REG       0x0d
78 #define PCL726_DI_MSB_REG       0x0e
79 #define PCL726_DI_LSB_REG       0x0f
80
81 #define PCL727_DI_MSB_REG       0x00
82 #define PCL727_DI_LSB_REG       0x01
83 #define PCL727_DO_MSB_REG       0x18
84 #define PCL727_DO_LSB_REG       0x19
85
86 static const struct comedi_lrange *const rangelist_726[] = {
87         &range_unipolar5,
88         &range_unipolar10,
89         &range_bipolar5,
90         &range_bipolar10,
91         &range_4_20mA,
92         &range_unknown
93 };
94
95 static const struct comedi_lrange *const rangelist_727[] = {
96         &range_unipolar5,
97         &range_unipolar10,
98         &range_bipolar5,
99         &range_4_20mA
100 };
101
102 static const struct comedi_lrange *const rangelist_728[] = {
103         &range_unipolar5,
104         &range_unipolar10,
105         &range_bipolar5,
106         &range_bipolar10,
107         &range_4_20mA,
108         &range_0_20mA
109 };
110
111 struct pcl726_board {
112         const char *name;       /*  driver name */
113         int n_aochan;           /*  num of D/A chans */
114         unsigned int IRQbits;   /*  allowed interrupts */
115         unsigned int io_range;  /*  len of IO space */
116         unsigned int have_dio:1;
117         unsigned int is_pcl727:1;
118         const struct comedi_lrange *const *ao_ranges;
119         int ao_num_ranges;
120 };
121
122 static const struct pcl726_board boardtypes[] = {
123         {
124                 .name           = "pcl726",
125                 .n_aochan       = 6,
126                 .io_range       = PCL726_SIZE,
127                 .have_dio       = 1,
128                 .ao_ranges      = &rangelist_726[0],
129                 .ao_num_ranges  = ARRAY_SIZE(rangelist_726),
130         }, {
131                 .name           = "pcl727",
132                 .n_aochan       = 12,
133                 .io_range       = PCL727_SIZE,
134                 .have_dio       = 1,
135                 .is_pcl727      = 1,
136                 .ao_ranges      = &rangelist_727[0],
137                 .ao_num_ranges  = ARRAY_SIZE(rangelist_727),
138         }, {
139                 .name           = "pcl728",
140                 .n_aochan       = 2,
141                 .io_range       = PCL728_SIZE,
142                 .ao_num_ranges  = ARRAY_SIZE(rangelist_728),
143                 .ao_ranges      = &rangelist_728[0],
144         }, {
145                 .name           = "acl6126",
146                 .n_aochan       = 6,
147                 .IRQbits        = 0x96e8,
148                 .io_range       = PCL726_SIZE,
149                 .have_dio       = 1,
150                 .ao_num_ranges  = ARRAY_SIZE(rangelist_726),
151                 .ao_ranges      = &rangelist_726[0],
152         }, {
153                 .name           = "acl6128",
154                 .n_aochan       = 2,
155                 .io_range       = PCL728_SIZE,
156                 .ao_num_ranges  = ARRAY_SIZE(rangelist_728),
157                 .ao_ranges      = &rangelist_728[0],
158         },
159 };
160
161 struct pcl726_private {
162         const struct comedi_lrange *rangelist[12];
163         unsigned int ao_readback[12];
164 };
165
166 static irqreturn_t pcl818_interrupt(int irq, void *d)
167 {
168         return IRQ_HANDLED;
169 }
170
171 static int pcl726_ao_insn_write(struct comedi_device *dev,
172                                 struct comedi_subdevice *s,
173                                 struct comedi_insn *insn,
174                                 unsigned int *data)
175 {
176         struct pcl726_private *devpriv = dev->private;
177         unsigned int chan = CR_CHAN(insn->chanspec);
178         unsigned int range = CR_RANGE(insn->chanspec);
179         unsigned int val;
180         int i;
181
182         for (i = 0; i < insn->n; i++) {
183                 val = data[i];
184                 devpriv->ao_readback[chan] = val;
185
186                 /* bipolar data to the DAC is two's complement */
187                 if (comedi_chan_range_is_bipolar(s, chan, range))
188                         val = comedi_offset_munge(s, val);
189
190                 /* order is important, MSB then LSB */
191                 outb((val >> 8) & 0xff, dev->iobase + PCL726_AO_MSB_REG(chan));
192                 outb(val & 0xff, dev->iobase + PCL726_AO_LSB_REG(chan));
193         }
194
195         return insn->n;
196 }
197
198 static int pcl726_ao_insn_read(struct comedi_device *dev,
199                                struct comedi_subdevice *s,
200                                struct comedi_insn *insn,
201                                unsigned int *data)
202 {
203         struct pcl726_private *devpriv = dev->private;
204         unsigned int chan = CR_CHAN(insn->chanspec);
205         int i;
206
207         for (i = 0; i < insn->n; i++)
208                 data[i] = devpriv->ao_readback[chan];
209
210         return insn->n;
211 }
212
213 static int pcl726_di_insn_bits(struct comedi_device *dev,
214                                struct comedi_subdevice *s,
215                                struct comedi_insn *insn,
216                                unsigned int *data)
217 {
218         const struct pcl726_board *board = comedi_board(dev);
219         unsigned int val;
220
221         if (board->is_pcl727) {
222                 val = inb(dev->iobase + PCL727_DI_LSB_REG);
223                 val |= (inb(dev->iobase + PCL727_DI_MSB_REG) << 8);
224         } else {
225                 val = inb(dev->iobase + PCL726_DI_LSB_REG);
226                 val |= (inb(dev->iobase + PCL726_DI_MSB_REG) << 8);
227         }
228
229         data[1] = val;
230
231         return insn->n;
232 }
233
234 static int pcl726_do_insn_bits(struct comedi_device *dev,
235                                struct comedi_subdevice *s,
236                                struct comedi_insn *insn,
237                                unsigned int *data)
238 {
239         const struct pcl726_board *board = comedi_board(dev);
240         unsigned long io = dev->iobase;
241         unsigned int mask;
242
243         mask = comedi_dio_update_state(s, data);
244         if (mask) {
245                 if (board->is_pcl727) {
246                         if (mask & 0x00ff)
247                                 outb(s->state & 0xff, io + PCL727_DO_LSB_REG);
248                         if (mask & 0xff00)
249                                 outb((s->state >> 8), io + PCL727_DO_MSB_REG);
250                 } else {
251                         if (mask & 0x00ff)
252                                 outb(s->state & 0xff, io + PCL726_DO_LSB_REG);
253                         if (mask & 0xff00)
254                                 outb((s->state >> 8), io + PCL726_DO_MSB_REG);
255                 }
256         }
257
258         data[1] = s->state;
259
260         return insn->n;
261 }
262
263 static int pcl726_attach(struct comedi_device *dev,
264                          struct comedi_devconfig *it)
265 {
266         const struct pcl726_board *board = comedi_board(dev);
267         struct pcl726_private *devpriv;
268         struct comedi_subdevice *s;
269         int ret;
270         int i;
271
272         ret = comedi_request_region(dev, it->options[0], board->io_range);
273         if (ret)
274                 return ret;
275
276         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
277         if (!devpriv)
278                 return -ENOMEM;
279
280         /*
281          * Hook up the external trigger source interrupt only if the
282          * user config option is valid and the board supports interrupts.
283          */
284         if (it->options[1] && (board->IRQbits & (1 << it->options[1]))) {
285                 ret = request_irq(it->options[1], pcl818_interrupt, 0,
286                                   dev->board_name, dev);
287                 if (ret == 0) {
288                         /* External trigger source is from Pin-17 of CN3 */
289                         dev->irq = it->options[1];
290                 }
291         }
292
293         /* setup the per-channel analog output range_table_list */
294         for (i = 0; i < 12; i++) {
295                 unsigned int opt = it->options[2 + i];
296
297                 if (opt < board->ao_num_ranges && i < board->n_aochan)
298                         devpriv->rangelist[i] = board->ao_ranges[opt];
299                 else
300                         devpriv->rangelist[i] = &range_unknown;
301         }
302
303         ret = comedi_alloc_subdevices(dev, board->have_dio ? 3 : 1);
304         if (ret)
305                 return ret;
306
307         /* Analog Output subdevice */
308         s = &dev->subdevices[0];
309         s->type         = COMEDI_SUBD_AO;
310         s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
311         s->n_chan       = board->n_aochan;
312         s->maxdata      = 0x0fff;
313         s->range_table_list = devpriv->rangelist;
314         s->insn_write   = pcl726_ao_insn_write;
315         s->insn_read    = pcl726_ao_insn_read;
316
317         if (board->have_dio) {
318                 /* Digital Input subdevice */
319                 s = &dev->subdevices[1];
320                 s->type         = COMEDI_SUBD_DI;
321                 s->subdev_flags = SDF_READABLE;
322                 s->n_chan       = 16;
323                 s->maxdata      = 1;
324                 s->insn_bits    = pcl726_di_insn_bits;
325                 s->range_table  = &range_digital;
326
327                 /* Digital Output subdevice */
328                 s = &dev->subdevices[2];
329                 s->type         = COMEDI_SUBD_DO;
330                 s->subdev_flags = SDF_WRITABLE;
331                 s->n_chan       = 16;
332                 s->maxdata      = 1;
333                 s->insn_bits    = pcl726_do_insn_bits;
334                 s->range_table  = &range_digital;
335         }
336
337         return 0;
338 }
339
340 static struct comedi_driver pcl726_driver = {
341         .driver_name    = "pcl726",
342         .module         = THIS_MODULE,
343         .attach         = pcl726_attach,
344         .detach         = comedi_legacy_detach,
345         .board_name     = &boardtypes[0].name,
346         .num_names      = ARRAY_SIZE(boardtypes),
347         .offset         = sizeof(struct pcl726_board),
348 };
349 module_comedi_driver(pcl726_driver);
350
351 MODULE_AUTHOR("Comedi http://www.comedi.org");
352 MODULE_DESCRIPTION("Comedi low-level driver");
353 MODULE_LICENSE("GPL");