Merge tag 'multiplatform' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / addi_apci_1516.c
1 /*
2  * addi_apci_1516.c
3  * Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.
4  * Project manager: Eric Stolz
5  *
6  *      ADDI-DATA GmbH
7  *      Dieselstrasse 3
8  *      D-77833 Ottersweier
9  *      Tel: +19(0)7223/9493-0
10  *      Fax: +49(0)7223/9493-92
11  *      http://www.addi-data.com
12  *      info@addi-data.com
13  *
14  * This program is free software; you can redistribute it and/or modify it
15  * under the terms of the GNU General Public License as published by the
16  * Free Software Foundation; either version 2 of the License, or (at your
17  * option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful, but WITHOUT
20  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22  * more details.
23  *
24  * You should have received a copy of the GNU General Public License along
25  * with this program; if not, write to the Free Software Foundation, Inc.,
26  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27  *
28  * You should also find the complete GPL in the COPYING file accompanying
29  * this source code.
30  */
31
32 #include "../comedidev.h"
33 #include "comedi_fc.h"
34
35 /*
36  * PCI device ids supported by this driver
37  */
38 #define PCI_DEVICE_ID_APCI1016          0x1000
39 #define PCI_DEVICE_ID_APCI1516          0x1001
40 #define PCI_DEVICE_ID_APCI2016          0x1002
41
42 /*
43  * PCI bar 1 I/O Register map - Digital input/output
44  */
45 #define APCI1516_DI_REG                 0x00
46 #define APCI1516_DO_REG                 0x04
47
48 /*
49  * PCI bar 2 I/O Register map - Watchdog (APCI-1516 and APCI-2016)
50  */
51 #define APCI1516_WDOG_REG               0x00
52 #define APCI1516_WDOG_RELOAD_REG        0x04
53 #define APCI1516_WDOG_CTRL_REG          0x0c
54 #define APCI1516_WDOG_CTRL_ENABLE       (1 << 0)
55 #define APCI1516_WDOG_CTRL_SW_TRIG      (1 << 9)
56 #define APCI1516_WDOG_STATUS_REG        0x10
57 #define APCI1516_WDOG_STATUS_ENABLED    (1 << 0)
58 #define APCI1516_WDOG_STATUS_SW_TRIG    (1 << 1)
59
60 struct apci1516_boardinfo {
61         const char *name;
62         unsigned short device;
63         int di_nchan;
64         int do_nchan;
65         int has_wdog;
66 };
67
68 static const struct apci1516_boardinfo apci1516_boardtypes[] = {
69         {
70                 .name           = "apci1016",
71                 .device         = PCI_DEVICE_ID_APCI1016,
72                 .di_nchan       = 16,
73         }, {
74                 .name           = "apci1516",
75                 .device         = PCI_DEVICE_ID_APCI1516,
76                 .di_nchan       = 8,
77                 .do_nchan       = 8,
78                 .has_wdog       = 1,
79         }, {
80                 .name           = "apci2016",
81                 .device         = PCI_DEVICE_ID_APCI2016,
82                 .do_nchan       = 16,
83                 .has_wdog       = 1,
84         },
85 };
86
87 struct apci1516_private {
88         unsigned long wdog_iobase;
89         unsigned int ctrl;
90 };
91
92 static int apci1516_di_insn_bits(struct comedi_device *dev,
93                                  struct comedi_subdevice *s,
94                                  struct comedi_insn *insn,
95                                  unsigned int *data)
96 {
97         data[1] = inw(dev->iobase + APCI1516_DI_REG);
98
99         return insn->n;
100 }
101
102 static int apci1516_do_insn_bits(struct comedi_device *dev,
103                                  struct comedi_subdevice *s,
104                                  struct comedi_insn *insn,
105                                  unsigned int *data)
106 {
107         unsigned int mask = data[0];
108         unsigned int bits = data[1];
109
110         s->state = inw(dev->iobase + APCI1516_DO_REG);
111         if (mask) {
112                 s->state &= ~mask;
113                 s->state |= (bits & mask);
114
115                 outw(s->state, dev->iobase + APCI1516_DO_REG);
116         }
117
118         data[1] = s->state;
119
120         return insn->n;
121 }
122
123 /*
124  * The watchdog subdevice is configured with two INSN_CONFIG instructions:
125  *
126  * Enable the watchdog and set the reload timeout:
127  *      data[0] = INSN_CONFIG_ARM
128  *      data[1] = timeout reload value
129  *
130  * Disable the watchdog:
131  *      data[0] = INSN_CONFIG_DISARM
132  */
133 static int apci1516_wdog_insn_config(struct comedi_device *dev,
134                                      struct comedi_subdevice *s,
135                                      struct comedi_insn *insn,
136                                      unsigned int *data)
137 {
138         struct apci1516_private *devpriv = dev->private;
139         unsigned int reload;
140
141         switch (data[0]) {
142         case INSN_CONFIG_ARM:
143                 devpriv->ctrl = APCI1516_WDOG_CTRL_ENABLE;
144                 reload = data[1] & s->maxdata;
145                 outw(reload, devpriv->wdog_iobase + APCI1516_WDOG_RELOAD_REG);
146
147                 /* Time base is 20ms, let the user know the timeout */
148                 dev_info(dev->class_dev, "watchdog enabled, timeout:%dms\n",
149                         20 * reload + 20);
150                 break;
151         case INSN_CONFIG_DISARM:
152                 devpriv->ctrl = 0;
153                 break;
154         default:
155                 return -EINVAL;
156         }
157
158         outw(devpriv->ctrl, devpriv->wdog_iobase + APCI1516_WDOG_CTRL_REG);
159
160         return insn->n;
161 }
162
163 static int apci1516_wdog_insn_write(struct comedi_device *dev,
164                                     struct comedi_subdevice *s,
165                                     struct comedi_insn *insn,
166                                     unsigned int *data)
167 {
168         struct apci1516_private *devpriv = dev->private;
169         int i;
170
171         if (devpriv->ctrl == 0) {
172                 dev_warn(dev->class_dev, "watchdog is disabled\n");
173                 return -EINVAL;
174         }
175
176         /* "ping" the watchdog */
177         for (i = 0; i < insn->n; i++) {
178                 outw(devpriv->ctrl | APCI1516_WDOG_CTRL_SW_TRIG,
179                         devpriv->wdog_iobase + APCI1516_WDOG_CTRL_REG);
180         }
181
182         return insn->n;
183 }
184
185 static int apci1516_wdog_insn_read(struct comedi_device *dev,
186                                    struct comedi_subdevice *s,
187                                    struct comedi_insn *insn,
188                                    unsigned int *data)
189 {
190         struct apci1516_private *devpriv = dev->private;
191         int i;
192
193         for (i = 0; i < insn->n; i++)
194                 data[i] = inw(devpriv->wdog_iobase + APCI1516_WDOG_STATUS_REG);
195
196         return insn->n;
197 }
198
199 static int apci1516_reset(struct comedi_device *dev)
200 {
201         const struct apci1516_boardinfo *this_board = comedi_board(dev);
202         struct apci1516_private *devpriv = dev->private;
203
204         if (!this_board->has_wdog)
205                 return 0;
206
207         outw(0x0, dev->iobase + APCI1516_DO_REG);
208         outw(0x0, devpriv->wdog_iobase + APCI1516_WDOG_CTRL_REG);
209         outw(0x0, devpriv->wdog_iobase + APCI1516_WDOG_RELOAD_REG);
210
211         return 0;
212 }
213
214 static const void *apci1516_find_boardinfo(struct comedi_device *dev,
215                                            struct pci_dev *pcidev)
216 {
217         const struct apci1516_boardinfo *this_board;
218         int i;
219
220         for (i = 0; i < dev->driver->num_names; i++) {
221                 this_board = &apci1516_boardtypes[i];
222                 if (this_board->device == pcidev->device)
223                         return this_board;
224         }
225         return NULL;
226 }
227
228 static int apci1516_auto_attach(struct comedi_device *dev,
229                                           unsigned long context_unused)
230 {
231         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
232         const struct apci1516_boardinfo *this_board;
233         struct apci1516_private *devpriv;
234         struct comedi_subdevice *s;
235         int ret;
236
237         this_board = apci1516_find_boardinfo(dev, pcidev);
238         if (!this_board)
239                 return -ENODEV;
240         dev->board_ptr = this_board;
241         dev->board_name = this_board->name;
242
243         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
244         if (!devpriv)
245                 return -ENOMEM;
246         dev->private = devpriv;
247
248         ret = comedi_pci_enable(pcidev, dev->board_name);
249         if (ret)
250                 return ret;
251
252         dev->iobase = pci_resource_start(pcidev, 1);
253         devpriv->wdog_iobase = pci_resource_start(pcidev, 2);
254
255         ret = comedi_alloc_subdevices(dev, 3);
256         if (ret)
257                 return ret;
258
259         /* Initialize the digital input subdevice */
260         s = &dev->subdevices[0];
261         if (this_board->di_nchan) {
262                 s->type         = COMEDI_SUBD_DI;
263                 s->subdev_flags = SDF_READABLE;
264                 s->n_chan       = this_board->di_nchan;
265                 s->maxdata      = 1;
266                 s->range_table  = &range_digital;
267                 s->insn_bits    = apci1516_di_insn_bits;
268         } else {
269                 s->type         = COMEDI_SUBD_UNUSED;
270         }
271
272         /* Initialize the digital output subdevice */
273         s = &dev->subdevices[1];
274         if (this_board->do_nchan) {
275                 s->type         = COMEDI_SUBD_DO;
276                 s->subdev_flags = SDF_WRITEABLE;
277                 s->n_chan       = this_board->do_nchan;
278                 s->maxdata      = 1;
279                 s->range_table  = &range_digital;
280                 s->insn_bits    = apci1516_do_insn_bits;
281         } else {
282                 s->type         = COMEDI_SUBD_UNUSED;
283         }
284
285         /* Initialize the watchdog subdevice */
286         s = &dev->subdevices[2];
287         if (this_board->has_wdog) {
288                 s->type         = COMEDI_SUBD_TIMER;
289                 s->subdev_flags = SDF_WRITEABLE;
290                 s->n_chan       = 1;
291                 s->maxdata      = 0xff;
292                 s->insn_write   = apci1516_wdog_insn_write;
293                 s->insn_read    = apci1516_wdog_insn_read;
294                 s->insn_config  = apci1516_wdog_insn_config;
295         } else {
296                 s->type         = COMEDI_SUBD_UNUSED;
297         }
298
299         apci1516_reset(dev);
300         return 0;
301 }
302
303 static void apci1516_detach(struct comedi_device *dev)
304 {
305         struct pci_dev *pcidev = comedi_to_pci_dev(dev);
306
307         if (dev->iobase) {
308                 apci1516_reset(dev);
309                 comedi_pci_disable(pcidev);
310         }
311 }
312
313 static struct comedi_driver apci1516_driver = {
314         .driver_name    = "addi_apci_1516",
315         .module         = THIS_MODULE,
316         .auto_attach    = apci1516_auto_attach,
317         .detach         = apci1516_detach,
318 };
319
320 static int apci1516_pci_probe(struct pci_dev *dev,
321                                         const struct pci_device_id *ent)
322 {
323         return comedi_pci_auto_config(dev, &apci1516_driver);
324 }
325
326 static void apci1516_pci_remove(struct pci_dev *dev)
327 {
328         comedi_pci_auto_unconfig(dev);
329 }
330
331 static DEFINE_PCI_DEVICE_TABLE(apci1516_pci_table) = {
332         { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, PCI_DEVICE_ID_APCI1016) },
333         { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, PCI_DEVICE_ID_APCI1516) },
334         { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, PCI_DEVICE_ID_APCI2016) },
335         { 0 }
336 };
337 MODULE_DEVICE_TABLE(pci, apci1516_pci_table);
338
339 static struct pci_driver apci1516_pci_driver = {
340         .name           = "addi_apci_1516",
341         .id_table       = apci1516_pci_table,
342         .probe          = apci1516_pci_probe,
343         .remove         = apci1516_pci_remove,
344 };
345 module_comedi_pci_driver(apci1516_driver, apci1516_pci_driver);
346
347 MODULE_DESCRIPTION("ADDI-DATA APCI-1016/1516/2016, 16 channel DIO boards");
348 MODULE_AUTHOR("Comedi http://www.comedi.org");
349 MODULE_LICENSE("GPL");