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