staging: comedi: remove the "Allocate the subdevice..." comments
[deliverable/linux.git] / drivers / staging / comedi / drivers / comedi_bond.c
CommitLineData
3cf840f6
DS
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/*
25Driver: comedi_bond
e7f2aa34
GKH
26Description: A driver to 'bond' (merge) multiple subdevices from multiple
27 devices together as one.
3cf840f6
DS
28Devices:
29Author: ds
30Updated: Mon, 10 Oct 00:18:25 -0500
31Status: works
32
33This driver allows you to 'bond' (merge) multiple comedi subdevices
34(coming from possibly difference boards and/or drivers) together. For
35example, if you had a board with 2 different DIO subdevices, and
36another with 1 DIO subdevice, you could 'bond' them with this driver
37so that they look like one big fat DIO subdevice. This makes writing
38applications slightly easier as you don't have to worry about managing
39different subdevices in the application -- you just worry about
40indexing one linear array of channel id's.
41
42Right now only DIO subdevices are supported as that's the personal itch
43I am scratching with this driver. If you want to add support for AI and AO
44subdevs, go right on ahead and do so!
45
46Commands aren't supported -- although it would be cool if they were.
47
48Configuration 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
3b6b25b5
GKH
53#include <linux/string.h>
54#include <linux/slab.h>
e2a0eab0 55#include "../comedi.h"
3cf840f6
DS
56#include "../comedilib.h"
57#include "../comedidev.h"
3cf840f6
DS
58
59/* The maxiumum number of channels per subdevice. */
60#define MAX_CHANS 256
61
62#define MODULE_NAME "comedi_bond"
3cf840f6
DS
63#ifndef STR
64# define STR1(x) #x
65# define STR(x) STR1(x)
66#endif
67
246c5418 68static int debug;
3cf840f6 69module_param(debug, int, 0644);
e7f2aa34
GKH
70MODULE_PARM_DESC(debug, "If true, print extra cryptic debugging output useful"
71 "only to developers.");
3cf840f6
DS
72
73#define LOG_MSG(x...) printk(KERN_INFO MODULE_NAME": "x)
e7f2aa34
GKH
74#define DEBUG(x...) \
75 do { \
76 if (debug) \
77 printk(KERN_DEBUG MODULE_NAME": DEBUG: "x); \
78 } while (0)
3cf840f6
DS
79#define WARNING(x...) printk(KERN_WARNING MODULE_NAME ": WARNING: "x)
80#define ERROR(x...) printk(KERN_ERR MODULE_NAME ": INTERNAL ERROR: "x)
3cf840f6
DS
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 */
87struct BondingBoard {
88 const char *name;
89};
3cf840f6 90
3cf840f6
DS
91/*
92 * Useful for shorthand access to the particular board structure
93 */
ff534766 94#define thisboard ((const struct BondingBoard *)dev->board_ptr)
3cf840f6
DS
95
96struct BondedDevice {
472dfe77 97 struct comedi_device *dev;
3cf840f6
DS
98 unsigned minor;
99 unsigned subdev;
100 unsigned subdev_type;
101 unsigned nchans;
e7f2aa34
GKH
102 unsigned chanid_offset; /* The offset into our unified linear
103 channel-id's of chanid 0 on this
104 subdevice. */
3cf840f6 105};
3cf840f6
DS
106
107/* this structure is for data unique to this hardware driver. If
108 several hardware drivers keep similar information in this structure,
71b5f4f1 109 feel free to suggest moving the variable to the struct comedi_device struct. */
3cf840f6
DS
110struct 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};
3cf840f6
DS
118
119/*
120 * most drivers define the following macro to make it easy to
121 * access the private structure.
122 */
ff534766 123#define devpriv ((struct Private *)dev->private)
3cf840f6 124
3cf840f6
DS
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 */
0a85b6f0
MT
130static int bonding_dio_insn_bits(struct comedi_device *dev,
131 struct comedi_subdevice *s,
90035c08 132 struct comedi_insn *insn, unsigned int *data)
3cf840f6 133{
790c5541 134#define LSAMPL_BITS (sizeof(unsigned int)*8)
3cf840f6
DS
135 unsigned nchans = LSAMPL_BITS, num_done = 0, i;
136 if (insn->n != 2)
137 return -EINVAL;
138
139 if (devpriv->nchans < nchans)
140 nchans = devpriv->nchans;
141
142 /* The insn data is a mask in data[0] and the new data
143 * in data[1], each channel cooresponding to a bit. */
144 for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) {
ff534766 145 struct BondedDevice *bdev = devpriv->devs[i];
3cf840f6
DS
146 /* Grab the channel mask and data of only the bits corresponding
147 to this subdevice.. need to shift them to zero position of
148 course. */
e7f2aa34 149 /* Bits corresponding to this subdev. */
790c5541
BP
150 unsigned int subdevMask = ((1 << bdev->nchans) - 1);
151 unsigned int writeMask, dataBits;
3cf840f6
DS
152
153 /* Argh, we have >= LSAMPL_BITS chans.. take all bits */
154 if (bdev->nchans >= LSAMPL_BITS)
0a85b6f0 155 subdevMask = (unsigned int)(-1);
3cf840f6
DS
156
157 writeMask = (data[0] >> num_done) & subdevMask;
158 dataBits = (data[1] >> num_done) & subdevMask;
159
160 /* Read/Write the new digital lines */
161 if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask,
0a85b6f0 162 &dataBits) != 2)
3cf840f6
DS
163 return -EINVAL;
164
165 /* Make room for the new bits in data[1], the return value */
166 data[1] &= ~(subdevMask << num_done);
167 /* Put the bits in the return value */
168 data[1] |= (dataBits & subdevMask) << num_done;
169 /* Save the new bits to the saved state.. */
170 s->state = data[1];
171
172 num_done += bdev->nchans;
173 }
174
175 return insn->n;
176}
177
0a85b6f0
MT
178static int bonding_dio_insn_config(struct comedi_device *dev,
179 struct comedi_subdevice *s,
90035c08 180 struct comedi_insn *insn, unsigned int *data)
3cf840f6
DS
181{
182 int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits;
183 unsigned int io;
ff534766 184 struct BondedDevice *bdev;
3cf840f6
DS
185
186 if (chan < 0 || chan >= devpriv->nchans)
187 return -EINVAL;
188 bdev = devpriv->chanIdDevMap[chan];
189
190 /* The input or output configuration of each digital line is
191 * configured by a special insn_config instruction. chanspec
192 * contains the channel to be changed, and data[0] contains the
193 * value COMEDI_INPUT or COMEDI_OUTPUT. */
194 switch (data[0]) {
195 case INSN_CONFIG_DIO_OUTPUT:
196 io = COMEDI_OUTPUT; /* is this really necessary? */
197 io_bits |= 1 << chan;
198 break;
199 case INSN_CONFIG_DIO_INPUT:
200 io = COMEDI_INPUT; /* is this really necessary? */
201 io_bits &= ~(1 << chan);
202 break;
203 case INSN_CONFIG_DIO_QUERY:
204 data[1] =
0a85b6f0 205 (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT;
3cf840f6
DS
206 return insn->n;
207 break;
208 default:
209 return -EINVAL;
210 break;
211 }
e7f2aa34
GKH
212 /* 'real' channel id for this subdev.. */
213 chan -= bdev->chanid_offset;
3cf840f6
DS
214 ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io);
215 if (ret != 1)
216 return -EINVAL;
217 /* Finally, save the new io_bits values since we didn't get
218 an error above. */
219 s->io_bits = io_bits;
220 return insn->n;
221}
222
223static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen)
224{
3cf840f6 225 void *newmem = kmalloc(newlen, GFP_KERNEL);
e7f2aa34 226
3cf840f6 227 if (newmem && oldmem)
e7f2aa34
GKH
228 memcpy(newmem, oldmem, min(oldlen, newlen));
229 kfree(oldmem);
3cf840f6
DS
230 return newmem;
231}
232
0707bb04 233static int doDevConfig(struct comedi_device *dev, struct comedi_devconfig *it)
3cf840f6
DS
234{
235 int i;
472dfe77 236 struct comedi_device *devs_opened[COMEDI_NUM_BOARD_MINORS];
3cf840f6
DS
237
238 memset(devs_opened, 0, sizeof(devs_opened));
859171ca 239 devpriv->name[0] = 0;
3cf840f6
DS
240 /* Loop through all comedi devices specified on the command-line,
241 building our device list */
242 for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) {
243 char file[] = "/dev/comediXXXXXX";
244 int minor = it->options[i];
472dfe77 245 struct comedi_device *d;
3cf840f6 246 int sdev = -1, nchans, tmp;
246c5418 247 struct BondedDevice *bdev = NULL;
3cf840f6 248
5d3aed74 249 if (minor < 0 || minor >= COMEDI_NUM_BOARD_MINORS) {
3cf840f6
DS
250 ERROR("Minor %d is invalid!\n", minor);
251 return 0;
252 }
253 if (minor == dev->minor) {
254 ERROR("Cannot bond this driver to itself!\n");
255 return 0;
256 }
257 if (devs_opened[minor]) {
258 ERROR("Minor %d specified more than once!\n", minor);
259 return 0;
260 }
261
262 snprintf(file, sizeof(file), "/dev/comedi%u", minor);
263 file[sizeof(file) - 1] = 0;
264
265 d = devs_opened[minor] = comedi_open(file);
266
267 if (!d) {
268 ERROR("Minor %u could not be opened\n", minor);
269 return 0;
270 }
271
272 /* Do DIO, as that's all we support now.. */
273 while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO,
0a85b6f0 274 sdev + 1)) > -1) {
e7f2aa34
GKH
275 nchans = comedi_get_n_channels(d, sdev);
276 if (nchans <= 0) {
277 ERROR("comedi_get_n_channels() returned %d "
278 "on minor %u subdev %d!\n",
279 nchans, minor, sdev);
3cf840f6
DS
280 return 0;
281 }
282 bdev = kmalloc(sizeof(*bdev), GFP_KERNEL);
283 if (!bdev) {
284 ERROR("Out of memory.\n");
285 return 0;
286 }
287 bdev->dev = d;
288 bdev->minor = minor;
289 bdev->subdev = sdev;
290 bdev->subdev_type = COMEDI_SUBD_DIO;
291 bdev->nchans = nchans;
292 bdev->chanid_offset = devpriv->nchans;
293
294 /* map channel id's to BondedDevice * pointer.. */
295 while (nchans--)
296 devpriv->chanIdDevMap[devpriv->nchans++] = bdev;
297
e7f2aa34
GKH
298 /* Now put bdev pointer at end of devpriv->devs array
299 * list.. */
3cf840f6
DS
300
301 /* ergh.. ugly.. we need to realloc :( */
302 tmp = devpriv->ndevs * sizeof(bdev);
303 devpriv->devs =
0a85b6f0
MT
304 Realloc(devpriv->devs,
305 ++devpriv->ndevs * sizeof(bdev), tmp);
3cf840f6 306 if (!devpriv->devs) {
e7f2aa34
GKH
307 ERROR("Could not allocate memory. "
308 "Out of memory?");
3cf840f6
DS
309 return 0;
310 }
311
312 devpriv->devs[devpriv->ndevs - 1] = bdev;
313 {
314 /** Append dev:subdev to devpriv->name */
315 char buf[20];
316 int left =
0a85b6f0 317 MAX_BOARD_NAME - strlen(devpriv->name) - 1;
3cf840f6 318 snprintf(buf, sizeof(buf), "%d:%d ", dev->minor,
0a85b6f0 319 bdev->subdev);
3cf840f6
DS
320 buf[sizeof(buf) - 1] = 0;
321 strncat(devpriv->name, buf, left);
322 }
323
324 }
325 }
326
327 if (!devpriv->nchans) {
328 ERROR("No channels found!\n");
329 return 0;
330 }
331
332 return 1;
333}
334
ecdc3e0d
HS
335static int bonding_attach(struct comedi_device *dev,
336 struct comedi_devconfig *it)
3cf840f6 337{
ecdc3e0d
HS
338 struct comedi_subdevice *s;
339
340 LOG_MSG("comedi%d\n", dev->minor);
341
342 /*
343 * Allocate the private structure area. alloc_private() is a
344 * convenient macro defined in comedidev.h.
345 */
346 if (alloc_private(dev, sizeof(struct Private)) < 0)
347 return -ENOMEM;
348
349 /*
350 * Setup our bonding from config params.. sets up our Private struct..
351 */
352 if (!doDevConfig(dev, it))
353 return -EINVAL;
354
355 /*
356 * Initialize dev->board_name. Note that we can use the "thisboard"
357 * macro now, since we just initialized it in the last line.
358 */
359 dev->board_name = devpriv->name;
360
2f0b9d08 361 if (comedi_alloc_subdevices(dev, 1) < 0)
ecdc3e0d
HS
362 return -ENOMEM;
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;
3cf840f6
DS
379}
380
484ecc95 381static void bonding_detach(struct comedi_device *dev)
3cf840f6 382{
484ecc95
HS
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 }
3cf840f6
DS
403}
404
ecdc3e0d
HS
405static const struct BondingBoard bondingBoards[] = {
406 {
407 .name = "comedi_bond",
408 },
409};
410
411static 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};
420module_comedi_driver(bonding_driver);
421
422MODULE_AUTHOR("Calin A. Culianu");
423MODULE_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...'");
426MODULE_LICENSE("GPL");
This page took 0.336089 seconds and 5 git commands to generate.