Merge tag 'asoc-v3.10-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / comedi_bond.c
1 /*
2     comedi/drivers/comedi_bond.c
3     A Comedi driver to 'bond' or merge multiple drivers and devices as one.
4
5     COMEDI - Linux Control and Measurement Device Interface
6     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
7     Copyright (C) 2005 Calin A. Culianu <calin@ajvar.org>
8
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
23 */
24 /*
25 Driver: comedi_bond
26 Description: A driver to 'bond' (merge) multiple subdevices from multiple
27              devices together as one.
28 Devices:
29 Author: ds
30 Updated: Mon, 10 Oct 00:18:25 -0500
31 Status: works
32
33 This driver allows you to 'bond' (merge) multiple comedi subdevices
34 (coming from possibly difference boards and/or drivers) together.  For
35 example, if you had a board with 2 different DIO subdevices, and
36 another with 1 DIO subdevice, you could 'bond' them with this driver
37 so that they look like one big fat DIO subdevice.  This makes writing
38 applications slightly easier as you don't have to worry about managing
39 different subdevices in the application -- you just worry about
40 indexing one linear array of channel id's.
41
42 Right now only DIO subdevices are supported as that's the personal itch
43 I am scratching with this driver.  If you want to add support for AI and AO
44 subdevs, go right on ahead and do so!
45
46 Commands aren't supported -- although it would be cool if they were.
47
48 Configuration Options:
49   List of comedi-minors to bond.  All subdevices of the same type
50   within each minor will be concatenated together in the order given here.
51 */
52
53 #include <linux/string.h>
54 #include <linux/slab.h>
55 #include "../comedi.h"
56 #include "../comedilib.h"
57 #include "../comedidev.h"
58
59 /* The maxiumum number of channels per subdevice. */
60 #define MAX_CHANS 256
61
62 struct BondedDevice {
63         struct comedi_device *dev;
64         unsigned minor;
65         unsigned subdev;
66         unsigned subdev_type;
67         unsigned nchans;
68         unsigned chanid_offset; /* The offset into our unified linear
69                                    channel-id's of chanid 0 on this
70                                    subdevice. */
71 };
72
73 /* this structure is for data unique to this hardware driver.  If
74    several hardware drivers keep similar information in this structure,
75    feel free to suggest moving the variable to the struct comedi_device struct.  */
76 struct comedi_bond_private {
77 # define MAX_BOARD_NAME 256
78         char name[MAX_BOARD_NAME];
79         struct BondedDevice **devs;
80         unsigned ndevs;
81         struct BondedDevice *chanIdDevMap[MAX_CHANS];
82         unsigned nchans;
83 };
84
85 /* DIO devices are slightly special.  Although it is possible to
86  * implement the insn_read/insn_write interface, it is much more
87  * useful to applications if you implement the insn_bits interface.
88  * This allows packed reading/writing of the DIO channels.  The
89  * comedi core can convert between insn_bits and insn_read/write */
90 static int bonding_dio_insn_bits(struct comedi_device *dev,
91                                  struct comedi_subdevice *s,
92                                  struct comedi_insn *insn, unsigned int *data)
93 {
94         struct comedi_bond_private *devpriv = dev->private;
95 #define LSAMPL_BITS (sizeof(unsigned int)*8)
96         unsigned nchans = LSAMPL_BITS, num_done = 0, i;
97
98         if (devpriv->nchans < nchans)
99                 nchans = devpriv->nchans;
100
101         /* The insn data is a mask in data[0] and the new data
102          * in data[1], each channel cooresponding to a bit. */
103         for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) {
104                 struct BondedDevice *bdev = devpriv->devs[i];
105                 /* Grab the channel mask and data of only the bits corresponding
106                    to this subdevice.. need to shift them to zero position of
107                    course. */
108                 /* Bits corresponding to this subdev. */
109                 unsigned int subdevMask = ((1 << bdev->nchans) - 1);
110                 unsigned int writeMask, dataBits;
111
112                 /* Argh, we have >= LSAMPL_BITS chans.. take all bits */
113                 if (bdev->nchans >= LSAMPL_BITS)
114                         subdevMask = (unsigned int)(-1);
115
116                 writeMask = (data[0] >> num_done) & subdevMask;
117                 dataBits = (data[1] >> num_done) & subdevMask;
118
119                 /* Read/Write the new digital lines */
120                 if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask,
121                                         &dataBits) != 2)
122                         return -EINVAL;
123
124                 /* Make room for the new bits in data[1], the return value */
125                 data[1] &= ~(subdevMask << num_done);
126                 /* Put the bits in the return value */
127                 data[1] |= (dataBits & subdevMask) << num_done;
128                 /* Save the new bits to the saved state.. */
129                 s->state = data[1];
130
131                 num_done += bdev->nchans;
132         }
133
134         return insn->n;
135 }
136
137 static int bonding_dio_insn_config(struct comedi_device *dev,
138                                    struct comedi_subdevice *s,
139                                    struct comedi_insn *insn, unsigned int *data)
140 {
141         struct comedi_bond_private *devpriv = dev->private;
142         int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits;
143         unsigned int io;
144         struct BondedDevice *bdev;
145
146         if (chan < 0 || chan >= devpriv->nchans)
147                 return -EINVAL;
148         bdev = devpriv->chanIdDevMap[chan];
149
150         /* The input or output configuration of each digital line is
151          * configured by a special insn_config instruction.  chanspec
152          * contains the channel to be changed, and data[0] contains the
153          * value COMEDI_INPUT or COMEDI_OUTPUT. */
154         switch (data[0]) {
155         case INSN_CONFIG_DIO_OUTPUT:
156                 io = COMEDI_OUTPUT;     /* is this really necessary? */
157                 io_bits |= 1 << chan;
158                 break;
159         case INSN_CONFIG_DIO_INPUT:
160                 io = COMEDI_INPUT;      /* is this really necessary? */
161                 io_bits &= ~(1 << chan);
162                 break;
163         case INSN_CONFIG_DIO_QUERY:
164                 data[1] =
165                     (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
166                 return insn->n;
167                 break;
168         default:
169                 return -EINVAL;
170                 break;
171         }
172         /* 'real' channel id for this subdev.. */
173         chan -= bdev->chanid_offset;
174         ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io);
175         if (ret != 1)
176                 return -EINVAL;
177         /* Finally, save the new io_bits values since we didn't get
178            an error above. */
179         s->io_bits = io_bits;
180         return insn->n;
181 }
182
183 static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen)
184 {
185         void *newmem = kmalloc(newlen, GFP_KERNEL);
186
187         if (newmem && oldmem)
188                 memcpy(newmem, oldmem, min(oldlen, newlen));
189         kfree(oldmem);
190         return newmem;
191 }
192
193 static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it)
194 {
195         struct comedi_bond_private *devpriv = dev->private;
196         int i;
197         struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
198
199         memset(devs_opened, 0, sizeof(devs_opened));
200         devpriv->name[0] = 0;
201         /* Loop through all comedi devices specified on the command-line,
202            building our device list */
203         for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
204                 char file[] = "/dev/comediXXXXXX";
205                 int minor = it->options[i];
206                 struct comedi_device *d;
207                 int sdev = -1, nchans, tmp;
208                 struct BondedDevice *bdev = NULL;
209
210                 if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
211                         dev_err(dev->class_dev,
212                                 "Minor %d is invalid!\n", minor);
213                         return 0;
214                 }
215                 if (minor == dev->minor) {
216                         dev_err(dev->class_dev,
217                                 "Cannot bond this driver to itself!\n");
218                         return 0;
219                 }
220                 if (devs_opened[minor]) {
221                         dev_err(dev->class_dev,
222                                 "Minor %d specified more than once!\n", minor);
223                         return 0;
224                 }
225
226                 snprintf(file, sizeof(file), "/dev/comedi%u", minor);
227                 file[sizeof(file) - 1] = 0;
228
229                 d = devs_opened[minor] = comedi_open(file);
230
231                 if (!d) {
232                         dev_err(dev->class_dev,
233                                 "Minor %u could not be opened\n", minor);
234                         return 0;
235                 }
236
237                 /* Do DIO, as that's all we support now.. */
238                 while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
239                                                              sdev + 1)) > -1) {
240                         nchans = comedi_get_n_channels(d, sdev);
241                         if (nchans <= 0) {
242                                 dev_err(dev->class_dev,
243                                         "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
244                                         nchans, minor, sdev);
245                                 return 0;
246                         }
247                         bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
248                         if (!bdev)
249                                 return 0;
250
251                         bdev->dev = d;
252                         bdev->minor = minor;
253                         bdev->subdev = sdev;
254                         bdev->subdev_type = COMEDI_SUBD_DIO;
255                         bdev->nchans = nchans;
256                         bdev->chanid_offset = devpriv->nchans;
257
258                         /* map channel id's to BondedDevice * pointer.. */
259                         while (nchans--)
260                                 devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
261
262                         /* Now put bdev pointer at end of devpriv->devs array
263                          * list.. */
264
265                         /* ergh.. ugly.. we need to realloc :(  */
266                         tmp = devpriv->ndevs * sizeof(bdev);
267                         devpriv->devs =
268                             Realloc(devpriv->devs,
269                                     ++devpriv->ndevs * sizeof(bdev), tmp);
270                         if (!devpriv->devs) {
271                                 dev_err(dev->class_dev,
272                                         "Could not allocate memory. Out of memory?\n");
273                                 return 0;
274                         }
275
276                         devpriv->devs[devpriv->ndevs - 1] = bdev;
277                         {
278         /** Append dev:subdev to devpriv->name */
279                                 char buf[20];
280                                 int left =
281                                     MAX_BOARD_NAME - strlen(devpriv->name) - 1;
282                                 snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
283                                          bdev->subdev);
284                                 buf[sizeof(buf) - 1] = 0;
285                                 strncat(devpriv->name, buf, left);
286                         }
287
288                 }
289         }
290
291         if (!devpriv->nchans) {
292                 dev_err(dev->class_dev, "No channels found!\n");
293                 return 0;
294         }
295
296         return 1;
297 }
298
299 static int bonding_attach(struct comedi_device *dev,
300                           struct comedi_devconfig *it)
301 {
302         struct comedi_bond_private *devpriv;
303         struct comedi_subdevice *s;
304         int ret;
305
306         devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
307         if (!devpriv)
308                 return -ENOMEM;
309         dev->private = devpriv;
310
311         /*
312          * Setup our bonding from config params.. sets up our private struct..
313          */
314         if (!doDevConfig(dev, it))
315                 return -EINVAL;
316
317         dev->board_name = devpriv->name;
318
319         ret = comedi_alloc_subdevices(dev, 1);
320         if (ret)
321                 return ret;
322
323         s = &dev->subdevices[0];
324         s->type = COMEDI_SUBD_DIO;
325         s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
326         s->n_chan = devpriv->nchans;
327         s->maxdata = 1;
328         s->range_table = &range_digital;
329         s->insn_bits = bonding_dio_insn_bits;
330         s->insn_config = bonding_dio_insn_config;
331
332         dev_info(dev->class_dev,
333                 "%s: %s attached, %u channels from %u devices\n",
334                 dev->driver->driver_name, dev->board_name,
335                 devpriv->nchans, devpriv->ndevs);
336
337         return 1;
338 }
339
340 static void bonding_detach(struct comedi_device *dev)
341 {
342         struct comedi_bond_private *devpriv = dev->private;
343         unsigned long devs_closed = 0;
344
345         if (devpriv) {
346                 while (devpriv->ndevs-- && devpriv->devs) {
347                         struct BondedDevice *bdev;
348
349                         bdev = devpriv->devs[devpriv->ndevs];
350                         if (!bdev)
351                                 continue;
352                         if (!(devs_closed & (0x1 << bdev->minor))) {
353                                 comedi_close(bdev->dev);
354                                 devs_closed |= (0x1 << bdev->minor);
355                         }
356                         kfree(bdev);
357                 }
358                 kfree(devpriv->devs);
359                 devpriv->devs = NULL;
360                 kfree(devpriv);
361                 dev->private = NULL;
362         }
363 }
364
365 static struct comedi_driver bonding_driver = {
366         .driver_name    = "comedi_bond",
367         .module         = THIS_MODULE,
368         .attach         = bonding_attach,
369         .detach         = bonding_detach,
370 };
371 module_comedi_driver(bonding_driver);
372
373 MODULE_AUTHOR("Calin A. Culianu");
374 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI "
375                    "devices together as one.  In the words of John Lennon: "
376                    "'And the world will live as one...'");
377 MODULE_LICENSE("GPL");