2 comedi/drivers/comedi_bond.c
3 A Comedi driver to 'bond' or merge multiple drivers and devices as one.
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>
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.
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.
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.
26 Description: A driver to 'bond' (merge) multiple subdevices from multiple
27 devices together as one.
30 Updated: Mon, 10 Oct 00:18:25 -0500
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.
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!
46 Commands aren't supported -- although it would be cool if they were.
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.
53 #include <linux/string.h>
54 #include <linux/slab.h>
55 #include "../comedi.h"
56 #include "../comedilib.h"
57 #include "../comedidev.h"
59 /* The maxiumum number of channels per subdevice. */
63 struct comedi_device *dev;
68 unsigned chanid_offset; /* The offset into our unified linear
69 channel-id's of chanid 0 on this
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;
81 struct BondedDevice *chanIdDevMap[MAX_CHANS];
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)
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;
98 if (devpriv->nchans < nchans)
99 nchans = devpriv->nchans;
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
108 /* Bits corresponding to this subdev. */
109 unsigned int subdevMask = ((1 << bdev->nchans) - 1);
110 unsigned int writeMask, dataBits;
112 /* Argh, we have >= LSAMPL_BITS chans.. take all bits */
113 if (bdev->nchans >= LSAMPL_BITS)
114 subdevMask = (unsigned int)(-1);
116 writeMask = (data[0] >> num_done) & subdevMask;
117 dataBits = (data[1] >> num_done) & subdevMask;
119 /* Read/Write the new digital lines */
120 if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask,
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.. */
131 num_done += bdev->nchans;
137 static int bonding_dio_insn_config(struct comedi_device *dev,
138 struct comedi_subdevice *s,
139 struct comedi_insn *insn, unsigned int *data)
141 struct comedi_bond_private *devpriv = dev->private;
142 int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits;
144 struct BondedDevice *bdev;
146 if (chan < 0 || chan >= devpriv->nchans)
148 bdev = devpriv->chanIdDevMap[chan];
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. */
155 case INSN_CONFIG_DIO_OUTPUT:
156 io = COMEDI_OUTPUT; /* is this really necessary? */
157 io_bits |= 1 << chan;
159 case INSN_CONFIG_DIO_INPUT:
160 io = COMEDI_INPUT; /* is this really necessary? */
161 io_bits &= ~(1 << chan);
163 case INSN_CONFIG_DIO_QUERY:
165 (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
172 /* 'real' channel id for this subdev.. */
173 chan -= bdev->chanid_offset;
174 ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io);
177 /* Finally, save the new io_bits values since we didn't get
179 s->io_bits = io_bits;
183 static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen)
185 void *newmem = kmalloc(newlen, GFP_KERNEL);
187 if (newmem && oldmem)
188 memcpy(newmem, oldmem, min(oldlen, newlen));
193 static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it)
195 struct comedi_bond_private *devpriv = dev->private;
197 struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
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;
210 if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
211 dev_err(dev->class_dev,
212 "Minor %d is invalid!\n", minor);
215 if (minor == dev->minor) {
216 dev_err(dev->class_dev,
217 "Cannot bond this driver to itself!\n");
220 if (devs_opened[minor]) {
221 dev_err(dev->class_dev,
222 "Minor %d specified more than once!\n", minor);
226 snprintf(file, sizeof(file), "/dev/comedi%u", minor);
227 file[sizeof(file) - 1] = 0;
229 d = devs_opened[minor] = comedi_open(file);
232 dev_err(dev->class_dev,
233 "Minor %u could not be opened\n", minor);
237 /* Do DIO, as that's all we support now.. */
238 while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
240 nchans = comedi_get_n_channels(d, sdev);
242 dev_err(dev->class_dev,
243 "comedi_get_n_channels() returned %d on minor %u subdev %d!\n",
244 nchans, minor, sdev);
247 bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
254 bdev->subdev_type = COMEDI_SUBD_DIO;
255 bdev->nchans = nchans;
256 bdev->chanid_offset = devpriv->nchans;
258 /* map channel id's to BondedDevice * pointer.. */
260 devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
262 /* Now put bdev pointer at end of devpriv->devs array
265 /* ergh.. ugly.. we need to realloc :( */
266 tmp = devpriv->ndevs * sizeof(bdev);
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");
276 devpriv->devs[devpriv->ndevs - 1] = bdev;
278 /** Append dev:subdev to devpriv->name */
281 MAX_BOARD_NAME - strlen(devpriv->name) - 1;
282 snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
284 buf[sizeof(buf) - 1] = 0;
285 strncat(devpriv->name, buf, left);
291 if (!devpriv->nchans) {
292 dev_err(dev->class_dev, "No channels found!\n");
299 static int bonding_attach(struct comedi_device *dev,
300 struct comedi_devconfig *it)
302 struct comedi_bond_private *devpriv;
303 struct comedi_subdevice *s;
306 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
309 dev->private = devpriv;
312 * Setup our bonding from config params.. sets up our private struct..
314 if (!doDevConfig(dev, it))
317 dev->board_name = devpriv->name;
319 ret = comedi_alloc_subdevices(dev, 1);
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;
328 s->range_table = &range_digital;
329 s->insn_bits = bonding_dio_insn_bits;
330 s->insn_config = bonding_dio_insn_config;
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);
340 static void bonding_detach(struct comedi_device *dev)
342 struct comedi_bond_private *devpriv = dev->private;
343 unsigned long devs_closed = 0;
346 while (devpriv->ndevs-- && devpriv->devs) {
347 struct BondedDevice *bdev;
349 bdev = devpriv->devs[devpriv->ndevs];
352 if (!(devs_closed & (0x1 << bdev->minor))) {
353 comedi_close(bdev->dev);
354 devs_closed |= (0x1 << bdev->minor);
358 kfree(devpriv->devs);
359 devpriv->devs = NULL;
365 static struct comedi_driver bonding_driver = {
366 .driver_name = "comedi_bond",
367 .module = THIS_MODULE,
368 .attach = bonding_attach,
369 .detach = bonding_detach,
371 module_comedi_driver(bonding_driver);
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");