]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/blame - drivers/staging/comedi/drivers/amplc_pc236.c
staging: comedi: conditionally build in PCI driver support
[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
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
100enum pc236_bustype { isa_bustype, pci_bustype };
101enum pc236_model { pc36at_model, pci236_model, anypci_model };
102
57ad8696 103struct 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};
109static 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 137struct 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 */
143static 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 */
149static 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
157static 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
172static 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 215static 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 231static 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 249static 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 269static 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
300static 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
312static 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 357static 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
367static 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 379static 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
394static 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
420static 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
464static 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 */
490static 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 530static 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
562static 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 */
593static 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
605static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
606 { PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
607 {0}
608};
609
610MODULE_DEVICE_TABLE(pci, pc236_pci_table);
611
a690b7e5 612static 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, &amplc_pc236_driver);
616}
617
7bd06f69
IA
618static struct pci_driver amplc_pc236_pci_driver = {
619 .name = PC236_DRIVER_NAME,
620 .id_table = pc236_pci_table,
621 .probe = &amplc_pc236_pci_probe,
9901a4d7 622 .remove = comedi_pci_auto_unconfig,
7bd06f69
IA
623};
624
625module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
626#else
627module_comedi_driver(amplc_pc236_driver);
628#endif
629
90f703d3
AT
630MODULE_AUTHOR("Comedi http://www.comedi.org");
631MODULE_DESCRIPTION("Comedi low-level driver");
632MODULE_LICENSE("GPL");