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