Commit | Line | Data |
---|---|---|
7f62d87d CC |
1 | /* |
2 | comedi/drivers/cb_pcimdda.c | |
3 | Computer Boards PCIM-DDA06-16 Comedi driver | |
4 | Author: Calin Culianu <calin@ajvar.org> | |
5 | ||
6 | COMEDI - Linux Control and Measurement Device Interface | |
7 | Copyright (C) 2000 David A. Schleef <ds@schleef.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 | /* | |
25 | Driver: cb_pcimdda | |
26 | Description: Measurement Computing PCIM-DDA06-16 | |
27 | Devices: [Measurement Computing] PCIM-DDA06-16 (cb_pcimdda) | |
28 | Author: Calin Culianu <calin@ajvar.org> | |
29 | Updated: Mon, 14 Apr 2008 15:15:51 +0100 | |
30 | Status: works | |
31 | ||
32 | All features of the PCIM-DDA06-16 board are supported. This board | |
33 | has 6 16-bit AO channels, and the usual 8255 DIO setup. (24 channels, | |
34 | configurable in banks of 8 and 4, etc.). This board does not support commands. | |
35 | ||
36 | The board has a peculiar way of specifying AO gain/range settings -- You have | |
37 | 1 jumper bank on the card, which either makes all 6 AO channels either | |
38 | 5 Volt unipolar, 5V bipolar, 10 Volt unipolar or 10V bipolar. | |
39 | ||
40 | Since there is absolutely _no_ way to tell in software how this jumper is set | |
41 | (well, at least according to the rather thin spec. from Measurement Computing | |
42 | that comes with the board), the driver assumes the jumper is at its factory | |
43 | default setting of +/-5V. | |
44 | ||
45 | Also of note is the fact that this board features another jumper, whose | |
46 | state is also completely invisible to software. It toggles two possible AO | |
47 | output modes on the board: | |
48 | ||
49 | - Update Mode: Writing to an AO channel instantaneously updates the actual | |
50 | signal output by the DAC on the board (this is the factory default). | |
51 | - Simultaneous XFER Mode: Writing to an AO channel has no effect until | |
52 | you read from any one of the AO channels. This is useful for loading | |
53 | all 6 AO values, and then reading from any one of the AO channels on the | |
54 | device to instantly update all 6 AO values in unison. Useful for some | |
55 | control apps, I would assume? If your jumper is in this setting, then you | |
56 | need to issue your comedi_data_write()s to load all the values you want, | |
57 | then issue one comedi_data_read() on any channel on the AO subdevice | |
58 | to initiate the simultaneous XFER. | |
59 | ||
60 | Configuration Options: | |
61 | [0] PCI bus (optional) | |
62 | [1] PCI slot (optional) | |
63 | [2] analog output range jumper setting | |
64 | 0 == +/- 5 V | |
65 | 1 == +/- 10 V | |
66 | */ | |
67 | ||
68 | /* | |
69 | This is a driver for the Computer Boards PCIM-DDA06-16 Analog Output | |
70 | card. This board has a unique register layout and as such probably | |
71 | deserves its own driver file. | |
72 | ||
73 | It is theoretically possible to integrate this board into the cb_pcidda | |
74 | file, but since that isn't my code, I didn't want to significantly | |
75 | modify that file to support this board (I thought it impolite to do so). | |
76 | ||
77 | At any rate, if you feel ambitious, please feel free to take | |
78 | the code out of this file and combine it with a more unified driver | |
79 | file. | |
80 | ||
81 | I would like to thank Timothy Curry <Timothy.Curry@rdec.redstone.army.mil> | |
82 | for lending me a board so that I could write this driver. | |
83 | ||
84 | -Calin Culianu <calin@ajvar.org> | |
85 | */ | |
86 | ||
87 | #include "../comedidev.h" | |
88 | ||
89 | #include "comedi_pci.h" | |
90 | ||
91 | #include "8255.h" | |
92 | ||
93 | /* device ids of the cards we support -- currently only 1 card supported */ | |
558587e2 GKH |
94 | #define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 |
95 | #define PCI_ID_PCIM_DDA06_16 0x0053 | |
7f62d87d CC |
96 | |
97 | /* | |
98 | * This is straight from skel.c -- I did this in case this source file | |
99 | * will someday support more than 1 board... | |
100 | */ | |
324da11e | 101 | struct board_struct { |
7f62d87d CC |
102 | const char *name; |
103 | unsigned short device_id; | |
104 | int ao_chans; | |
105 | int ao_bits; | |
106 | int dio_chans; | |
107 | int dio_method; | |
108 | int dio_offset; /* how many bytes into the BADR are the DIO ports */ | |
109 | int regs_badrindex; /* IO Region for the control, analog output, | |
110 | and DIO registers */ | |
111 | int reg_sz; /* number of bytes of registers in io region */ | |
324da11e | 112 | }; |
7f62d87d CC |
113 | |
114 | enum DIO_METHODS { | |
115 | DIO_NONE = 0, | |
116 | DIO_8255, | |
117 | DIO_INTERNAL /* unimplemented */ | |
118 | }; | |
119 | ||
324da11e | 120 | static const struct board_struct boards[] = { |
7f62d87d | 121 | { |
0a85b6f0 MT |
122 | .name = "cb_pcimdda06-16", |
123 | .device_id = PCI_ID_PCIM_DDA06_16, | |
124 | .ao_chans = 6, | |
125 | .ao_bits = 16, | |
126 | .dio_chans = 24, | |
127 | .dio_method = DIO_8255, | |
128 | .dio_offset = 12, | |
129 | .regs_badrindex = 3, | |
130 | .reg_sz = 16, | |
131 | } | |
7f62d87d CC |
132 | }; |
133 | ||
134 | /* | |
135 | * Useful for shorthand access to the particular board structure | |
136 | */ | |
324da11e | 137 | #define thisboard ((const struct board_struct *)dev->board_ptr) |
7f62d87d | 138 | |
7f62d87d CC |
139 | #define REG_SZ (thisboard->reg_sz) |
140 | #define REGS_BADRINDEX (thisboard->regs_badrindex) | |
141 | ||
142 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
143 | * only be used for PCI and ISA-PnP devices */ | |
144 | /* Please add your PCI vendor ID to comedidev.h, and it will be forwarded | |
145 | * upstream. */ | |
146 | static DEFINE_PCI_DEVICE_TABLE(pci_table) = { | |
0a85b6f0 MT |
147 | { |
148 | PCI_VENDOR_ID_COMPUTERBOARDS, PCI_ID_PCIM_DDA06_16, PCI_ANY_ID, | |
149 | PCI_ANY_ID, 0, 0, 0}, { | |
150 | 0} | |
7f62d87d CC |
151 | }; |
152 | ||
153 | MODULE_DEVICE_TABLE(pci, pci_table); | |
154 | ||
155 | /* this structure is for data unique to this hardware driver. If | |
156 | several hardware drivers keep similar information in this structure, | |
71b5f4f1 | 157 | feel free to suggest moving the variable to the struct comedi_device struct. */ |
0ff297c4 | 158 | struct board_private_struct { |
7f62d87d CC |
159 | unsigned long registers; /* set by probe */ |
160 | unsigned long dio_registers; | |
161 | char attached_to_8255; /* boolean */ | |
162 | char attached_successfully; /* boolean */ | |
163 | /* would be useful for a PCI device */ | |
164 | struct pci_dev *pci_dev; | |
165 | ||
166 | #define MAX_AO_READBACK_CHANNELS 6 | |
167 | /* Used for AO readback */ | |
790c5541 | 168 | unsigned int ao_readback[MAX_AO_READBACK_CHANNELS]; |
7f62d87d | 169 | |
0ff297c4 | 170 | }; |
7f62d87d CC |
171 | |
172 | /* | |
173 | * most drivers define the following macro to make it easy to | |
174 | * access the private structure. | |
175 | */ | |
0ff297c4 | 176 | #define devpriv ((struct board_private_struct *)dev->private) |
7f62d87d CC |
177 | |
178 | /* | |
139dfbdf | 179 | * The struct comedi_driver structure tells the Comedi core module |
7f62d87d CC |
180 | * which functions to call to configure/deconfigure (attach/detach) |
181 | * the board, and also about the kernel module that contains | |
182 | * the device code. | |
183 | */ | |
da91b269 BP |
184 | static int attach(struct comedi_device *dev, struct comedi_devconfig *it); |
185 | static int detach(struct comedi_device *dev); | |
139dfbdf | 186 | static struct comedi_driver cb_pcimdda_driver = { |
68c3dbff BP |
187 | .driver_name = "cb_pcimdda", |
188 | .module = THIS_MODULE, | |
189 | .attach = attach, | |
190 | .detach = detach, | |
7f62d87d CC |
191 | }; |
192 | ||
193 | MODULE_AUTHOR("Calin A. Culianu <calin@rtlab.org>"); | |
194 | MODULE_DESCRIPTION("Comedi low-level driver for the Computerboards PCIM-DDA " | |
0a85b6f0 MT |
195 | "series. Currently only supports PCIM-DDA06-16 (which " |
196 | "also happens to be the only board in this series. :) ) "); | |
7f62d87d | 197 | MODULE_LICENSE("GPL"); |
727b286b AT |
198 | static int __devinit cb_pcimdda_driver_pci_probe(struct pci_dev *dev, |
199 | const struct pci_device_id | |
200 | *ent) | |
201 | { | |
202 | return comedi_pci_auto_config(dev, cb_pcimdda_driver.driver_name); | |
203 | } | |
204 | ||
205 | static void __devexit cb_pcimdda_driver_pci_remove(struct pci_dev *dev) | |
206 | { | |
207 | comedi_pci_auto_unconfig(dev); | |
208 | } | |
209 | ||
210 | static struct pci_driver cb_pcimdda_driver_pci_driver = { | |
211 | .id_table = pci_table, | |
212 | .probe = &cb_pcimdda_driver_pci_probe, | |
213 | .remove = __devexit_p(&cb_pcimdda_driver_pci_remove) | |
214 | }; | |
215 | ||
216 | static int __init cb_pcimdda_driver_init_module(void) | |
217 | { | |
218 | int retval; | |
219 | ||
220 | retval = comedi_driver_register(&cb_pcimdda_driver); | |
221 | if (retval < 0) | |
222 | return retval; | |
223 | ||
224 | cb_pcimdda_driver_pci_driver.name = | |
225 | (char *)cb_pcimdda_driver.driver_name; | |
226 | return pci_register_driver(&cb_pcimdda_driver_pci_driver); | |
227 | } | |
228 | ||
229 | static void __exit cb_pcimdda_driver_cleanup_module(void) | |
230 | { | |
231 | pci_unregister_driver(&cb_pcimdda_driver_pci_driver); | |
232 | comedi_driver_unregister(&cb_pcimdda_driver); | |
233 | } | |
234 | ||
235 | module_init(cb_pcimdda_driver_init_module); | |
236 | module_exit(cb_pcimdda_driver_cleanup_module); | |
7f62d87d | 237 | |
da91b269 | 238 | static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 239 | struct comedi_insn *insn, unsigned int *data); |
da91b269 | 240 | static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 241 | struct comedi_insn *insn, unsigned int *data); |
7f62d87d CC |
242 | |
243 | /*--------------------------------------------------------------------------- | |
244 | HELPER FUNCTION DECLARATIONS | |
245 | -----------------------------------------------------------------------------*/ | |
246 | ||
247 | /* returns a maxdata value for a given n_bits */ | |
790c5541 | 248 | static inline unsigned int figure_out_maxdata(int bits) |
7f62d87d | 249 | { |
0a85b6f0 | 250 | return ((unsigned int)1 << bits) - 1; |
7f62d87d CC |
251 | } |
252 | ||
253 | /* | |
254 | * Probes for a supported device. | |
255 | * | |
256 | * Prerequisite: private be allocated already inside dev | |
257 | * | |
258 | * If the device is found, it returns 0 and has the following side effects: | |
259 | * | |
260 | * o assigns a struct pci_dev * to dev->private->pci_dev | |
261 | * o assigns a struct board * to dev->board_ptr | |
262 | * o sets dev->private->registers | |
263 | * o sets dev->private->dio_registers | |
264 | * | |
265 | * Otherwise, returns a -errno on error | |
266 | */ | |
da91b269 | 267 | static int probe(struct comedi_device *dev, const struct comedi_devconfig *it); |
7f62d87d CC |
268 | |
269 | /*--------------------------------------------------------------------------- | |
270 | FUNCTION DEFINITIONS | |
271 | -----------------------------------------------------------------------------*/ | |
272 | ||
273 | /* | |
274 | * Attach is called by the Comedi core to configure the driver | |
275 | * for a particular board. If you specified a board_name array | |
276 | * in the driver structure, dev->board_ptr contains that | |
277 | * address. | |
278 | */ | |
da91b269 | 279 | static int attach(struct comedi_device *dev, struct comedi_devconfig *it) |
7f62d87d | 280 | { |
34c43922 | 281 | struct comedi_subdevice *s; |
7f62d87d CC |
282 | int err; |
283 | ||
284 | /* | |
285 | * Allocate the private structure area. alloc_private() is a | |
286 | * convenient macro defined in comedidev.h. | |
287 | * if this function fails (returns negative) then the private area is | |
288 | * kfree'd by comedi | |
289 | */ | |
0ff297c4 | 290 | if (alloc_private(dev, sizeof(struct board_private_struct)) < 0) |
7f62d87d CC |
291 | return -ENOMEM; |
292 | ||
293 | /* | |
294 | * If you can probe the device to determine what device in a series | |
295 | * it is, this is the place to do it. Otherwise, dev->board_ptr | |
296 | * should already be initialized. | |
297 | */ | |
c3744138 BP |
298 | err = probe(dev, it); |
299 | if (err) | |
7f62d87d CC |
300 | return err; |
301 | ||
302 | /* Output some info */ | |
303 | printk("comedi%d: %s: ", dev->minor, thisboard->name); | |
304 | ||
305 | /* | |
306 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
307 | * macro now, since we just initialized it in the last line. | |
308 | */ | |
309 | dev->board_name = thisboard->name; | |
310 | ||
311 | /* | |
312 | * Allocate the subdevice structures. alloc_subdevice() is a | |
313 | * convenient macro defined in comedidev.h. | |
314 | */ | |
315 | if (alloc_subdevices(dev, 2) < 0) | |
316 | return -ENOMEM; | |
317 | ||
318 | s = dev->subdevices + 0; | |
319 | ||
320 | /* analog output subdevice */ | |
321 | s->type = COMEDI_SUBD_AO; | |
322 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
323 | s->n_chan = thisboard->ao_chans; | |
324 | s->maxdata = figure_out_maxdata(thisboard->ao_bits); | |
325 | /* this is hard-coded here */ | |
f1c54e82 | 326 | if (it->options[2]) |
7f62d87d | 327 | s->range_table = &range_bipolar10; |
f1c54e82 | 328 | else |
7f62d87d | 329 | s->range_table = &range_bipolar5; |
7f62d87d CC |
330 | s->insn_write = &ao_winsn; |
331 | s->insn_read = &ao_rinsn; | |
332 | ||
333 | s = dev->subdevices + 1; | |
334 | /* digital i/o subdevice */ | |
335 | if (thisboard->dio_chans) { | |
336 | switch (thisboard->dio_method) { | |
337 | case DIO_8255: | |
338 | /* this is a straight 8255, so register us with the 8255 driver */ | |
339 | subdev_8255_init(dev, s, NULL, devpriv->dio_registers); | |
340 | devpriv->attached_to_8255 = 1; | |
341 | break; | |
342 | case DIO_INTERNAL: | |
343 | default: | |
344 | printk("DIO_INTERNAL not implemented yet!\n"); | |
345 | return -ENXIO; | |
346 | break; | |
347 | } | |
348 | } else { | |
349 | s->type = COMEDI_SUBD_UNUSED; | |
350 | } | |
351 | ||
352 | devpriv->attached_successfully = 1; | |
353 | ||
354 | printk("attached\n"); | |
355 | ||
356 | return 1; | |
357 | } | |
358 | ||
359 | /* | |
360 | * _detach is called to deconfigure a device. It should deallocate | |
361 | * resources. | |
362 | * This function is also called when _attach() fails, so it should be | |
363 | * careful not to release resources that were not necessarily | |
364 | * allocated by _attach(). dev->private and dev->subdevices are | |
365 | * deallocated automatically by the core. | |
366 | */ | |
da91b269 | 367 | static int detach(struct comedi_device *dev) |
7f62d87d CC |
368 | { |
369 | if (devpriv) { | |
370 | ||
371 | if (dev->subdevices && devpriv->attached_to_8255) { | |
372 | /* de-register us from the 8255 driver */ | |
373 | subdev_8255_cleanup(dev, dev->subdevices + 2); | |
374 | devpriv->attached_to_8255 = 0; | |
375 | } | |
376 | ||
377 | if (devpriv->pci_dev) { | |
f1c54e82 | 378 | if (devpriv->registers) |
7f62d87d | 379 | comedi_pci_disable(devpriv->pci_dev); |
7f62d87d CC |
380 | pci_dev_put(devpriv->pci_dev); |
381 | } | |
382 | ||
383 | if (devpriv->attached_successfully && thisboard) | |
384 | printk("comedi%d: %s: detached\n", dev->minor, | |
0a85b6f0 | 385 | thisboard->name); |
7f62d87d CC |
386 | |
387 | } | |
388 | ||
389 | return 0; | |
390 | } | |
391 | ||
da91b269 | 392 | static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 393 | struct comedi_insn *insn, unsigned int *data) |
7f62d87d CC |
394 | { |
395 | int i; | |
396 | int chan = CR_CHAN(insn->chanspec); | |
397 | unsigned long offset = devpriv->registers + chan * 2; | |
398 | ||
399 | /* Writing a list of values to an AO channel is probably not | |
400 | * very useful, but that's how the interface is defined. */ | |
401 | for (i = 0; i < insn->n; i++) { | |
402 | /* first, load the low byte */ | |
403 | outb((char)(data[i] & 0x00ff), offset); | |
404 | /* next, write the high byte -- only after this is written is | |
405 | the channel voltage updated in the DAC, unless | |
406 | we're in simultaneous xfer mode (jumper on card) | |
407 | then a rinsn is necessary to actually update the DAC -- | |
408 | see ao_rinsn() below... */ | |
409 | outb((char)(data[i] >> 8 & 0x00ff), offset + 1); | |
410 | ||
411 | /* for testing only.. the actual rinsn SHOULD do an inw! | |
412 | (see the stuff about simultaneous XFER mode on this board) */ | |
413 | devpriv->ao_readback[chan] = data[i]; | |
414 | } | |
415 | ||
416 | /* return the number of samples read/written */ | |
417 | return i; | |
418 | } | |
419 | ||
420 | /* AO subdevices should have a read insn as well as a write insn. | |
421 | ||
422 | Usually this means copying a value stored in devpriv->ao_readback. | |
423 | However, since this board has this jumper setting called "Simultaneous | |
424 | Xfer mode" (off by default), we will support it. Simultaneaous xfer | |
425 | mode is accomplished by loading ALL the values you want for AO in all the | |
426 | channels, then READing off one of the AO registers to initiate the | |
427 | instantaneous simultaneous update of all DAC outputs, which makes | |
428 | all AO channels update simultaneously. This is useful for some control | |
429 | applications, I would imagine. | |
430 | */ | |
da91b269 | 431 | static int ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s, |
0a85b6f0 | 432 | struct comedi_insn *insn, unsigned int *data) |
7f62d87d CC |
433 | { |
434 | int i; | |
435 | int chan = CR_CHAN(insn->chanspec); | |
436 | ||
437 | for (i = 0; i < insn->n; i++) { | |
438 | inw(devpriv->registers + chan * 2); | |
439 | /* should I set data[i] to the result of the actual read on the register | |
790c5541 | 440 | or the cached unsigned int in devpriv->ao_readback[]? */ |
7f62d87d CC |
441 | data[i] = devpriv->ao_readback[chan]; |
442 | } | |
443 | ||
444 | return i; | |
445 | } | |
446 | ||
447 | /*--------------------------------------------------------------------------- | |
448 | HELPER FUNCTION DEFINITIONS | |
449 | -----------------------------------------------------------------------------*/ | |
450 | ||
451 | /* | |
452 | * Probes for a supported device. | |
453 | * | |
454 | * Prerequisite: private be allocated already inside dev | |
455 | * | |
456 | * If the device is found, it returns 0 and has the following side effects: | |
457 | * | |
458 | * o assigns a struct pci_dev * to dev->private->pci_dev | |
459 | * o assigns a struct board * to dev->board_ptr | |
460 | * o sets dev->private->registers | |
461 | * o sets dev->private->dio_registers | |
462 | * | |
463 | * Otherwise, returns a -errno on error | |
464 | */ | |
da91b269 | 465 | static int probe(struct comedi_device *dev, const struct comedi_devconfig *it) |
7f62d87d | 466 | { |
20fb2280 | 467 | struct pci_dev *pcidev = NULL; |
7f62d87d CC |
468 | int index; |
469 | unsigned long registers; | |
470 | ||
20fb2280 | 471 | for_each_pci_dev(pcidev) { |
2696fb57 | 472 | /* is it not a computer boards card? */ |
7f62d87d CC |
473 | if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS) |
474 | continue; | |
2696fb57 | 475 | /* loop through cards supported by this driver */ |
8629efa4 | 476 | for (index = 0; index < ARRAY_SIZE(boards); index++) { |
7f62d87d CC |
477 | if (boards[index].device_id != pcidev->device) |
478 | continue; | |
2696fb57 | 479 | /* was a particular bus/slot requested? */ |
7f62d87d | 480 | if (it->options[0] || it->options[1]) { |
2696fb57 | 481 | /* are we on the wrong bus/slot? */ |
7f62d87d | 482 | if (pcidev->bus->number != it->options[0] || |
0a85b6f0 | 483 | PCI_SLOT(pcidev->devfn) != it->options[1]) { |
7f62d87d CC |
484 | continue; |
485 | } | |
486 | } | |
487 | /* found ! */ | |
488 | ||
489 | devpriv->pci_dev = pcidev; | |
490 | dev->board_ptr = boards + index; | |
491 | if (comedi_pci_enable(pcidev, thisboard->name)) { | |
0a85b6f0 MT |
492 | printk |
493 | ("cb_pcimdda: Failed to enable PCI device and request regions\n"); | |
7f62d87d CC |
494 | return -EIO; |
495 | } | |
496 | registers = | |
0a85b6f0 MT |
497 | pci_resource_start(devpriv->pci_dev, |
498 | REGS_BADRINDEX); | |
7f62d87d CC |
499 | devpriv->registers = registers; |
500 | devpriv->dio_registers | |
0a85b6f0 | 501 | = devpriv->registers + thisboard->dio_offset; |
7f62d87d CC |
502 | return 0; |
503 | } | |
504 | } | |
505 | ||
506 | printk("cb_pcimdda: No supported ComputerBoards/MeasurementComputing " | |
0a85b6f0 | 507 | "card found at the requested position\n"); |
7f62d87d CC |
508 | return -ENODEV; |
509 | } |