]>
Commit | Line | Data |
---|---|---|
6a5c8664 IA |
1 | /* |
2 | comedi/drivers/amplc_pc236.c | |
3 | Driver for Amplicon PC36AT and PCI236 DIO 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_pc236 | |
27 | Description: Amplicon PC36AT, PCI236 | |
28 | Author: Ian Abbott <abbotti@mev.co.uk> | |
29 | Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236) | |
0d6e5dad | 30 | Updated: Wed, 01 Apr 2009 15:41:25 +0100 |
6a5c8664 IA |
31 | Status: works |
32 | ||
33 | Configuration options - PC36AT: | |
34 | [0] - I/O port base address | |
35 | [1] - IRQ (optional) | |
36 | ||
37 | Configuration options - PCI236: | |
38 | [0] - PCI bus of device (optional) | |
39 | [1] - PCI slot of device (optional) | |
40 | If bus/slot is not specified, the first available PCI device will be | |
41 | used. | |
42 | ||
43 | The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing | |
44 | as subdevice 0. | |
45 | ||
46 | Subdevice 1 pretends to be a digital input device, but it always returns | |
47 | 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT, | |
0d6e5dad | 48 | a rising edge on port C bit 3 acts as an external trigger, which can be |
6a5c8664 IA |
49 | used to wake up tasks. This is like the comedi_parport device, but the |
50 | only way to physically disable the interrupt on the PC36AT is to remove | |
51 | the IRQ jumper. If no interrupt is connected, then subdevice 1 is | |
52 | unused. | |
53 | */ | |
54 | ||
33782dd5 | 55 | #include <linux/pci.h> |
70265d24 JS |
56 | #include <linux/interrupt.h> |
57 | ||
6a5c8664 IA |
58 | #include "../comedidev.h" |
59 | ||
27020ffe | 60 | #include "comedi_fc.h" |
6a5c8664 IA |
61 | #include "8255.h" |
62 | #include "plx9052.h" | |
63 | ||
64 | #define PC236_DRIVER_NAME "amplc_pc236" | |
65 | ||
00255d19 IA |
66 | #define DO_ISA IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA) |
67 | #define DO_PCI IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) | |
68 | ||
6a5c8664 | 69 | /* PCI236 PCI configuration register information */ |
6a5c8664 IA |
70 | #define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009 |
71 | #define PCI_DEVICE_ID_INVALID 0xffff | |
72 | ||
73 | /* PC36AT / PCI236 registers */ | |
74 | ||
75 | #define PC236_IO_SIZE 4 | |
76 | #define PC236_LCR_IO_SIZE 128 | |
77 | ||
78 | /* | |
79 | * INTCSR values for PCI236. | |
80 | */ | |
81 | /* Disable interrupt, also clear any interrupt there */ | |
53106ae6 | 82 | #define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \ |
9f7a344b BA |
83 | | PLX9052_INTCSR_LI1POL_HIGH \ |
84 | | PLX9052_INTCSR_LI2POL_HIGH \ | |
85 | | PLX9052_INTCSR_PCIENAB_DISABLED \ | |
86 | | PLX9052_INTCSR_LI1SEL_EDGE \ | |
87 | | PLX9052_INTCSR_LI1CLRINT_ASSERTED) | |
6a5c8664 | 88 | /* Enable interrupt, also clear any interrupt there. */ |
53106ae6 | 89 | #define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \ |
9f7a344b BA |
90 | | PLX9052_INTCSR_LI1POL_HIGH \ |
91 | | PLX9052_INTCSR_LI2POL_HIGH \ | |
92 | | PLX9052_INTCSR_PCIENAB_ENABLED \ | |
93 | | PLX9052_INTCSR_LI1SEL_EDGE \ | |
94 | | PLX9052_INTCSR_LI1CLRINT_ASSERTED) | |
6a5c8664 IA |
95 | |
96 | /* | |
97 | * Board descriptions for Amplicon PC36AT and PCI236. | |
98 | */ | |
99 | ||
100 | enum pc236_bustype { isa_bustype, pci_bustype }; | |
101 | enum pc236_model { pc36at_model, pci236_model, anypci_model }; | |
102 | ||
57ad8696 | 103 | struct pc236_board { |
6a5c8664 | 104 | const char *name; |
6a5c8664 IA |
105 | unsigned short devid; |
106 | enum pc236_bustype bustype; | |
107 | enum pc236_model model; | |
57ad8696 BP |
108 | }; |
109 | static const struct pc236_board pc236_boards[] = { | |
00255d19 | 110 | #if DO_ISA |
6a5c8664 | 111 | { |
916d7028 IA |
112 | .name = "pc36at", |
113 | .bustype = isa_bustype, | |
114 | .model = pc36at_model, | |
115 | }, | |
717ab674 | 116 | #endif |
00255d19 | 117 | #if DO_PCI |
6a5c8664 | 118 | { |
916d7028 IA |
119 | .name = "pci236", |
120 | .devid = PCI_DEVICE_ID_AMPLICON_PCI236, | |
121 | .bustype = pci_bustype, | |
122 | .model = pci236_model, | |
123 | }, | |
6a5c8664 | 124 | { |
916d7028 IA |
125 | .name = PC236_DRIVER_NAME, |
126 | .devid = PCI_DEVICE_ID_INVALID, | |
127 | .bustype = pci_bustype, | |
128 | .model = anypci_model, /* wildcard */ | |
129 | }, | |
6a5c8664 IA |
130 | #endif |
131 | }; | |
132 | ||
6a5c8664 IA |
133 | /* this structure is for data unique to this hardware driver. If |
134 | several hardware drivers keep similar information in this structure, | |
9f7a344b BA |
135 | feel free to suggest moving the variable to the struct comedi_device struct. |
136 | */ | |
f1ee810a | 137 | struct pc236_private { |
9f7a344b | 138 | unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */ |
6a5c8664 | 139 | int enable_irq; |
f1ee810a | 140 | }; |
6a5c8664 | 141 | |
409861ff IA |
142 | /* test if ISA supported and this is an ISA board */ |
143 | static inline bool is_isa_board(const struct pc236_board *board) | |
144 | { | |
00255d19 | 145 | return DO_ISA && board->bustype == isa_bustype; |
409861ff IA |
146 | } |
147 | ||
148 | /* test if PCI supported and this is a PCI board */ | |
149 | static inline bool is_pci_board(const struct pc236_board *board) | |
150 | { | |
00255d19 | 151 | return DO_PCI && board->bustype == pci_bustype; |
409861ff IA |
152 | } |
153 | ||
18e41de0 IA |
154 | /* |
155 | * This function looks for a board matching the supplied PCI device. | |
156 | */ | |
18e41de0 IA |
157 | static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev) |
158 | { | |
159 | unsigned int i; | |
160 | ||
161 | for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) | |
409861ff | 162 | if (is_pci_board(&pc236_boards[i]) && |
18e41de0 IA |
163 | pci_dev->device == pc236_boards[i].devid) |
164 | return &pc236_boards[i]; | |
165 | return NULL; | |
166 | } | |
18e41de0 | 167 | |
6a5c8664 IA |
168 | /* |
169 | * This function looks for a PCI device matching the requested board name, | |
170 | * bus and slot. | |
171 | */ | |
ed7fd225 HS |
172 | static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev, |
173 | struct comedi_devconfig *it) | |
6a5c8664 | 174 | { |
24ffe74c | 175 | const struct pc236_board *thisboard = comedi_board(dev); |
6a5c8664 | 176 | struct pci_dev *pci_dev = NULL; |
ed7fd225 HS |
177 | int bus = it->options[0]; |
178 | int slot = it->options[1]; | |
6a5c8664 | 179 | |
ed7fd225 | 180 | for_each_pci_dev(pci_dev) { |
6a5c8664 | 181 | if (bus || slot) { |
ed7fd225 HS |
182 | if (bus != pci_dev->bus->number || |
183 | slot != PCI_SLOT(pci_dev->devfn)) | |
6a5c8664 IA |
184 | continue; |
185 | } | |
ed7fd225 HS |
186 | if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) |
187 | continue; | |
188 | ||
6a5c8664 | 189 | if (thisboard->model == anypci_model) { |
18e41de0 IA |
190 | /* Wildcard board matches any supported PCI board. */ |
191 | const struct pc236_board *foundboard; | |
ed7fd225 | 192 | |
18e41de0 IA |
193 | foundboard = pc236_find_pci_board(pci_dev); |
194 | if (foundboard == NULL) | |
6a5c8664 | 195 | continue; |
18e41de0 | 196 | /* Replace wildcard board_ptr. */ |
ed7fd225 | 197 | dev->board_ptr = foundboard; |
6a5c8664 IA |
198 | } else { |
199 | /* Match specific model name. */ | |
200 | if (pci_dev->device != thisboard->devid) | |
201 | continue; | |
202 | } | |
273ba547 | 203 | return pci_dev; |
6a5c8664 | 204 | } |
ed7fd225 HS |
205 | dev_err(dev->class_dev, |
206 | "No supported board found! (req. bus %d, slot %d)\n", | |
207 | bus, slot); | |
273ba547 | 208 | return NULL; |
6a5c8664 | 209 | } |
6a5c8664 | 210 | |
6a5c8664 IA |
211 | /* |
212 | * This function checks and requests an I/O region, reporting an error | |
213 | * if there is a conflict. | |
214 | */ | |
62bc23d1 | 215 | static int pc236_request_region(struct comedi_device *dev, unsigned long from, |
0a85b6f0 | 216 | unsigned long extent) |
6a5c8664 IA |
217 | { |
218 | if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) { | |
62bc23d1 IA |
219 | dev_err(dev->class_dev, "I/O port conflict (%#lx,%lu)!\n", |
220 | from, extent); | |
6a5c8664 IA |
221 | return -EIO; |
222 | } | |
223 | return 0; | |
224 | } | |
225 | ||
226 | /* | |
227 | * This function is called to mark the interrupt as disabled (no command | |
228 | * configured on subdevice 1) and to physically disable the interrupt | |
229 | * (not possible on the PC36AT, except by removing the IRQ jumper!). | |
230 | */ | |
da91b269 | 231 | static void pc236_intr_disable(struct comedi_device *dev) |
6a5c8664 | 232 | { |
15bad7b5 | 233 | const struct pc236_board *thisboard = comedi_board(dev); |
24ffe74c | 234 | struct pc236_private *devpriv = dev->private; |
6a5c8664 IA |
235 | unsigned long flags; |
236 | ||
5f74ea14 | 237 | spin_lock_irqsave(&dev->spinlock, flags); |
6a5c8664 | 238 | devpriv->enable_irq = 0; |
15bad7b5 | 239 | if (is_pci_board(thisboard)) |
6a5c8664 | 240 | outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR); |
5f74ea14 | 241 | spin_unlock_irqrestore(&dev->spinlock, flags); |
6a5c8664 IA |
242 | } |
243 | ||
244 | /* | |
245 | * This function is called to mark the interrupt as enabled (a command | |
246 | * configured on subdevice 1) and to physically enable the interrupt | |
247 | * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!). | |
248 | */ | |
da91b269 | 249 | static void pc236_intr_enable(struct comedi_device *dev) |
6a5c8664 | 250 | { |
15bad7b5 | 251 | const struct pc236_board *thisboard = comedi_board(dev); |
24ffe74c | 252 | struct pc236_private *devpriv = dev->private; |
6a5c8664 IA |
253 | unsigned long flags; |
254 | ||
5f74ea14 | 255 | spin_lock_irqsave(&dev->spinlock, flags); |
6a5c8664 | 256 | devpriv->enable_irq = 1; |
15bad7b5 | 257 | if (is_pci_board(thisboard)) |
6a5c8664 | 258 | outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR); |
5f74ea14 | 259 | spin_unlock_irqrestore(&dev->spinlock, flags); |
6a5c8664 IA |
260 | } |
261 | ||
262 | /* | |
263 | * This function is called when an interrupt occurs to check whether | |
264 | * the interrupt has been marked as enabled and was generated by the | |
265 | * board. If so, the function prepares the hardware for the next | |
266 | * interrupt. | |
267 | * Returns 0 if the interrupt should be ignored. | |
268 | */ | |
da91b269 | 269 | static int pc236_intr_check(struct comedi_device *dev) |
6a5c8664 | 270 | { |
15bad7b5 | 271 | const struct pc236_board *thisboard = comedi_board(dev); |
24ffe74c | 272 | struct pc236_private *devpriv = dev->private; |
6a5c8664 IA |
273 | int retval = 0; |
274 | unsigned long flags; | |
275 | ||
5f74ea14 | 276 | spin_lock_irqsave(&dev->spinlock, flags); |
6a5c8664 IA |
277 | if (devpriv->enable_irq) { |
278 | retval = 1; | |
15bad7b5 | 279 | if (is_pci_board(thisboard)) { |
6a5c8664 | 280 | if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR) |
0a85b6f0 MT |
281 | & PLX9052_INTCSR_LI1STAT_MASK) |
282 | == PLX9052_INTCSR_LI1STAT_INACTIVE) { | |
6a5c8664 IA |
283 | retval = 0; |
284 | } else { | |
285 | /* Clear interrupt and keep it enabled. */ | |
286 | outl(PCI236_INTR_ENABLE, | |
0a85b6f0 | 287 | devpriv->lcr_iobase + PLX9052_INTCSR); |
6a5c8664 IA |
288 | } |
289 | } | |
6a5c8664 | 290 | } |
5f74ea14 | 291 | spin_unlock_irqrestore(&dev->spinlock, flags); |
6a5c8664 IA |
292 | |
293 | return retval; | |
294 | } | |
295 | ||
296 | /* | |
297 | * Input from subdevice 1. | |
298 | * Copied from the comedi_parport driver. | |
299 | */ | |
0a85b6f0 MT |
300 | static int pc236_intr_insn(struct comedi_device *dev, |
301 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
302 | unsigned int *data) | |
6a5c8664 IA |
303 | { |
304 | data[1] = 0; | |
a2714e3e | 305 | return insn->n; |
6a5c8664 IA |
306 | } |
307 | ||
308 | /* | |
309 | * Subdevice 1 command test. | |
310 | * Copied from the comedi_parport driver. | |
311 | */ | |
0a85b6f0 MT |
312 | static int pc236_intr_cmdtest(struct comedi_device *dev, |
313 | struct comedi_subdevice *s, | |
314 | struct comedi_cmd *cmd) | |
6a5c8664 IA |
315 | { |
316 | int err = 0; | |
6a5c8664 | 317 | |
27020ffe | 318 | /* Step 1 : check if triggers are trivially valid */ |
6a5c8664 | 319 | |
27020ffe HS |
320 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); |
321 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); | |
322 | err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); | |
323 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); | |
324 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE); | |
6a5c8664 IA |
325 | |
326 | if (err) | |
327 | return 1; | |
328 | ||
27020ffe HS |
329 | /* Step 2a : make sure trigger sources are unique */ |
330 | /* Step 2b : and mutually compatible */ | |
6a5c8664 IA |
331 | |
332 | if (err) | |
333 | return 2; | |
334 | ||
7efbbc37 | 335 | /* Step 3: check it arguments are trivially valid */ |
6a5c8664 | 336 | |
7efbbc37 HS |
337 | err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); |
338 | err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); | |
339 | err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); | |
340 | err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, 1); | |
341 | err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); | |
6a5c8664 IA |
342 | |
343 | if (err) | |
344 | return 3; | |
345 | ||
346 | /* step 4: ignored */ | |
347 | ||
348 | if (err) | |
349 | return 4; | |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
354 | /* | |
355 | * Subdevice 1 command. | |
356 | */ | |
da91b269 | 357 | static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
6a5c8664 IA |
358 | { |
359 | pc236_intr_enable(dev); | |
360 | ||
361 | return 0; | |
362 | } | |
363 | ||
364 | /* | |
365 | * Subdevice 1 cancel command. | |
366 | */ | |
0a85b6f0 MT |
367 | static int pc236_intr_cancel(struct comedi_device *dev, |
368 | struct comedi_subdevice *s) | |
6a5c8664 IA |
369 | { |
370 | pc236_intr_disable(dev); | |
371 | ||
372 | return 0; | |
373 | } | |
374 | ||
375 | /* | |
376 | * Interrupt service routine. | |
377 | * Based on the comedi_parport driver. | |
378 | */ | |
70265d24 | 379 | static irqreturn_t pc236_interrupt(int irq, void *d) |
6a5c8664 | 380 | { |
71b5f4f1 | 381 | struct comedi_device *dev = d; |
39d37be4 | 382 | struct comedi_subdevice *s = &dev->subdevices[1]; |
6a5c8664 IA |
383 | int handled; |
384 | ||
385 | handled = pc236_intr_check(dev); | |
386 | if (dev->attached && handled) { | |
387 | comedi_buf_put(s->async, 0); | |
388 | s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; | |
389 | comedi_event(dev, s); | |
390 | } | |
391 | return IRQ_RETVAL(handled); | |
392 | } | |
90f703d3 | 393 | |
62bc23d1 IA |
394 | static void pc236_report_attach(struct comedi_device *dev, unsigned int irq) |
395 | { | |
24ffe74c | 396 | const struct pc236_board *thisboard = comedi_board(dev); |
b6d446b5 | 397 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
62bc23d1 IA |
398 | char tmpbuf[60]; |
399 | int tmplen; | |
400 | ||
409861ff | 401 | if (is_isa_board(thisboard)) |
62bc23d1 IA |
402 | tmplen = scnprintf(tmpbuf, sizeof(tmpbuf), |
403 | "(base %#lx) ", dev->iobase); | |
409861ff | 404 | else if (is_pci_board(thisboard)) |
a46e759f | 405 | tmplen = scnprintf(tmpbuf, sizeof(tmpbuf), |
b6d446b5 | 406 | "(pci %s) ", pci_name(pcidev)); |
409861ff | 407 | else |
62bc23d1 | 408 | tmplen = 0; |
62bc23d1 IA |
409 | if (irq) |
410 | tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen, | |
411 | "(irq %u%s) ", irq, | |
412 | (dev->irq ? "" : " UNAVAILABLE")); | |
413 | else | |
414 | tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen, | |
415 | "(no irq) "); | |
416 | dev_info(dev->class_dev, "%s %sattached\n", | |
417 | dev->board_name, tmpbuf); | |
418 | } | |
419 | ||
18e41de0 IA |
420 | static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase, |
421 | unsigned int irq, unsigned long req_irq_flags) | |
7bd06f69 | 422 | { |
24ffe74c | 423 | const struct pc236_board *thisboard = comedi_board(dev); |
7bd06f69 | 424 | struct comedi_subdevice *s; |
7bd06f69 IA |
425 | int ret; |
426 | ||
7bd06f69 | 427 | dev->board_name = thisboard->name; |
7bd06f69 IA |
428 | dev->iobase = iobase; |
429 | ||
2f0b9d08 | 430 | ret = comedi_alloc_subdevices(dev, 2); |
8b6c5694 | 431 | if (ret) |
7bd06f69 | 432 | return ret; |
7bd06f69 | 433 | |
39d37be4 | 434 | s = &dev->subdevices[0]; |
7bd06f69 IA |
435 | /* digital i/o subdevice (8255) */ |
436 | ret = subdev_8255_init(dev, s, NULL, iobase); | |
437 | if (ret < 0) { | |
62bc23d1 | 438 | dev_err(dev->class_dev, "error! out of memory!\n"); |
7bd06f69 IA |
439 | return ret; |
440 | } | |
39d37be4 | 441 | s = &dev->subdevices[1]; |
7bd06f69 IA |
442 | dev->read_subdev = s; |
443 | s->type = COMEDI_SUBD_UNUSED; | |
444 | pc236_intr_disable(dev); | |
445 | if (irq) { | |
18e41de0 | 446 | if (request_irq(irq, pc236_interrupt, req_irq_flags, |
7bd06f69 IA |
447 | PC236_DRIVER_NAME, dev) >= 0) { |
448 | dev->irq = irq; | |
449 | s->type = COMEDI_SUBD_DI; | |
450 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; | |
451 | s->n_chan = 1; | |
452 | s->maxdata = 1; | |
453 | s->range_table = &range_digital; | |
454 | s->insn_bits = pc236_intr_insn; | |
455 | s->do_cmdtest = pc236_intr_cmdtest; | |
456 | s->do_cmd = pc236_intr_cmd; | |
457 | s->cancel = pc236_intr_cancel; | |
458 | } | |
459 | } | |
62bc23d1 | 460 | pc236_report_attach(dev, irq); |
7bd06f69 IA |
461 | return 1; |
462 | } | |
463 | ||
18e41de0 IA |
464 | static int pc236_pci_common_attach(struct comedi_device *dev, |
465 | struct pci_dev *pci_dev) | |
466 | { | |
467 | struct pc236_private *devpriv = dev->private; | |
468 | unsigned long iobase; | |
469 | int ret; | |
470 | ||
b6d446b5 HS |
471 | comedi_set_hw_dev(dev, &pci_dev->dev); |
472 | ||
18e41de0 IA |
473 | ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME); |
474 | if (ret < 0) { | |
475 | dev_err(dev->class_dev, | |
476 | "error! cannot enable PCI device and request regions!\n"); | |
477 | return ret; | |
478 | } | |
479 | devpriv->lcr_iobase = pci_resource_start(pci_dev, 1); | |
480 | iobase = pci_resource_start(pci_dev, 2); | |
481 | return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED); | |
482 | } | |
18e41de0 IA |
483 | |
484 | /* | |
485 | * Attach is called by the Comedi core to configure the driver | |
486 | * for a particular board. If you specified a board_name array | |
487 | * in the driver structure, dev->board_ptr contains that | |
488 | * address. | |
489 | */ | |
490 | static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it) | |
491 | { | |
492 | const struct pc236_board *thisboard = comedi_board(dev); | |
9a1a6cf8 | 493 | struct pc236_private *devpriv; |
18e41de0 IA |
494 | int ret; |
495 | ||
496 | dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach\n"); | |
9a1a6cf8 | 497 | |
c34fa261 HS |
498 | devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); |
499 | if (!devpriv) | |
500 | return -ENOMEM; | |
501 | dev->private = devpriv; | |
9a1a6cf8 | 502 | |
18e41de0 | 503 | /* Process options according to bus type. */ |
409861ff | 504 | if (is_isa_board(thisboard)) { |
a46e759f IA |
505 | unsigned long iobase = it->options[0]; |
506 | unsigned int irq = it->options[1]; | |
507 | ret = pc236_request_region(dev, iobase, PC236_IO_SIZE); | |
508 | if (ret < 0) | |
509 | return ret; | |
510 | return pc236_common_attach(dev, iobase, irq, 0); | |
409861ff | 511 | } else if (is_pci_board(thisboard)) { |
a46e759f IA |
512 | struct pci_dev *pci_dev; |
513 | ||
ed7fd225 HS |
514 | pci_dev = pc236_find_pci_dev(dev, it); |
515 | if (!pci_dev) | |
a46e759f IA |
516 | return -EIO; |
517 | return pc236_pci_common_attach(dev, pci_dev); | |
518 | } else { | |
18e41de0 IA |
519 | dev_err(dev->class_dev, PC236_DRIVER_NAME |
520 | ": BUG! cannot determine board type!\n"); | |
a46e759f | 521 | return -EINVAL; |
18e41de0 | 522 | } |
18e41de0 IA |
523 | } |
524 | ||
525 | /* | |
750af5e5 IA |
526 | * The auto_attach hook is called at PCI probe time via |
527 | * comedi_pci_auto_config(). dev->board_ptr is NULL on entry. | |
528 | * There should be a board entry matching the supplied PCI device. | |
18e41de0 | 529 | */ |
a690b7e5 | 530 | static int pc236_auto_attach(struct comedi_device *dev, |
750af5e5 | 531 | unsigned long context_unused) |
18e41de0 | 532 | { |
750af5e5 | 533 | struct pci_dev *pci_dev = comedi_to_pci_dev(dev); |
9a1a6cf8 | 534 | struct pc236_private *devpriv; |
18e41de0 | 535 | |
00255d19 | 536 | if (!DO_PCI) |
a46e759f IA |
537 | return -EINVAL; |
538 | ||
18e41de0 IA |
539 | dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n", |
540 | pci_name(pci_dev)); | |
9a1a6cf8 | 541 | |
c34fa261 HS |
542 | devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); |
543 | if (!devpriv) | |
544 | return -ENOMEM; | |
545 | dev->private = devpriv; | |
9a1a6cf8 | 546 | |
18e41de0 IA |
547 | dev->board_ptr = pc236_find_pci_board(pci_dev); |
548 | if (dev->board_ptr == NULL) { | |
549 | dev_err(dev->class_dev, "BUG! cannot determine board type!\n"); | |
550 | return -EINVAL; | |
551 | } | |
3abcfe0e IA |
552 | /* |
553 | * Need to 'get' the PCI device to match the 'put' in pc236_detach(). | |
554 | * TODO: Remove the pci_dev_get() and matching pci_dev_put() once | |
555 | * support for manual attachment of PCI devices via pc236_attach() | |
556 | * has been removed. | |
557 | */ | |
558 | pci_dev_get(pci_dev); | |
18e41de0 IA |
559 | return pc236_pci_common_attach(dev, pci_dev); |
560 | } | |
18e41de0 | 561 | |
7bd06f69 IA |
562 | static void pc236_detach(struct comedi_device *dev) |
563 | { | |
02918c00 | 564 | const struct pc236_board *thisboard = comedi_board(dev); |
24ffe74c | 565 | |
2db012bd IA |
566 | if (!thisboard) |
567 | return; | |
aaeb61a9 | 568 | if (dev->iobase) |
7bd06f69 IA |
569 | pc236_intr_disable(dev); |
570 | if (dev->irq) | |
571 | free_irq(dev->irq, dev); | |
572 | if (dev->subdevices) | |
39d37be4 | 573 | subdev_8255_cleanup(dev, &dev->subdevices[0]); |
02918c00 | 574 | if (is_isa_board(thisboard)) { |
b6d446b5 HS |
575 | if (dev->iobase) |
576 | release_region(dev->iobase, PC236_IO_SIZE); | |
02918c00 IA |
577 | } else if (is_pci_board(thisboard)) { |
578 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); | |
579 | if (pcidev) { | |
580 | if (dev->iobase) | |
581 | comedi_pci_disable(pcidev); | |
582 | pci_dev_put(pcidev); | |
583 | } | |
7bd06f69 IA |
584 | } |
585 | } | |
586 | ||
587 | /* | |
588 | * The struct comedi_driver structure tells the Comedi core module | |
589 | * which functions to call to configure/deconfigure (attach/detach) | |
590 | * the board, and also about the kernel module that contains | |
591 | * the device code. | |
592 | */ | |
593 | static struct comedi_driver amplc_pc236_driver = { | |
594 | .driver_name = PC236_DRIVER_NAME, | |
595 | .module = THIS_MODULE, | |
596 | .attach = pc236_attach, | |
750af5e5 | 597 | .auto_attach = pc236_auto_attach, |
7bd06f69 IA |
598 | .detach = pc236_detach, |
599 | .board_name = &pc236_boards[0].name, | |
600 | .offset = sizeof(struct pc236_board), | |
601 | .num_names = ARRAY_SIZE(pc236_boards), | |
602 | }; | |
603 | ||
00255d19 | 604 | #if DO_PCI |
7bd06f69 IA |
605 | static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = { |
606 | { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) }, | |
607 | {0} | |
608 | }; | |
609 | ||
610 | MODULE_DEVICE_TABLE(pci, pc236_pci_table); | |
611 | ||
a690b7e5 | 612 | static int amplc_pc236_pci_probe(struct pci_dev *dev, |
7bd06f69 IA |
613 | const struct pci_device_id *ent) |
614 | { | |
615 | return comedi_pci_auto_config(dev, &lc_pc236_driver); | |
616 | } | |
617 | ||
7bd06f69 IA |
618 | static struct pci_driver amplc_pc236_pci_driver = { |
619 | .name = PC236_DRIVER_NAME, | |
620 | .id_table = pc236_pci_table, | |
621 | .probe = &lc_pc236_pci_probe, | |
9901a4d7 | 622 | .remove = comedi_pci_auto_unconfig, |
7bd06f69 IA |
623 | }; |
624 | ||
625 | module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver); | |
626 | #else | |
627 | module_comedi_driver(amplc_pc236_driver); | |
628 | #endif | |
629 | ||
90f703d3 AT |
630 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
631 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
632 | MODULE_LICENSE("GPL"); |