]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blame - drivers/staging/comedi/drivers/amplc_pc236.c
staging: comedi: amplc_pc236: add helper functions to check bus type
[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 {
9f7a344b 134 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
6a5c8664 135 int enable_irq;
f1ee810a 136};
6a5c8664 137
409861ff
IA
138/* test if ISA supported and this is an ISA board */
139static inline bool is_isa_board(const struct pc236_board *board)
140{
141 return IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
142 && board->bustype == isa_bustype;
143}
144
145/* test if PCI supported and this is a PCI board */
146static inline bool is_pci_board(const struct pc236_board *board)
147{
148 return IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
149 && board->bustype == pci_bustype;
150}
151
18e41de0
IA
152/*
153 * This function looks for a board matching the supplied PCI device.
154 */
18e41de0
IA
155static const struct pc236_board *pc236_find_pci_board(struct pci_dev *pci_dev)
156{
157 unsigned int i;
158
159 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++)
409861ff 160 if (is_pci_board(&pc236_boards[i]) &&
18e41de0
IA
161 pci_dev->device == pc236_boards[i].devid)
162 return &pc236_boards[i];
163 return NULL;
164}
18e41de0 165
6a5c8664
IA
166/*
167 * This function looks for a PCI device matching the requested board name,
168 * bus and slot.
169 */
ed7fd225
HS
170static struct pci_dev *pc236_find_pci_dev(struct comedi_device *dev,
171 struct comedi_devconfig *it)
6a5c8664 172{
24ffe74c 173 const struct pc236_board *thisboard = comedi_board(dev);
6a5c8664 174 struct pci_dev *pci_dev = NULL;
ed7fd225
HS
175 int bus = it->options[0];
176 int slot = it->options[1];
6a5c8664 177
ed7fd225 178 for_each_pci_dev(pci_dev) {
6a5c8664 179 if (bus || slot) {
ed7fd225
HS
180 if (bus != pci_dev->bus->number ||
181 slot != PCI_SLOT(pci_dev->devfn))
6a5c8664
IA
182 continue;
183 }
ed7fd225
HS
184 if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON)
185 continue;
186
6a5c8664 187 if (thisboard->model == anypci_model) {
18e41de0
IA
188 /* Wildcard board matches any supported PCI board. */
189 const struct pc236_board *foundboard;
ed7fd225 190
18e41de0
IA
191 foundboard = pc236_find_pci_board(pci_dev);
192 if (foundboard == NULL)
6a5c8664 193 continue;
18e41de0 194 /* Replace wildcard board_ptr. */
ed7fd225 195 dev->board_ptr = foundboard;
6a5c8664
IA
196 } else {
197 /* Match specific model name. */
198 if (pci_dev->device != thisboard->devid)
199 continue;
200 }
273ba547 201 return pci_dev;
6a5c8664 202 }
ed7fd225
HS
203 dev_err(dev->class_dev,
204 "No supported board found! (req. bus %d, slot %d)\n",
205 bus, slot);
273ba547 206 return NULL;
6a5c8664 207}
6a5c8664 208
6a5c8664
IA
209/*
210 * This function checks and requests an I/O region, reporting an error
211 * if there is a conflict.
212 */
62bc23d1 213static int pc236_request_region(struct comedi_device *dev, unsigned long from,
0a85b6f0 214 unsigned long extent)
6a5c8664
IA
215{
216 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
62bc23d1
IA
217 dev_err(dev->class_dev, "I/O port conflict (%#lx,%lu)!\n",
218 from, extent);
6a5c8664
IA
219 return -EIO;
220 }
221 return 0;
222}
223
224/*
225 * This function is called to mark the interrupt as disabled (no command
226 * configured on subdevice 1) and to physically disable the interrupt
227 * (not possible on the PC36AT, except by removing the IRQ jumper!).
228 */
da91b269 229static void pc236_intr_disable(struct comedi_device *dev)
6a5c8664 230{
24ffe74c 231 struct pc236_private *devpriv = dev->private;
6a5c8664
IA
232 unsigned long flags;
233
5f74ea14 234 spin_lock_irqsave(&dev->spinlock, flags);
6a5c8664 235 devpriv->enable_irq = 0;
a46e759f 236 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) && devpriv->lcr_iobase)
6a5c8664 237 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
5f74ea14 238 spin_unlock_irqrestore(&dev->spinlock, flags);
6a5c8664
IA
239}
240
241/*
242 * This function is called to mark the interrupt as enabled (a command
243 * configured on subdevice 1) and to physically enable the interrupt
244 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
245 */
da91b269 246static void pc236_intr_enable(struct comedi_device *dev)
6a5c8664 247{
24ffe74c 248 struct pc236_private *devpriv = dev->private;
6a5c8664
IA
249 unsigned long flags;
250
5f74ea14 251 spin_lock_irqsave(&dev->spinlock, flags);
6a5c8664 252 devpriv->enable_irq = 1;
a46e759f 253 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) && devpriv->lcr_iobase)
6a5c8664 254 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
5f74ea14 255 spin_unlock_irqrestore(&dev->spinlock, flags);
6a5c8664
IA
256}
257
258/*
259 * This function is called when an interrupt occurs to check whether
260 * the interrupt has been marked as enabled and was generated by the
261 * board. If so, the function prepares the hardware for the next
262 * interrupt.
263 * Returns 0 if the interrupt should be ignored.
264 */
da91b269 265static int pc236_intr_check(struct comedi_device *dev)
6a5c8664 266{
24ffe74c 267 struct pc236_private *devpriv = dev->private;
6a5c8664
IA
268 int retval = 0;
269 unsigned long flags;
270
5f74ea14 271 spin_lock_irqsave(&dev->spinlock, flags);
6a5c8664
IA
272 if (devpriv->enable_irq) {
273 retval = 1;
a46e759f
IA
274 if (IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI) &&
275 devpriv->lcr_iobase) {
6a5c8664 276 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
0a85b6f0
MT
277 & PLX9052_INTCSR_LI1STAT_MASK)
278 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
6a5c8664
IA
279 retval = 0;
280 } else {
281 /* Clear interrupt and keep it enabled. */
282 outl(PCI236_INTR_ENABLE,
0a85b6f0 283 devpriv->lcr_iobase + PLX9052_INTCSR);
6a5c8664
IA
284 }
285 }
6a5c8664 286 }
5f74ea14 287 spin_unlock_irqrestore(&dev->spinlock, flags);
6a5c8664
IA
288
289 return retval;
290}
291
292/*
293 * Input from subdevice 1.
294 * Copied from the comedi_parport driver.
295 */
0a85b6f0
MT
296static int pc236_intr_insn(struct comedi_device *dev,
297 struct comedi_subdevice *s, struct comedi_insn *insn,
298 unsigned int *data)
6a5c8664
IA
299{
300 data[1] = 0;
a2714e3e 301 return insn->n;
6a5c8664
IA
302}
303
304/*
305 * Subdevice 1 command test.
306 * Copied from the comedi_parport driver.
307 */
0a85b6f0
MT
308static int pc236_intr_cmdtest(struct comedi_device *dev,
309 struct comedi_subdevice *s,
310 struct comedi_cmd *cmd)
6a5c8664
IA
311{
312 int err = 0;
313 int tmp;
314
315 /* step 1 */
316
317 tmp = cmd->start_src;
318 cmd->start_src &= TRIG_NOW;
319 if (!cmd->start_src || tmp != cmd->start_src)
320 err++;
321
322 tmp = cmd->scan_begin_src;
323 cmd->scan_begin_src &= TRIG_EXT;
324 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
325 err++;
326
327 tmp = cmd->convert_src;
328 cmd->convert_src &= TRIG_FOLLOW;
329 if (!cmd->convert_src || tmp != cmd->convert_src)
330 err++;
331
332 tmp = cmd->scan_end_src;
333 cmd->scan_end_src &= TRIG_COUNT;
334 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
335 err++;
336
337 tmp = cmd->stop_src;
338 cmd->stop_src &= TRIG_NONE;
339 if (!cmd->stop_src || tmp != cmd->stop_src)
340 err++;
341
342 if (err)
343 return 1;
344
345 /* step 2: ignored */
346
347 if (err)
348 return 2;
349
350 /* step 3: */
351
352 if (cmd->start_arg != 0) {
353 cmd->start_arg = 0;
354 err++;
355 }
356 if (cmd->scan_begin_arg != 0) {
357 cmd->scan_begin_arg = 0;
358 err++;
359 }
360 if (cmd->convert_arg != 0) {
361 cmd->convert_arg = 0;
362 err++;
363 }
364 if (cmd->scan_end_arg != 1) {
365 cmd->scan_end_arg = 1;
366 err++;
367 }
368 if (cmd->stop_arg != 0) {
369 cmd->stop_arg = 0;
370 err++;
371 }
372
373 if (err)
374 return 3;
375
376 /* step 4: ignored */
377
378 if (err)
379 return 4;
380
381 return 0;
382}
383
384/*
385 * Subdevice 1 command.
386 */
da91b269 387static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
6a5c8664
IA
388{
389 pc236_intr_enable(dev);
390
391 return 0;
392}
393
394/*
395 * Subdevice 1 cancel command.
396 */
0a85b6f0
MT
397static int pc236_intr_cancel(struct comedi_device *dev,
398 struct comedi_subdevice *s)
6a5c8664
IA
399{
400 pc236_intr_disable(dev);
401
402 return 0;
403}
404
405/*
406 * Interrupt service routine.
407 * Based on the comedi_parport driver.
408 */
70265d24 409static irqreturn_t pc236_interrupt(int irq, void *d)
6a5c8664 410{
71b5f4f1 411 struct comedi_device *dev = d;
34c43922 412 struct comedi_subdevice *s = dev->subdevices + 1;
6a5c8664
IA
413 int handled;
414
415 handled = pc236_intr_check(dev);
416 if (dev->attached && handled) {
417 comedi_buf_put(s->async, 0);
418 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
419 comedi_event(dev, s);
420 }
421 return IRQ_RETVAL(handled);
422}
90f703d3 423
62bc23d1
IA
424static void pc236_report_attach(struct comedi_device *dev, unsigned int irq)
425{
24ffe74c 426 const struct pc236_board *thisboard = comedi_board(dev);
b6d446b5 427 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
62bc23d1
IA
428 char tmpbuf[60];
429 int tmplen;
430
409861ff 431 if (is_isa_board(thisboard))
62bc23d1
IA
432 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
433 "(base %#lx) ", dev->iobase);
409861ff 434 else if (is_pci_board(thisboard))
a46e759f 435 tmplen = scnprintf(tmpbuf, sizeof(tmpbuf),
b6d446b5 436 "(pci %s) ", pci_name(pcidev));
409861ff 437 else
62bc23d1 438 tmplen = 0;
62bc23d1
IA
439 if (irq)
440 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
441 "(irq %u%s) ", irq,
442 (dev->irq ? "" : " UNAVAILABLE"));
443 else
444 tmplen += scnprintf(&tmpbuf[tmplen], sizeof(tmpbuf) - tmplen,
445 "(no irq) ");
446 dev_info(dev->class_dev, "%s %sattached\n",
447 dev->board_name, tmpbuf);
448}
449
18e41de0
IA
450static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
451 unsigned int irq, unsigned long req_irq_flags)
7bd06f69 452{
24ffe74c 453 const struct pc236_board *thisboard = comedi_board(dev);
7bd06f69 454 struct comedi_subdevice *s;
7bd06f69
IA
455 int ret;
456
7bd06f69 457 dev->board_name = thisboard->name;
7bd06f69
IA
458 dev->iobase = iobase;
459
2f0b9d08 460 ret = comedi_alloc_subdevices(dev, 2);
8b6c5694 461 if (ret)
7bd06f69 462 return ret;
7bd06f69
IA
463
464 s = dev->subdevices + 0;
465 /* digital i/o subdevice (8255) */
466 ret = subdev_8255_init(dev, s, NULL, iobase);
467 if (ret < 0) {
62bc23d1 468 dev_err(dev->class_dev, "error! out of memory!\n");
7bd06f69
IA
469 return ret;
470 }
471 s = dev->subdevices + 1;
472 dev->read_subdev = s;
473 s->type = COMEDI_SUBD_UNUSED;
474 pc236_intr_disable(dev);
475 if (irq) {
18e41de0 476 if (request_irq(irq, pc236_interrupt, req_irq_flags,
7bd06f69
IA
477 PC236_DRIVER_NAME, dev) >= 0) {
478 dev->irq = irq;
479 s->type = COMEDI_SUBD_DI;
480 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
481 s->n_chan = 1;
482 s->maxdata = 1;
483 s->range_table = &range_digital;
484 s->insn_bits = pc236_intr_insn;
485 s->do_cmdtest = pc236_intr_cmdtest;
486 s->do_cmd = pc236_intr_cmd;
487 s->cancel = pc236_intr_cancel;
488 }
489 }
62bc23d1 490 pc236_report_attach(dev, irq);
7bd06f69
IA
491 return 1;
492}
493
18e41de0
IA
494static int pc236_pci_common_attach(struct comedi_device *dev,
495 struct pci_dev *pci_dev)
496{
497 struct pc236_private *devpriv = dev->private;
498 unsigned long iobase;
499 int ret;
500
b6d446b5
HS
501 comedi_set_hw_dev(dev, &pci_dev->dev);
502
18e41de0
IA
503 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
504 if (ret < 0) {
505 dev_err(dev->class_dev,
506 "error! cannot enable PCI device and request regions!\n");
507 return ret;
508 }
509 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
510 iobase = pci_resource_start(pci_dev, 2);
511 return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
512}
18e41de0
IA
513
514/*
515 * Attach is called by the Comedi core to configure the driver
516 * for a particular board. If you specified a board_name array
517 * in the driver structure, dev->board_ptr contains that
518 * address.
519 */
520static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
521{
522 const struct pc236_board *thisboard = comedi_board(dev);
523 int ret;
524
525 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach\n");
526 ret = alloc_private(dev, sizeof(struct pc236_private));
527 if (ret < 0) {
528 dev_err(dev->class_dev, "error! out of memory!\n");
529 return ret;
530 }
531 /* Process options according to bus type. */
409861ff 532 if (is_isa_board(thisboard)) {
a46e759f
IA
533 unsigned long iobase = it->options[0];
534 unsigned int irq = it->options[1];
535 ret = pc236_request_region(dev, iobase, PC236_IO_SIZE);
536 if (ret < 0)
537 return ret;
538 return pc236_common_attach(dev, iobase, irq, 0);
409861ff 539 } else if (is_pci_board(thisboard)) {
a46e759f
IA
540 struct pci_dev *pci_dev;
541
ed7fd225
HS
542 pci_dev = pc236_find_pci_dev(dev, it);
543 if (!pci_dev)
a46e759f
IA
544 return -EIO;
545 return pc236_pci_common_attach(dev, pci_dev);
546 } else {
18e41de0
IA
547 dev_err(dev->class_dev, PC236_DRIVER_NAME
548 ": BUG! cannot determine board type!\n");
a46e759f 549 return -EINVAL;
18e41de0 550 }
18e41de0
IA
551}
552
553/*
554 * The attach_pci hook (if non-NULL) is called at PCI probe time in preference
555 * to the "manual" attach hook. dev->board_ptr is NULL on entry. There should
556 * be a board entry matching the supplied PCI device.
557 */
18e41de0
IA
558static int __devinit pc236_attach_pci(struct comedi_device *dev,
559 struct pci_dev *pci_dev)
560{
561 int ret;
562
a46e759f
IA
563 if (!IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI))
564 return -EINVAL;
565
18e41de0
IA
566 dev_info(dev->class_dev, PC236_DRIVER_NAME ": attach pci %s\n",
567 pci_name(pci_dev));
568 ret = alloc_private(dev, sizeof(struct pc236_private));
569 if (ret < 0) {
570 dev_err(dev->class_dev, "error! out of memory!\n");
571 return ret;
572 }
573 dev->board_ptr = pc236_find_pci_board(pci_dev);
574 if (dev->board_ptr == NULL) {
575 dev_err(dev->class_dev, "BUG! cannot determine board type!\n");
576 return -EINVAL;
577 }
578 return pc236_pci_common_attach(dev, pci_dev);
579}
18e41de0 580
7bd06f69
IA
581static void pc236_detach(struct comedi_device *dev)
582{
24ffe74c 583 struct pc236_private *devpriv = dev->private;
b6d446b5 584 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
24ffe74c 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);
b6d446b5
HS
592 if (pcidev) {
593 if (dev->iobase)
594 comedi_pci_disable(pcidev);
595 pci_dev_put(pcidev);
596 } else {
597 if (dev->iobase)
598 release_region(dev->iobase, PC236_IO_SIZE);
7bd06f69
IA
599 }
600}
601
602/*
603 * The struct comedi_driver structure tells the Comedi core module
604 * which functions to call to configure/deconfigure (attach/detach)
605 * the board, and also about the kernel module that contains
606 * the device code.
607 */
608static struct comedi_driver amplc_pc236_driver = {
609 .driver_name = PC236_DRIVER_NAME,
610 .module = THIS_MODULE,
611 .attach = pc236_attach,
18e41de0 612 .attach_pci = pc236_attach_pci,
7bd06f69
IA
613 .detach = pc236_detach,
614 .board_name = &pc236_boards[0].name,
615 .offset = sizeof(struct pc236_board),
616 .num_names = ARRAY_SIZE(pc236_boards),
617};
618
619#if IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
620static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
621 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
622 {0}
623};
624
625MODULE_DEVICE_TABLE(pci, pc236_pci_table);
626
627static int __devinit amplc_pc236_pci_probe(struct pci_dev *dev,
628 const struct pci_device_id *ent)
629{
630 return comedi_pci_auto_config(dev, &amplc_pc236_driver);
631}
632
633static void __devexit amplc_pc236_pci_remove(struct pci_dev *dev)
634{
635 comedi_pci_auto_unconfig(dev);
636}
637
638static struct pci_driver amplc_pc236_pci_driver = {
639 .name = PC236_DRIVER_NAME,
640 .id_table = pc236_pci_table,
641 .probe = &amplc_pc236_pci_probe,
642 .remove = __devexit_p(&amplc_pc236_pci_remove)
643};
644
645module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
646#else
647module_comedi_driver(amplc_pc236_driver);
648#endif
649
90f703d3
AT
650MODULE_AUTHOR("Comedi http://www.comedi.org");
651MODULE_DESCRIPTION("Comedi low-level driver");
652MODULE_LICENSE("GPL");