Commit | Line | Data |
---|---|---|
cfd02b71 IA |
1 | /* |
2 | comedi/drivers/amplc_pc263.c | |
3 | Driver for Amplicon PC263 and PCI263 relay boards. | |
4 | ||
5 | Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/> | |
6 | ||
7 | COMEDI - Linux Control and Measurement Device Interface | |
8 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
9 | ||
10 | This program is free software; you can redistribute it and/or modify | |
11 | it under the terms of the GNU General Public License as published by | |
12 | the Free Software Foundation; either version 2 of the License, or | |
13 | (at your option) any later version. | |
14 | ||
15 | This program is distributed in the hope that it will be useful, | |
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
18 | GNU General Public License for more details. | |
19 | ||
20 | You should have received a copy of the GNU General Public License | |
21 | along with this program; if not, write to the Free Software | |
22 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
23 | ||
24 | */ | |
25 | /* | |
26 | Driver: amplc_pc263 | |
27 | Description: Amplicon PC263, PCI263 | |
28 | Author: Ian Abbott <abbotti@mev.co.uk> | |
29 | Devices: [Amplicon] PC263 (pc263), PCI263 (pci263 or amplc_pc263) | |
30 | Updated: Wed, 22 Oct 2008 14:10:53 +0100 | |
31 | Status: works | |
32 | ||
33 | Configuration options - PC263: | |
34 | [0] - I/O port base address | |
35 | ||
36 | Configuration options - PCI263: | |
37 | [0] - PCI bus of device (optional) | |
38 | [1] - PCI slot of device (optional) | |
39 | If bus/slot is not specified, the first available PCI device will be | |
40 | used. | |
41 | ||
42 | Each board appears as one subdevice, with 16 digital outputs, each | |
43 | connected to a reed-relay. Relay contacts are closed when output is 1. | |
44 | The state of the outputs can be read. | |
45 | */ | |
46 | ||
47 | #include "../comedidev.h" | |
48 | ||
cfd02b71 IA |
49 | #define PC263_DRIVER_NAME "amplc_pc263" |
50 | ||
51 | /* PCI263 PCI configuration register information */ | |
52 | #define PCI_VENDOR_ID_AMPLICON 0x14dc | |
53 | #define PCI_DEVICE_ID_AMPLICON_PCI263 0x000c | |
54 | #define PCI_DEVICE_ID_INVALID 0xffff | |
55 | ||
56 | /* PC263 / PCI263 registers */ | |
57 | #define PC263_IO_SIZE 2 | |
58 | ||
59 | /* | |
60 | * Board descriptions for Amplicon PC263 / PCI263. | |
61 | */ | |
62 | ||
63 | enum pc263_bustype { isa_bustype, pci_bustype }; | |
64 | enum pc263_model { pc263_model, pci263_model, anypci_model }; | |
65 | ||
4beb86c2 | 66 | struct pc263_board { |
cfd02b71 | 67 | const char *name; |
cfd02b71 IA |
68 | unsigned short devid; |
69 | enum pc263_bustype bustype; | |
70 | enum pc263_model model; | |
4beb86c2 BP |
71 | }; |
72 | static const struct pc263_board pc263_boards[] = { | |
b4843c19 | 73 | #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_ISA) |
cfd02b71 | 74 | { |
b4843c19 | 75 | .name = "pc263", |
b4843c19 IA |
76 | .bustype = isa_bustype, |
77 | .model = pc263_model, | |
78 | }, | |
3e6be97e | 79 | #endif |
b4843c19 | 80 | #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI) |
cfd02b71 | 81 | { |
b4843c19 | 82 | .name = "pci263", |
b4843c19 IA |
83 | .devid = PCI_DEVICE_ID_AMPLICON_PCI263, |
84 | .bustype = pci_bustype, | |
85 | .model = pci263_model, | |
86 | }, | |
cfd02b71 | 87 | { |
b4843c19 | 88 | .name = PC263_DRIVER_NAME, |
b4843c19 IA |
89 | .devid = PCI_DEVICE_ID_INVALID, |
90 | .bustype = pci_bustype, | |
91 | .model = anypci_model, /* wildcard */ | |
92 | }, | |
cfd02b71 IA |
93 | #endif |
94 | }; | |
95 | ||
cfd02b71 IA |
96 | /* this structure is for data unique to this hardware driver. If |
97 | several hardware drivers keep similar information in this structure, | |
abdedefe KAP |
98 | feel free to suggest moving the variable to the struct comedi_device struct. |
99 | */ | |
cd60d1ec | 100 | struct pc263_private { |
cfd02b71 IA |
101 | /* PCI device. */ |
102 | struct pci_dev *pci_dev; | |
cd60d1ec | 103 | }; |
cfd02b71 | 104 | |
d8967b6e IA |
105 | /* |
106 | * This function looks for a board matching the supplied PCI device. | |
107 | */ | |
108 | static const struct pc263_board *pc263_find_pci_board(struct pci_dev *pci_dev) | |
109 | { | |
110 | unsigned int i; | |
111 | ||
112 | for (i = 0; i < ARRAY_SIZE(pc263_boards); i++) | |
113 | if (pc263_boards[i].bustype == pci_bustype && | |
114 | pci_dev->device == pc263_boards[i].devid) | |
115 | return &pc263_boards[i]; | |
116 | return NULL; | |
117 | } | |
118 | ||
119 | ||
cfd02b71 IA |
120 | /* |
121 | * This function looks for a PCI device matching the requested board name, | |
122 | * bus and slot. | |
123 | */ | |
7ac75ba4 IA |
124 | static struct pci_dev * |
125 | pc263_find_pci(struct comedi_device *dev, int bus, int slot) | |
cfd02b71 | 126 | { |
04d66968 | 127 | const struct pc263_board *thisboard = comedi_board(dev); |
cfd02b71 IA |
128 | struct pci_dev *pci_dev = NULL; |
129 | ||
cfd02b71 IA |
130 | /* Look for matching PCI device. */ |
131 | for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL); | |
0a85b6f0 MT |
132 | pci_dev != NULL; |
133 | pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, | |
134 | PCI_ANY_ID, pci_dev)) { | |
cfd02b71 IA |
135 | /* If bus/slot specified, check them. */ |
136 | if (bus || slot) { | |
137 | if (bus != pci_dev->bus->number | |
0a85b6f0 | 138 | || slot != PCI_SLOT(pci_dev->devfn)) |
cfd02b71 IA |
139 | continue; |
140 | } | |
141 | if (thisboard->model == anypci_model) { | |
d8967b6e IA |
142 | /* Wildcard board matches any supported PCI board. */ |
143 | const struct pc263_board *foundboard; | |
144 | foundboard = pc263_find_pci_board(pci_dev); | |
145 | if (foundboard == NULL) | |
cfd02b71 | 146 | continue; |
d8967b6e IA |
147 | /* Replace wildcard board_ptr. */ |
148 | dev->board_ptr = thisboard = foundboard; | |
cfd02b71 IA |
149 | } else { |
150 | /* Match specific model name. */ | |
151 | if (pci_dev->device != thisboard->devid) | |
152 | continue; | |
153 | } | |
154 | ||
155 | /* Found a match. */ | |
7ac75ba4 | 156 | return pci_dev; |
cfd02b71 IA |
157 | } |
158 | /* No match found. */ | |
159 | if (bus || slot) { | |
03668b10 IA |
160 | dev_err(dev->class_dev, |
161 | "error! no %s found at pci %02x:%02x!\n", | |
162 | thisboard->name, bus, slot); | |
cfd02b71 | 163 | } else { |
03668b10 IA |
164 | dev_err(dev->class_dev, "error! no %s found!\n", |
165 | thisboard->name); | |
cfd02b71 | 166 | } |
7ac75ba4 | 167 | return NULL; |
cfd02b71 | 168 | } |
ba7914cd IA |
169 | /* |
170 | * This function checks and requests an I/O region, reporting an error | |
171 | * if there is a conflict. | |
172 | */ | |
03668b10 | 173 | static int pc263_request_region(struct comedi_device *dev, unsigned long from, |
ba7914cd IA |
174 | unsigned long extent) |
175 | { | |
176 | if (!from || !request_region(from, extent, PC263_DRIVER_NAME)) { | |
03668b10 IA |
177 | dev_err(dev->class_dev, "I/O port conflict (%#lx,%lu)!\n", |
178 | from, extent); | |
ba7914cd IA |
179 | return -EIO; |
180 | } | |
181 | return 0; | |
182 | } | |
183 | ||
184 | static int pc263_do_insn_bits(struct comedi_device *dev, | |
185 | struct comedi_subdevice *s, | |
186 | struct comedi_insn *insn, unsigned int *data) | |
187 | { | |
188 | if (insn->n != 2) | |
189 | return -EINVAL; | |
190 | ||
191 | /* The insn data is a mask in data[0] and the new data | |
192 | * in data[1], each channel cooresponding to a bit. */ | |
193 | if (data[0]) { | |
194 | s->state &= ~data[0]; | |
195 | s->state |= data[0] & data[1]; | |
196 | /* Write out the new digital output lines */ | |
197 | outb(s->state & 0xFF, dev->iobase); | |
198 | outb(s->state >> 8, dev->iobase + 1); | |
199 | } | |
200 | return 2; | |
201 | } | |
cfd02b71 | 202 | |
03668b10 IA |
203 | static void pc263_report_attach(struct comedi_device *dev) |
204 | { | |
04d66968 IA |
205 | const struct pc263_board *thisboard = comedi_board(dev); |
206 | struct pc263_private *devpriv = dev->private; | |
03668b10 IA |
207 | char tmpbuf[40]; |
208 | ||
209 | if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_ISA) && | |
210 | thisboard->bustype == isa_bustype) | |
211 | snprintf(tmpbuf, sizeof(tmpbuf), "(base %#lx) ", dev->iobase); | |
212 | else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI) && | |
213 | thisboard->bustype == pci_bustype) | |
214 | snprintf(tmpbuf, sizeof(tmpbuf), "(pci %s) ", | |
215 | pci_name(devpriv->pci_dev)); | |
216 | else | |
217 | tmpbuf[0] = '\0'; | |
218 | dev_info(dev->class_dev, "%s %sattached\n", dev->board_name, tmpbuf); | |
219 | } | |
220 | ||
d8967b6e IA |
221 | static int pc263_common_attach(struct comedi_device *dev, unsigned long iobase) |
222 | { | |
223 | const struct pc263_board *thisboard = comedi_board(dev); | |
224 | struct comedi_subdevice *s; | |
225 | int ret; | |
226 | ||
227 | dev->board_name = thisboard->name; | |
228 | dev->iobase = iobase; | |
229 | ||
2f0b9d08 | 230 | ret = comedi_alloc_subdevices(dev, 1); |
0e4039f3 | 231 | if (ret < 0) |
d8967b6e | 232 | return ret; |
d8967b6e IA |
233 | |
234 | s = dev->subdevices + 0; | |
235 | /* digital output subdevice */ | |
236 | s->type = COMEDI_SUBD_DO; | |
237 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
238 | s->n_chan = 16; | |
239 | s->maxdata = 1; | |
240 | s->range_table = &range_digital; | |
241 | s->insn_bits = pc263_do_insn_bits; | |
242 | /* read initial relay state */ | |
243 | s->state = inb(dev->iobase) | (inb(dev->iobase + 1) << 8); | |
244 | ||
245 | pc263_report_attach(dev); | |
246 | return 1; | |
247 | } | |
248 | ||
249 | static int pc263_pci_common_attach(struct comedi_device *dev, | |
250 | struct pci_dev *pci_dev) | |
251 | { | |
252 | struct pc263_private *devpriv = dev->private; | |
253 | unsigned long iobase; | |
254 | int ret; | |
255 | ||
256 | devpriv->pci_dev = pci_dev; | |
257 | ret = comedi_pci_enable(pci_dev, PC263_DRIVER_NAME); | |
258 | if (ret < 0) { | |
259 | dev_err(dev->class_dev, | |
260 | "error! cannot enable PCI device and request regions!\n"); | |
261 | return ret; | |
262 | } | |
263 | iobase = pci_resource_start(pci_dev, 2); | |
264 | return pc263_common_attach(dev, iobase); | |
265 | } | |
266 | ||
cfd02b71 IA |
267 | /* |
268 | * Attach is called by the Comedi core to configure the driver | |
269 | * for a particular board. If you specified a board_name array | |
270 | * in the driver structure, dev->board_ptr contains that | |
271 | * address. | |
272 | */ | |
da91b269 | 273 | static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
cfd02b71 | 274 | { |
04d66968 | 275 | const struct pc263_board *thisboard = comedi_board(dev); |
cfd02b71 IA |
276 | int ret; |
277 | ||
03668b10 | 278 | dev_info(dev->class_dev, PC263_DRIVER_NAME ": attach\n"); |
b4843c19 IA |
279 | |
280 | /* Process options and reserve resources according to bus type. */ | |
281 | if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_ISA) && | |
282 | thisboard->bustype == isa_bustype) { | |
d8967b6e | 283 | unsigned long iobase = it->options[0]; |
03668b10 | 284 | ret = pc263_request_region(dev, iobase, PC263_IO_SIZE); |
b4843c19 IA |
285 | if (ret < 0) |
286 | return ret; | |
d8967b6e | 287 | return pc263_common_attach(dev, iobase); |
b4843c19 IA |
288 | } else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI) && |
289 | thisboard->bustype == pci_bustype) { | |
d8967b6e | 290 | struct pci_dev *pci_dev; |
b4843c19 IA |
291 | int bus, slot; |
292 | ||
293 | ret = alloc_private(dev, sizeof(struct pc263_private)); | |
294 | if (ret < 0) { | |
03668b10 | 295 | dev_err(dev->class_dev, "error! out of memory!\n"); |
b4843c19 IA |
296 | return ret; |
297 | } | |
cfd02b71 IA |
298 | bus = it->options[0]; |
299 | slot = it->options[1]; | |
7ac75ba4 IA |
300 | pci_dev = pc263_find_pci(dev, bus, slot); |
301 | if (pci_dev == NULL) | |
302 | return -EIO; | |
d8967b6e | 303 | return pc263_pci_common_attach(dev, pci_dev); |
b4843c19 | 304 | } else { |
03668b10 IA |
305 | dev_err(dev->class_dev, PC263_DRIVER_NAME |
306 | ": BUG! cannot determine board type!\n"); | |
b4843c19 | 307 | return -EINVAL; |
cfd02b71 | 308 | } |
d8967b6e IA |
309 | } |
310 | /* | |
311 | * The attach_pci hook (if non-NULL) is called at PCI probe time in preference | |
312 | * to the "manual" attach hook. dev->board_ptr is NULL on entry. There should | |
313 | * be a board entry matching the supplied PCI device. | |
314 | */ | |
315 | static int __devinit pc263_attach_pci(struct comedi_device *dev, | |
316 | struct pci_dev *pci_dev) | |
317 | { | |
318 | int ret; | |
b4843c19 | 319 | |
d8967b6e IA |
320 | if (!IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI)) |
321 | return -EINVAL; | |
cfd02b71 | 322 | |
d8967b6e IA |
323 | dev_info(dev->class_dev, PC263_DRIVER_NAME ": attach pci %s\n", |
324 | pci_name(pci_dev)); | |
325 | ret = alloc_private(dev, sizeof(struct pc263_private)); | |
c3744138 | 326 | if (ret < 0) { |
03668b10 | 327 | dev_err(dev->class_dev, "error! out of memory!\n"); |
cfd02b71 IA |
328 | return ret; |
329 | } | |
d8967b6e IA |
330 | dev->board_ptr = pc263_find_pci_board(pci_dev); |
331 | if (dev->board_ptr == NULL) { | |
332 | dev_err(dev->class_dev, "BUG! cannot determine board type!\n"); | |
333 | return -EINVAL; | |
334 | } | |
335 | return pc263_pci_common_attach(dev, pci_dev); | |
cfd02b71 IA |
336 | } |
337 | ||
484ecc95 | 338 | static void pc263_detach(struct comedi_device *dev) |
cfd02b71 | 339 | { |
04d66968 IA |
340 | struct pc263_private *devpriv = dev->private; |
341 | ||
b4843c19 IA |
342 | if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI) && devpriv && |
343 | devpriv->pci_dev) { | |
344 | if (dev->iobase) | |
345 | comedi_pci_disable(devpriv->pci_dev); | |
346 | pci_dev_put(devpriv->pci_dev); | |
347 | } else if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_ISA)) { | |
348 | if (dev->iobase) | |
349 | release_region(dev->iobase, PC263_IO_SIZE); | |
cfd02b71 | 350 | } |
cfd02b71 IA |
351 | } |
352 | ||
353 | /* | |
ba7914cd IA |
354 | * The struct comedi_driver structure tells the Comedi core module |
355 | * which functions to call to configure/deconfigure (attach/detach) | |
356 | * the board, and also about the kernel module that contains | |
357 | * the device code. | |
cfd02b71 | 358 | */ |
ba7914cd IA |
359 | static struct comedi_driver amplc_pc263_driver = { |
360 | .driver_name = PC263_DRIVER_NAME, | |
361 | .module = THIS_MODULE, | |
362 | .attach = pc263_attach, | |
d8967b6e | 363 | .attach_pci = pc263_attach_pci, |
ba7914cd IA |
364 | .detach = pc263_detach, |
365 | .board_name = &pc263_boards[0].name, | |
366 | .offset = sizeof(struct pc263_board), | |
367 | .num_names = ARRAY_SIZE(pc263_boards), | |
368 | }; | |
cfd02b71 | 369 | |
b4843c19 | 370 | #if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI) |
ba7914cd IA |
371 | static DEFINE_PCI_DEVICE_TABLE(pc263_pci_table) = { |
372 | { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263) }, | |
373 | {0} | |
374 | }; | |
375 | MODULE_DEVICE_TABLE(pci, pc263_pci_table); | |
376 | ||
40372f5f | 377 | static int __devinit amplc_pc263_pci_probe(struct pci_dev *dev, |
727b286b AT |
378 | const struct pci_device_id |
379 | *ent) | |
380 | { | |
40372f5f | 381 | return comedi_pci_auto_config(dev, &lc_pc263_driver); |
727b286b AT |
382 | } |
383 | ||
40372f5f | 384 | static void __devexit amplc_pc263_pci_remove(struct pci_dev *dev) |
727b286b AT |
385 | { |
386 | comedi_pci_auto_unconfig(dev); | |
387 | } | |
388 | ||
40372f5f IA |
389 | static struct pci_driver amplc_pc263_pci_driver = { |
390 | .name = PC263_DRIVER_NAME, | |
727b286b | 391 | .id_table = pc263_pci_table, |
40372f5f IA |
392 | .probe = &lc_pc263_pci_probe, |
393 | .remove = __devexit_p(&lc_pc263_pci_remove) | |
727b286b | 394 | }; |
40372f5f | 395 | module_comedi_pci_driver(amplc_pc263_driver, amplc_pc263_pci_driver); |
cfd02b71 | 396 | #else |
40372f5f | 397 | module_comedi_driver(amplc_pc263_driver); |
cfd02b71 | 398 | #endif |
90f703d3 AT |
399 | |
400 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
401 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
402 | MODULE_LICENSE("GPL"); |