Commit | Line | Data |
---|---|---|
fc6a12e5 DS |
1 | /* |
2 | comedi/drivers/skel.c | |
3 | Skeleton code for a Comedi driver | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
7 | ||
8 | This program is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with this program; if not, write to the Free Software | |
20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
21 | ||
22 | */ | |
23 | /* | |
24 | Driver: skel | |
25 | Description: Skeleton driver, an example for driver writers | |
26 | Devices: | |
27 | Author: ds | |
28 | Updated: Mon, 18 Mar 2002 15:34:01 -0800 | |
29 | Status: works | |
30 | ||
31 | This driver is a documented example on how Comedi drivers are | |
32 | written. | |
33 | ||
34 | Configuration Options: | |
35 | none | |
36 | */ | |
37 | ||
38 | /* | |
39 | * The previous block comment is used to automatically generate | |
40 | * documentation in Comedi and Comedilib. The fields: | |
41 | * | |
42 | * Driver: the name of the driver | |
43 | * Description: a short phrase describing the driver. Don't list boards. | |
44 | * Devices: a full list of the boards that attempt to be supported by | |
45 | * the driver. Format is "(manufacturer) board name [comedi name]", | |
46 | * where comedi_name is the name that is used to configure the board. | |
139dfbdf | 47 | * See the comment near board_name: in the struct comedi_driver structure |
fc6a12e5 DS |
48 | * below. If (manufacturer) or [comedi name] is missing, the previous |
49 | * value is used. | |
50 | * Author: you | |
51 | * Updated: date when the _documentation_ was last updated. Use 'date -R' | |
52 | * to get a value for this. | |
53 | * Status: a one-word description of the status. Valid values are: | |
54 | * works - driver works correctly on most boards supported, and | |
55 | * passes comedi_test. | |
56 | * unknown - unknown. Usually put there by ds. | |
57 | * experimental - may not work in any particular release. Author | |
58 | * probably wants assistance testing it. | |
59 | * bitrotten - driver has not been update in a long time, probably | |
60 | * doesn't work, and probably is missing support for significant | |
61 | * Comedi interface features. | |
62 | * untested - author probably wrote it "blind", and is believed to | |
63 | * work, but no confirmation. | |
64 | * | |
65 | * These headers should be followed by a blank line, and any comments | |
66 | * you wish to say about the driver. The comment area is the place | |
67 | * to put any known bugs, limitations, unsupported features, supported | |
68 | * command triggers, whether or not commands are supported on particular | |
69 | * subdevices, etc. | |
70 | * | |
71 | * Somewhere in the comment should be information about configuration | |
72 | * options that are used with comedi_config. | |
73 | */ | |
74 | ||
75 | #include "../comedidev.h" | |
76 | ||
77 | #include <linux/pci.h> /* for PCI devices */ | |
78 | ||
79 | /* Imaginary registers for the imaginary board */ | |
80 | ||
81 | #define SKEL_SIZE 0 | |
82 | ||
83 | #define SKEL_START_AI_CONV 0 | |
84 | #define SKEL_AI_READ 0 | |
85 | ||
86 | /* | |
87 | * Board descriptions for two imaginary boards. Describing the | |
88 | * boards in this way is optional, and completely driver-dependent. | |
89 | * Some drivers use arrays such as this, other do not. | |
90 | */ | |
91 | typedef struct skel_board_struct { | |
92 | const char *name; | |
93 | int ai_chans; | |
94 | int ai_bits; | |
95 | int have_dio; | |
96 | } skel_board; | |
97 | static const skel_board skel_boards[] = { | |
98 | { | |
99 | name: "skel-100", | |
100 | ai_chans:16, | |
101 | ai_bits: 12, | |
102 | have_dio:1, | |
103 | }, | |
104 | { | |
105 | name: "skel-200", | |
106 | ai_chans:8, | |
107 | ai_bits: 16, | |
108 | have_dio:0, | |
109 | }, | |
110 | }; | |
111 | ||
112 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
113 | * only be used for PCI and ISA-PnP devices */ | |
114 | /* Please add your PCI vendor ID to comedidev.h, and it will be forwarded | |
115 | * upstream. */ | |
116 | #define PCI_VENDOR_ID_SKEL 0xdafe | |
117 | static DEFINE_PCI_DEVICE_TABLE(skel_pci_table) = { | |
118 | {PCI_VENDOR_ID_SKEL, 0x0100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, | |
119 | {PCI_VENDOR_ID_SKEL, 0x0200, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, | |
120 | {0} | |
121 | }; | |
122 | ||
123 | MODULE_DEVICE_TABLE(pci, skel_pci_table); | |
124 | ||
125 | /* | |
126 | * Useful for shorthand access to the particular board structure | |
127 | */ | |
128 | #define thisboard ((const skel_board *)dev->board_ptr) | |
129 | ||
130 | /* this structure is for data unique to this hardware driver. If | |
131 | several hardware drivers keep similar information in this structure, | |
71b5f4f1 | 132 | feel free to suggest moving the variable to the struct comedi_device struct. */ |
fc6a12e5 DS |
133 | typedef struct { |
134 | int data; | |
135 | ||
136 | /* would be useful for a PCI device */ | |
137 | struct pci_dev *pci_dev; | |
138 | ||
139 | /* Used for AO readback */ | |
790c5541 | 140 | unsigned int ao_readback[2]; |
fc6a12e5 DS |
141 | } skel_private; |
142 | /* | |
143 | * most drivers define the following macro to make it easy to | |
144 | * access the private structure. | |
145 | */ | |
146 | #define devpriv ((skel_private *)dev->private) | |
147 | ||
148 | /* | |
139dfbdf | 149 | * The struct comedi_driver structure tells the Comedi core module |
fc6a12e5 DS |
150 | * which functions to call to configure/deconfigure (attach/detach) |
151 | * the board, and also about the kernel module that contains | |
152 | * the device code. | |
153 | */ | |
71b5f4f1 BP |
154 | static int skel_attach(struct comedi_device * dev, comedi_devconfig * it); |
155 | static int skel_detach(struct comedi_device * dev); | |
139dfbdf | 156 | static struct comedi_driver driver_skel = { |
fc6a12e5 DS |
157 | driver_name:"dummy", |
158 | module:THIS_MODULE, | |
159 | attach:skel_attach, | |
160 | detach:skel_detach, | |
161 | /* It is not necessary to implement the following members if you are | |
162 | * writing a driver for a ISA PnP or PCI card */ | |
163 | /* Most drivers will support multiple types of boards by | |
164 | * having an array of board structures. These were defined | |
165 | * in skel_boards[] above. Note that the element 'name' | |
166 | * was first in the structure -- Comedi uses this fact to | |
167 | * extract the name of the board without knowing any details | |
168 | * about the structure except for its length. | |
169 | * When a device is attached (by comedi_config), the name | |
170 | * of the device is given to Comedi, and Comedi tries to | |
171 | * match it by going through the list of board names. If | |
172 | * there is a match, the address of the pointer is put | |
173 | * into dev->board_ptr and driver->attach() is called. | |
174 | * | |
175 | * Note that these are not necessary if you can determine | |
176 | * the type of board in software. ISA PnP, PCI, and PCMCIA | |
177 | * devices are such boards. | |
178 | */ | |
179 | board_name:&skel_boards[0].name, | |
180 | offset:sizeof(skel_board), | |
181 | num_names:sizeof(skel_boards) / sizeof(skel_board), | |
182 | }; | |
183 | ||
34c43922 | 184 | static int skel_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 185 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 186 | static int skel_ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 187 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 188 | static int skel_ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 189 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 190 | static int skel_dio_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 191 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 192 | static int skel_dio_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 193 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 194 | static int skel_ai_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 195 | struct comedi_cmd * cmd); |
fc6a12e5 DS |
196 | static int skel_ns_to_timer(unsigned int *ns, int round); |
197 | ||
198 | /* | |
199 | * Attach is called by the Comedi core to configure the driver | |
200 | * for a particular board. If you specified a board_name array | |
201 | * in the driver structure, dev->board_ptr contains that | |
202 | * address. | |
203 | */ | |
71b5f4f1 | 204 | static int skel_attach(struct comedi_device * dev, comedi_devconfig * it) |
fc6a12e5 | 205 | { |
34c43922 | 206 | struct comedi_subdevice *s; |
fc6a12e5 DS |
207 | |
208 | printk("comedi%d: skel: ", dev->minor); | |
209 | ||
210 | /* | |
211 | * If you can probe the device to determine what device in a series | |
212 | * it is, this is the place to do it. Otherwise, dev->board_ptr | |
213 | * should already be initialized. | |
214 | */ | |
215 | //dev->board_ptr = skel_probe(dev, it); | |
216 | ||
217 | /* | |
218 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
219 | * macro now, since we just initialized it in the last line. | |
220 | */ | |
221 | dev->board_name = thisboard->name; | |
222 | ||
223 | /* | |
224 | * Allocate the private structure area. alloc_private() is a | |
225 | * convenient macro defined in comedidev.h. | |
226 | */ | |
227 | if (alloc_private(dev, sizeof(skel_private)) < 0) | |
228 | return -ENOMEM; | |
229 | ||
230 | /* | |
231 | * Allocate the subdevice structures. alloc_subdevice() is a | |
232 | * convenient macro defined in comedidev.h. | |
233 | */ | |
234 | if (alloc_subdevices(dev, 3) < 0) | |
235 | return -ENOMEM; | |
236 | ||
237 | s = dev->subdevices + 0; | |
238 | //dev->read_subdev=s; | |
239 | /* analog input subdevice */ | |
240 | s->type = COMEDI_SUBD_AI; | |
241 | /* we support single-ended (ground) and differential */ | |
242 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; | |
243 | s->n_chan = thisboard->ai_chans; | |
244 | s->maxdata = (1 << thisboard->ai_bits) - 1; | |
245 | s->range_table = &range_bipolar10; | |
246 | s->len_chanlist = 16; /* This is the maximum chanlist length that | |
247 | the board can handle */ | |
248 | s->insn_read = skel_ai_rinsn; | |
249 | // s->subdev_flags |= SDF_CMD_READ; | |
250 | // s->do_cmd = skel_ai_cmd; | |
251 | s->do_cmdtest = skel_ai_cmdtest; | |
252 | ||
253 | s = dev->subdevices + 1; | |
254 | /* analog output subdevice */ | |
255 | s->type = COMEDI_SUBD_AO; | |
256 | s->subdev_flags = SDF_WRITABLE; | |
257 | s->n_chan = 1; | |
258 | s->maxdata = 0xffff; | |
259 | s->range_table = &range_bipolar5; | |
260 | s->insn_write = skel_ao_winsn; | |
261 | s->insn_read = skel_ao_rinsn; | |
262 | ||
263 | s = dev->subdevices + 2; | |
264 | /* digital i/o subdevice */ | |
265 | if (thisboard->have_dio) { | |
266 | s->type = COMEDI_SUBD_DIO; | |
267 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
268 | s->n_chan = 16; | |
269 | s->maxdata = 1; | |
270 | s->range_table = &range_digital; | |
271 | s->insn_bits = skel_dio_insn_bits; | |
272 | s->insn_config = skel_dio_insn_config; | |
273 | } else { | |
274 | s->type = COMEDI_SUBD_UNUSED; | |
275 | } | |
276 | ||
277 | printk("attached\n"); | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | /* | |
283 | * _detach is called to deconfigure a device. It should deallocate | |
284 | * resources. | |
285 | * This function is also called when _attach() fails, so it should be | |
286 | * careful not to release resources that were not necessarily | |
287 | * allocated by _attach(). dev->private and dev->subdevices are | |
288 | * deallocated automatically by the core. | |
289 | */ | |
71b5f4f1 | 290 | static int skel_detach(struct comedi_device * dev) |
fc6a12e5 DS |
291 | { |
292 | printk("comedi%d: skel: remove\n", dev->minor); | |
293 | ||
294 | return 0; | |
295 | } | |
296 | ||
297 | /* | |
298 | * "instructions" read/write data in "one-shot" or "software-triggered" | |
299 | * mode. | |
300 | */ | |
34c43922 | 301 | static int skel_ai_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 302 | struct comedi_insn * insn, unsigned int * data) |
fc6a12e5 DS |
303 | { |
304 | int n, i; | |
305 | unsigned int d; | |
306 | unsigned int status; | |
307 | ||
308 | /* a typical programming sequence */ | |
309 | ||
310 | /* write channel to multiplexer */ | |
311 | //outw(chan,dev->iobase + SKEL_MUX); | |
312 | ||
313 | /* don't wait for mux to settle */ | |
314 | ||
315 | /* convert n samples */ | |
316 | for (n = 0; n < insn->n; n++) { | |
317 | /* trigger conversion */ | |
318 | //outw(0,dev->iobase + SKEL_CONVERT); | |
319 | ||
320 | #define TIMEOUT 100 | |
321 | /* wait for conversion to end */ | |
322 | for (i = 0; i < TIMEOUT; i++) { | |
323 | status = 1; | |
324 | //status = inb(dev->iobase + SKEL_STATUS); | |
325 | if (status) | |
326 | break; | |
327 | } | |
328 | if (i == TIMEOUT) { | |
329 | /* rt_printk() should be used instead of printk() | |
330 | * whenever the code can be called from real-time. */ | |
331 | rt_printk("timeout\n"); | |
332 | return -ETIMEDOUT; | |
333 | } | |
334 | ||
335 | /* read data */ | |
336 | //d = inw(dev->iobase + SKEL_AI_DATA); | |
337 | d = 0; | |
338 | ||
339 | /* mangle the data as necessary */ | |
340 | d ^= 1 << (thisboard->ai_bits - 1); | |
341 | ||
342 | data[n] = d; | |
343 | } | |
344 | ||
345 | /* return the number of samples read/written */ | |
346 | return n; | |
347 | } | |
348 | ||
34c43922 | 349 | static int skel_ai_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, |
ea6d0d4c | 350 | struct comedi_cmd * cmd) |
fc6a12e5 DS |
351 | { |
352 | int err = 0; | |
353 | int tmp; | |
354 | ||
355 | /* cmdtest tests a particular command to see if it is valid. | |
356 | * Using the cmdtest ioctl, a user can create a valid cmd | |
357 | * and then have it executes by the cmd ioctl. | |
358 | * | |
359 | * cmdtest returns 1,2,3,4 or 0, depending on which tests | |
360 | * the command passes. */ | |
361 | ||
362 | /* step 1: make sure trigger sources are trivially valid */ | |
363 | ||
364 | tmp = cmd->start_src; | |
365 | cmd->start_src &= TRIG_NOW; | |
366 | if (!cmd->start_src || tmp != cmd->start_src) | |
367 | err++; | |
368 | ||
369 | tmp = cmd->scan_begin_src; | |
370 | cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; | |
371 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
372 | err++; | |
373 | ||
374 | tmp = cmd->convert_src; | |
375 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
376 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
377 | err++; | |
378 | ||
379 | tmp = cmd->scan_end_src; | |
380 | cmd->scan_end_src &= TRIG_COUNT; | |
381 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
382 | err++; | |
383 | ||
384 | tmp = cmd->stop_src; | |
385 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
386 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
387 | err++; | |
388 | ||
389 | if (err) | |
390 | return 1; | |
391 | ||
392 | /* step 2: make sure trigger sources are unique and mutually compatible */ | |
393 | ||
394 | /* note that mutual compatiblity is not an issue here */ | |
395 | if (cmd->scan_begin_src != TRIG_TIMER && | |
396 | cmd->scan_begin_src != TRIG_EXT) | |
397 | err++; | |
398 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) | |
399 | err++; | |
400 | if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) | |
401 | err++; | |
402 | ||
403 | if (err) | |
404 | return 2; | |
405 | ||
406 | /* step 3: make sure arguments are trivially compatible */ | |
407 | ||
408 | if (cmd->start_arg != 0) { | |
409 | cmd->start_arg = 0; | |
410 | err++; | |
411 | } | |
412 | #define MAX_SPEED 10000 /* in nanoseconds */ | |
413 | #define MIN_SPEED 1000000000 /* in nanoseconds */ | |
414 | ||
415 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
416 | if (cmd->scan_begin_arg < MAX_SPEED) { | |
417 | cmd->scan_begin_arg = MAX_SPEED; | |
418 | err++; | |
419 | } | |
420 | if (cmd->scan_begin_arg > MIN_SPEED) { | |
421 | cmd->scan_begin_arg = MIN_SPEED; | |
422 | err++; | |
423 | } | |
424 | } else { | |
425 | /* external trigger */ | |
426 | /* should be level/edge, hi/lo specification here */ | |
427 | /* should specify multiple external triggers */ | |
428 | if (cmd->scan_begin_arg > 9) { | |
429 | cmd->scan_begin_arg = 9; | |
430 | err++; | |
431 | } | |
432 | } | |
433 | if (cmd->convert_src == TRIG_TIMER) { | |
434 | if (cmd->convert_arg < MAX_SPEED) { | |
435 | cmd->convert_arg = MAX_SPEED; | |
436 | err++; | |
437 | } | |
438 | if (cmd->convert_arg > MIN_SPEED) { | |
439 | cmd->convert_arg = MIN_SPEED; | |
440 | err++; | |
441 | } | |
442 | } else { | |
443 | /* external trigger */ | |
444 | /* see above */ | |
445 | if (cmd->convert_arg > 9) { | |
446 | cmd->convert_arg = 9; | |
447 | err++; | |
448 | } | |
449 | } | |
450 | ||
451 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
452 | cmd->scan_end_arg = cmd->chanlist_len; | |
453 | err++; | |
454 | } | |
455 | if (cmd->stop_src == TRIG_COUNT) { | |
456 | if (cmd->stop_arg > 0x00ffffff) { | |
457 | cmd->stop_arg = 0x00ffffff; | |
458 | err++; | |
459 | } | |
460 | } else { | |
461 | /* TRIG_NONE */ | |
462 | if (cmd->stop_arg != 0) { | |
463 | cmd->stop_arg = 0; | |
464 | err++; | |
465 | } | |
466 | } | |
467 | ||
468 | if (err) | |
469 | return 3; | |
470 | ||
471 | /* step 4: fix up any arguments */ | |
472 | ||
473 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
474 | tmp = cmd->scan_begin_arg; | |
475 | skel_ns_to_timer(&cmd->scan_begin_arg, | |
476 | cmd->flags & TRIG_ROUND_MASK); | |
477 | if (tmp != cmd->scan_begin_arg) | |
478 | err++; | |
479 | } | |
480 | if (cmd->convert_src == TRIG_TIMER) { | |
481 | tmp = cmd->convert_arg; | |
482 | skel_ns_to_timer(&cmd->convert_arg, | |
483 | cmd->flags & TRIG_ROUND_MASK); | |
484 | if (tmp != cmd->convert_arg) | |
485 | err++; | |
486 | if (cmd->scan_begin_src == TRIG_TIMER && | |
487 | cmd->scan_begin_arg < | |
488 | cmd->convert_arg * cmd->scan_end_arg) { | |
489 | cmd->scan_begin_arg = | |
490 | cmd->convert_arg * cmd->scan_end_arg; | |
491 | err++; | |
492 | } | |
493 | } | |
494 | ||
495 | if (err) | |
496 | return 4; | |
497 | ||
498 | return 0; | |
499 | } | |
500 | ||
501 | /* This function doesn't require a particular form, this is just | |
502 | * what happens to be used in some of the drivers. It should | |
503 | * convert ns nanoseconds to a counter value suitable for programming | |
504 | * the device. Also, it should adjust ns so that it cooresponds to | |
505 | * the actual time that the device will use. */ | |
506 | static int skel_ns_to_timer(unsigned int *ns, int round) | |
507 | { | |
508 | /* trivial timer */ | |
509 | /* if your timing is done through two cascaded timers, the | |
510 | * i8253_cascade_ns_to_timer() function in 8253.h can be | |
511 | * very helpful. There are also i8254_load() and i8254_mm_load() | |
512 | * which can be used to load values into the ubiquitous 8254 counters | |
513 | */ | |
514 | ||
515 | return *ns; | |
516 | } | |
517 | ||
34c43922 | 518 | static int skel_ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 519 | struct comedi_insn * insn, unsigned int * data) |
fc6a12e5 DS |
520 | { |
521 | int i; | |
522 | int chan = CR_CHAN(insn->chanspec); | |
523 | ||
524 | printk("skel_ao_winsn\n"); | |
525 | /* Writing a list of values to an AO channel is probably not | |
526 | * very useful, but that's how the interface is defined. */ | |
527 | for (i = 0; i < insn->n; i++) { | |
528 | /* a typical programming sequence */ | |
529 | //outw(data[i],dev->iobase + SKEL_DA0 + chan); | |
530 | devpriv->ao_readback[chan] = data[i]; | |
531 | } | |
532 | ||
533 | /* return the number of samples read/written */ | |
534 | return i; | |
535 | } | |
536 | ||
537 | /* AO subdevices should have a read insn as well as a write insn. | |
538 | * Usually this means copying a value stored in devpriv. */ | |
34c43922 | 539 | static int skel_ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 540 | struct comedi_insn * insn, unsigned int * data) |
fc6a12e5 DS |
541 | { |
542 | int i; | |
543 | int chan = CR_CHAN(insn->chanspec); | |
544 | ||
545 | for (i = 0; i < insn->n; i++) | |
546 | data[i] = devpriv->ao_readback[chan]; | |
547 | ||
548 | return i; | |
549 | } | |
550 | ||
551 | /* DIO devices are slightly special. Although it is possible to | |
552 | * implement the insn_read/insn_write interface, it is much more | |
553 | * useful to applications if you implement the insn_bits interface. | |
554 | * This allows packed reading/writing of the DIO channels. The | |
555 | * comedi core can convert between insn_bits and insn_read/write */ | |
34c43922 | 556 | static int skel_dio_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 557 | struct comedi_insn * insn, unsigned int * data) |
fc6a12e5 DS |
558 | { |
559 | if (insn->n != 2) | |
560 | return -EINVAL; | |
561 | ||
562 | /* The insn data is a mask in data[0] and the new data | |
563 | * in data[1], each channel cooresponding to a bit. */ | |
564 | if (data[0]) { | |
565 | s->state &= ~data[0]; | |
566 | s->state |= data[0] & data[1]; | |
567 | /* Write out the new digital output lines */ | |
568 | //outw(s->state,dev->iobase + SKEL_DIO); | |
569 | } | |
570 | ||
571 | /* on return, data[1] contains the value of the digital | |
572 | * input and output lines. */ | |
573 | //data[1]=inw(dev->iobase + SKEL_DIO); | |
574 | /* or we could just return the software copy of the output values if | |
575 | * it was a purely digital output subdevice */ | |
576 | //data[1]=s->state; | |
577 | ||
578 | return 2; | |
579 | } | |
580 | ||
34c43922 | 581 | static int skel_dio_insn_config(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 582 | struct comedi_insn * insn, unsigned int * data) |
fc6a12e5 DS |
583 | { |
584 | int chan = CR_CHAN(insn->chanspec); | |
585 | ||
586 | /* The input or output configuration of each digital line is | |
587 | * configured by a special insn_config instruction. chanspec | |
588 | * contains the channel to be changed, and data[0] contains the | |
589 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ | |
590 | switch (data[0]) { | |
591 | case INSN_CONFIG_DIO_OUTPUT: | |
592 | s->io_bits |= 1 << chan; | |
593 | break; | |
594 | case INSN_CONFIG_DIO_INPUT: | |
595 | s->io_bits &= ~(1 << chan); | |
596 | break; | |
597 | case INSN_CONFIG_DIO_QUERY: | |
598 | data[1] = | |
599 | (s-> | |
600 | io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; | |
601 | return insn->n; | |
602 | break; | |
603 | default: | |
604 | return -EINVAL; | |
605 | break; | |
606 | } | |
607 | //outw(s->io_bits,dev->iobase + SKEL_DIO_CONFIG); | |
608 | ||
609 | return insn->n; | |
610 | } | |
611 | ||
612 | /* | |
613 | * A convenient macro that defines init_module() and cleanup_module(), | |
614 | * as necessary. | |
615 | */ | |
616 | COMEDI_INITCLEANUP(driver_skel); | |
617 | /* If you are writing a PCI driver you should use COMEDI_PCI_INITCLEANUP instead. | |
618 | */ | |
619 | // COMEDI_PCI_INITCLEANUP(driver_skel, skel_pci_table) |