Staging: comedi: remove space after ampersand
[deliverable/linux.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
59#include "comedi_pci.h"
60
61#include "8255.h"
62#include "plx9052.h"
63
64#define PC236_DRIVER_NAME "amplc_pc236"
65
66/* PCI236 PCI configuration register information */
67#define PCI_VENDOR_ID_AMPLICON 0x14dc
68#define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
69#define PCI_DEVICE_ID_INVALID 0xffff
70
71/* PC36AT / PCI236 registers */
72
73#define PC236_IO_SIZE 4
74#define PC236_LCR_IO_SIZE 128
75
76/*
77 * INTCSR values for PCI236.
78 */
79/* Disable interrupt, also clear any interrupt there */
53106ae6 80#define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \
6a5c8664
IA
81 | PLX9052_INTCSR_LI1POL_HIGH \
82 | PLX9052_INTCSR_LI2POL_HIGH \
83 | PLX9052_INTCSR_PCIENAB_DISABLED \
84 | PLX9052_INTCSR_LI1SEL_EDGE \
53106ae6 85 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
6a5c8664 86/* Enable interrupt, also clear any interrupt there. */
53106ae6 87#define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \
6a5c8664
IA
88 | PLX9052_INTCSR_LI1POL_HIGH \
89 | PLX9052_INTCSR_LI2POL_HIGH \
90 | PLX9052_INTCSR_PCIENAB_ENABLED \
91 | PLX9052_INTCSR_LI1SEL_EDGE \
53106ae6 92 | PLX9052_INTCSR_LI1CLRINT_ASSERTED)
6a5c8664
IA
93
94/*
95 * Board descriptions for Amplicon PC36AT and PCI236.
96 */
97
98enum pc236_bustype { isa_bustype, pci_bustype };
99enum pc236_model { pc36at_model, pci236_model, anypci_model };
100
57ad8696 101struct pc236_board {
6a5c8664
IA
102 const char *name;
103 const char *fancy_name;
104 unsigned short devid;
105 enum pc236_bustype bustype;
106 enum pc236_model model;
57ad8696
BP
107};
108static const struct pc236_board pc236_boards[] = {
6a5c8664 109 {
68c3dbff
BP
110 .name = "pc36at",
111 .fancy_name = "PC36AT",
112 .bustype = isa_bustype,
113 .model = pc36at_model,
6a5c8664
IA
114 },
115#ifdef CONFIG_COMEDI_PCI
116 {
68c3dbff
BP
117 .name = "pci236",
118 .fancy_name = "PCI236",
119 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
120 .bustype = pci_bustype,
121 .model = pci236_model,
6a5c8664
IA
122 },
123#endif
124#ifdef CONFIG_COMEDI_PCI
125 {
68c3dbff
BP
126 .name = PC236_DRIVER_NAME,
127 .fancy_name = PC236_DRIVER_NAME,
128 .devid = PCI_DEVICE_ID_INVALID,
129 .bustype = pci_bustype,
130 .model = anypci_model, /* wildcard */
6a5c8664
IA
131 },
132#endif
133};
134
135#ifdef CONFIG_COMEDI_PCI
136static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
137 {PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236, PCI_ANY_ID,
138 PCI_ANY_ID, 0, 0, 0},
139 {0}
140};
141
142MODULE_DEVICE_TABLE(pci, pc236_pci_table);
143#endif /* CONFIG_COMEDI_PCI */
144
145/*
146 * Useful for shorthand access to the particular board structure
147 */
57ad8696 148#define thisboard ((const struct pc236_board *)dev->board_ptr)
6a5c8664
IA
149
150/* this structure is for data unique to this hardware driver. If
151 several hardware drivers keep similar information in this structure,
71b5f4f1 152 feel free to suggest moving the variable to the struct comedi_device struct. */
f1ee810a 153struct pc236_private {
6a5c8664
IA
154#ifdef CONFIG_COMEDI_PCI
155 /* PCI device */
156 struct pci_dev *pci_dev;
157 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
158#endif
159 int enable_irq;
f1ee810a 160};
6a5c8664 161
f1ee810a 162#define devpriv ((struct pc236_private *)dev->private)
6a5c8664
IA
163
164/*
139dfbdf 165 * The struct comedi_driver structure tells the Comedi core module
6a5c8664
IA
166 * which functions to call to configure/deconfigure (attach/detach)
167 * the board, and also about the kernel module that contains
168 * the device code.
169 */
da91b269
BP
170static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
171static int pc236_detach(struct comedi_device *dev);
139dfbdf 172static struct comedi_driver driver_amplc_pc236 = {
68c3dbff
BP
173 .driver_name = PC236_DRIVER_NAME,
174 .module = THIS_MODULE,
175 .attach = pc236_attach,
176 .detach = pc236_detach,
177 .board_name = &pc236_boards[0].name,
178 .offset = sizeof(struct pc236_board),
179 .num_names = sizeof(pc236_boards) / sizeof(struct pc236_board),
6a5c8664
IA
180};
181
182#ifdef CONFIG_COMEDI_PCI
183COMEDI_PCI_INITCLEANUP(driver_amplc_pc236, pc236_pci_table);
184#else
185COMEDI_INITCLEANUP(driver_amplc_pc236);
186#endif
187
188static int pc236_request_region(unsigned minor, unsigned long from,
189 unsigned long extent);
71b5f4f1
BP
190static void pc236_intr_disable(struct comedi_device * dev);
191static void pc236_intr_enable(struct comedi_device * dev);
192static int pc236_intr_check(struct comedi_device * dev);
34c43922 193static int pc236_intr_insn(struct comedi_device * dev, struct comedi_subdevice * s,
90035c08 194 struct comedi_insn * insn, unsigned int * data);
34c43922 195static int pc236_intr_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s,
ea6d0d4c 196 struct comedi_cmd * cmd);
34c43922
BP
197static int pc236_intr_cmd(struct comedi_device * dev, struct comedi_subdevice * s);
198static int pc236_intr_cancel(struct comedi_device * dev, struct comedi_subdevice * s);
70265d24 199static irqreturn_t pc236_interrupt(int irq, void *d);
6a5c8664
IA
200
201/*
202 * This function looks for a PCI device matching the requested board name,
203 * bus and slot.
204 */
205#ifdef CONFIG_COMEDI_PCI
206static int
da91b269 207pc236_find_pci(struct comedi_device *dev, int bus, int slot,
6a5c8664
IA
208 struct pci_dev **pci_dev_p)
209{
210 struct pci_dev *pci_dev = NULL;
211
212 *pci_dev_p = NULL;
213
214 /* Look for matching PCI device. */
215 for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
216 pci_dev != NULL;
217 pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
218 PCI_ANY_ID, pci_dev)) {
219 /* If bus/slot specified, check them. */
220 if (bus || slot) {
221 if (bus != pci_dev->bus->number
222 || slot != PCI_SLOT(pci_dev->devfn))
223 continue;
224 }
225 if (thisboard->model == anypci_model) {
226 /* Match any supported model. */
227 int i;
228
229 for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
230 if (pc236_boards[i].bustype != pci_bustype)
231 continue;
232 if (pci_dev->device == pc236_boards[i].devid) {
233 /* Change board_ptr to matched board. */
234 dev->board_ptr = &pc236_boards[i];
235 break;
236 }
237 }
238 if (i == ARRAY_SIZE(pc236_boards))
239 continue;
240 } else {
241 /* Match specific model name. */
242 if (pci_dev->device != thisboard->devid)
243 continue;
244 }
245
246 /* Found a match. */
247 *pci_dev_p = pci_dev;
248 return 0;
249 }
250 /* No match found. */
251 if (bus || slot) {
252 printk(KERN_ERR
253 "comedi%d: error! no %s found at pci %02x:%02x!\n",
254 dev->minor, thisboard->name, bus, slot);
255 } else {
256 printk(KERN_ERR "comedi%d: error! no %s found!\n",
257 dev->minor, thisboard->name);
258 }
259 return -EIO;
260}
261#endif
262
263/*
264 * Attach is called by the Comedi core to configure the driver
265 * for a particular board. If you specified a board_name array
266 * in the driver structure, dev->board_ptr contains that
267 * address.
268 */
da91b269 269static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
6a5c8664 270{
34c43922 271 struct comedi_subdevice *s;
6a5c8664
IA
272 unsigned long iobase = 0;
273 unsigned int irq = 0;
274#ifdef CONFIG_COMEDI_PCI
275 struct pci_dev *pci_dev = NULL;
276 int bus = 0, slot = 0;
277#endif
278 int share_irq = 0;
279 int ret;
280
281 printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
282 PC236_DRIVER_NAME);
283/*
284 * Allocate the private structure area. alloc_private() is a
285 * convenient macro defined in comedidev.h.
286 */
c3744138
BP
287 ret = alloc_private(dev, sizeof(struct pc236_private));
288 if (ret < 0) {
6a5c8664
IA
289 printk(KERN_ERR "comedi%d: error! out of memory!\n",
290 dev->minor);
291 return ret;
292 }
293 /* Process options. */
294 switch (thisboard->bustype) {
295 case isa_bustype:
296 iobase = it->options[0];
297 irq = it->options[1];
298 share_irq = 0;
299 break;
300#ifdef CONFIG_COMEDI_PCI
301 case pci_bustype:
302 bus = it->options[0];
303 slot = it->options[1];
304 share_irq = 1;
305
c3744138
BP
306 ret = pc236_find_pci(dev, bus, slot, &pci_dev);
307 if (ret < 0)
6a5c8664
IA
308 return ret;
309 devpriv->pci_dev = pci_dev;
310 break;
311#endif /* CONFIG_COMEDI_PCI */
312 default:
313 printk(KERN_ERR
314 "comedi%d: %s: BUG! cannot determine board type!\n",
315 dev->minor, PC236_DRIVER_NAME);
316 return -EINVAL;
317 break;
318 }
319
320/*
321 * Initialize dev->board_name.
322 */
323 dev->board_name = thisboard->name;
324
325 /* Enable device and reserve I/O spaces. */
326#ifdef CONFIG_COMEDI_PCI
327 if (pci_dev) {
c3744138
BP
328
329 ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
330 if (ret < 0) {
6a5c8664
IA
331 printk(KERN_ERR
332 "comedi%d: error! cannot enable PCI device and request regions!\n",
333 dev->minor);
334 return ret;
335 }
336 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
337 iobase = pci_resource_start(pci_dev, 2);
338 irq = pci_dev->irq;
339 } else
340#endif
341 {
342 ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
343 if (ret < 0) {
344 return ret;
345 }
346 }
347 dev->iobase = iobase;
348
349/*
350 * Allocate the subdevice structures. alloc_subdevice() is a
351 * convenient macro defined in comedidev.h.
352 */
c3744138
BP
353 ret = alloc_subdevices(dev, 2);
354 if (ret < 0) {
6a5c8664
IA
355 printk(KERN_ERR "comedi%d: error! out of memory!\n",
356 dev->minor);
357 return ret;
358 }
359
360 s = dev->subdevices + 0;
361 /* digital i/o subdevice (8255) */
c3744138
BP
362 ret = subdev_8255_init(dev, s, NULL, iobase);
363 if (ret < 0) {
6a5c8664
IA
364 printk(KERN_ERR "comedi%d: error! out of memory!\n",
365 dev->minor);
366 return ret;
367 }
368 s = dev->subdevices + 1;
369 dev->read_subdev = s;
370 s->type = COMEDI_SUBD_UNUSED;
371 pc236_intr_disable(dev);
372 if (irq) {
373 unsigned long flags = share_irq ? IRQF_SHARED : 0;
374
375 if (comedi_request_irq(irq, pc236_interrupt, flags,
376 PC236_DRIVER_NAME, dev) >= 0) {
377 dev->irq = irq;
378 s->type = COMEDI_SUBD_DI;
379 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
380 s->n_chan = 1;
381 s->maxdata = 1;
382 s->range_table = &range_digital;
383 s->insn_bits = pc236_intr_insn;
384 s->do_cmdtest = pc236_intr_cmdtest;
385 s->do_cmd = pc236_intr_cmd;
386 s->cancel = pc236_intr_cancel;
387 }
388 }
389 printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
390 if (thisboard->bustype == isa_bustype) {
391 printk("(base %#lx) ", iobase);
392 } else {
393#ifdef CONFIG_COMEDI_PCI
394 printk("(pci %s) ", pci_name(pci_dev));
395#endif
396 }
397 if (irq) {
398 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
399 } else {
400 printk("(no irq) ");
401 }
402
403 printk("attached\n");
404
405 return 1;
406}
407
408/*
409 * _detach is called to deconfigure a device. It should deallocate
410 * resources.
411 * This function is also called when _attach() fails, so it should be
412 * careful not to release resources that were not necessarily
413 * allocated by _attach(). dev->private and dev->subdevices are
414 * deallocated automatically by the core.
415 */
da91b269 416static int pc236_detach(struct comedi_device *dev)
6a5c8664
IA
417{
418 printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
419 PC236_DRIVER_NAME);
420 if (devpriv) {
421 pc236_intr_disable(dev);
422 }
423 if (dev->irq)
424 comedi_free_irq(dev->irq, dev);
425 if (dev->subdevices) {
426 subdev_8255_cleanup(dev, dev->subdevices + 0);
427 }
428 if (devpriv) {
429#ifdef CONFIG_COMEDI_PCI
430 if (devpriv->pci_dev) {
431 if (dev->iobase) {
432 comedi_pci_disable(devpriv->pci_dev);
433 }
434 pci_dev_put(devpriv->pci_dev);
435 } else
436#endif
437 {
438 if (dev->iobase) {
439 release_region(dev->iobase, PC236_IO_SIZE);
440 }
441 }
442 }
443 if (dev->board_name) {
444 printk(KERN_INFO "comedi%d: %s removed\n",
445 dev->minor, dev->board_name);
446 }
447 return 0;
448}
449
450/*
451 * This function checks and requests an I/O region, reporting an error
452 * if there is a conflict.
453 */
454static int pc236_request_region(unsigned minor, unsigned long from,
455 unsigned long extent)
456{
457 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
458 printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
459 minor, from, extent);
460 return -EIO;
461 }
462 return 0;
463}
464
465/*
466 * This function is called to mark the interrupt as disabled (no command
467 * configured on subdevice 1) and to physically disable the interrupt
468 * (not possible on the PC36AT, except by removing the IRQ jumper!).
469 */
da91b269 470static void pc236_intr_disable(struct comedi_device *dev)
6a5c8664
IA
471{
472 unsigned long flags;
473
474 comedi_spin_lock_irqsave(&dev->spinlock, flags);
475 devpriv->enable_irq = 0;
476#ifdef CONFIG_COMEDI_PCI
477 if (devpriv->lcr_iobase)
478 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
479#endif
480 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
481}
482
483/*
484 * This function is called to mark the interrupt as enabled (a command
485 * configured on subdevice 1) and to physically enable the interrupt
486 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
487 */
da91b269 488static void pc236_intr_enable(struct comedi_device *dev)
6a5c8664
IA
489{
490 unsigned long flags;
491
492 comedi_spin_lock_irqsave(&dev->spinlock, flags);
493 devpriv->enable_irq = 1;
494#ifdef CONFIG_COMEDI_PCI
495 if (devpriv->lcr_iobase)
496 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
497#endif
498 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
499}
500
501/*
502 * This function is called when an interrupt occurs to check whether
503 * the interrupt has been marked as enabled and was generated by the
504 * board. If so, the function prepares the hardware for the next
505 * interrupt.
506 * Returns 0 if the interrupt should be ignored.
507 */
da91b269 508static int pc236_intr_check(struct comedi_device *dev)
6a5c8664
IA
509{
510 int retval = 0;
511 unsigned long flags;
512
513 comedi_spin_lock_irqsave(&dev->spinlock, flags);
514 if (devpriv->enable_irq) {
515 retval = 1;
516#ifdef CONFIG_COMEDI_PCI
517 if (devpriv->lcr_iobase) {
518 if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
519 & PLX9052_INTCSR_LI1STAT_MASK)
520 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
521 retval = 0;
522 } else {
523 /* Clear interrupt and keep it enabled. */
524 outl(PCI236_INTR_ENABLE,
525 devpriv->lcr_iobase + PLX9052_INTCSR);
526 }
527 }
528#endif
529 }
530 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
531
532 return retval;
533}
534
535/*
536 * Input from subdevice 1.
537 * Copied from the comedi_parport driver.
538 */
da91b269
BP
539static int pc236_intr_insn(struct comedi_device *dev, struct comedi_subdevice *s,
540 struct comedi_insn *insn, unsigned int *data)
6a5c8664
IA
541{
542 data[1] = 0;
543 return 2;
544}
545
546/*
547 * Subdevice 1 command test.
548 * Copied from the comedi_parport driver.
549 */
da91b269
BP
550static int pc236_intr_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
551 struct comedi_cmd *cmd)
6a5c8664
IA
552{
553 int err = 0;
554 int tmp;
555
556 /* step 1 */
557
558 tmp = cmd->start_src;
559 cmd->start_src &= TRIG_NOW;
560 if (!cmd->start_src || tmp != cmd->start_src)
561 err++;
562
563 tmp = cmd->scan_begin_src;
564 cmd->scan_begin_src &= TRIG_EXT;
565 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
566 err++;
567
568 tmp = cmd->convert_src;
569 cmd->convert_src &= TRIG_FOLLOW;
570 if (!cmd->convert_src || tmp != cmd->convert_src)
571 err++;
572
573 tmp = cmd->scan_end_src;
574 cmd->scan_end_src &= TRIG_COUNT;
575 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
576 err++;
577
578 tmp = cmd->stop_src;
579 cmd->stop_src &= TRIG_NONE;
580 if (!cmd->stop_src || tmp != cmd->stop_src)
581 err++;
582
583 if (err)
584 return 1;
585
586 /* step 2: ignored */
587
588 if (err)
589 return 2;
590
591 /* step 3: */
592
593 if (cmd->start_arg != 0) {
594 cmd->start_arg = 0;
595 err++;
596 }
597 if (cmd->scan_begin_arg != 0) {
598 cmd->scan_begin_arg = 0;
599 err++;
600 }
601 if (cmd->convert_arg != 0) {
602 cmd->convert_arg = 0;
603 err++;
604 }
605 if (cmd->scan_end_arg != 1) {
606 cmd->scan_end_arg = 1;
607 err++;
608 }
609 if (cmd->stop_arg != 0) {
610 cmd->stop_arg = 0;
611 err++;
612 }
613
614 if (err)
615 return 3;
616
617 /* step 4: ignored */
618
619 if (err)
620 return 4;
621
622 return 0;
623}
624
625/*
626 * Subdevice 1 command.
627 */
da91b269 628static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
6a5c8664
IA
629{
630 pc236_intr_enable(dev);
631
632 return 0;
633}
634
635/*
636 * Subdevice 1 cancel command.
637 */
da91b269 638static int pc236_intr_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
6a5c8664
IA
639{
640 pc236_intr_disable(dev);
641
642 return 0;
643}
644
645/*
646 * Interrupt service routine.
647 * Based on the comedi_parport driver.
648 */
70265d24 649static irqreturn_t pc236_interrupt(int irq, void *d)
6a5c8664 650{
71b5f4f1 651 struct comedi_device *dev = d;
34c43922 652 struct comedi_subdevice *s = dev->subdevices + 1;
6a5c8664
IA
653 int handled;
654
655 handled = pc236_intr_check(dev);
656 if (dev->attached && handled) {
657 comedi_buf_put(s->async, 0);
658 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
659 comedi_event(dev, s);
660 }
661 return IRQ_RETVAL(handled);
662}
This page took 0.137237 seconds and 5 git commands to generate.