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 | ||
33782dd5 HS |
47 | #include <linux/pci.h> |
48 | ||
cfd02b71 IA |
49 | #include "../comedidev.h" |
50 | ||
cfd02b71 IA |
51 | #define PC263_DRIVER_NAME "amplc_pc263" |
52 | ||
87f8b20d IA |
53 | #define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_ISA) |
54 | #define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_PC263_PCI) | |
55 | ||
cfd02b71 | 56 | /* PCI263 PCI configuration register information */ |
cfd02b71 IA |
57 | #define PCI_DEVICE_ID_AMPLICON_PCI263 0x000c |
58 | #define PCI_DEVICE_ID_INVALID 0xffff | |
59 | ||
60 | /* PC263 / PCI263 registers */ | |
61 | #define PC263_IO_SIZE 2 | |
62 | ||
63 | /* | |
64 | * Board descriptions for Amplicon PC263 / PCI263. | |
65 | */ | |
66 | ||
67 | enum pc263_bustype { isa_bustype, pci_bustype }; | |
68 | enum pc263_model { pc263_model, pci263_model, anypci_model }; | |
69 | ||
4beb86c2 | 70 | struct pc263_board { |
cfd02b71 | 71 | const char *name; |
cfd02b71 IA |
72 | unsigned short devid; |
73 | enum pc263_bustype bustype; | |
74 | enum pc263_model model; | |
4beb86c2 BP |
75 | }; |
76 | static const struct pc263_board pc263_boards[] = { | |
87f8b20d | 77 | #if DO_ISA |
cfd02b71 | 78 | { |
b4843c19 | 79 | .name = "pc263", |
b4843c19 IA |
80 | .bustype = isa_bustype, |
81 | .model = pc263_model, | |
82 | }, | |
3e6be97e | 83 | #endif |
87f8b20d | 84 | #if DO_PCI |
cfd02b71 | 85 | { |
b4843c19 | 86 | .name = "pci263", |
b4843c19 IA |
87 | .devid = PCI_DEVICE_ID_AMPLICON_PCI263, |
88 | .bustype = pci_bustype, | |
89 | .model = pci263_model, | |
90 | }, | |
cfd02b71 | 91 | { |
b4843c19 | 92 | .name = PC263_DRIVER_NAME, |
b4843c19 IA |
93 | .devid = PCI_DEVICE_ID_INVALID, |
94 | .bustype = pci_bustype, | |
95 | .model = anypci_model, /* wildcard */ | |
96 | }, | |
cfd02b71 IA |
97 | #endif |
98 | }; | |
99 | ||
d2676944 IA |
100 | /* test if ISA supported and this is an ISA board */ |
101 | static inline bool is_isa_board(const struct pc263_board *board) | |
102 | { | |
87f8b20d | 103 | return DO_ISA && board->bustype == isa_bustype; |
d2676944 IA |
104 | } |
105 | ||
106 | /* test if PCI supported and this is a PCI board */ | |
107 | static inline bool is_pci_board(const struct pc263_board *board) | |
108 | { | |
87f8b20d | 109 | return DO_PCI && board->bustype == pci_bustype; |
d2676944 IA |
110 | } |
111 | ||
d8967b6e IA |
112 | /* |
113 | * This function looks for a board matching the supplied PCI device. | |
114 | */ | |
115 | static const struct pc263_board *pc263_find_pci_board(struct pci_dev *pci_dev) | |
116 | { | |
117 | unsigned int i; | |
118 | ||
119 | for (i = 0; i < ARRAY_SIZE(pc263_boards); i++) | |
d2676944 | 120 | if (is_pci_board(&pc263_boards[i]) && |
d8967b6e IA |
121 | pci_dev->device == pc263_boards[i].devid) |
122 | return &pc263_boards[i]; | |
123 | return NULL; | |
124 | } | |
125 | ||
126 | ||
cfd02b71 IA |
127 | /* |
128 | * This function looks for a PCI device matching the requested board name, | |
129 | * bus and slot. | |
130 | */ | |
1c16519f HS |
131 | static struct pci_dev *pc263_find_pci_dev(struct comedi_device *dev, |
132 | struct comedi_devconfig *it) | |
cfd02b71 | 133 | { |
04d66968 | 134 | const struct pc263_board *thisboard = comedi_board(dev); |
cfd02b71 | 135 | struct pci_dev *pci_dev = NULL; |
1c16519f HS |
136 | int bus = it->options[0]; |
137 | int slot = it->options[1]; | |
cfd02b71 | 138 | |
1c16519f | 139 | for_each_pci_dev(pci_dev) { |
cfd02b71 | 140 | if (bus || slot) { |
1c16519f HS |
141 | if (bus != pci_dev->bus->number || |
142 | slot != PCI_SLOT(pci_dev->devfn)) | |
cfd02b71 IA |
143 | continue; |
144 | } | |
1c16519f HS |
145 | if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) |
146 | continue; | |
147 | ||
cfd02b71 | 148 | if (thisboard->model == anypci_model) { |
d8967b6e IA |
149 | /* Wildcard board matches any supported PCI board. */ |
150 | const struct pc263_board *foundboard; | |
1c16519f | 151 | |
d8967b6e IA |
152 | foundboard = pc263_find_pci_board(pci_dev); |
153 | if (foundboard == NULL) | |
cfd02b71 | 154 | continue; |
d8967b6e IA |
155 | /* Replace wildcard board_ptr. */ |
156 | dev->board_ptr = thisboard = foundboard; | |
cfd02b71 IA |
157 | } else { |
158 | /* Match specific model name. */ | |
159 | if (pci_dev->device != thisboard->devid) | |
160 | continue; | |
161 | } | |
7ac75ba4 | 162 | return pci_dev; |
cfd02b71 | 163 | } |
1c16519f HS |
164 | dev_err(dev->class_dev, |
165 | "No supported board found! (req. bus %d, slot %d)\n", | |
166 | bus, slot); | |
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 | { | |
ba7914cd IA |
188 | /* The insn data is a mask in data[0] and the new data |
189 | * in data[1], each channel cooresponding to a bit. */ | |
190 | if (data[0]) { | |
191 | s->state &= ~data[0]; | |
192 | s->state |= data[0] & data[1]; | |
193 | /* Write out the new digital output lines */ | |
194 | outb(s->state & 0xFF, dev->iobase); | |
195 | outb(s->state >> 8, dev->iobase + 1); | |
196 | } | |
a2714e3e | 197 | return insn->n; |
ba7914cd | 198 | } |
cfd02b71 | 199 | |
03668b10 IA |
200 | static void pc263_report_attach(struct comedi_device *dev) |
201 | { | |
04d66968 | 202 | const struct pc263_board *thisboard = comedi_board(dev); |
69fa3b75 | 203 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
03668b10 IA |
204 | char tmpbuf[40]; |
205 | ||
d2676944 | 206 | if (is_isa_board(thisboard)) |
03668b10 | 207 | snprintf(tmpbuf, sizeof(tmpbuf), "(base %#lx) ", dev->iobase); |
d2676944 | 208 | else if (is_pci_board(thisboard)) |
03668b10 | 209 | snprintf(tmpbuf, sizeof(tmpbuf), "(pci %s) ", |
69fa3b75 | 210 | pci_name(pcidev)); |
03668b10 IA |
211 | else |
212 | tmpbuf[0] = '\0'; | |
213 | dev_info(dev->class_dev, "%s %sattached\n", dev->board_name, tmpbuf); | |
214 | } | |
215 | ||
d8967b6e IA |
216 | static int pc263_common_attach(struct comedi_device *dev, unsigned long iobase) |
217 | { | |
218 | const struct pc263_board *thisboard = comedi_board(dev); | |
219 | struct comedi_subdevice *s; | |
220 | int ret; | |
221 | ||
222 | dev->board_name = thisboard->name; | |
223 | dev->iobase = iobase; | |
224 | ||
2f0b9d08 | 225 | ret = comedi_alloc_subdevices(dev, 1); |
8b6c5694 | 226 | if (ret) |
d8967b6e | 227 | return ret; |
d8967b6e | 228 | |
d8029dcf | 229 | s = &dev->subdevices[0]; |
d8967b6e IA |
230 | /* digital output subdevice */ |
231 | s->type = COMEDI_SUBD_DO; | |
232 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
233 | s->n_chan = 16; | |
234 | s->maxdata = 1; | |
235 | s->range_table = &range_digital; | |
236 | s->insn_bits = pc263_do_insn_bits; | |
237 | /* read initial relay state */ | |
238 | s->state = inb(dev->iobase) | (inb(dev->iobase + 1) << 8); | |
239 | ||
240 | pc263_report_attach(dev); | |
241 | return 1; | |
242 | } | |
243 | ||
244 | static int pc263_pci_common_attach(struct comedi_device *dev, | |
245 | struct pci_dev *pci_dev) | |
246 | { | |
d8967b6e IA |
247 | unsigned long iobase; |
248 | int ret; | |
249 | ||
69fa3b75 HS |
250 | comedi_set_hw_dev(dev, &pci_dev->dev); |
251 | ||
818f569f HS |
252 | ret = comedi_pci_enable(dev); |
253 | if (ret) | |
d8967b6e | 254 | return ret; |
d8967b6e | 255 | iobase = pci_resource_start(pci_dev, 2); |
818f569f | 256 | |
d8967b6e IA |
257 | return pc263_common_attach(dev, iobase); |
258 | } | |
259 | ||
cfd02b71 IA |
260 | /* |
261 | * Attach is called by the Comedi core to configure the driver | |
262 | * for a particular board. If you specified a board_name array | |
263 | * in the driver structure, dev->board_ptr contains that | |
264 | * address. | |
265 | */ | |
da91b269 | 266 | static int pc263_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
cfd02b71 | 267 | { |
04d66968 | 268 | const struct pc263_board *thisboard = comedi_board(dev); |
cfd02b71 IA |
269 | int ret; |
270 | ||
03668b10 | 271 | dev_info(dev->class_dev, PC263_DRIVER_NAME ": attach\n"); |
b4843c19 IA |
272 | |
273 | /* Process options and reserve resources according to bus type. */ | |
d2676944 | 274 | if (is_isa_board(thisboard)) { |
d8967b6e | 275 | unsigned long iobase = it->options[0]; |
03668b10 | 276 | ret = pc263_request_region(dev, iobase, PC263_IO_SIZE); |
b4843c19 IA |
277 | if (ret < 0) |
278 | return ret; | |
d8967b6e | 279 | return pc263_common_attach(dev, iobase); |
d2676944 | 280 | } else if (is_pci_board(thisboard)) { |
d8967b6e | 281 | struct pci_dev *pci_dev; |
b4843c19 | 282 | |
1c16519f HS |
283 | pci_dev = pc263_find_pci_dev(dev, it); |
284 | if (!pci_dev) | |
7ac75ba4 | 285 | return -EIO; |
d8967b6e | 286 | return pc263_pci_common_attach(dev, pci_dev); |
b4843c19 | 287 | } else { |
03668b10 IA |
288 | dev_err(dev->class_dev, PC263_DRIVER_NAME |
289 | ": BUG! cannot determine board type!\n"); | |
b4843c19 | 290 | return -EINVAL; |
cfd02b71 | 291 | } |
d8967b6e | 292 | } |
750af5e5 | 293 | |
d8967b6e | 294 | /* |
750af5e5 IA |
295 | * The auto_attach hook is called at PCI probe time via |
296 | * comedi_pci_auto_config(). dev->board_ptr is NULL on entry. | |
297 | * There should be a board entry matching the supplied PCI device. | |
d8967b6e | 298 | */ |
a690b7e5 | 299 | static int pc263_auto_attach(struct comedi_device *dev, |
750af5e5 | 300 | unsigned long context_unused) |
d8967b6e | 301 | { |
750af5e5 IA |
302 | struct pci_dev *pci_dev; |
303 | ||
87f8b20d | 304 | if (!DO_PCI) |
d8967b6e | 305 | return -EINVAL; |
cfd02b71 | 306 | |
750af5e5 | 307 | pci_dev = comedi_to_pci_dev(dev); |
d8967b6e IA |
308 | dev_info(dev->class_dev, PC263_DRIVER_NAME ": attach pci %s\n", |
309 | pci_name(pci_dev)); | |
d8967b6e IA |
310 | dev->board_ptr = pc263_find_pci_board(pci_dev); |
311 | if (dev->board_ptr == NULL) { | |
312 | dev_err(dev->class_dev, "BUG! cannot determine board type!\n"); | |
313 | return -EINVAL; | |
314 | } | |
a0234ade IA |
315 | /* |
316 | * Need to 'get' the PCI device to match the 'put' in pc263_detach(). | |
317 | * TODO: Remove the pci_dev_get() and matching pci_dev_put() once | |
318 | * support for manual attachment of PCI devices via pc263_attach() | |
319 | * has been removed. | |
320 | */ | |
321 | pci_dev_get(pci_dev); | |
d8967b6e | 322 | return pc263_pci_common_attach(dev, pci_dev); |
cfd02b71 IA |
323 | } |
324 | ||
484ecc95 | 325 | static void pc263_detach(struct comedi_device *dev) |
cfd02b71 | 326 | { |
6c292278 | 327 | const struct pc263_board *thisboard = comedi_board(dev); |
04d66968 | 328 | |
1d1171ff IA |
329 | if (!thisboard) |
330 | return; | |
6c292278 | 331 | if (is_isa_board(thisboard)) { |
b4843c19 IA |
332 | if (dev->iobase) |
333 | release_region(dev->iobase, PC263_IO_SIZE); | |
6c292278 IA |
334 | } else if (is_pci_board(thisboard)) { |
335 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); | |
7f072f54 HS |
336 | comedi_pci_disable(dev); |
337 | if (pcidev) | |
6c292278 | 338 | pci_dev_put(pcidev); |
cfd02b71 | 339 | } |
cfd02b71 IA |
340 | } |
341 | ||
342 | /* | |
ba7914cd IA |
343 | * The struct comedi_driver structure tells the Comedi core module |
344 | * which functions to call to configure/deconfigure (attach/detach) | |
345 | * the board, and also about the kernel module that contains | |
346 | * the device code. | |
cfd02b71 | 347 | */ |
ba7914cd IA |
348 | static struct comedi_driver amplc_pc263_driver = { |
349 | .driver_name = PC263_DRIVER_NAME, | |
350 | .module = THIS_MODULE, | |
351 | .attach = pc263_attach, | |
750af5e5 | 352 | .auto_attach = pc263_auto_attach, |
ba7914cd IA |
353 | .detach = pc263_detach, |
354 | .board_name = &pc263_boards[0].name, | |
355 | .offset = sizeof(struct pc263_board), | |
356 | .num_names = ARRAY_SIZE(pc263_boards), | |
357 | }; | |
cfd02b71 | 358 | |
87f8b20d | 359 | #if DO_PCI |
ba7914cd IA |
360 | static DEFINE_PCI_DEVICE_TABLE(pc263_pci_table) = { |
361 | { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI263) }, | |
362 | {0} | |
363 | }; | |
364 | MODULE_DEVICE_TABLE(pci, pc263_pci_table); | |
365 | ||
a690b7e5 | 366 | static int amplc_pc263_pci_probe(struct pci_dev *dev, |
b8f4ac23 | 367 | const struct pci_device_id *id) |
727b286b | 368 | { |
b8f4ac23 HS |
369 | return comedi_pci_auto_config(dev, &lc_pc263_driver, |
370 | id->driver_data); | |
727b286b AT |
371 | } |
372 | ||
40372f5f IA |
373 | static struct pci_driver amplc_pc263_pci_driver = { |
374 | .name = PC263_DRIVER_NAME, | |
727b286b | 375 | .id_table = pc263_pci_table, |
40372f5f | 376 | .probe = &lc_pc263_pci_probe, |
9901a4d7 | 377 | .remove = comedi_pci_auto_unconfig, |
727b286b | 378 | }; |
40372f5f | 379 | module_comedi_pci_driver(amplc_pc263_driver, amplc_pc263_pci_driver); |
cfd02b71 | 380 | #else |
40372f5f | 381 | module_comedi_driver(amplc_pc263_driver); |
cfd02b71 | 382 | #endif |
90f703d3 AT |
383 | |
384 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
385 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
386 | MODULE_LICENSE("GPL"); |