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 | */ | |
c1dd2fa6 | 91 | struct skel_board { |
fc6a12e5 DS |
92 | const char *name; |
93 | int ai_chans; | |
94 | int ai_bits; | |
95 | int have_dio; | |
c1dd2fa6 BP |
96 | }; |
97 | ||
98 | static const struct skel_board skel_boards[] = { | |
fc6a12e5 | 99 | { |
0a85b6f0 MT |
100 | .name = "skel-100", |
101 | .ai_chans = 16, | |
102 | .ai_bits = 12, | |
103 | .have_dio = 1, | |
104 | }, | |
fc6a12e5 | 105 | { |
0a85b6f0 MT |
106 | .name = "skel-200", |
107 | .ai_chans = 8, | |
108 | .ai_bits = 16, | |
109 | .have_dio = 0, | |
110 | }, | |
fc6a12e5 DS |
111 | }; |
112 | ||
113 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
114 | * only be used for PCI and ISA-PnP devices */ | |
115 | /* Please add your PCI vendor ID to comedidev.h, and it will be forwarded | |
116 | * upstream. */ | |
117 | #define PCI_VENDOR_ID_SKEL 0xdafe | |
118 | static DEFINE_PCI_DEVICE_TABLE(skel_pci_table) = { | |
0a85b6f0 MT |
119 | { |
120 | PCI_VENDOR_ID_SKEL, 0x0100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, { | |
121 | PCI_VENDOR_ID_SKEL, 0x0200, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, { | |
122 | 0} | |
fc6a12e5 DS |
123 | }; |
124 | ||
125 | MODULE_DEVICE_TABLE(pci, skel_pci_table); | |
126 | ||
127 | /* | |
128 | * Useful for shorthand access to the particular board structure | |
129 | */ | |
c1dd2fa6 | 130 | #define thisboard ((const struct skel_board *)dev->board_ptr) |
fc6a12e5 DS |
131 | |
132 | /* this structure is for data unique to this hardware driver. If | |
133 | several hardware drivers keep similar information in this structure, | |
588063a1 EC |
134 | feel free to suggest moving the variable to the struct comedi_device struct. |
135 | */ | |
404d108d BP |
136 | struct skel_private { |
137 | ||
fc6a12e5 DS |
138 | int data; |
139 | ||
140 | /* would be useful for a PCI device */ | |
141 | struct pci_dev *pci_dev; | |
142 | ||
143 | /* Used for AO readback */ | |
790c5541 | 144 | unsigned int ao_readback[2]; |
404d108d BP |
145 | }; |
146 | ||
fc6a12e5 DS |
147 | /* |
148 | * most drivers define the following macro to make it easy to | |
149 | * access the private structure. | |
150 | */ | |
404d108d | 151 | #define devpriv ((struct skel_private *)dev->private) |
fc6a12e5 DS |
152 | |
153 | /* | |
139dfbdf | 154 | * The struct comedi_driver structure tells the Comedi core module |
fc6a12e5 DS |
155 | * which functions to call to configure/deconfigure (attach/detach) |
156 | * the board, and also about the kernel module that contains | |
157 | * the device code. | |
158 | */ | |
da91b269 BP |
159 | static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it); |
160 | static int skel_detach(struct comedi_device *dev); | |
139dfbdf | 161 | static struct comedi_driver driver_skel = { |
68c3dbff BP |
162 | .driver_name = "dummy", |
163 | .module = THIS_MODULE, | |
164 | .attach = skel_attach, | |
165 | .detach = skel_detach, | |
fc6a12e5 DS |
166 | /* It is not necessary to implement the following members if you are |
167 | * writing a driver for a ISA PnP or PCI card */ | |
168 | /* Most drivers will support multiple types of boards by | |
169 | * having an array of board structures. These were defined | |
170 | * in skel_boards[] above. Note that the element 'name' | |
171 | * was first in the structure -- Comedi uses this fact to | |
172 | * extract the name of the board without knowing any details | |
173 | * about the structure except for its length. | |
174 | * When a device is attached (by comedi_config), the name | |
175 | * of the device is given to Comedi, and Comedi tries to | |
176 | * match it by going through the list of board names. If | |
177 | * there is a match, the address of the pointer is put | |
178 | * into dev->board_ptr and driver->attach() is called. | |
179 | * | |
180 | * Note that these are not necessary if you can determine | |
181 | * the type of board in software. ISA PnP, PCI, and PCMCIA | |
182 | * devices are such boards. | |
183 | */ | |
68c3dbff BP |
184 | .board_name = &skel_boards[0].name, |
185 | .offset = sizeof(struct skel_board), | |
8629efa4 | 186 | .num_names = ARRAY_SIZE(skel_boards), |
fc6a12e5 DS |
187 | }; |
188 | ||
da91b269 | 189 | static int skel_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 190 | struct comedi_insn *insn, unsigned int *data); |
da91b269 | 191 | static int skel_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 192 | struct comedi_insn *insn, unsigned int *data); |
da91b269 | 193 | static int skel_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 MT |
194 | struct comedi_insn *insn, unsigned int *data); |
195 | static int skel_dio_insn_bits(struct comedi_device *dev, | |
196 | struct comedi_subdevice *s, | |
197 | struct comedi_insn *insn, unsigned int *data); | |
198 | static int skel_dio_insn_config(struct comedi_device *dev, | |
199 | struct comedi_subdevice *s, | |
200 | struct comedi_insn *insn, unsigned int *data); | |
201 | static int skel_ai_cmdtest(struct comedi_device *dev, | |
202 | struct comedi_subdevice *s, struct comedi_cmd *cmd); | |
fc6a12e5 DS |
203 | static int skel_ns_to_timer(unsigned int *ns, int round); |
204 | ||
205 | /* | |
206 | * Attach is called by the Comedi core to configure the driver | |
207 | * for a particular board. If you specified a board_name array | |
208 | * in the driver structure, dev->board_ptr contains that | |
209 | * address. | |
210 | */ | |
da91b269 | 211 | static int skel_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
fc6a12e5 | 212 | { |
34c43922 | 213 | struct comedi_subdevice *s; |
fc6a12e5 | 214 | |
588063a1 | 215 | pr_info("comedi%d: skel: ", dev->minor); |
fc6a12e5 DS |
216 | |
217 | /* | |
218 | * If you can probe the device to determine what device in a series | |
219 | * it is, this is the place to do it. Otherwise, dev->board_ptr | |
220 | * should already be initialized. | |
221 | */ | |
97070b32 | 222 | /* dev->board_ptr = skel_probe(dev, it); */ |
fc6a12e5 DS |
223 | |
224 | /* | |
225 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
226 | * macro now, since we just initialized it in the last line. | |
227 | */ | |
228 | dev->board_name = thisboard->name; | |
229 | ||
230 | /* | |
231 | * Allocate the private structure area. alloc_private() is a | |
232 | * convenient macro defined in comedidev.h. | |
233 | */ | |
404d108d | 234 | if (alloc_private(dev, sizeof(struct skel_private)) < 0) |
fc6a12e5 DS |
235 | return -ENOMEM; |
236 | ||
237 | /* | |
238 | * Allocate the subdevice structures. alloc_subdevice() is a | |
239 | * convenient macro defined in comedidev.h. | |
240 | */ | |
241 | if (alloc_subdevices(dev, 3) < 0) | |
242 | return -ENOMEM; | |
243 | ||
244 | s = dev->subdevices + 0; | |
97070b32 | 245 | /* dev->read_subdev=s; */ |
fc6a12e5 DS |
246 | /* analog input subdevice */ |
247 | s->type = COMEDI_SUBD_AI; | |
248 | /* we support single-ended (ground) and differential */ | |
249 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; | |
250 | s->n_chan = thisboard->ai_chans; | |
251 | s->maxdata = (1 << thisboard->ai_bits) - 1; | |
252 | s->range_table = &range_bipolar10; | |
253 | s->len_chanlist = 16; /* This is the maximum chanlist length that | |
254 | the board can handle */ | |
255 | s->insn_read = skel_ai_rinsn; | |
97070b32 BP |
256 | /* |
257 | * s->subdev_flags |= SDF_CMD_READ; | |
258 | * s->do_cmd = skel_ai_cmd; | |
259 | */ | |
fc6a12e5 DS |
260 | s->do_cmdtest = skel_ai_cmdtest; |
261 | ||
262 | s = dev->subdevices + 1; | |
263 | /* analog output subdevice */ | |
264 | s->type = COMEDI_SUBD_AO; | |
265 | s->subdev_flags = SDF_WRITABLE; | |
266 | s->n_chan = 1; | |
267 | s->maxdata = 0xffff; | |
268 | s->range_table = &range_bipolar5; | |
269 | s->insn_write = skel_ao_winsn; | |
270 | s->insn_read = skel_ao_rinsn; | |
271 | ||
272 | s = dev->subdevices + 2; | |
273 | /* digital i/o subdevice */ | |
274 | if (thisboard->have_dio) { | |
275 | s->type = COMEDI_SUBD_DIO; | |
276 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
277 | s->n_chan = 16; | |
278 | s->maxdata = 1; | |
279 | s->range_table = &range_digital; | |
280 | s->insn_bits = skel_dio_insn_bits; | |
281 | s->insn_config = skel_dio_insn_config; | |
282 | } else { | |
283 | s->type = COMEDI_SUBD_UNUSED; | |
284 | } | |
285 | ||
588063a1 | 286 | pr_info("attached\n"); |
fc6a12e5 DS |
287 | |
288 | return 0; | |
289 | } | |
290 | ||
291 | /* | |
292 | * _detach is called to deconfigure a device. It should deallocate | |
293 | * resources. | |
294 | * This function is also called when _attach() fails, so it should be | |
295 | * careful not to release resources that were not necessarily | |
296 | * allocated by _attach(). dev->private and dev->subdevices are | |
297 | * deallocated automatically by the core. | |
298 | */ | |
da91b269 | 299 | static int skel_detach(struct comedi_device *dev) |
fc6a12e5 | 300 | { |
588063a1 | 301 | pr_info("comedi%d: skel: remove\n", dev->minor); |
fc6a12e5 DS |
302 | |
303 | return 0; | |
304 | } | |
305 | ||
306 | /* | |
307 | * "instructions" read/write data in "one-shot" or "software-triggered" | |
308 | * mode. | |
309 | */ | |
da91b269 | 310 | static int skel_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 311 | struct comedi_insn *insn, unsigned int *data) |
fc6a12e5 DS |
312 | { |
313 | int n, i; | |
314 | unsigned int d; | |
315 | unsigned int status; | |
316 | ||
317 | /* a typical programming sequence */ | |
318 | ||
319 | /* write channel to multiplexer */ | |
97070b32 | 320 | /* outw(chan,dev->iobase + SKEL_MUX); */ |
fc6a12e5 DS |
321 | |
322 | /* don't wait for mux to settle */ | |
323 | ||
324 | /* convert n samples */ | |
325 | for (n = 0; n < insn->n; n++) { | |
326 | /* trigger conversion */ | |
97070b32 | 327 | /* outw(0,dev->iobase + SKEL_CONVERT); */ |
fc6a12e5 DS |
328 | |
329 | #define TIMEOUT 100 | |
330 | /* wait for conversion to end */ | |
331 | for (i = 0; i < TIMEOUT; i++) { | |
332 | status = 1; | |
97070b32 | 333 | /* status = inb(dev->iobase + SKEL_STATUS); */ |
fc6a12e5 DS |
334 | if (status) |
335 | break; | |
336 | } | |
337 | if (i == TIMEOUT) { | |
5f74ea14 | 338 | /* printk() should be used instead of printk() |
fc6a12e5 | 339 | * whenever the code can be called from real-time. */ |
588063a1 | 340 | pr_info("timeout\n"); |
fc6a12e5 DS |
341 | return -ETIMEDOUT; |
342 | } | |
343 | ||
344 | /* read data */ | |
97070b32 | 345 | /* d = inw(dev->iobase + SKEL_AI_DATA); */ |
fc6a12e5 DS |
346 | d = 0; |
347 | ||
348 | /* mangle the data as necessary */ | |
349 | d ^= 1 << (thisboard->ai_bits - 1); | |
350 | ||
351 | data[n] = d; | |
352 | } | |
353 | ||
354 | /* return the number of samples read/written */ | |
355 | return n; | |
356 | } | |
357 | ||
0a85b6f0 MT |
358 | static int skel_ai_cmdtest(struct comedi_device *dev, |
359 | struct comedi_subdevice *s, struct comedi_cmd *cmd) | |
fc6a12e5 DS |
360 | { |
361 | int err = 0; | |
362 | int tmp; | |
363 | ||
364 | /* cmdtest tests a particular command to see if it is valid. | |
365 | * Using the cmdtest ioctl, a user can create a valid cmd | |
366 | * and then have it executes by the cmd ioctl. | |
367 | * | |
368 | * cmdtest returns 1,2,3,4 or 0, depending on which tests | |
369 | * the command passes. */ | |
370 | ||
371 | /* step 1: make sure trigger sources are trivially valid */ | |
372 | ||
373 | tmp = cmd->start_src; | |
374 | cmd->start_src &= TRIG_NOW; | |
375 | if (!cmd->start_src || tmp != cmd->start_src) | |
376 | err++; | |
377 | ||
378 | tmp = cmd->scan_begin_src; | |
379 | cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; | |
380 | if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) | |
381 | err++; | |
382 | ||
383 | tmp = cmd->convert_src; | |
384 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
385 | if (!cmd->convert_src || tmp != cmd->convert_src) | |
386 | err++; | |
387 | ||
388 | tmp = cmd->scan_end_src; | |
389 | cmd->scan_end_src &= TRIG_COUNT; | |
390 | if (!cmd->scan_end_src || tmp != cmd->scan_end_src) | |
391 | err++; | |
392 | ||
393 | tmp = cmd->stop_src; | |
394 | cmd->stop_src &= TRIG_COUNT | TRIG_NONE; | |
395 | if (!cmd->stop_src || tmp != cmd->stop_src) | |
396 | err++; | |
397 | ||
398 | if (err) | |
399 | return 1; | |
400 | ||
588063a1 EC |
401 | /* step 2: make sure trigger sources are unique and mutually compatible |
402 | */ | |
fc6a12e5 | 403 | |
828684f9 | 404 | /* note that mutual compatibility is not an issue here */ |
fc6a12e5 | 405 | if (cmd->scan_begin_src != TRIG_TIMER && |
0a85b6f0 | 406 | cmd->scan_begin_src != TRIG_EXT) |
fc6a12e5 DS |
407 | err++; |
408 | if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) | |
409 | err++; | |
410 | if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) | |
411 | err++; | |
412 | ||
413 | if (err) | |
414 | return 2; | |
415 | ||
416 | /* step 3: make sure arguments are trivially compatible */ | |
417 | ||
418 | if (cmd->start_arg != 0) { | |
419 | cmd->start_arg = 0; | |
420 | err++; | |
421 | } | |
422 | #define MAX_SPEED 10000 /* in nanoseconds */ | |
423 | #define MIN_SPEED 1000000000 /* in nanoseconds */ | |
424 | ||
425 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
426 | if (cmd->scan_begin_arg < MAX_SPEED) { | |
427 | cmd->scan_begin_arg = MAX_SPEED; | |
428 | err++; | |
429 | } | |
430 | if (cmd->scan_begin_arg > MIN_SPEED) { | |
431 | cmd->scan_begin_arg = MIN_SPEED; | |
432 | err++; | |
433 | } | |
434 | } else { | |
435 | /* external trigger */ | |
436 | /* should be level/edge, hi/lo specification here */ | |
437 | /* should specify multiple external triggers */ | |
438 | if (cmd->scan_begin_arg > 9) { | |
439 | cmd->scan_begin_arg = 9; | |
440 | err++; | |
441 | } | |
442 | } | |
443 | if (cmd->convert_src == TRIG_TIMER) { | |
444 | if (cmd->convert_arg < MAX_SPEED) { | |
445 | cmd->convert_arg = MAX_SPEED; | |
446 | err++; | |
447 | } | |
448 | if (cmd->convert_arg > MIN_SPEED) { | |
449 | cmd->convert_arg = MIN_SPEED; | |
450 | err++; | |
451 | } | |
452 | } else { | |
453 | /* external trigger */ | |
454 | /* see above */ | |
455 | if (cmd->convert_arg > 9) { | |
456 | cmd->convert_arg = 9; | |
457 | err++; | |
458 | } | |
459 | } | |
460 | ||
461 | if (cmd->scan_end_arg != cmd->chanlist_len) { | |
462 | cmd->scan_end_arg = cmd->chanlist_len; | |
463 | err++; | |
464 | } | |
465 | if (cmd->stop_src == TRIG_COUNT) { | |
466 | if (cmd->stop_arg > 0x00ffffff) { | |
467 | cmd->stop_arg = 0x00ffffff; | |
468 | err++; | |
469 | } | |
470 | } else { | |
471 | /* TRIG_NONE */ | |
472 | if (cmd->stop_arg != 0) { | |
473 | cmd->stop_arg = 0; | |
474 | err++; | |
475 | } | |
476 | } | |
477 | ||
478 | if (err) | |
479 | return 3; | |
480 | ||
481 | /* step 4: fix up any arguments */ | |
482 | ||
483 | if (cmd->scan_begin_src == TRIG_TIMER) { | |
484 | tmp = cmd->scan_begin_arg; | |
485 | skel_ns_to_timer(&cmd->scan_begin_arg, | |
0a85b6f0 | 486 | cmd->flags & TRIG_ROUND_MASK); |
fc6a12e5 DS |
487 | if (tmp != cmd->scan_begin_arg) |
488 | err++; | |
489 | } | |
490 | if (cmd->convert_src == TRIG_TIMER) { | |
491 | tmp = cmd->convert_arg; | |
492 | skel_ns_to_timer(&cmd->convert_arg, | |
0a85b6f0 | 493 | cmd->flags & TRIG_ROUND_MASK); |
fc6a12e5 DS |
494 | if (tmp != cmd->convert_arg) |
495 | err++; | |
496 | if (cmd->scan_begin_src == TRIG_TIMER && | |
0a85b6f0 MT |
497 | cmd->scan_begin_arg < |
498 | cmd->convert_arg * cmd->scan_end_arg) { | |
fc6a12e5 | 499 | cmd->scan_begin_arg = |
0a85b6f0 | 500 | cmd->convert_arg * cmd->scan_end_arg; |
fc6a12e5 DS |
501 | err++; |
502 | } | |
503 | } | |
504 | ||
505 | if (err) | |
506 | return 4; | |
507 | ||
508 | return 0; | |
509 | } | |
510 | ||
511 | /* This function doesn't require a particular form, this is just | |
512 | * what happens to be used in some of the drivers. It should | |
513 | * convert ns nanoseconds to a counter value suitable for programming | |
514 | * the device. Also, it should adjust ns so that it cooresponds to | |
515 | * the actual time that the device will use. */ | |
516 | static int skel_ns_to_timer(unsigned int *ns, int round) | |
517 | { | |
518 | /* trivial timer */ | |
519 | /* if your timing is done through two cascaded timers, the | |
520 | * i8253_cascade_ns_to_timer() function in 8253.h can be | |
521 | * very helpful. There are also i8254_load() and i8254_mm_load() | |
522 | * which can be used to load values into the ubiquitous 8254 counters | |
523 | */ | |
524 | ||
525 | return *ns; | |
526 | } | |
527 | ||
da91b269 | 528 | static int skel_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 529 | struct comedi_insn *insn, unsigned int *data) |
fc6a12e5 DS |
530 | { |
531 | int i; | |
532 | int chan = CR_CHAN(insn->chanspec); | |
533 | ||
588063a1 | 534 | pr_info("skel_ao_winsn\n"); |
fc6a12e5 DS |
535 | /* Writing a list of values to an AO channel is probably not |
536 | * very useful, but that's how the interface is defined. */ | |
537 | for (i = 0; i < insn->n; i++) { | |
538 | /* a typical programming sequence */ | |
97070b32 | 539 | /* outw(data[i],dev->iobase + SKEL_DA0 + chan); */ |
fc6a12e5 DS |
540 | devpriv->ao_readback[chan] = data[i]; |
541 | } | |
542 | ||
543 | /* return the number of samples read/written */ | |
544 | return i; | |
545 | } | |
546 | ||
547 | /* AO subdevices should have a read insn as well as a write insn. | |
548 | * Usually this means copying a value stored in devpriv. */ | |
da91b269 | 549 | static int skel_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 550 | struct comedi_insn *insn, unsigned int *data) |
fc6a12e5 DS |
551 | { |
552 | int i; | |
553 | int chan = CR_CHAN(insn->chanspec); | |
554 | ||
555 | for (i = 0; i < insn->n; i++) | |
556 | data[i] = devpriv->ao_readback[chan]; | |
557 | ||
558 | return i; | |
559 | } | |
560 | ||
561 | /* DIO devices are slightly special. Although it is possible to | |
562 | * implement the insn_read/insn_write interface, it is much more | |
563 | * useful to applications if you implement the insn_bits interface. | |
564 | * This allows packed reading/writing of the DIO channels. The | |
565 | * comedi core can convert between insn_bits and insn_read/write */ | |
0a85b6f0 MT |
566 | static int skel_dio_insn_bits(struct comedi_device *dev, |
567 | struct comedi_subdevice *s, | |
568 | struct comedi_insn *insn, unsigned int *data) | |
fc6a12e5 DS |
569 | { |
570 | if (insn->n != 2) | |
571 | return -EINVAL; | |
572 | ||
573 | /* The insn data is a mask in data[0] and the new data | |
574 | * in data[1], each channel cooresponding to a bit. */ | |
575 | if (data[0]) { | |
576 | s->state &= ~data[0]; | |
577 | s->state |= data[0] & data[1]; | |
578 | /* Write out the new digital output lines */ | |
97070b32 | 579 | /* outw(s->state,dev->iobase + SKEL_DIO); */ |
fc6a12e5 DS |
580 | } |
581 | ||
582 | /* on return, data[1] contains the value of the digital | |
583 | * input and output lines. */ | |
97070b32 | 584 | /* data[1]=inw(dev->iobase + SKEL_DIO); */ |
fc6a12e5 DS |
585 | /* or we could just return the software copy of the output values if |
586 | * it was a purely digital output subdevice */ | |
97070b32 | 587 | /* data[1]=s->state; */ |
fc6a12e5 DS |
588 | |
589 | return 2; | |
590 | } | |
591 | ||
0a85b6f0 MT |
592 | static int skel_dio_insn_config(struct comedi_device *dev, |
593 | struct comedi_subdevice *s, | |
594 | struct comedi_insn *insn, unsigned int *data) | |
fc6a12e5 DS |
595 | { |
596 | int chan = CR_CHAN(insn->chanspec); | |
597 | ||
598 | /* The input or output configuration of each digital line is | |
599 | * configured by a special insn_config instruction. chanspec | |
600 | * contains the channel to be changed, and data[0] contains the | |
601 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ | |
602 | switch (data[0]) { | |
603 | case INSN_CONFIG_DIO_OUTPUT: | |
604 | s->io_bits |= 1 << chan; | |
605 | break; | |
606 | case INSN_CONFIG_DIO_INPUT: | |
607 | s->io_bits &= ~(1 << chan); | |
608 | break; | |
609 | case INSN_CONFIG_DIO_QUERY: | |
610 | data[1] = | |
0a85b6f0 | 611 | (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; |
fc6a12e5 DS |
612 | return insn->n; |
613 | break; | |
614 | default: | |
615 | return -EINVAL; | |
616 | break; | |
617 | } | |
97070b32 | 618 | /* outw(s->io_bits,dev->iobase + SKEL_DIO_CONFIG); */ |
fc6a12e5 DS |
619 | |
620 | return insn->n; | |
621 | } | |
622 | ||
623 | /* | |
624 | * A convenient macro that defines init_module() and cleanup_module(), | |
625 | * as necessary. | |
626 | */ | |
627 | COMEDI_INITCLEANUP(driver_skel); | |
588063a1 EC |
628 | /* If you are writing a PCI driver you should use COMEDI_PCI_INITCLEANUP |
629 | * instead. | |
630 | */ | |
97070b32 | 631 | /* COMEDI_PCI_INITCLEANUP(driver_skel, skel_pci_table) */ |
90f703d3 AT |
632 | |
633 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
634 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
635 | MODULE_LICENSE("GPL"); |