]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/comedi_bond.c
Merge branch 'for-4.8/core' of git://git.kernel.dk/linux-block
[karo-tx-linux.git] / drivers / staging / comedi / drivers / comedi_bond.c
1 /*
2  * 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
20 /*
21  * Driver: comedi_bond
22  * Description: A driver to 'bond' (merge) multiple subdevices from multiple
23  * devices together as one.
24  * Devices:
25  * Author: ds
26  * Updated: Mon, 10 Oct 00:18:25 -0500
27  * Status: works
28  *
29  * This driver allows you to 'bond' (merge) multiple comedi subdevices
30  * (coming from possibly difference boards and/or drivers) together.  For
31  * example, if you had a board with 2 different DIO subdevices, and
32  * another with 1 DIO subdevice, you could 'bond' them with this driver
33  * so that they look like one big fat DIO subdevice.  This makes writing
34  * applications slightly easier as you don't have to worry about managing
35  * different subdevices in the application -- you just worry about
36  * indexing one linear array of channel id's.
37  *
38  * Right now only DIO subdevices are supported as that's the personal itch
39  * I am scratching with this driver.  If you want to add support for AI and AO
40  * subdevs, go right on ahead and do so!
41  *
42  * Commands aren't supported -- although it would be cool if they were.
43  *
44  * Configuration Options:
45  *   List of comedi-minors to bond.  All subdevices of the same type
46  *   within each minor will be concatenated together in the order given here.
47  */
48
49 #include <linux/module.h>
50 #include <linux/string.h>
51 #include <linux/slab.h>
52 #include "../comedi.h"
53 #include "../comedilib.h"
54 #include "../comedidev.h"
55
56 struct bonded_device {
57         struct comedi_device *dev;
58         unsigned int minor;
59         unsigned int subdev;
60         unsigned int nchans;
61 };
62
63 struct comedi_bond_private {
64         char name[256];
65         struct bonded_device **devs;
66         unsigned int ndevs;
67         unsigned int nchans;
68 };
69
70 static int bonding_dio_insn_bits(struct comedi_device *dev,
71                                  struct comedi_subdevice *s,
72                                  struct comedi_insn *insn, unsigned int *data)
73 {
74         struct comedi_bond_private *devpriv = dev->private;
75         unsigned int n_left, n_done, base_chan;
76         unsigned int write_mask, data_bits;
77         struct bonded_device **devs;
78
79         write_mask = data[0];
80         data_bits = data[1];
81         base_chan = CR_CHAN(insn->chanspec);
82         /* do a maximum of 32 channels, starting from base_chan. */
83         n_left = devpriv->nchans - base_chan;
84         if (n_left > 32)
85                 n_left = 32;
86
87         n_done = 0;
88         devs = devpriv->devs;
89         do {
90                 struct bonded_device *bdev = *devs++;
91
92                 if (base_chan < bdev->nchans) {
93                         /* base channel falls within bonded device */
94                         unsigned int b_chans, b_mask, b_write_mask, b_data_bits;
95                         int ret;
96
97                         /*
98                          * Get num channels to do for bonded device and set
99                          * up mask and data bits for bonded device.
100                          */
101                         b_chans = bdev->nchans - base_chan;
102                         if (b_chans > n_left)
103                                 b_chans = n_left;
104                         b_mask = (b_chans < 32) ? ((1 << b_chans) - 1)
105                                                 : 0xffffffff;
106                         b_write_mask = (write_mask >> n_done) & b_mask;
107                         b_data_bits = (data_bits >> n_done) & b_mask;
108                         /* Read/Write the new digital lines. */
109                         ret = comedi_dio_bitfield2(bdev->dev, bdev->subdev,
110                                                    b_write_mask, &b_data_bits,
111                                                    base_chan);
112                         if (ret < 0)
113                                 return ret;
114                         /* Place read bits into data[1]. */
115                         data[1] &= ~(b_mask << n_done);
116                         data[1] |= (b_data_bits & b_mask) << n_done;
117                         /*
118                          * Set up for following bonded device (if still have
119                          * channels to read/write).
120                          */
121                         base_chan = 0;
122                         n_done += b_chans;
123                         n_left -= b_chans;
124                 } else {
125                         /* Skip bonded devices before base channel. */
126                         base_chan -= bdev->nchans;
127                 }
128         } while (n_left);
129
130         return insn->n;
131 }
132
133 static int bonding_dio_insn_config(struct comedi_device *dev,
134                                    struct comedi_subdevice *s,
135                                    struct comedi_insn *insn, unsigned int *data)
136 {
137         struct comedi_bond_private *devpriv = dev->private;
138         unsigned int chan = CR_CHAN(insn->chanspec);
139         int ret;
140         struct bonded_device *bdev;
141         struct bonded_device **devs;
142
143         /*
144          * Locate bonded subdevice and adjust channel.
145          */
146         devs = devpriv->devs;
147         for (bdev = *devs++; chan >= bdev->nchans; bdev = *devs++)
148                 chan -= bdev->nchans;
149
150         /*
151          * The input or output configuration of each digital line is
152          * configured by a special insn_config instruction.  chanspec
153          * contains the channel to be changed, and data[0] contains the
154          * configuration instruction INSN_CONFIG_DIO_OUTPUT,
155          * INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_QUERY.
156          *
157          * Note that INSN_CONFIG_DIO_OUTPUT == COMEDI_OUTPUT,
158          * and INSN_CONFIG_DIO_INPUT == COMEDI_INPUT.  This is deliberate ;)
159          */
160         switch (data[0]) {
161         case INSN_CONFIG_DIO_OUTPUT:
162         case INSN_CONFIG_DIO_INPUT:
163                 ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, data[0]);
164                 break;
165         case INSN_CONFIG_DIO_QUERY:
166                 ret = comedi_dio_get_config(bdev->dev, bdev->subdev, chan,
167                                             &data[1]);
168                 break;
169         default:
170                 ret = -EINVAL;
171                 break;
172         }
173         if (ret >= 0)
174                 ret = insn->n;
175         return ret;
176 }
177
178 static int do_dev_config(struct comedi_device *dev, struct comedi_devconfig *it)
179 {
180         struct comedi_bond_private *devpriv = dev->private;
181         DECLARE_BITMAP(devs_opened, COMEDI_NUM_BOARD_MINORS);
182         int i;
183
184         memset(&devs_opened, 0, sizeof(devs_opened));
185         devpriv->name[0] = 0;
186         /*
187          * Loop through all comedi devices specified on the command-line,
188          * building our device list.
189          */
190         for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
191                 char file[sizeof("/dev/comediXXXXXX")];
192                 int minor = it->options[i];
193                 struct comedi_device *d;
194                 int sdev = -1, nchans;
195                 struct bonded_device *bdev;
196                 struct bonded_device **devs;
197
198                 if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
199                         dev_err(dev->class_dev,
200                                 "Minor %d is invalid!\n", minor);
201                         return -EINVAL;
202                 }
203                 if (minor == dev->minor) {
204                         dev_err(dev->class_dev,
205                                 "Cannot bond this driver to itself!\n");
206                         return -EINVAL;
207                 }
208                 if (test_and_set_bit(minor, devs_opened)) {
209                         dev_err(dev->class_dev,
210                                 "Minor %d specified more than once!\n", minor);
211                         return -EINVAL;
212                 }
213
214                 snprintf(file, sizeof(file), "/dev/comedi%d", minor);
215                 file[sizeof(file) - 1] = 0;
216
217                 d = comedi_open(file);
218
219                 if (!d) {
220                         dev_err(dev->class_dev,
221                                 "Minor %u could not be opened\n", minor);
222                         return -ENODEV;
223                 }
224
225                 /* Do DIO, as that's all we support now.. */
226                 while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
227                                                              sdev + 1)) > -1) {
228                         nchans = comedi_get_n_channels(d, sdev);
229                         if (nchans <= 0) {
230                                 dev_err(dev->class_dev,
231                                         "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
232                                         nchans, minor, sdev);
233                                 return -EINVAL;
234                         }
235                         bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
236                         if (!bdev)
237                                 return -ENOMEM;
238
239                         bdev->dev = d;
240                         bdev->minor = minor;
241                         bdev->subdev = sdev;
242                         bdev->nchans = nchans;
243                         devpriv->nchans += nchans;
244
245                         /*
246                          * Now put bdev pointer at end of devpriv->devs array
247                          * list..
248                          */
249
250                         /* ergh.. ugly.. we need to realloc :(  */
251                         devs = krealloc(devpriv->devs,
252                                         (devpriv->ndevs + 1) * sizeof(*devs),
253                                         GFP_KERNEL);
254                         if (!devs) {
255                                 dev_err(dev->class_dev,
256                                         "Could not allocate memory. Out of memory?\n");
257                                 kfree(bdev);
258                                 return -ENOMEM;
259                         }
260                         devpriv->devs = devs;
261                         devpriv->devs[devpriv->ndevs++] = bdev;
262                         {
263                                 /* Append dev:subdev to devpriv->name */
264                                 char buf[20];
265
266                                 snprintf(buf, sizeof(buf), "%u:%u ",
267                                          bdev->minor, bdev->subdev);
268                                 strlcat(devpriv->name, buf,
269                                         sizeof(devpriv->name));
270                         }
271                 }
272         }
273
274         if (!devpriv->nchans) {
275                 dev_err(dev->class_dev, "No channels found!\n");
276                 return -EINVAL;
277         }
278
279         return 0;
280 }
281
282 static int bonding_attach(struct comedi_device *dev,
283                           struct comedi_devconfig *it)
284 {
285         struct comedi_bond_private *devpriv;
286         struct comedi_subdevice *s;
287         int ret;
288
289         devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
290         if (!devpriv)
291                 return -ENOMEM;
292
293         /*
294          * Setup our bonding from config params.. sets up our private struct..
295          */
296         ret = do_dev_config(dev, it);
297         if (ret)
298                 return ret;
299
300         dev->board_name = devpriv->name;
301
302         ret = comedi_alloc_subdevices(dev, 1);
303         if (ret)
304                 return ret;
305
306         s = &dev->subdevices[0];
307         s->type = COMEDI_SUBD_DIO;
308         s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
309         s->n_chan = devpriv->nchans;
310         s->maxdata = 1;
311         s->range_table = &range_digital;
312         s->insn_bits = bonding_dio_insn_bits;
313         s->insn_config = bonding_dio_insn_config;
314
315         dev_info(dev->class_dev,
316                  "%s: %s attached, %u channels from %u devices\n",
317                  dev->driver->driver_name, dev->board_name,
318                  devpriv->nchans, devpriv->ndevs);
319
320         return 0;
321 }
322
323 static void bonding_detach(struct comedi_device *dev)
324 {
325         struct comedi_bond_private *devpriv = dev->private;
326
327         if (devpriv && devpriv->devs) {
328                 DECLARE_BITMAP(devs_closed, COMEDI_NUM_BOARD_MINORS);
329
330                 memset(&devs_closed, 0, sizeof(devs_closed));
331                 while (devpriv->ndevs--) {
332                         struct bonded_device *bdev;
333
334                         bdev = devpriv->devs[devpriv->ndevs];
335                         if (!bdev)
336                                 continue;
337                         if (!test_and_set_bit(bdev->minor, devs_closed))
338                                 comedi_close(bdev->dev);
339                         kfree(bdev);
340                 }
341                 kfree(devpriv->devs);
342                 devpriv->devs = NULL;
343         }
344 }
345
346 static struct comedi_driver bonding_driver = {
347         .driver_name    = "comedi_bond",
348         .module         = THIS_MODULE,
349         .attach         = bonding_attach,
350         .detach         = bonding_detach,
351 };
352 module_comedi_driver(bonding_driver);
353
354 MODULE_AUTHOR("Calin A. Culianu");
355 MODULE_DESCRIPTION("comedi_bond: A driver for COMEDI to bond multiple COMEDI devices together as one.");
356 MODULE_LICENSE("GPL");