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 */ | |
94 | #define PCI_ID_PCIM_DDA06_16 0x0053 | |
95 | ||
96 | /* | |
97 | * This is straight from skel.c -- I did this in case this source file | |
98 | * will someday support more than 1 board... | |
99 | */ | |
100 | typedef struct board_struct { | |
101 | const char *name; | |
102 | unsigned short device_id; | |
103 | int ao_chans; | |
104 | int ao_bits; | |
105 | int dio_chans; | |
106 | int dio_method; | |
107 | int dio_offset; /* how many bytes into the BADR are the DIO ports */ | |
108 | int regs_badrindex; /* IO Region for the control, analog output, | |
109 | and DIO registers */ | |
110 | int reg_sz; /* number of bytes of registers in io region */ | |
111 | } board; | |
112 | ||
113 | enum DIO_METHODS { | |
114 | DIO_NONE = 0, | |
115 | DIO_8255, | |
116 | DIO_INTERNAL /* unimplemented */ | |
117 | }; | |
118 | ||
119 | static const board boards[] = { | |
120 | { | |
121 | name: "cb_pcimdda06-16", | |
122 | device_id:PCI_ID_PCIM_DDA06_16, | |
123 | ao_chans:6, | |
124 | ao_bits: 16, | |
125 | dio_chans:24, | |
126 | dio_method:DIO_8255, | |
127 | dio_offset:12, | |
128 | regs_badrindex:3, | |
129 | reg_sz: 16, | |
130 | } | |
131 | }; | |
132 | ||
133 | /* | |
134 | * Useful for shorthand access to the particular board structure | |
135 | */ | |
136 | #define thisboard ((const board *)dev->board_ptr) | |
137 | ||
138 | /* Number of boards in boards[] */ | |
139 | #define N_BOARDS (sizeof(boards) / sizeof(board)) | |
140 | #define REG_SZ (thisboard->reg_sz) | |
141 | #define REGS_BADRINDEX (thisboard->regs_badrindex) | |
142 | ||
143 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
144 | * only be used for PCI and ISA-PnP devices */ | |
145 | /* Please add your PCI vendor ID to comedidev.h, and it will be forwarded | |
146 | * upstream. */ | |
147 | static DEFINE_PCI_DEVICE_TABLE(pci_table) = { | |
148 | {PCI_VENDOR_ID_COMPUTERBOARDS, PCI_ID_PCIM_DDA06_16, PCI_ANY_ID, | |
149 | PCI_ANY_ID, 0, 0, 0}, | |
150 | {0} | |
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. */ |
7f62d87d CC |
158 | typedef struct { |
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 CC |
169 | |
170 | } private; | |
171 | ||
172 | /* | |
173 | * most drivers define the following macro to make it easy to | |
174 | * access the private structure. | |
175 | */ | |
176 | #define devpriv ((private *)dev->private) | |
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 | */ | |
71b5f4f1 BP |
184 | static int attach(struct comedi_device * dev, comedi_devconfig * it); |
185 | static int detach(struct comedi_device * dev); | |
139dfbdf | 186 | static struct comedi_driver cb_pcimdda_driver = { |
7f62d87d CC |
187 | driver_name:"cb_pcimdda", |
188 | module:THIS_MODULE, | |
189 | attach:attach, | |
190 | detach:detach, | |
191 | }; | |
192 | ||
193 | MODULE_AUTHOR("Calin A. Culianu <calin@rtlab.org>"); | |
194 | MODULE_DESCRIPTION("Comedi low-level driver for the Computerboards PCIM-DDA " | |
195 | "series. Currently only supports PCIM-DDA06-16 (which " | |
196 | "also happens to be the only board in this series. :) ) "); | |
197 | MODULE_LICENSE("GPL"); | |
198 | COMEDI_PCI_INITCLEANUP_NOMODULE(cb_pcimdda_driver, pci_table); | |
199 | ||
34c43922 | 200 | static int ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 201 | struct comedi_insn * insn, unsigned int * data); |
34c43922 | 202 | static int ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 203 | struct comedi_insn * insn, unsigned int * data); |
7f62d87d CC |
204 | |
205 | /*--------------------------------------------------------------------------- | |
206 | HELPER FUNCTION DECLARATIONS | |
207 | -----------------------------------------------------------------------------*/ | |
208 | ||
209 | /* returns a maxdata value for a given n_bits */ | |
790c5541 | 210 | static inline unsigned int figure_out_maxdata(int bits) |
7f62d87d | 211 | { |
790c5541 | 212 | return (((unsigned int) 1 << bits) - 1); |
7f62d87d CC |
213 | } |
214 | ||
215 | /* | |
216 | * Probes for a supported device. | |
217 | * | |
218 | * Prerequisite: private be allocated already inside dev | |
219 | * | |
220 | * If the device is found, it returns 0 and has the following side effects: | |
221 | * | |
222 | * o assigns a struct pci_dev * to dev->private->pci_dev | |
223 | * o assigns a struct board * to dev->board_ptr | |
224 | * o sets dev->private->registers | |
225 | * o sets dev->private->dio_registers | |
226 | * | |
227 | * Otherwise, returns a -errno on error | |
228 | */ | |
71b5f4f1 | 229 | static int probe(struct comedi_device * dev, const comedi_devconfig * it); |
7f62d87d CC |
230 | |
231 | /*--------------------------------------------------------------------------- | |
232 | FUNCTION DEFINITIONS | |
233 | -----------------------------------------------------------------------------*/ | |
234 | ||
235 | /* | |
236 | * Attach is called by the Comedi core to configure the driver | |
237 | * for a particular board. If you specified a board_name array | |
238 | * in the driver structure, dev->board_ptr contains that | |
239 | * address. | |
240 | */ | |
71b5f4f1 | 241 | static int attach(struct comedi_device * dev, comedi_devconfig * it) |
7f62d87d | 242 | { |
34c43922 | 243 | struct comedi_subdevice *s; |
7f62d87d CC |
244 | int err; |
245 | ||
246 | /* | |
247 | * Allocate the private structure area. alloc_private() is a | |
248 | * convenient macro defined in comedidev.h. | |
249 | * if this function fails (returns negative) then the private area is | |
250 | * kfree'd by comedi | |
251 | */ | |
252 | if (alloc_private(dev, sizeof(private)) < 0) | |
253 | return -ENOMEM; | |
254 | ||
255 | /* | |
256 | * If you can probe the device to determine what device in a series | |
257 | * it is, this is the place to do it. Otherwise, dev->board_ptr | |
258 | * should already be initialized. | |
259 | */ | |
260 | if ((err = probe(dev, it))) | |
261 | return err; | |
262 | ||
263 | /* Output some info */ | |
264 | printk("comedi%d: %s: ", dev->minor, thisboard->name); | |
265 | ||
266 | /* | |
267 | * Initialize dev->board_name. Note that we can use the "thisboard" | |
268 | * macro now, since we just initialized it in the last line. | |
269 | */ | |
270 | dev->board_name = thisboard->name; | |
271 | ||
272 | /* | |
273 | * Allocate the subdevice structures. alloc_subdevice() is a | |
274 | * convenient macro defined in comedidev.h. | |
275 | */ | |
276 | if (alloc_subdevices(dev, 2) < 0) | |
277 | return -ENOMEM; | |
278 | ||
279 | s = dev->subdevices + 0; | |
280 | ||
281 | /* analog output subdevice */ | |
282 | s->type = COMEDI_SUBD_AO; | |
283 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; | |
284 | s->n_chan = thisboard->ao_chans; | |
285 | s->maxdata = figure_out_maxdata(thisboard->ao_bits); | |
286 | /* this is hard-coded here */ | |
287 | if (it->options[2]) { | |
288 | s->range_table = &range_bipolar10; | |
289 | } else { | |
290 | s->range_table = &range_bipolar5; | |
291 | } | |
292 | s->insn_write = &ao_winsn; | |
293 | s->insn_read = &ao_rinsn; | |
294 | ||
295 | s = dev->subdevices + 1; | |
296 | /* digital i/o subdevice */ | |
297 | if (thisboard->dio_chans) { | |
298 | switch (thisboard->dio_method) { | |
299 | case DIO_8255: | |
300 | /* this is a straight 8255, so register us with the 8255 driver */ | |
301 | subdev_8255_init(dev, s, NULL, devpriv->dio_registers); | |
302 | devpriv->attached_to_8255 = 1; | |
303 | break; | |
304 | case DIO_INTERNAL: | |
305 | default: | |
306 | printk("DIO_INTERNAL not implemented yet!\n"); | |
307 | return -ENXIO; | |
308 | break; | |
309 | } | |
310 | } else { | |
311 | s->type = COMEDI_SUBD_UNUSED; | |
312 | } | |
313 | ||
314 | devpriv->attached_successfully = 1; | |
315 | ||
316 | printk("attached\n"); | |
317 | ||
318 | return 1; | |
319 | } | |
320 | ||
321 | /* | |
322 | * _detach is called to deconfigure a device. It should deallocate | |
323 | * resources. | |
324 | * This function is also called when _attach() fails, so it should be | |
325 | * careful not to release resources that were not necessarily | |
326 | * allocated by _attach(). dev->private and dev->subdevices are | |
327 | * deallocated automatically by the core. | |
328 | */ | |
71b5f4f1 | 329 | static int detach(struct comedi_device * dev) |
7f62d87d CC |
330 | { |
331 | if (devpriv) { | |
332 | ||
333 | if (dev->subdevices && devpriv->attached_to_8255) { | |
334 | /* de-register us from the 8255 driver */ | |
335 | subdev_8255_cleanup(dev, dev->subdevices + 2); | |
336 | devpriv->attached_to_8255 = 0; | |
337 | } | |
338 | ||
339 | if (devpriv->pci_dev) { | |
340 | if (devpriv->registers) { | |
341 | comedi_pci_disable(devpriv->pci_dev); | |
342 | } | |
343 | pci_dev_put(devpriv->pci_dev); | |
344 | } | |
345 | ||
346 | if (devpriv->attached_successfully && thisboard) | |
347 | printk("comedi%d: %s: detached\n", dev->minor, | |
348 | thisboard->name); | |
349 | ||
350 | } | |
351 | ||
352 | return 0; | |
353 | } | |
354 | ||
34c43922 | 355 | static int ao_winsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 356 | struct comedi_insn * insn, unsigned int * data) |
7f62d87d CC |
357 | { |
358 | int i; | |
359 | int chan = CR_CHAN(insn->chanspec); | |
360 | unsigned long offset = devpriv->registers + chan * 2; | |
361 | ||
362 | /* Writing a list of values to an AO channel is probably not | |
363 | * very useful, but that's how the interface is defined. */ | |
364 | for (i = 0; i < insn->n; i++) { | |
365 | /* first, load the low byte */ | |
366 | outb((char)(data[i] & 0x00ff), offset); | |
367 | /* next, write the high byte -- only after this is written is | |
368 | the channel voltage updated in the DAC, unless | |
369 | we're in simultaneous xfer mode (jumper on card) | |
370 | then a rinsn is necessary to actually update the DAC -- | |
371 | see ao_rinsn() below... */ | |
372 | outb((char)(data[i] >> 8 & 0x00ff), offset + 1); | |
373 | ||
374 | /* for testing only.. the actual rinsn SHOULD do an inw! | |
375 | (see the stuff about simultaneous XFER mode on this board) */ | |
376 | devpriv->ao_readback[chan] = data[i]; | |
377 | } | |
378 | ||
379 | /* return the number of samples read/written */ | |
380 | return i; | |
381 | } | |
382 | ||
383 | /* AO subdevices should have a read insn as well as a write insn. | |
384 | ||
385 | Usually this means copying a value stored in devpriv->ao_readback. | |
386 | However, since this board has this jumper setting called "Simultaneous | |
387 | Xfer mode" (off by default), we will support it. Simultaneaous xfer | |
388 | mode is accomplished by loading ALL the values you want for AO in all the | |
389 | channels, then READing off one of the AO registers to initiate the | |
390 | instantaneous simultaneous update of all DAC outputs, which makes | |
391 | all AO channels update simultaneously. This is useful for some control | |
392 | applications, I would imagine. | |
393 | */ | |
34c43922 | 394 | static int ao_rinsn(struct comedi_device * dev, struct comedi_subdevice * s, |
90035c08 | 395 | struct comedi_insn * insn, unsigned int * data) |
7f62d87d CC |
396 | { |
397 | int i; | |
398 | int chan = CR_CHAN(insn->chanspec); | |
399 | ||
400 | for (i = 0; i < insn->n; i++) { | |
401 | inw(devpriv->registers + chan * 2); | |
402 | /* should I set data[i] to the result of the actual read on the register | |
790c5541 | 403 | or the cached unsigned int in devpriv->ao_readback[]? */ |
7f62d87d CC |
404 | data[i] = devpriv->ao_readback[chan]; |
405 | } | |
406 | ||
407 | return i; | |
408 | } | |
409 | ||
410 | /*--------------------------------------------------------------------------- | |
411 | HELPER FUNCTION DEFINITIONS | |
412 | -----------------------------------------------------------------------------*/ | |
413 | ||
414 | /* | |
415 | * Probes for a supported device. | |
416 | * | |
417 | * Prerequisite: private be allocated already inside dev | |
418 | * | |
419 | * If the device is found, it returns 0 and has the following side effects: | |
420 | * | |
421 | * o assigns a struct pci_dev * to dev->private->pci_dev | |
422 | * o assigns a struct board * to dev->board_ptr | |
423 | * o sets dev->private->registers | |
424 | * o sets dev->private->dio_registers | |
425 | * | |
426 | * Otherwise, returns a -errno on error | |
427 | */ | |
71b5f4f1 | 428 | static int probe(struct comedi_device * dev, const comedi_devconfig * it) |
7f62d87d CC |
429 | { |
430 | struct pci_dev *pcidev; | |
431 | int index; | |
432 | unsigned long registers; | |
433 | ||
434 | for (pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); | |
435 | pcidev != NULL; | |
436 | pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pcidev)) { | |
437 | // is it not a computer boards card? | |
438 | if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS) | |
439 | continue; | |
440 | // loop through cards supported by this driver | |
441 | for (index = 0; index < N_BOARDS; index++) { | |
442 | if (boards[index].device_id != pcidev->device) | |
443 | continue; | |
444 | // was a particular bus/slot requested? | |
445 | if (it->options[0] || it->options[1]) { | |
446 | // are we on the wrong bus/slot? | |
447 | if (pcidev->bus->number != it->options[0] || | |
448 | PCI_SLOT(pcidev->devfn) != | |
449 | it->options[1]) { | |
450 | continue; | |
451 | } | |
452 | } | |
453 | /* found ! */ | |
454 | ||
455 | devpriv->pci_dev = pcidev; | |
456 | dev->board_ptr = boards + index; | |
457 | if (comedi_pci_enable(pcidev, thisboard->name)) { | |
458 | printk("cb_pcimdda: Failed to enable PCI device and request regions\n"); | |
459 | return -EIO; | |
460 | } | |
461 | registers = | |
462 | pci_resource_start(devpriv->pci_dev, | |
463 | REGS_BADRINDEX); | |
464 | devpriv->registers = registers; | |
465 | devpriv->dio_registers | |
466 | = devpriv->registers + thisboard->dio_offset; | |
467 | return 0; | |
468 | } | |
469 | } | |
470 | ||
471 | printk("cb_pcimdda: No supported ComputerBoards/MeasurementComputing " | |
472 | "card found at the requested position\n"); | |
473 | return -ENODEV; | |
474 | } |