staging: comedi: adl_pci7x3x: fix digital output on PCI-7230
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / adl_pci7x3x.c
1 /*
2  * COMEDI driver for the ADLINK PCI-723x/743x series boards.
3  * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
4  *
5  * Based on the adl_pci7230 driver written by:
6  *      David Fernandez <dfcastelao@gmail.com>
7  * and the adl_pci7432 driver written by:
8  *      Michel Lachaine <mike@mikelachaine.ca>
9  *
10  * COMEDI - Linux Control and Measurement Device Interface
11  * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26  */
27
28 /*
29 Driver: adl_pci7x3x
30 Description: 32/64-Channel Isolated Digital I/O Boards
31 Devices: (ADLink) PCI-7230 [adl_pci7230] - 16 input / 16 output
32          (ADLink) PCI-7233 [adl_pci7233] - 32 input
33          (ADLink) PCI-7234 [adl_pci7234] - 32 output
34          (ADLink) PCI-7432 [adl_pci7432] - 32 input / 32 output
35          (ADLink) PCI-7433 [adl_pci7433] - 64 input
36          (ADLink) PCI-7434 [adl_pci7434] - 64 output
37 Author: H Hartley Sweeten <hsweeten@visionengravers.com>
38 Updated: Thu, 02 Aug 2012 14:27:46 -0700
39 Status: untested
40
41 The PCI-7230, PCI-7432 and PCI-7433 boards also support external
42 interrupt signals on digital input channels 0 and 1. The PCI-7233
43 has dual-interrupt sources for change-of-state (COS) on any 16
44 digital input channels of LSB and for COS on any 16 digital input
45 lines of MSB. Interrupts are not currently supported by this
46 driver.
47
48 Configuration Options: not applicable, uses comedi PCI auto config
49 */
50
51 #include <linux/pci.h>
52
53 #include "../comedidev.h"
54
55 /*
56  * Register I/O map (32-bit access only)
57  */
58 #define PCI7X3X_DIO_REG         0x00
59 #define PCI743X_DIO_REG         0x04
60
61 enum apci1516_boardid {
62         BOARD_PCI7230,
63         BOARD_PCI7233,
64         BOARD_PCI7234,
65         BOARD_PCI7432,
66         BOARD_PCI7433,
67         BOARD_PCI7434,
68 };
69
70 struct adl_pci7x3x_boardinfo {
71         const char *name;
72         int nsubdevs;
73         int di_nchan;
74         int do_nchan;
75 };
76
77 static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = {
78         [BOARD_PCI7230] = {
79                 .name           = "adl_pci7230",
80                 .nsubdevs       = 2,
81                 .di_nchan       = 16,
82                 .do_nchan       = 16,
83         },
84         [BOARD_PCI7233] = {
85                 .name           = "adl_pci7233",
86                 .nsubdevs       = 1,
87                 .di_nchan       = 32,
88         },
89         [BOARD_PCI7234] = {
90                 .name           = "adl_pci7234",
91                 .nsubdevs       = 1,
92                 .do_nchan       = 32,
93         },
94         [BOARD_PCI7432] = {
95                 .name           = "adl_pci7432",
96                 .nsubdevs       = 2,
97                 .di_nchan       = 32,
98                 .do_nchan       = 32,
99         },
100         [BOARD_PCI7433] = {
101                 .name           = "adl_pci7433",
102                 .nsubdevs       = 2,
103                 .di_nchan       = 64,
104         },
105         [BOARD_PCI7434] = {
106                 .name           = "adl_pci7434",
107                 .nsubdevs       = 2,
108                 .do_nchan       = 64,
109         }
110 };
111
112 static int adl_pci7x3x_do_insn_bits(struct comedi_device *dev,
113                                     struct comedi_subdevice *s,
114                                     struct comedi_insn *insn,
115                                     unsigned int *data)
116 {
117         unsigned long reg = (unsigned long)s->private;
118         unsigned int mask = data[0];
119         unsigned int bits = data[1];
120
121         if (mask) {
122                 unsigned int val;
123
124                 s->state &= ~mask;
125                 s->state |= (bits & mask);
126                 val = s->state;
127                 if (s->n_chan == 16) {
128                         /*
129                          * It seems the PCI-7230 needs the 16-bit DO state
130                          * to be shifted left by 16 bits before being written
131                          * to the 32-bit register.  Set the value in both
132                          * halves of the register to be sure.
133                          */
134                         val |= val << 16;
135                 }
136                 outl(val, dev->iobase + reg);
137         }
138
139         /*
140          * NOTE: The output register is not readable.
141          * This returned state will not be correct until all the
142          * outputs have been updated.
143          */
144         data[1] = s->state;
145
146         return insn->n;
147 }
148
149 static int adl_pci7x3x_di_insn_bits(struct comedi_device *dev,
150                                     struct comedi_subdevice *s,
151                                     struct comedi_insn *insn,
152                                     unsigned int *data)
153 {
154         unsigned long reg = (unsigned long)s->private;
155
156         data[1] = inl(dev->iobase + reg);
157
158         return insn->n;
159 }
160
161 static int adl_pci7x3x_auto_attach(struct comedi_device *dev,
162                                    unsigned long context)
163 {
164         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
165         const struct adl_pci7x3x_boardinfo *board = NULL;
166         struct comedi_subdevice *s;
167         int subdev;
168         int nchan;
169         int ret;
170
171         if (context < ARRAY_SIZE(adl_pci7x3x_boards))
172                 board = &adl_pci7x3x_boards[context];
173         if (!board)
174                 return -ENODEV;
175         dev->board_ptr = board;
176         dev->board_name = board->name;
177
178         ret = comedi_pci_enable(dev);
179         if (ret)
180                 return ret;
181         dev->iobase = pci_resource_start(pcidev, 2);
182
183         /*
184          * One or two subdevices are setup by this driver depending on
185          * the number of digital inputs and/or outputs provided by the
186          * board. Each subdevice has a maximum of 32 channels.
187          *
188          *      PCI-7230 - 2 subdevices: 0 - 16 input, 1 - 16 output
189          *      PCI-7233 - 1 subdevice: 0 - 32 input
190          *      PCI-7234 - 1 subdevice: 0 - 32 output
191          *      PCI-7432 - 2 subdevices: 0 - 32 input, 1 - 32 output
192          *      PCI-7433 - 2 subdevices: 0 - 32 input, 1 - 32 input
193          *      PCI-7434 - 2 subdevices: 0 - 32 output, 1 - 32 output
194          */
195         ret = comedi_alloc_subdevices(dev, board->nsubdevs);
196         if (ret)
197                 return ret;
198
199         subdev = 0;
200
201         if (board->di_nchan) {
202                 nchan = min(board->di_nchan, 32);
203
204                 s = &dev->subdevices[subdev];
205                 /* Isolated digital inputs 0 to 15/31 */
206                 s->type         = COMEDI_SUBD_DI;
207                 s->subdev_flags = SDF_READABLE;
208                 s->n_chan       = nchan;
209                 s->maxdata      = 1;
210                 s->insn_bits    = adl_pci7x3x_di_insn_bits;
211                 s->range_table  = &range_digital;
212
213                 s->private      = (void *)PCI7X3X_DIO_REG;
214
215                 subdev++;
216
217                 nchan = board->di_nchan - nchan;
218                 if (nchan) {
219                         s = &dev->subdevices[subdev];
220                         /* Isolated digital inputs 32 to 63 */
221                         s->type         = COMEDI_SUBD_DI;
222                         s->subdev_flags = SDF_READABLE;
223                         s->n_chan       = nchan;
224                         s->maxdata      = 1;
225                         s->insn_bits    = adl_pci7x3x_di_insn_bits;
226                         s->range_table  = &range_digital;
227
228                         s->private      = (void *)PCI743X_DIO_REG;
229
230                         subdev++;
231                 }
232         }
233
234         if (board->do_nchan) {
235                 nchan = min(board->do_nchan, 32);
236
237                 s = &dev->subdevices[subdev];
238                 /* Isolated digital outputs 0 to 15/31 */
239                 s->type         = COMEDI_SUBD_DO;
240                 s->subdev_flags = SDF_WRITABLE;
241                 s->n_chan       = nchan;
242                 s->maxdata      = 1;
243                 s->insn_bits    = adl_pci7x3x_do_insn_bits;
244                 s->range_table  = &range_digital;
245
246                 s->private      = (void *)PCI7X3X_DIO_REG;
247
248                 subdev++;
249
250                 nchan = board->do_nchan - nchan;
251                 if (nchan) {
252                         s = &dev->subdevices[subdev];
253                         /* Isolated digital outputs 32 to 63 */
254                         s->type         = COMEDI_SUBD_DO;
255                         s->subdev_flags = SDF_WRITABLE;
256                         s->n_chan       = nchan;
257                         s->maxdata      = 1;
258                         s->insn_bits    = adl_pci7x3x_do_insn_bits;
259                         s->range_table  = &range_digital;
260
261                         s->private      = (void *)PCI743X_DIO_REG;
262
263                         subdev++;
264                 }
265         }
266
267         dev_info(dev->class_dev, "%s attached (%d inputs/%d outputs)\n",
268                 dev->board_name, board->di_nchan, board->do_nchan);
269
270         return 0;
271 }
272
273 static struct comedi_driver adl_pci7x3x_driver = {
274         .driver_name    = "adl_pci7x3x",
275         .module         = THIS_MODULE,
276         .auto_attach    = adl_pci7x3x_auto_attach,
277         .detach         = comedi_pci_disable,
278 };
279
280 static int adl_pci7x3x_pci_probe(struct pci_dev *dev,
281                                  const struct pci_device_id *id)
282 {
283         return comedi_pci_auto_config(dev, &adl_pci7x3x_driver,
284                                       id->driver_data);
285 }
286
287 static DEFINE_PCI_DEVICE_TABLE(adl_pci7x3x_pci_table) = {
288         { PCI_VDEVICE(ADLINK, 0x7230), BOARD_PCI7230 },
289         { PCI_VDEVICE(ADLINK, 0x7233), BOARD_PCI7233 },
290         { PCI_VDEVICE(ADLINK, 0x7234), BOARD_PCI7234 },
291         { PCI_VDEVICE(ADLINK, 0x7432), BOARD_PCI7432 },
292         { PCI_VDEVICE(ADLINK, 0x7433), BOARD_PCI7433 },
293         { PCI_VDEVICE(ADLINK, 0x7434), BOARD_PCI7434 },
294         { 0 }
295 };
296 MODULE_DEVICE_TABLE(pci, adl_pci7x3x_pci_table);
297
298 static struct pci_driver adl_pci7x3x_pci_driver = {
299         .name           = "adl_pci7x3x",
300         .id_table       = adl_pci7x3x_pci_table,
301         .probe          = adl_pci7x3x_pci_probe,
302         .remove         = comedi_pci_auto_unconfig,
303 };
304 module_comedi_pci_driver(adl_pci7x3x_driver, adl_pci7x3x_pci_driver);
305
306 MODULE_DESCRIPTION("ADLINK PCI-723x/743x Isolated Digital I/O boards");
307 MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
308 MODULE_LICENSE("GPL");