Commit | Line | Data |
---|---|---|
8b93f903 | 1 | /* |
2 | comedi/drivers/adl_pci6208.c | |
3 | ||
4 | Hardware driver for ADLink 6208 series cards: | |
5 | card | voltage output | current output | |
6 | -------------+-------------------+--------------- | |
7 | PCI-6208V | 8 channels | - | |
8 | PCI-6216V | 16 channels | - | |
9 | PCI-6208A | 8 channels | 8 channels | |
10 | ||
11 | COMEDI - Linux Control and Measurement Device Interface | |
12 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
13 | ||
14 | This program is free software; you can redistribute it and/or modify | |
15 | it under the terms of the GNU General Public License as published by | |
16 | the Free Software Foundation; either version 2 of the License, or | |
17 | (at your option) any later version. | |
18 | ||
19 | This program is distributed in the hope that it will be useful, | |
20 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
22 | GNU General Public License for more details. | |
23 | ||
24 | You should have received a copy of the GNU General Public License | |
25 | along with this program; if not, write to the Free Software | |
26 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
27 | */ | |
28 | /* | |
29 | Driver: adl_pci6208 | |
30 | Description: ADLink PCI-6208A | |
31 | Devices: [ADLink] PCI-6208A (adl_pci6208) | |
32 | Author: nsyeow <nsyeow@pd.jaring.my> | |
33 | Updated: Fri, 30 Jan 2004 14:44:27 +0800 | |
34 | Status: untested | |
35 | ||
36 | Configuration Options: | |
37 | none | |
38 | ||
39 | References: | |
40 | - ni_660x.c | |
41 | - adl_pci9111.c copied the entire pci setup section | |
42 | - adl_pci9118.c | |
43 | */ | |
44 | /* | |
45 | * These headers should be followed by a blank line, and any comments | |
46 | * you wish to say about the driver. The comment area is the place | |
47 | * to put any known bugs, limitations, unsupported features, supported | |
48 | * command triggers, whether or not commands are supported on particular | |
49 | * subdevices, etc. | |
50 | * | |
51 | * Somewhere in the comment should be information about configuration | |
52 | * options that are used with comedi_config. | |
53 | */ | |
54 | #include "../comedidev.h" | |
55 | #include "comedi_pci.h" | |
56 | ||
69121fa8 | 57 | #define PCI6208_DRIVER_NAME "adl_pci6208" |
8b93f903 | 58 | |
59 | /* Board descriptions */ | |
c1b31c44 | 60 | struct pci6208_board { |
8b93f903 | 61 | const char *name; |
62 | unsigned short dev_id; /* `lspci` will show you this */ | |
63 | int ao_chans; | |
14458b19 | 64 | /* int ao_bits; */ |
c1b31c44 BP |
65 | }; |
66 | ||
67 | static const struct pci6208_board pci6208_boards[] = { | |
8b93f903 | 68 | /*{ |
0a85b6f0 MT |
69 | .name = "pci6208v", |
70 | .dev_id = 0x6208, // not sure | |
71 | .ao_chans = 8 | |
72 | // , .ao_bits = 16 | |
8b93f903 | 73 | }, |
74 | { | |
0a85b6f0 MT |
75 | .name = "pci6216v", |
76 | .dev_id = 0x6208, // not sure | |
77 | .ao_chans = 16 | |
78 | // , .ao_bits = 16 | |
8b93f903 | 79 | }, */ |
80 | { | |
0a85b6f0 MT |
81 | .name = "pci6208a", |
82 | .dev_id = 0x6208, | |
83 | .ao_chans = 8 | |
84 | /* , .ao_bits = 16 */ | |
85 | } | |
8b93f903 | 86 | }; |
87 | ||
88 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
89 | * only be used for PCI and ISA-PnP devices */ | |
90 | static DEFINE_PCI_DEVICE_TABLE(pci6208_pci_table) = { | |
14458b19 BP |
91 | /* { PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ |
92 | /* { PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ | |
0a85b6f0 MT |
93 | { |
94 | PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, { | |
95 | 0} | |
8b93f903 | 96 | }; |
97 | ||
98 | MODULE_DEVICE_TABLE(pci, pci6208_pci_table); | |
99 | ||
100 | /* Will be initialized in pci6208_find device(). */ | |
c1b31c44 | 101 | #define thisboard ((const struct pci6208_board *)dev->board_ptr) |
8b93f903 | 102 | |
3d393c86 | 103 | struct pci6208_private { |
8b93f903 | 104 | int data; |
105 | struct pci_dev *pci_dev; /* for a PCI device */ | |
790c5541 | 106 | unsigned int ao_readback[2]; /* Used for AO readback */ |
3d393c86 | 107 | }; |
8b93f903 | 108 | |
3d393c86 | 109 | #define devpriv ((struct pci6208_private *)dev->private) |
8b93f903 | 110 | |
0a85b6f0 MT |
111 | static int pci6208_attach(struct comedi_device *dev, |
112 | struct comedi_devconfig *it); | |
da91b269 | 113 | static int pci6208_detach(struct comedi_device *dev); |
8b93f903 | 114 | |
139dfbdf | 115 | static struct comedi_driver driver_pci6208 = { |
68c3dbff BP |
116 | .driver_name = PCI6208_DRIVER_NAME, |
117 | .module = THIS_MODULE, | |
118 | .attach = pci6208_attach, | |
119 | .detach = pci6208_detach, | |
8b93f903 | 120 | }; |
121 | ||
727b286b AT |
122 | static int __devinit driver_pci6208_pci_probe(struct pci_dev *dev, |
123 | const struct pci_device_id *ent) | |
124 | { | |
125 | return comedi_pci_auto_config(dev, driver_pci6208.driver_name); | |
126 | } | |
127 | ||
128 | static void __devexit driver_pci6208_pci_remove(struct pci_dev *dev) | |
129 | { | |
130 | comedi_pci_auto_unconfig(dev); | |
131 | } | |
132 | ||
133 | static struct pci_driver driver_pci6208_pci_driver = { | |
134 | .id_table = pci6208_pci_table, | |
135 | .probe = &driver_pci6208_pci_probe, | |
136 | .remove = __devexit_p(&driver_pci6208_pci_remove) | |
137 | }; | |
138 | ||
139 | static int __init driver_pci6208_init_module(void) | |
140 | { | |
141 | int retval; | |
142 | ||
143 | retval = comedi_driver_register(&driver_pci6208); | |
144 | if (retval < 0) | |
145 | return retval; | |
146 | ||
147 | driver_pci6208_pci_driver.name = (char *)driver_pci6208.driver_name; | |
148 | return pci_register_driver(&driver_pci6208_pci_driver); | |
149 | } | |
150 | ||
151 | static void __exit driver_pci6208_cleanup_module(void) | |
152 | { | |
153 | pci_unregister_driver(&driver_pci6208_pci_driver); | |
154 | comedi_driver_unregister(&driver_pci6208); | |
155 | } | |
156 | ||
157 | module_init(driver_pci6208_init_module); | |
158 | module_exit(driver_pci6208_cleanup_module); | |
8b93f903 | 159 | |
da91b269 | 160 | static int pci6208_find_device(struct comedi_device *dev, int bus, int slot); |
8b93f903 | 161 | static int |
162 | pci6208_pci_setup(struct pci_dev *pci_dev, unsigned long *io_base_ptr, | |
0a85b6f0 | 163 | int dev_minor); |
8b93f903 | 164 | |
165 | /*read/write functions*/ | |
0a85b6f0 MT |
166 | static int pci6208_ao_winsn(struct comedi_device *dev, |
167 | struct comedi_subdevice *s, | |
168 | struct comedi_insn *insn, unsigned int *data); | |
169 | static int pci6208_ao_rinsn(struct comedi_device *dev, | |
170 | struct comedi_subdevice *s, | |
171 | struct comedi_insn *insn, unsigned int *data); | |
becdaa83 | 172 | /* static int pci6208_dio_insn_bits (struct comedi_device *dev, |
69121fa8 | 173 | * struct comedi_subdevice *s, */ |
14458b19 | 174 | /* struct comedi_insn *insn,unsigned int *data); */ |
becdaa83 | 175 | /* static int pci6208_dio_insn_config(struct comedi_device *dev, |
69121fa8 | 176 | * struct comedi_subdevice *s, */ |
14458b19 | 177 | /* struct comedi_insn *insn,unsigned int *data); */ |
8b93f903 | 178 | |
179 | /* | |
180 | * Attach is called by the Comedi core to configure the driver | |
181 | * for a particular board. If you specified a board_name array | |
182 | * in the driver structure, dev->board_ptr contains that | |
183 | * address. | |
184 | */ | |
0a85b6f0 MT |
185 | static int pci6208_attach(struct comedi_device *dev, |
186 | struct comedi_devconfig *it) | |
8b93f903 | 187 | { |
34c43922 | 188 | struct comedi_subdevice *s; |
8b93f903 | 189 | int retval; |
190 | unsigned long io_base; | |
191 | ||
becdaa83 | 192 | printk(KERN_INFO "comedi%d: pci6208: ", dev->minor); |
8b93f903 | 193 | |
3d393c86 | 194 | retval = alloc_private(dev, sizeof(struct pci6208_private)); |
8b93f903 | 195 | if (retval < 0) |
196 | return retval; | |
197 | ||
198 | retval = pci6208_find_device(dev, it->options[0], it->options[1]); | |
199 | if (retval < 0) | |
200 | return retval; | |
201 | ||
202 | retval = pci6208_pci_setup(devpriv->pci_dev, &io_base, dev->minor); | |
203 | if (retval < 0) | |
204 | return retval; | |
205 | ||
206 | dev->iobase = io_base; | |
207 | dev->board_name = thisboard->name; | |
208 | ||
209 | /* | |
210 | * Allocate the subdevice structures. alloc_subdevice() is a | |
211 | * convenient macro defined in comedidev.h. | |
212 | */ | |
213 | if (alloc_subdevices(dev, 2) < 0) | |
214 | return -ENOMEM; | |
215 | ||
216 | s = dev->subdevices + 0; | |
217 | /* analog output subdevice */ | |
218 | s->type = COMEDI_SUBD_AO; | |
14458b19 | 219 | s->subdev_flags = SDF_WRITABLE; /* anything else to add here?? */ |
8b93f903 | 220 | s->n_chan = thisboard->ao_chans; |
14458b19 BP |
221 | s->maxdata = 0xffff; /* 16-bit DAC */ |
222 | s->range_table = &range_bipolar10; /* this needs to be checked. */ | |
8b93f903 | 223 | s->insn_write = pci6208_ao_winsn; |
224 | s->insn_read = pci6208_ao_rinsn; | |
225 | ||
14458b19 | 226 | /* s=dev->subdevices+1; */ |
8b93f903 | 227 | /* digital i/o subdevice */ |
14458b19 BP |
228 | /* s->type=COMEDI_SUBD_DIO; */ |
229 | /* s->subdev_flags=SDF_READABLE|SDF_WRITABLE; */ | |
230 | /* s->n_chan=16; */ | |
231 | /* s->maxdata=1; */ | |
232 | /* s->range_table=&range_digital; */ | |
233 | /* s->insn_bits = pci6208_dio_insn_bits; */ | |
234 | /* s->insn_config = pci6208_dio_insn_config; */ | |
8b93f903 | 235 | |
becdaa83 | 236 | printk(KERN_INFO "attached\n"); |
8b93f903 | 237 | |
238 | return 1; | |
239 | } | |
240 | ||
241 | /* | |
242 | * _detach is called to deconfigure a device. It should deallocate | |
243 | * resources. | |
244 | * This function is also called when _attach() fails, so it should be | |
245 | * careful not to release resources that were not necessarily | |
246 | * allocated by _attach(). dev->private and dev->subdevices are | |
247 | * deallocated automatically by the core. | |
248 | */ | |
da91b269 | 249 | static int pci6208_detach(struct comedi_device *dev) |
8b93f903 | 250 | { |
becdaa83 | 251 | printk(KERN_INFO "comedi%d: pci6208: remove\n", dev->minor); |
8b93f903 | 252 | |
253 | if (devpriv && devpriv->pci_dev) { | |
becdaa83 | 254 | if (dev->iobase) |
8b93f903 | 255 | comedi_pci_disable(devpriv->pci_dev); |
8b93f903 | 256 | pci_dev_put(devpriv->pci_dev); |
257 | } | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
0a85b6f0 MT |
262 | static int pci6208_ao_winsn(struct comedi_device *dev, |
263 | struct comedi_subdevice *s, | |
264 | struct comedi_insn *insn, unsigned int *data) | |
8b93f903 | 265 | { |
266 | int i = 0, Data_Read; | |
267 | unsigned short chan = CR_CHAN(insn->chanspec); | |
268 | unsigned long invert = 1 << (16 - 1); | |
269 | unsigned long out_value; | |
270 | /* Writing a list of values to an AO channel is probably not | |
271 | * very useful, but that's how the interface is defined. */ | |
272 | for (i = 0; i < insn->n; i++) { | |
273 | out_value = data[i] ^ invert; | |
274 | /* a typical programming sequence */ | |
275 | do { | |
276 | Data_Read = (inw(dev->iobase) & 1); | |
277 | } while (Data_Read); | |
278 | outw(out_value, dev->iobase + (0x02 * chan)); | |
279 | devpriv->ao_readback[chan] = out_value; | |
280 | } | |
281 | ||
282 | /* return the number of samples read/written */ | |
283 | return i; | |
284 | } | |
285 | ||
286 | /* AO subdevices should have a read insn as well as a write insn. | |
287 | * Usually this means copying a value stored in devpriv. */ | |
0a85b6f0 MT |
288 | static int pci6208_ao_rinsn(struct comedi_device *dev, |
289 | struct comedi_subdevice *s, | |
290 | struct comedi_insn *insn, unsigned int *data) | |
8b93f903 | 291 | { |
292 | int i; | |
293 | int chan = CR_CHAN(insn->chanspec); | |
294 | ||
295 | for (i = 0; i < insn->n; i++) | |
296 | data[i] = devpriv->ao_readback[chan]; | |
297 | ||
298 | return i; | |
299 | } | |
300 | ||
301 | /* DIO devices are slightly special. Although it is possible to | |
302 | * implement the insn_read/insn_write interface, it is much more | |
303 | * useful to applications if you implement the insn_bits interface. | |
304 | * This allows packed reading/writing of the DIO channels. The | |
305 | * comedi core can convert between insn_bits and insn_read/write */ | |
becdaa83 | 306 | /* static int pci6208_dio_insn_bits(struct comedi_device *dev, |
69121fa8 | 307 | * struct comedi_subdevice *s, */ |
14458b19 BP |
308 | /* struct comedi_insn *insn,unsigned int *data) */ |
309 | /* { */ | |
310 | /* if(insn->n!=2)return -EINVAL; */ | |
8b93f903 | 311 | |
312 | /* The insn data is a mask in data[0] and the new data | |
313 | * in data[1], each channel cooresponding to a bit. */ | |
14458b19 BP |
314 | /* if(data[0]){ */ |
315 | /* s->state &= ~data[0]; */ | |
316 | /* s->state |= data[0]&data[1]; */ | |
8b93f903 | 317 | /* Write out the new digital output lines */ |
14458b19 BP |
318 | /* outw(s->state,dev->iobase + SKEL_DIO); */ |
319 | /* } */ | |
8b93f903 | 320 | |
321 | /* on return, data[1] contains the value of the digital | |
322 | * input and output lines. */ | |
14458b19 | 323 | /* data[1]=inw(dev->iobase + SKEL_DIO); */ |
8b93f903 | 324 | /* or we could just return the software copy of the output values if |
325 | * it was a purely digital output subdevice */ | |
14458b19 | 326 | /* data[1]=s->state; */ |
8b93f903 | 327 | |
14458b19 BP |
328 | /* return 2; */ |
329 | /* } */ | |
8b93f903 | 330 | |
becdaa83 | 331 | /* static int pci6208_dio_insn_config(struct comedi_device *dev, |
69121fa8 | 332 | * struct comedi_subdevice *s, */ |
14458b19 BP |
333 | /* struct comedi_insn *insn,unsigned int *data) */ |
334 | /* { */ | |
335 | /* int chan=CR_CHAN(insn->chanspec); */ | |
8b93f903 | 336 | |
337 | /* The input or output configuration of each digital line is | |
338 | * configured by a special insn_config instruction. chanspec | |
339 | * contains the channel to be changed, and data[0] contains the | |
340 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ | |
341 | ||
14458b19 BP |
342 | /* if(data[0]==COMEDI_OUTPUT){ */ |
343 | /* s->io_bits |= 1<<chan; */ | |
344 | /* }else{ */ | |
345 | /* s->io_bits &= ~(1<<chan); */ | |
346 | /* } */ | |
347 | /* outw(s->io_bits,dev->iobase + SKEL_DIO_CONFIG); */ | |
8b93f903 | 348 | |
14458b19 BP |
349 | /* return 1; */ |
350 | /* } */ | |
8b93f903 | 351 | |
da91b269 | 352 | static int pci6208_find_device(struct comedi_device *dev, int bus, int slot) |
8b93f903 | 353 | { |
20fb2280 | 354 | struct pci_dev *pci_dev = NULL; |
8b93f903 | 355 | int i; |
356 | ||
20fb2280 | 357 | for_each_pci_dev(pci_dev) { |
8b93f903 | 358 | if (pci_dev->vendor == PCI_VENDOR_ID_ADLINK) { |
8629efa4 | 359 | for (i = 0; i < ARRAY_SIZE(pci6208_boards); i++) { |
becdaa83 VK |
360 | if (pci6208_boards[i].dev_id == |
361 | pci_dev->device) { | |
362 | /* | |
363 | * was a particular bus/slot requested? | |
364 | */ | |
8b93f903 | 365 | if ((bus != 0) || (slot != 0)) { |
becdaa83 VK |
366 | /* |
367 | * are we on the | |
368 | * wrong bus/slot? | |
369 | */ | |
8b93f903 | 370 | if (pci_dev->bus->number |
0a85b6f0 MT |
371 | != bus || |
372 | PCI_SLOT(pci_dev->devfn) | |
373 | != slot) { | |
8b93f903 | 374 | continue; |
375 | } | |
376 | } | |
377 | dev->board_ptr = pci6208_boards + i; | |
378 | goto found; | |
379 | } | |
380 | } | |
381 | } | |
382 | } | |
383 | ||
becdaa83 VK |
384 | printk(KERN_ERR "comedi%d: no supported board found! " |
385 | "(req. bus/slot : %d/%d)\n", | |
386 | dev->minor, bus, slot); | |
8b93f903 | 387 | return -EIO; |
388 | ||
0a85b6f0 | 389 | found: |
8b93f903 | 390 | printk("comedi%d: found %s (b:s:f=%d:%d:%d) , irq=%d\n", |
0a85b6f0 MT |
391 | dev->minor, |
392 | pci6208_boards[i].name, | |
393 | pci_dev->bus->number, | |
394 | PCI_SLOT(pci_dev->devfn), | |
395 | PCI_FUNC(pci_dev->devfn), pci_dev->irq); | |
8b93f903 | 396 | |
14458b19 BP |
397 | /* TODO: Warn about non-tested boards. */ |
398 | /* switch(board->device_id) */ | |
399 | /* { */ | |
400 | /* }; */ | |
8b93f903 | 401 | |
402 | devpriv->pci_dev = pci_dev; | |
403 | ||
404 | return 0; | |
405 | } | |
406 | ||
407 | static int | |
408 | pci6208_pci_setup(struct pci_dev *pci_dev, unsigned long *io_base_ptr, | |
0a85b6f0 | 409 | int dev_minor) |
8b93f903 | 410 | { |
411 | unsigned long io_base, io_range, lcr_io_base, lcr_io_range; | |
412 | ||
14458b19 | 413 | /* Enable PCI device and request regions */ |
8b93f903 | 414 | if (comedi_pci_enable(pci_dev, PCI6208_DRIVER_NAME) < 0) { |
becdaa83 VK |
415 | printk(KERN_ERR "comedi%d: Failed to enable PCI device " |
416 | "and request regions\n", | |
417 | dev_minor); | |
8b93f903 | 418 | return -EIO; |
419 | } | |
becdaa83 VK |
420 | /* Read local configuration register |
421 | * base address [PCI_BASE_ADDRESS #1]. | |
422 | */ | |
8b93f903 | 423 | lcr_io_base = pci_resource_start(pci_dev, 1); |
424 | lcr_io_range = pci_resource_len(pci_dev, 1); | |
425 | ||
becdaa83 VK |
426 | printk(KERN_INFO "comedi%d: local config registers at address" |
427 | " 0x%4lx [0x%4lx]\n", | |
428 | dev_minor, lcr_io_base, lcr_io_range); | |
8b93f903 | 429 | |
14458b19 | 430 | /* Read PCI6208 register base address [PCI_BASE_ADDRESS #2]. */ |
8b93f903 | 431 | io_base = pci_resource_start(pci_dev, 2); |
432 | io_range = pci_resource_end(pci_dev, 2) - io_base + 1; | |
433 | ||
434 | printk("comedi%d: 6208 registers at address 0x%4lx [0x%4lx]\n", | |
0a85b6f0 | 435 | dev_minor, io_base, io_range); |
8b93f903 | 436 | |
437 | *io_base_ptr = io_base; | |
14458b19 BP |
438 | /* devpriv->io_range = io_range; */ |
439 | /* devpriv->is_valid=0; */ | |
440 | /* devpriv->lcr_io_base=lcr_io_base; */ | |
441 | /* devpriv->lcr_io_range=lcr_io_range; */ | |
8b93f903 | 442 | |
443 | return 0; | |
444 | } | |
90f703d3 AT |
445 | |
446 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
447 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
448 | MODULE_LICENSE("GPL"); |