Commit | Line | Data |
---|---|---|
f4bd8abb RB |
1 | /* |
2 | comedi/drivers/cb_pcimdas.c | |
3 | Comedi driver for Computer Boards PCIM-DAS1602/16 | |
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: cb_pcimdas | |
25 | Description: Measurement Computing PCI Migration series boards | |
26 | Devices: [ComputerBoards] PCIM-DAS1602/16 (cb_pcimdas) | |
27 | Author: Richard Bytheway | |
28 | Updated: Wed, 13 Nov 2002 12:34:56 +0000 | |
29 | Status: experimental | |
30 | ||
31 | Written to support the PCIM-DAS1602/16 on a 2.4 series kernel. | |
32 | ||
33 | Configuration Options: | |
34 | [0] - PCI bus number | |
35 | [1] - PCI slot number | |
36 | ||
37 | Developed from cb_pcidas and skel by Richard Bytheway (mocelet@sucs.org). | |
38 | Only supports DIO, AO and simple AI in it's present form. | |
39 | No interrupts, multi channel or FIFO AI, although the card looks like it could support this. | |
40 | See http://www.measurementcomputing.com/PDFManuals/pcim-das1602_16.pdf for more details. | |
41 | */ | |
42 | ||
43 | #include "../comedidev.h" | |
44 | ||
45 | #include <linux/delay.h> | |
25436dc9 | 46 | #include <linux/interrupt.h> |
f4bd8abb RB |
47 | |
48 | #include "comedi_pci.h" | |
49 | #include "plx9052.h" | |
50 | #include "8255.h" | |
51 | ||
6eef3af5 | 52 | /* #define CBPCIMDAS_DEBUG */ |
f4bd8abb RB |
53 | #undef CBPCIMDAS_DEBUG |
54 | ||
558587e2 GKH |
55 | #define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 |
56 | ||
f4bd8abb RB |
57 | /* Registers for the PCIM-DAS1602/16 */ |
58 | ||
6eef3af5 BP |
59 | /* sizes of io regions (bytes) */ |
60 | #define BADR0_SIZE 2 /* ?? */ | |
f4bd8abb RB |
61 | #define BADR1_SIZE 4 |
62 | #define BADR2_SIZE 6 | |
63 | #define BADR3_SIZE 16 | |
64 | #define BADR4_SIZE 4 | |
65 | ||
6eef3af5 | 66 | /* DAC Offsets */ |
f4bd8abb RB |
67 | #define ADC_TRIG 0 |
68 | #define DAC0_OFFSET 2 | |
69 | #define DAC1_OFFSET 4 | |
70 | ||
6eef3af5 | 71 | /* AI and Counter Constants */ |
f4bd8abb RB |
72 | #define MUX_LIMITS 0 |
73 | #define MAIN_CONN_DIO 1 | |
74 | #define ADC_STAT 2 | |
75 | #define ADC_CONV_STAT 3 | |
76 | #define ADC_INT 4 | |
77 | #define ADC_PACER 5 | |
78 | #define BURST_MODE 6 | |
79 | #define PROG_GAIN 7 | |
80 | #define CLK8254_1_DATA 8 | |
81 | #define CLK8254_2_DATA 9 | |
82 | #define CLK8254_3_DATA 10 | |
83 | #define CLK8254_CONTROL 11 | |
84 | #define USER_COUNTER 12 | |
85 | #define RESID_COUNT_H 13 | |
86 | #define RESID_COUNT_L 14 | |
87 | ||
88 | /* Board description */ | |
cfee7b99 | 89 | struct cb_pcimdas_board { |
f4bd8abb RB |
90 | const char *name; |
91 | unsigned short device_id; | |
6eef3af5 BP |
92 | int ai_se_chans; /* Inputs in single-ended mode */ |
93 | int ai_diff_chans; /* Inputs in differential mode */ | |
94 | int ai_bits; /* analog input resolution */ | |
95 | int ai_speed; /* fastest conversion period in ns */ | |
96 | int ao_nchan; /* number of analog out channels */ | |
97 | int ao_bits; /* analogue output resolution */ | |
98 | int has_ao_fifo; /* analog output has fifo */ | |
99 | int ao_scan_speed; /* analog output speed for 1602 series (for a scan, not conversion) */ | |
100 | int fifo_size; /* number of samples fifo can hold */ | |
101 | int dio_bits; /* number of dio bits */ | |
102 | int has_dio; /* has DIO */ | |
9ced1de6 | 103 | const struct comedi_lrange *ranges; |
cfee7b99 | 104 | }; |
f4bd8abb | 105 | |
cfee7b99 | 106 | static const struct cb_pcimdas_board cb_pcimdas_boards[] = { |
f4bd8abb | 107 | { |
0a85b6f0 MT |
108 | .name = "PCIM-DAS1602/16", |
109 | .device_id = 0x56, | |
110 | .ai_se_chans = 16, | |
111 | .ai_diff_chans = 8, | |
112 | .ai_bits = 16, | |
113 | .ai_speed = 10000, /* ?? */ | |
114 | .ao_nchan = 2, | |
115 | .ao_bits = 12, | |
116 | .has_ao_fifo = 0, /* ?? */ | |
117 | .ao_scan_speed = 10000, | |
118 | /* ?? */ | |
119 | .fifo_size = 1024, | |
120 | .dio_bits = 24, | |
121 | .has_dio = 1, | |
68c3dbff | 122 | /* .ranges = &cb_pcimdas_ranges, */ |
0a85b6f0 | 123 | }, |
f4bd8abb RB |
124 | }; |
125 | ||
126 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
127 | * only be used for PCI and ISA-PnP devices */ | |
128 | static DEFINE_PCI_DEVICE_TABLE(cb_pcimdas_pci_table) = { | |
0a85b6f0 MT |
129 | { |
130 | PCI_VENDOR_ID_COMPUTERBOARDS, 0x0056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, | |
131 | { | |
132 | 0} | |
f4bd8abb RB |
133 | }; |
134 | ||
135 | MODULE_DEVICE_TABLE(pci, cb_pcimdas_pci_table); | |
136 | ||
6eef3af5 | 137 | #define N_BOARDS 1 /* Max number of boards supported */ |
f4bd8abb RB |
138 | |
139 | /* | |
140 | * Useful for shorthand access to the particular board structure | |
141 | */ | |
cfee7b99 | 142 | #define thisboard ((const struct cb_pcimdas_board *)dev->board_ptr) |
f4bd8abb RB |
143 | |
144 | /* this structure is for data unique to this hardware driver. If | |
145 | several hardware drivers keep similar information in this structure, | |
71b5f4f1 | 146 | feel free to suggest moving the variable to the struct comedi_device struct. */ |
b90c76fd | 147 | struct cb_pcimdas_private { |
f4bd8abb RB |
148 | int data; |
149 | ||
6eef3af5 | 150 | /* would be useful for a PCI device */ |
f4bd8abb RB |
151 | struct pci_dev *pci_dev; |
152 | ||
6eef3af5 | 153 | /* base addresses */ |
f4bd8abb RB |
154 | unsigned long BADR0; |
155 | unsigned long BADR1; | |
156 | unsigned long BADR2; | |
157 | unsigned long BADR3; | |
158 | unsigned long BADR4; | |
159 | ||
160 | /* Used for AO readback */ | |
790c5541 | 161 | unsigned int ao_readback[2]; |
f4bd8abb | 162 | |
6eef3af5 BP |
163 | /* Used for DIO */ |
164 | unsigned short int port_a; /* copy of BADR4+0 */ | |
165 | unsigned short int port_b; /* copy of BADR4+1 */ | |
166 | unsigned short int port_c; /* copy of BADR4+2 */ | |
167 | unsigned short int dio_mode; /* copy of BADR4+3 */ | |
f4bd8abb | 168 | |
b90c76fd | 169 | }; |
f4bd8abb RB |
170 | |
171 | /* | |
172 | * most drivers define the following macro to make it easy to | |
173 | * access the private structure. | |
174 | */ | |
b90c76fd | 175 | #define devpriv ((struct cb_pcimdas_private *)dev->private) |
f4bd8abb RB |
176 | |
177 | /* | |
139dfbdf | 178 | * The struct comedi_driver structure tells the Comedi core module |
f4bd8abb RB |
179 | * which functions to call to configure/deconfigure (attach/detach) |
180 | * the board, and also about the kernel module that contains | |
181 | * the device code. | |
182 | */ | |
0a85b6f0 MT |
183 | static int cb_pcimdas_attach(struct comedi_device *dev, |
184 | struct comedi_devconfig *it); | |
da91b269 | 185 | static int cb_pcimdas_detach(struct comedi_device *dev); |
139dfbdf | 186 | static struct comedi_driver driver_cb_pcimdas = { |
68c3dbff BP |
187 | .driver_name = "cb_pcimdas", |
188 | .module = THIS_MODULE, | |
189 | .attach = cb_pcimdas_attach, | |
190 | .detach = cb_pcimdas_detach, | |
f4bd8abb RB |
191 | }; |
192 | ||
0a85b6f0 MT |
193 | static int cb_pcimdas_ai_rinsn(struct comedi_device *dev, |
194 | struct comedi_subdevice *s, | |
195 | struct comedi_insn *insn, unsigned int *data); | |
196 | static int cb_pcimdas_ao_winsn(struct comedi_device *dev, | |
197 | struct comedi_subdevice *s, | |
198 | struct comedi_insn *insn, unsigned int *data); | |
199 | static int cb_pcimdas_ao_rinsn(struct comedi_device *dev, | |
200 | struct comedi_subdevice *s, | |
201 | struct comedi_insn *insn, unsigned int *data); | |
f4bd8abb RB |
202 | |
203 | /* | |
204 | * Attach is called by the Comedi core to configure the driver | |
205 | * for a particular board. If you specified a board_name array | |
206 | * in the driver structure, dev->board_ptr contains that | |
207 | * address. | |
208 | */ | |
0a85b6f0 MT |
209 | static int cb_pcimdas_attach(struct comedi_device *dev, |
210 | struct comedi_devconfig *it) | |
f4bd8abb | 211 | { |
34c43922 | 212 | struct comedi_subdevice *s; |
20fb2280 | 213 | struct pci_dev *pcidev = NULL; |
f4bd8abb | 214 | int index; |
6eef3af5 | 215 | /* int i; */ |
f4bd8abb RB |
216 | |
217 | printk("comedi%d: cb_pcimdas: ", dev->minor); | |
218 | ||
219 | /* | |
220 | * Allocate the private structure area. | |
221 | */ | |
b90c76fd | 222 | if (alloc_private(dev, sizeof(struct cb_pcimdas_private)) < 0) |
f4bd8abb RB |
223 | return -ENOMEM; |
224 | ||
225 | /* | |
226 | * Probe the device to determine what device in the series it is. | |
227 | */ | |
228 | printk("\n"); | |
229 | ||
20fb2280 | 230 | for_each_pci_dev(pcidev) { |
6eef3af5 | 231 | /* is it not a computer boards card? */ |
f4bd8abb RB |
232 | if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS) |
233 | continue; | |
6eef3af5 | 234 | /* loop through cards supported by this driver */ |
f4bd8abb RB |
235 | for (index = 0; index < N_BOARDS; index++) { |
236 | if (cb_pcimdas_boards[index].device_id != | |
0a85b6f0 | 237 | pcidev->device) |
f4bd8abb | 238 | continue; |
6eef3af5 | 239 | /* was a particular bus/slot requested? */ |
f4bd8abb | 240 | if (it->options[0] || it->options[1]) { |
6eef3af5 | 241 | /* are we on the wrong bus/slot? */ |
f4bd8abb | 242 | if (pcidev->bus->number != it->options[0] || |
0a85b6f0 | 243 | PCI_SLOT(pcidev->devfn) != it->options[1]) { |
f4bd8abb RB |
244 | continue; |
245 | } | |
246 | } | |
247 | devpriv->pci_dev = pcidev; | |
248 | dev->board_ptr = cb_pcimdas_boards + index; | |
249 | goto found; | |
250 | } | |
251 | } | |
252 | ||
253 | printk("No supported ComputerBoards/MeasurementComputing card found on " | |
0a85b6f0 | 254 | "requested position\n"); |
f4bd8abb RB |
255 | return -EIO; |
256 | ||
0a85b6f0 | 257 | found: |
f4bd8abb RB |
258 | |
259 | printk("Found %s on bus %i, slot %i\n", cb_pcimdas_boards[index].name, | |
0a85b6f0 | 260 | pcidev->bus->number, PCI_SLOT(pcidev->devfn)); |
f4bd8abb | 261 | |
6eef3af5 | 262 | /* Warn about non-tested features */ |
f4bd8abb RB |
263 | switch (thisboard->device_id) { |
264 | case 0x56: | |
265 | break; | |
266 | default: | |
267 | printk("THIS CARD IS UNSUPPORTED.\n" | |
0a85b6f0 | 268 | "PLEASE REPORT USAGE TO <mocelet@sucs.org>\n"); |
f4bd8abb RB |
269 | }; |
270 | ||
271 | if (comedi_pci_enable(pcidev, "cb_pcimdas")) { | |
272 | printk(" Failed to enable PCI device and request regions\n"); | |
273 | return -EIO; | |
274 | } | |
275 | ||
276 | devpriv->BADR0 = pci_resource_start(devpriv->pci_dev, 0); | |
277 | devpriv->BADR1 = pci_resource_start(devpriv->pci_dev, 1); | |
278 | devpriv->BADR2 = pci_resource_start(devpriv->pci_dev, 2); | |
279 | devpriv->BADR3 = pci_resource_start(devpriv->pci_dev, 3); | |
280 | devpriv->BADR4 = pci_resource_start(devpriv->pci_dev, 4); | |
281 | ||
282 | #ifdef CBPCIMDAS_DEBUG | |
283 | printk("devpriv->BADR0 = 0x%lx\n", devpriv->BADR0); | |
284 | printk("devpriv->BADR1 = 0x%lx\n", devpriv->BADR1); | |
285 | printk("devpriv->BADR2 = 0x%lx\n", devpriv->BADR2); | |
286 | printk("devpriv->BADR3 = 0x%lx\n", devpriv->BADR3); | |
287 | printk("devpriv->BADR4 = 0x%lx\n", devpriv->BADR4); | |
288 | #endif | |
289 | ||
6eef3af5 BP |
290 | /* Dont support IRQ yet */ |
291 | /* get irq */ | |
5f74ea14 | 292 | /* if(request_irq(devpriv->pci_dev->irq, cb_pcimdas_interrupt, IRQF_SHARED, "cb_pcimdas", dev )) */ |
6eef3af5 BP |
293 | /* { */ |
294 | /* printk(" unable to allocate irq %u\n", devpriv->pci_dev->irq); */ | |
295 | /* return -EINVAL; */ | |
296 | /* } */ | |
297 | /* dev->irq = devpriv->pci_dev->irq; */ | |
f4bd8abb | 298 | |
6eef3af5 | 299 | /* Initialize dev->board_name */ |
f4bd8abb RB |
300 | dev->board_name = thisboard->name; |
301 | ||
302 | /* | |
303 | * Allocate the subdevice structures. alloc_subdevice() is a | |
304 | * convenient macro defined in comedidev.h. | |
305 | */ | |
306 | if (alloc_subdevices(dev, 3) < 0) | |
307 | return -ENOMEM; | |
308 | ||
309 | s = dev->subdevices + 0; | |
6eef3af5 BP |
310 | /* dev->read_subdev=s; */ |
311 | /* analog input subdevice */ | |
f4bd8abb RB |
312 | s->type = COMEDI_SUBD_AI; |
313 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
314 | s->n_chan = thisboard->ai_se_chans; | |
315 | s->maxdata = (1 << thisboard->ai_bits) - 1; | |
316 | s->range_table = &range_unknown; | |
6eef3af5 BP |
317 | s->len_chanlist = 1; /* This is the maximum chanlist length that */ |
318 | /* the board can handle */ | |
f4bd8abb RB |
319 | s->insn_read = cb_pcimdas_ai_rinsn; |
320 | ||
321 | s = dev->subdevices + 1; | |
6eef3af5 | 322 | /* analog output subdevice */ |
f4bd8abb RB |
323 | s->type = COMEDI_SUBD_AO; |
324 | s->subdev_flags = SDF_WRITABLE; | |
325 | s->n_chan = thisboard->ao_nchan; | |
326 | s->maxdata = 1 << thisboard->ao_bits; | |
6eef3af5 | 327 | s->range_table = &range_unknown; /* ranges are hardware settable, but not software readable. */ |
f4bd8abb RB |
328 | s->insn_write = &cb_pcimdas_ao_winsn; |
329 | s->insn_read = &cb_pcimdas_ao_rinsn; | |
330 | ||
331 | s = dev->subdevices + 2; | |
332 | /* digital i/o subdevice */ | |
7c2f1946 | 333 | if (thisboard->has_dio) |
f4bd8abb | 334 | subdev_8255_init(dev, s, NULL, devpriv->BADR4); |
7c2f1946 | 335 | else |
f4bd8abb | 336 | s->type = COMEDI_SUBD_UNUSED; |
f4bd8abb RB |
337 | |
338 | printk("attached\n"); | |
339 | ||
340 | return 1; | |
341 | } | |
342 | ||
343 | /* | |
344 | * _detach is called to deconfigure a device. It should deallocate | |
345 | * resources. | |
346 | * This function is also called when _attach() fails, so it should be | |
347 | * careful not to release resources that were not necessarily | |
348 | * allocated by _attach(). dev->private and dev->subdevices are | |
349 | * deallocated automatically by the core. | |
350 | */ | |
da91b269 | 351 | static int cb_pcimdas_detach(struct comedi_device *dev) |
f4bd8abb RB |
352 | { |
353 | #ifdef CBPCIMDAS_DEBUG | |
354 | if (devpriv) { | |
355 | printk("devpriv->BADR0 = 0x%lx\n", devpriv->BADR0); | |
356 | printk("devpriv->BADR1 = 0x%lx\n", devpriv->BADR1); | |
357 | printk("devpriv->BADR2 = 0x%lx\n", devpriv->BADR2); | |
358 | printk("devpriv->BADR3 = 0x%lx\n", devpriv->BADR3); | |
359 | printk("devpriv->BADR4 = 0x%lx\n", devpriv->BADR4); | |
360 | } | |
361 | #endif | |
362 | printk("comedi%d: cb_pcimdas: remove\n", dev->minor); | |
363 | if (dev->irq) | |
5f74ea14 | 364 | free_irq(dev->irq, dev); |
f4bd8abb RB |
365 | if (devpriv) { |
366 | if (devpriv->pci_dev) { | |
7c2f1946 | 367 | if (devpriv->BADR0) |
f4bd8abb | 368 | comedi_pci_disable(devpriv->pci_dev); |
f4bd8abb RB |
369 | pci_dev_put(devpriv->pci_dev); |
370 | } | |
371 | } | |
372 | ||
373 | return 0; | |
374 | } | |
375 | ||
376 | /* | |
377 | * "instructions" read/write data in "one-shot" or "software-triggered" | |
378 | * mode. | |
379 | */ | |
0a85b6f0 MT |
380 | static int cb_pcimdas_ai_rinsn(struct comedi_device *dev, |
381 | struct comedi_subdevice *s, | |
382 | struct comedi_insn *insn, unsigned int *data) | |
f4bd8abb RB |
383 | { |
384 | int n, i; | |
385 | unsigned int d; | |
386 | unsigned int busy; | |
387 | int chan = CR_CHAN(insn->chanspec); | |
388 | unsigned short chanlims; | |
389 | int maxchans; | |
390 | ||
6eef3af5 | 391 | /* only support sw initiated reads from a single channel */ |
f4bd8abb | 392 | |
6eef3af5 BP |
393 | /* check channel number */ |
394 | if ((inb(devpriv->BADR3 + 2) & 0x20) == 0) /* differential mode */ | |
f4bd8abb RB |
395 | maxchans = thisboard->ai_diff_chans; |
396 | else | |
397 | maxchans = thisboard->ai_se_chans; | |
398 | ||
399 | if (chan > (maxchans - 1)) | |
6eef3af5 | 400 | return -ETIMEDOUT; /* *** Wrong error code. Fixme. */ |
f4bd8abb | 401 | |
6eef3af5 | 402 | /* configure for sw initiated read */ |
f4bd8abb | 403 | d = inb(devpriv->BADR3 + 5); |
6eef3af5 | 404 | if ((d & 0x03) > 0) { /* only reset if needed. */ |
f4bd8abb RB |
405 | d = d & 0xfd; |
406 | outb(d, devpriv->BADR3 + 5); | |
407 | } | |
6eef3af5 BP |
408 | outb(0x01, devpriv->BADR3 + 6); /* set bursting off, conversions on */ |
409 | outb(0x00, devpriv->BADR3 + 7); /* set range to 10V. UP/BP is controlled by a switch on the board */ | |
f4bd8abb | 410 | |
6eef3af5 | 411 | /* write channel limits to multiplexer, set Low (bits 0-3) and High (bits 4-7) channels to chan. */ |
f4bd8abb RB |
412 | chanlims = chan | (chan << 4); |
413 | outb(chanlims, devpriv->BADR3 + 0); | |
414 | ||
415 | /* convert n samples */ | |
416 | for (n = 0; n < insn->n; n++) { | |
417 | /* trigger conversion */ | |
418 | outw(0, devpriv->BADR2 + 0); | |
419 | ||
6eef3af5 BP |
420 | #define TIMEOUT 1000 /* typically takes 5 loops on a lightly loaded Pentium 100MHz, */ |
421 | /* this is likely to be 100 loops on a 2GHz machine, so set 1000 as the limit. */ | |
f4bd8abb RB |
422 | |
423 | /* wait for conversion to end */ | |
424 | for (i = 0; i < TIMEOUT; i++) { | |
425 | busy = inb(devpriv->BADR3 + 2) & 0x80; | |
426 | if (!busy) | |
427 | break; | |
428 | } | |
429 | if (i == TIMEOUT) { | |
430 | printk("timeout\n"); | |
431 | return -ETIMEDOUT; | |
432 | } | |
433 | /* read data */ | |
434 | d = inw(devpriv->BADR2 + 0); | |
435 | ||
436 | /* mangle the data as necessary */ | |
6eef3af5 | 437 | /* d ^= 1<<(thisboard->ai_bits-1); // 16 bit data from ADC, so no mangle needed. */ |
f4bd8abb RB |
438 | |
439 | data[n] = d; | |
440 | } | |
441 | ||
442 | /* return the number of samples read/written */ | |
443 | return n; | |
444 | } | |
445 | ||
0a85b6f0 MT |
446 | static int cb_pcimdas_ao_winsn(struct comedi_device *dev, |
447 | struct comedi_subdevice *s, | |
448 | struct comedi_insn *insn, unsigned int *data) | |
f4bd8abb RB |
449 | { |
450 | int i; | |
451 | int chan = CR_CHAN(insn->chanspec); | |
452 | ||
453 | /* Writing a list of values to an AO channel is probably not | |
454 | * very useful, but that's how the interface is defined. */ | |
455 | for (i = 0; i < insn->n; i++) { | |
456 | switch (chan) { | |
457 | case 0: | |
458 | outw(data[i] & 0x0FFF, devpriv->BADR2 + DAC0_OFFSET); | |
459 | break; | |
460 | case 1: | |
461 | outw(data[i] & 0x0FFF, devpriv->BADR2 + DAC1_OFFSET); | |
462 | break; | |
463 | default: | |
464 | return -1; | |
465 | } | |
466 | devpriv->ao_readback[chan] = data[i]; | |
467 | } | |
468 | ||
469 | /* return the number of samples read/written */ | |
470 | return i; | |
471 | } | |
472 | ||
473 | /* AO subdevices should have a read insn as well as a write insn. | |
474 | * Usually this means copying a value stored in devpriv. */ | |
0a85b6f0 MT |
475 | static int cb_pcimdas_ao_rinsn(struct comedi_device *dev, |
476 | struct comedi_subdevice *s, | |
477 | struct comedi_insn *insn, unsigned int *data) | |
f4bd8abb RB |
478 | { |
479 | int i; | |
480 | int chan = CR_CHAN(insn->chanspec); | |
481 | ||
482 | for (i = 0; i < insn->n; i++) | |
483 | data[i] = devpriv->ao_readback[chan]; | |
484 | ||
485 | return i; | |
486 | } | |
487 | ||
488 | /* | |
489 | * A convenient macro that defines init_module() and cleanup_module(), | |
490 | * as necessary. | |
491 | */ | |
727b286b AT |
492 | static int __devinit driver_cb_pcimdas_pci_probe(struct pci_dev *dev, |
493 | const struct pci_device_id | |
494 | *ent) | |
495 | { | |
496 | return comedi_pci_auto_config(dev, driver_cb_pcimdas.driver_name); | |
497 | } | |
498 | ||
499 | static void __devexit driver_cb_pcimdas_pci_remove(struct pci_dev *dev) | |
500 | { | |
501 | comedi_pci_auto_unconfig(dev); | |
502 | } | |
503 | ||
504 | static struct pci_driver driver_cb_pcimdas_pci_driver = { | |
505 | .id_table = cb_pcimdas_pci_table, | |
506 | .probe = &driver_cb_pcimdas_pci_probe, | |
507 | .remove = __devexit_p(&driver_cb_pcimdas_pci_remove) | |
508 | }; | |
509 | ||
510 | static int __init driver_cb_pcimdas_init_module(void) | |
511 | { | |
512 | int retval; | |
513 | ||
514 | retval = comedi_driver_register(&driver_cb_pcimdas); | |
515 | if (retval < 0) | |
516 | return retval; | |
517 | ||
518 | driver_cb_pcimdas_pci_driver.name = | |
519 | (char *)driver_cb_pcimdas.driver_name; | |
520 | return pci_register_driver(&driver_cb_pcimdas_pci_driver); | |
521 | } | |
522 | ||
523 | static void __exit driver_cb_pcimdas_cleanup_module(void) | |
524 | { | |
525 | pci_unregister_driver(&driver_cb_pcimdas_pci_driver); | |
526 | comedi_driver_unregister(&driver_cb_pcimdas); | |
527 | } | |
528 | ||
529 | module_init(driver_cb_pcimdas_init_module); | |
530 | module_exit(driver_cb_pcimdas_cleanup_module); | |
90f703d3 AT |
531 | |
532 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
533 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
534 | MODULE_LICENSE("GPL"); |