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