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