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" | |
8b93f903 | 55 | |
8b93f903 | 56 | /* Board descriptions */ |
c1b31c44 | 57 | struct pci6208_board { |
8b93f903 | 58 | const char *name; |
59 | unsigned short dev_id; /* `lspci` will show you this */ | |
60 | int ao_chans; | |
14458b19 | 61 | /* int ao_bits; */ |
c1b31c44 BP |
62 | }; |
63 | ||
64 | static const struct pci6208_board pci6208_boards[] = { | |
8b93f903 | 65 | /*{ |
0a85b6f0 MT |
66 | .name = "pci6208v", |
67 | .dev_id = 0x6208, // not sure | |
68 | .ao_chans = 8 | |
69 | // , .ao_bits = 16 | |
8b93f903 | 70 | }, |
71 | { | |
0a85b6f0 MT |
72 | .name = "pci6216v", |
73 | .dev_id = 0x6208, // not sure | |
74 | .ao_chans = 16 | |
75 | // , .ao_bits = 16 | |
8b93f903 | 76 | }, */ |
77 | { | |
0a85b6f0 MT |
78 | .name = "pci6208a", |
79 | .dev_id = 0x6208, | |
80 | .ao_chans = 8 | |
81 | /* , .ao_bits = 16 */ | |
82 | } | |
8b93f903 | 83 | }; |
84 | ||
8b93f903 | 85 | /* Will be initialized in pci6208_find device(). */ |
c1b31c44 | 86 | #define thisboard ((const struct pci6208_board *)dev->board_ptr) |
8b93f903 | 87 | |
3d393c86 | 88 | struct pci6208_private { |
8b93f903 | 89 | int data; |
90 | struct pci_dev *pci_dev; /* for a PCI device */ | |
790c5541 | 91 | unsigned int ao_readback[2]; /* Used for AO readback */ |
3d393c86 | 92 | }; |
8b93f903 | 93 | |
3d393c86 | 94 | #define devpriv ((struct pci6208_private *)dev->private) |
8b93f903 | 95 | |
0a85b6f0 MT |
96 | static int pci6208_ao_winsn(struct comedi_device *dev, |
97 | struct comedi_subdevice *s, | |
98 | struct comedi_insn *insn, unsigned int *data) | |
8b93f903 | 99 | { |
100 | int i = 0, Data_Read; | |
101 | unsigned short chan = CR_CHAN(insn->chanspec); | |
102 | unsigned long invert = 1 << (16 - 1); | |
103 | unsigned long out_value; | |
104 | /* Writing a list of values to an AO channel is probably not | |
105 | * very useful, but that's how the interface is defined. */ | |
106 | for (i = 0; i < insn->n; i++) { | |
107 | out_value = data[i] ^ invert; | |
108 | /* a typical programming sequence */ | |
109 | do { | |
110 | Data_Read = (inw(dev->iobase) & 1); | |
111 | } while (Data_Read); | |
112 | outw(out_value, dev->iobase + (0x02 * chan)); | |
113 | devpriv->ao_readback[chan] = out_value; | |
114 | } | |
115 | ||
116 | /* return the number of samples read/written */ | |
117 | return i; | |
118 | } | |
119 | ||
120 | /* AO subdevices should have a read insn as well as a write insn. | |
121 | * Usually this means copying a value stored in devpriv. */ | |
0a85b6f0 MT |
122 | static int pci6208_ao_rinsn(struct comedi_device *dev, |
123 | struct comedi_subdevice *s, | |
124 | struct comedi_insn *insn, unsigned int *data) | |
8b93f903 | 125 | { |
126 | int i; | |
127 | int chan = CR_CHAN(insn->chanspec); | |
128 | ||
129 | for (i = 0; i < insn->n; i++) | |
130 | data[i] = devpriv->ao_readback[chan]; | |
131 | ||
132 | return i; | |
133 | } | |
134 | ||
135 | /* DIO devices are slightly special. Although it is possible to | |
136 | * implement the insn_read/insn_write interface, it is much more | |
137 | * useful to applications if you implement the insn_bits interface. | |
138 | * This allows packed reading/writing of the DIO channels. The | |
139 | * comedi core can convert between insn_bits and insn_read/write */ | |
becdaa83 | 140 | /* static int pci6208_dio_insn_bits(struct comedi_device *dev, |
69121fa8 | 141 | * struct comedi_subdevice *s, */ |
14458b19 BP |
142 | /* struct comedi_insn *insn,unsigned int *data) */ |
143 | /* { */ | |
8b93f903 | 144 | /* The insn data is a mask in data[0] and the new data |
145 | * in data[1], each channel cooresponding to a bit. */ | |
14458b19 BP |
146 | /* if(data[0]){ */ |
147 | /* s->state &= ~data[0]; */ | |
148 | /* s->state |= data[0]&data[1]; */ | |
8b93f903 | 149 | /* Write out the new digital output lines */ |
14458b19 BP |
150 | /* outw(s->state,dev->iobase + SKEL_DIO); */ |
151 | /* } */ | |
8b93f903 | 152 | |
153 | /* on return, data[1] contains the value of the digital | |
154 | * input and output lines. */ | |
14458b19 | 155 | /* data[1]=inw(dev->iobase + SKEL_DIO); */ |
8b93f903 | 156 | /* or we could just return the software copy of the output values if |
157 | * it was a purely digital output subdevice */ | |
14458b19 | 158 | /* data[1]=s->state; */ |
8b93f903 | 159 | |
a2714e3e | 160 | /* return insn->n; */ |
14458b19 | 161 | /* } */ |
8b93f903 | 162 | |
becdaa83 | 163 | /* static int pci6208_dio_insn_config(struct comedi_device *dev, |
69121fa8 | 164 | * struct comedi_subdevice *s, */ |
14458b19 BP |
165 | /* struct comedi_insn *insn,unsigned int *data) */ |
166 | /* { */ | |
167 | /* int chan=CR_CHAN(insn->chanspec); */ | |
8b93f903 | 168 | |
169 | /* The input or output configuration of each digital line is | |
170 | * configured by a special insn_config instruction. chanspec | |
171 | * contains the channel to be changed, and data[0] contains the | |
172 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ | |
173 | ||
14458b19 BP |
174 | /* if(data[0]==COMEDI_OUTPUT){ */ |
175 | /* s->io_bits |= 1<<chan; */ | |
176 | /* }else{ */ | |
177 | /* s->io_bits &= ~(1<<chan); */ | |
178 | /* } */ | |
179 | /* outw(s->io_bits,dev->iobase + SKEL_DIO_CONFIG); */ | |
8b93f903 | 180 | |
14458b19 BP |
181 | /* return 1; */ |
182 | /* } */ | |
8b93f903 | 183 | |
da91b269 | 184 | static int pci6208_find_device(struct comedi_device *dev, int bus, int slot) |
8b93f903 | 185 | { |
20fb2280 | 186 | struct pci_dev *pci_dev = NULL; |
8b93f903 | 187 | int i; |
188 | ||
20fb2280 | 189 | for_each_pci_dev(pci_dev) { |
8b93f903 | 190 | if (pci_dev->vendor == PCI_VENDOR_ID_ADLINK) { |
8629efa4 | 191 | for (i = 0; i < ARRAY_SIZE(pci6208_boards); i++) { |
becdaa83 VK |
192 | if (pci6208_boards[i].dev_id == |
193 | pci_dev->device) { | |
194 | /* | |
195 | * was a particular bus/slot requested? | |
196 | */ | |
8b93f903 | 197 | if ((bus != 0) || (slot != 0)) { |
becdaa83 VK |
198 | /* |
199 | * are we on the | |
200 | * wrong bus/slot? | |
201 | */ | |
8b93f903 | 202 | if (pci_dev->bus->number |
0a85b6f0 MT |
203 | != bus || |
204 | PCI_SLOT(pci_dev->devfn) | |
205 | != slot) { | |
8b93f903 | 206 | continue; |
207 | } | |
208 | } | |
209 | dev->board_ptr = pci6208_boards + i; | |
210 | goto found; | |
211 | } | |
212 | } | |
213 | } | |
214 | } | |
215 | ||
becdaa83 VK |
216 | printk(KERN_ERR "comedi%d: no supported board found! " |
217 | "(req. bus/slot : %d/%d)\n", | |
218 | dev->minor, bus, slot); | |
8b93f903 | 219 | return -EIO; |
220 | ||
0a85b6f0 | 221 | found: |
8b93f903 | 222 | printk("comedi%d: found %s (b:s:f=%d:%d:%d) , irq=%d\n", |
0a85b6f0 MT |
223 | dev->minor, |
224 | pci6208_boards[i].name, | |
225 | pci_dev->bus->number, | |
226 | PCI_SLOT(pci_dev->devfn), | |
227 | PCI_FUNC(pci_dev->devfn), pci_dev->irq); | |
8b93f903 | 228 | |
14458b19 BP |
229 | /* TODO: Warn about non-tested boards. */ |
230 | /* switch(board->device_id) */ | |
231 | /* { */ | |
232 | /* }; */ | |
8b93f903 | 233 | |
234 | devpriv->pci_dev = pci_dev; | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
239 | static int | |
240 | pci6208_pci_setup(struct pci_dev *pci_dev, unsigned long *io_base_ptr, | |
0a85b6f0 | 241 | int dev_minor) |
8b93f903 | 242 | { |
243 | unsigned long io_base, io_range, lcr_io_base, lcr_io_range; | |
244 | ||
14458b19 | 245 | /* Enable PCI device and request regions */ |
75e6301b | 246 | if (comedi_pci_enable(pci_dev, "adl_pci6208") < 0) { |
becdaa83 VK |
247 | printk(KERN_ERR "comedi%d: Failed to enable PCI device " |
248 | "and request regions\n", | |
249 | dev_minor); | |
8b93f903 | 250 | return -EIO; |
251 | } | |
becdaa83 VK |
252 | /* Read local configuration register |
253 | * base address [PCI_BASE_ADDRESS #1]. | |
254 | */ | |
8b93f903 | 255 | lcr_io_base = pci_resource_start(pci_dev, 1); |
256 | lcr_io_range = pci_resource_len(pci_dev, 1); | |
257 | ||
becdaa83 VK |
258 | printk(KERN_INFO "comedi%d: local config registers at address" |
259 | " 0x%4lx [0x%4lx]\n", | |
260 | dev_minor, lcr_io_base, lcr_io_range); | |
8b93f903 | 261 | |
14458b19 | 262 | /* Read PCI6208 register base address [PCI_BASE_ADDRESS #2]. */ |
8b93f903 | 263 | io_base = pci_resource_start(pci_dev, 2); |
264 | io_range = pci_resource_end(pci_dev, 2) - io_base + 1; | |
265 | ||
266 | printk("comedi%d: 6208 registers at address 0x%4lx [0x%4lx]\n", | |
0a85b6f0 | 267 | dev_minor, io_base, io_range); |
8b93f903 | 268 | |
269 | *io_base_ptr = io_base; | |
14458b19 BP |
270 | /* devpriv->io_range = io_range; */ |
271 | /* devpriv->is_valid=0; */ | |
272 | /* devpriv->lcr_io_base=lcr_io_base; */ | |
273 | /* devpriv->lcr_io_range=lcr_io_range; */ | |
8b93f903 | 274 | |
275 | return 0; | |
276 | } | |
90f703d3 | 277 | |
c8d87bcc HS |
278 | static int pci6208_attach(struct comedi_device *dev, |
279 | struct comedi_devconfig *it) | |
280 | { | |
281 | struct comedi_subdevice *s; | |
282 | int retval; | |
283 | unsigned long io_base; | |
284 | ||
285 | printk(KERN_INFO "comedi%d: pci6208: ", dev->minor); | |
286 | ||
287 | retval = alloc_private(dev, sizeof(struct pci6208_private)); | |
288 | if (retval < 0) | |
289 | return retval; | |
290 | ||
291 | retval = pci6208_find_device(dev, it->options[0], it->options[1]); | |
292 | if (retval < 0) | |
293 | return retval; | |
294 | ||
295 | retval = pci6208_pci_setup(devpriv->pci_dev, &io_base, dev->minor); | |
296 | if (retval < 0) | |
297 | return retval; | |
298 | ||
299 | dev->iobase = io_base; | |
300 | dev->board_name = thisboard->name; | |
301 | ||
8b6c5694 HS |
302 | retval = comedi_alloc_subdevices(dev, 2); |
303 | if (retval) | |
304 | return retval; | |
c8d87bcc HS |
305 | |
306 | s = dev->subdevices + 0; | |
307 | /* analog output subdevice */ | |
308 | s->type = COMEDI_SUBD_AO; | |
309 | s->subdev_flags = SDF_WRITABLE; /* anything else to add here?? */ | |
310 | s->n_chan = thisboard->ao_chans; | |
311 | s->maxdata = 0xffff; /* 16-bit DAC */ | |
312 | s->range_table = &range_bipolar10; /* this needs to be checked. */ | |
313 | s->insn_write = pci6208_ao_winsn; | |
314 | s->insn_read = pci6208_ao_rinsn; | |
315 | ||
316 | /* s=dev->subdevices+1; */ | |
317 | /* digital i/o subdevice */ | |
318 | /* s->type=COMEDI_SUBD_DIO; */ | |
319 | /* s->subdev_flags=SDF_READABLE|SDF_WRITABLE; */ | |
320 | /* s->n_chan=16; */ | |
321 | /* s->maxdata=1; */ | |
322 | /* s->range_table=&range_digital; */ | |
323 | /* s->insn_bits = pci6208_dio_insn_bits; */ | |
324 | /* s->insn_config = pci6208_dio_insn_config; */ | |
325 | ||
326 | printk(KERN_INFO "attached\n"); | |
327 | ||
328 | return 1; | |
329 | } | |
330 | ||
484ecc95 | 331 | static void pci6208_detach(struct comedi_device *dev) |
c8d87bcc | 332 | { |
c8d87bcc HS |
333 | if (devpriv && devpriv->pci_dev) { |
334 | if (dev->iobase) | |
335 | comedi_pci_disable(devpriv->pci_dev); | |
336 | pci_dev_put(devpriv->pci_dev); | |
337 | } | |
c8d87bcc HS |
338 | } |
339 | ||
75e6301b HS |
340 | static struct comedi_driver adl_pci6208_driver = { |
341 | .driver_name = "adl_pci6208", | |
c8d87bcc HS |
342 | .module = THIS_MODULE, |
343 | .attach = pci6208_attach, | |
344 | .detach = pci6208_detach, | |
345 | }; | |
346 | ||
75e6301b HS |
347 | static int __devinit adl_pci6208_pci_probe(struct pci_dev *dev, |
348 | const struct pci_device_id *ent) | |
c8d87bcc | 349 | { |
75e6301b | 350 | return comedi_pci_auto_config(dev, &adl_pci6208_driver); |
c8d87bcc HS |
351 | } |
352 | ||
75e6301b | 353 | static void __devexit adl_pci6208_pci_remove(struct pci_dev *dev) |
c8d87bcc HS |
354 | { |
355 | comedi_pci_auto_unconfig(dev); | |
356 | } | |
357 | ||
358 | /* This is used by modprobe to translate PCI IDs to drivers. Should | |
359 | * only be used for PCI and ISA-PnP devices */ | |
75e6301b | 360 | static DEFINE_PCI_DEVICE_TABLE(adl_pci6208_pci_table) = { |
c8d87bcc HS |
361 | /* { PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ |
362 | /* { PCI_VENDOR_ID_ADLINK, 0x6208, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, */ | |
363 | { PCI_DEVICE(PCI_VENDOR_ID_ADLINK, 0x6208) }, | |
364 | { 0 } | |
365 | }; | |
75e6301b | 366 | MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table); |
c8d87bcc | 367 | |
75e6301b HS |
368 | static struct pci_driver adl_pci6208_pci_driver = { |
369 | .name = "adl_pci6208", | |
370 | .id_table = adl_pci6208_pci_table, | |
371 | .probe = adl_pci6208_pci_probe, | |
372 | .remove = __devexit_p(adl_pci6208_pci_remove), | |
c8d87bcc | 373 | }; |
75e6301b | 374 | module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver); |
c8d87bcc | 375 | |
90f703d3 AT |
376 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
377 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
378 | MODULE_LICENSE("GPL"); |