]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/staging/comedi/drivers/comedi_bond.c
Merge branch 'nfsd-next' of git://linux-nfs.org/~bfields/linux
[karo-tx-linux.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 #define MODULE_NAME "comedi_bond"
63 #ifndef STR
64 #  define STR1(x) #x
65 #  define STR(x) STR1(x)
66 #endif
67
68 static int debug;
69 module_param(debug, int, 0644);
70 MODULE_PARM_DESC(debug, "If true, print extra cryptic debugging output useful"
71                  "only to developers.");
72
73 #define LOG_MSG(x...) printk(KERN_INFO MODULE_NAME": "x)
74 #define DEBUG(x...)                                                     \
75         do {                                                            \
76                 if (debug)                                              \
77                         printk(KERN_DEBUG MODULE_NAME": DEBUG: "x);     \
78         } while (0)
79 #define WARNING(x...)  printk(KERN_WARNING MODULE_NAME ": WARNING: "x)
80 #define ERROR(x...)  printk(KERN_ERR MODULE_NAME ": INTERNAL ERROR: "x)
81
82 /*
83  * Board descriptions for two imaginary boards.  Describing the
84  * boards in this way is optional, and completely driver-dependent.
85  * Some drivers use arrays such as this, other do not.
86  */
87 struct BondingBoard {
88         const char *name;
89 };
90
91 /*
92  * Useful for shorthand access to the particular board structure
93  */
94 #define thisboard ((const struct BondingBoard *)dev->board_ptr)
95
96 struct BondedDevice {
97         struct comedi_device *dev;
98         unsigned minor;
99         unsigned subdev;
100         unsigned subdev_type;
101         unsigned nchans;
102         unsigned chanid_offset; /* The offset into our unified linear
103                                    channel-id's of chanid 0 on this
104                                    subdevice. */
105 };
106
107 /* this structure is for data unique to this hardware driver.  If
108    several hardware drivers keep similar information in this structure,
109    feel free to suggest moving the variable to the struct comedi_device struct.  */
110 struct Private {
111 # define MAX_BOARD_NAME 256
112         char name[MAX_BOARD_NAME];
113         struct BondedDevice **devs;
114         unsigned ndevs;
115         struct BondedDevice *chanIdDevMap[MAX_CHANS];
116         unsigned nchans;
117 };
118
119 /*
120  * most drivers define the following macro to make it easy to
121  * access the private structure.
122  */
123 #define devpriv ((struct Private *)dev->private)
124
125 /* DIO devices are slightly special.  Although it is possible to
126  * implement the insn_read/insn_write interface, it is much more
127  * useful to applications if you implement the insn_bits interface.
128  * This allows packed reading/writing of the DIO channels.  The
129  * comedi core can convert between insn_bits and insn_read/write */
130 static int bonding_dio_insn_bits(struct comedi_device *dev,
131                                  struct comedi_subdevice *s,
132                                  struct comedi_insn *insn, unsigned int *data)
133 {
134 #define LSAMPL_BITS (sizeof(unsigned int)*8)
135         unsigned nchans = LSAMPL_BITS, num_done = 0, i;
136
137         if (devpriv->nchans < nchans)
138                 nchans = devpriv->nchans;
139
140         /* The insn data is a mask in data[0] and the new data
141          * in data[1], each channel cooresponding to a bit. */
142         for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) {
143                 struct BondedDevice *bdev = devpriv->devs[i];
144                 /* Grab the channel mask and data of only the bits corresponding
145                    to this subdevice.. need to shift them to zero position of
146                    course. */
147                 /* Bits corresponding to this subdev. */
148                 unsigned int subdevMask = ((1 << bdev->nchans) - 1);
149                 unsigned int writeMask, dataBits;
150
151                 /* Argh, we have >= LSAMPL_BITS chans.. take all bits */
152                 if (bdev->nchans >= LSAMPL_BITS)
153                         subdevMask = (unsigned int)(-1);
154
155                 writeMask = (data[0] >> num_done) & subdevMask;
156                 dataBits = (data[1] >> num_done) & subdevMask;
157
158                 /* Read/Write the new digital lines */
159                 if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask,
160                                         &dataBits) != 2)
161                         return -EINVAL;
162
163                 /* Make room for the new bits in data[1], the return value */
164                 data[1] &= ~(subdevMask << num_done);
165                 /* Put the bits in the return value */
166                 data[1] |= (dataBits & subdevMask) << num_done;
167                 /* Save the new bits to the saved state.. */
168                 s->state = data[1];
169
170                 num_done += bdev->nchans;
171         }
172
173         return insn->n;
174 }
175
176 static int bonding_dio_insn_config(struct comedi_device *dev,
177                                    struct comedi_subdevice *s,
178                                    struct comedi_insn *insn, unsigned int *data)
179 {
180         int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits;
181         unsigned int io;
182         struct BondedDevice *bdev;
183
184         if (chan < 0 || chan >= devpriv->nchans)
185                 return -EINVAL;
186         bdev = devpriv->chanIdDevMap[chan];
187
188         /* The input or output configuration of each digital line is
189          * configured by a special insn_config instruction.  chanspec
190          * contains the channel to be changed, and data[0] contains the
191          * value COMEDI_INPUT or COMEDI_OUTPUT. */
192         switch (data[0]) {
193         case INSN_CONFIG_DIO_OUTPUT:
194                 io = COMEDI_OUTPUT;     /* is this really necessary? */
195                 io_bits |= 1 << chan;
196                 break;
197         case INSN_CONFIG_DIO_INPUT:
198                 io = COMEDI_INPUT;      /* is this really necessary? */
199                 io_bits &= ~(1 << chan);
200                 break;
201         case INSN_CONFIG_DIO_QUERY:
202                 data[1] =
203                     (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
204                 return insn->n;
205                 break;
206         default:
207                 return -EINVAL;
208                 break;
209         }
210         /* 'real' channel id for this subdev.. */
211         chan -= bdev->chanid_offset;
212         ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io);
213         if (ret != 1)
214                 return -EINVAL;
215         /* Finally, save the new io_bits values since we didn't get
216            an error above. */
217         s->io_bits = io_bits;
218         return insn->n;
219 }
220
221 static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen)
222 {
223         void *newmem = kmalloc(newlen, GFP_KERNEL);
224
225         if (newmem && oldmem)
226                 memcpy(newmem, oldmem, min(oldlen, newlen));
227         kfree(oldmem);
228         return newmem;
229 }
230
231 static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it)
232 {
233         int i;
234         struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
235
236         memset(devs_opened, 0, sizeof(devs_opened));
237         devpriv->name[0] = 0;
238         /* Loop through all comedi devices specified on the command-line,
239            building our device list */
240         for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
241                 char file[] = "/dev/comediXXXXXX";
242                 int minor = it->options[i];
243                 struct comedi_device *d;
244                 int sdev = -1, nchans, tmp;
245                 struct BondedDevice *bdev = NULL;
246
247                 if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
248                         ERROR("Minor %d is invalid!\n", minor);
249                         return 0;
250                 }
251                 if (minor == dev->minor) {
252                         ERROR("Cannot bond this driver to itself!\n");
253                         return 0;
254                 }
255                 if (devs_opened[minor]) {
256                         ERROR("Minor %d specified more than once!\n", minor);
257                         return 0;
258                 }
259
260                 snprintf(file, sizeof(file), "/dev/comedi%u", minor);
261                 file[sizeof(file) - 1] = 0;
262
263                 d = devs_opened[minor] = comedi_open(file);
264
265                 if (!d) {
266                         ERROR("Minor %u could not be opened\n", minor);
267                         return 0;
268                 }
269
270                 /* Do DIO, as that's all we support now.. */
271                 while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
272                                                              sdev + 1)) > -1) {
273                         nchans = comedi_get_n_channels(d, sdev);
274                         if (nchans <= 0) {
275                                 ERROR("comedi_get_n_channels() returned %d "
276                                       "on minor %u subdev %d!\n",
277                                       nchans, minor, sdev);
278                                 return 0;
279                         }
280                         bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
281                         if (!bdev) {
282                                 ERROR("Out of memory.\n");
283                                 return 0;
284                         }
285                         bdev->dev = d;
286                         bdev->minor = minor;
287                         bdev->subdev = sdev;
288                         bdev->subdev_type = COMEDI_SUBD_DIO;
289                         bdev->nchans = nchans;
290                         bdev->chanid_offset = devpriv->nchans;
291
292                         /* map channel id's to BondedDevice * pointer.. */
293                         while (nchans--)
294                                 devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
295
296                         /* Now put bdev pointer at end of devpriv->devs array
297                          * list.. */
298
299                         /* ergh.. ugly.. we need to realloc :(  */
300                         tmp = devpriv->ndevs * sizeof(bdev);
301                         devpriv->devs =
302                             Realloc(devpriv->devs,
303                                     ++devpriv->ndevs * sizeof(bdev), tmp);
304                         if (!devpriv->devs) {
305                                 ERROR("Could not allocate memory. "
306                                       "Out of memory?");
307                                 return 0;
308                         }
309
310                         devpriv->devs[devpriv->ndevs - 1] = bdev;
311                         {
312         /** Append dev:subdev to devpriv->name */
313                                 char buf[20];
314                                 int left =
315                                     MAX_BOARD_NAME - strlen(devpriv->name) - 1;
316                                 snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
317                                          bdev->subdev);
318                                 buf[sizeof(buf) - 1] = 0;
319                                 strncat(devpriv->name, buf, left);
320                         }
321
322                 }
323         }
324
325         if (!devpriv->nchans) {
326                 ERROR("No channels found!\n");
327                 return 0;
328         }
329
330         return 1;
331 }
332
333 static int bonding_attach(struct comedi_device *dev,
334                           struct comedi_devconfig *it)
335 {
336         struct comedi_subdevice *s;
337         int ret;
338
339         LOG_MSG("comedi%d\n", dev->minor);
340
341         /*
342          * Allocate the private structure area.  alloc_private() is a
343          * convenient macro defined in comedidev.h.
344          */
345         if (alloc_private(dev, sizeof(struct Private)) < 0)
346                 return -ENOMEM;
347
348         /*
349          * Setup our bonding from config params.. sets up our Private struct..
350          */
351         if (!doDevConfig(dev, it))
352                 return -EINVAL;
353
354         /*
355          * Initialize dev->board_name.  Note that we can use the "thisboard"
356          * macro now, since we just initialized it in the last line.
357          */
358         dev->board_name = devpriv->name;
359
360         ret = comedi_alloc_subdevices(dev, 1);
361         if (ret)
362                 return ret;
363
364         s = dev->subdevices + 0;
365         s->type = COMEDI_SUBD_DIO;
366         s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
367         s->n_chan = devpriv->nchans;
368         s->maxdata = 1;
369         s->range_table = &range_digital;
370         s->insn_bits = bonding_dio_insn_bits;
371         s->insn_config = bonding_dio_insn_config;
372
373         LOG_MSG("attached with %u DIO channels coming from %u different "
374                 "subdevices all bonded together.  "
375                 "John Lennon would be proud!\n",
376                 devpriv->nchans, devpriv->ndevs);
377
378         return 1;
379 }
380
381 static void bonding_detach(struct comedi_device *dev)
382 {
383         unsigned long devs_closed = 0;
384
385         if (devpriv) {
386                 while (devpriv->ndevs-- && devpriv->devs) {
387                         struct BondedDevice *bdev;
388
389                         bdev = devpriv->devs[devpriv->ndevs];
390                         if (!bdev)
391                                 continue;
392                         if (!(devs_closed & (0x1 << bdev->minor))) {
393                                 comedi_close(bdev->dev);
394                                 devs_closed |= (0x1 << bdev->minor);
395                         }
396                         kfree(bdev);
397                 }
398                 kfree(devpriv->devs);
399                 devpriv->devs = NULL;
400                 kfree(devpriv);
401                 dev->private = NULL;
402         }
403 }
404
405 static const struct BondingBoard bondingBoards[] = {
406         {
407                 .name           = "comedi_bond",
408         },
409 };
410
411 static struct comedi_driver bonding_driver = {
412         .driver_name    = "comedi_bond",
413         .module         = THIS_MODULE,
414         .attach         = bonding_attach,
415         .detach         = bonding_detach,
416         .board_name     = &bondingBoards[0].name,
417         .offset         = sizeof(struct BondingBoard),
418         .num_names      = ARRAY_SIZE(bondingBoards),
419 };
420 module_comedi_driver(bonding_driver);
421
422 MODULE_AUTHOR("Calin A. Culianu");
423 MODULE_DESCRIPTION(MODULE_NAME "A driver for COMEDI to bond multiple COMEDI "
424                    "devices together as one.  In the words of John Lennon: "
425                    "'And the world will live as one...'");
426 MODULE_LICENSE("GPL");