2 comedi/drivers/amplc_pc236.c
3 Driver for Amplicon PC36AT and PCI236 DIO boards.
5 Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
7 COMEDI - Linux Control and Measurement Device Interface
8 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
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.
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.
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.
27 Description: Amplicon PC36AT, PCI236
28 Author: Ian Abbott <abbotti@mev.co.uk>
29 Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236)
30 Updated: Fri, 23 Aug 2002 11:41:11 +0100
33 Configuration options - PC36AT:
34 [0] - I/O port base address
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
43 The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
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
55 #include <linux/comedidev.h>
57 #include <linux/pci.h>
62 #define PC236_DRIVER_NAME "amplc_pc236"
64 /* PCI236 PCI configuration register information */
65 #define PCI_VENDOR_ID_AMPLICON 0x14dc
66 #define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
69 /* PC36AT / PCI236 registers */
71 #define PC236_IO_SIZE 4
72 #define PC236_LCR_IO_SIZE 128
75 * INTCSR values for PCI236.
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 )
93 * Board descriptions for Amplicon PC36AT and PCI236.
96 enum pc236_bustype {isa_bustype, pci_bustype};
97 enum pc236_model {pc36at_model, pci236_model};
99 typedef struct pc236_board_struct{
102 enum pc236_bustype bustype;
103 enum pc236_model model;
105 static pc236_board pc236_boards[] = {
108 fancy_name: "PC36AT",
109 bustype: isa_bustype,
114 fancy_name: "PCI236",
115 bustype: pci_bustype,
120 static struct pci_device_id pc236_pci_table[] __devinitdata = {
121 { PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236, PCI_ANY_ID, PCI_ANY_ID, 0, 0, pci236_model },
124 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
127 * Useful for shorthand access to the particular board structure
129 #define thisboard ((pc236_board *)dev->board_ptr)
131 /* this structure is for data unique to this hardware driver. If
132 several hardware drivers keep similar information in this structure,
133 feel free to suggest moving the variable to the comedi_device struct. */
136 struct pci_dev *pci_dev;
137 unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
141 #define devpriv ((pc236_private *)dev->private)
144 * The comedi_driver structure tells the Comedi core module
145 * which functions to call to configure/deconfigure (attach/detach)
146 * the board, and also about the kernel module that contains
149 static int pc236_attach(comedi_device *dev,comedi_devconfig *it);
150 static int pc236_detach(comedi_device *dev);
151 static comedi_driver driver_amplc_pc236={
152 driver_name: PC236_DRIVER_NAME,
154 attach: pc236_attach,
155 detach: pc236_detach,
156 board_name: (const char**)pc236_boards,
157 offset: sizeof(pc236_board),
158 num_names: sizeof(pc236_boards) / sizeof(pc236_board),
160 COMEDI_INITCLEANUP(driver_amplc_pc236);
163 static int pc236_request_region(unsigned long from, unsigned long extent);
164 static void pc236_intr_disable(comedi_device *dev);
165 static void pc236_intr_enable(comedi_device *dev);
166 static int pc236_intr_check(comedi_device *dev);
167 static int pc236_intr_insn(comedi_device *dev,comedi_subdevice *s,
168 comedi_insn *insn,lsampl_t *data);
169 static int pc236_intr_cmdtest(comedi_device *dev,comedi_subdevice *s,
171 static int pc236_intr_cmd(comedi_device *dev,comedi_subdevice *s);
172 static int pc236_intr_cancel(comedi_device *dev,comedi_subdevice *s);
173 static irqreturn_t pc236_interrupt(int irq,void *d PT_REGS_ARG);
176 * Attach is called by the Comedi core to configure the driver
177 * for a particular board. If you specified a board_name array
178 * in the driver structure, dev->board_ptr contains that
181 static int pc236_attach(comedi_device *dev,comedi_devconfig *it)
184 struct pci_dev *pci_dev = NULL;
185 unsigned long iobase = 0;
186 unsigned int irq = 0;
187 int bus = 0, slot = 0;
188 struct pci_device_id *pci_id;
192 printk("comedi%d: %s: ",dev->minor, PC236_DRIVER_NAME);
194 * Allocate the private structure area. alloc_private() is a
195 * convenient macro defined in comedidev.h.
197 if ((ret=alloc_private(dev,sizeof(pc236_private))) < 0) {
198 printk("out of memory!\n");
201 /* Process options. */
202 switch (thisboard->bustype) {
204 iobase = it->options[0];
205 irq = it->options[1];
209 bus = it->options[0];
210 slot = it->options[1];
213 /* Look for PCI table entry for this model. */
214 for (pci_id = pc236_pci_table; pci_id->vendor != 0; pci_id++) {
215 if (pci_id->driver_data == thisboard->model)
218 if (pci_id->vendor == 0) {
219 printk("bug! cannot determine board type!\n");
223 /* Look for matching PCI device. */
224 for(pci_dev = pci_get_device(pci_id->vendor, pci_id->device,
225 NULL); pci_dev != NULL;
226 pci_dev = pci_get_device(pci_id->vendor,
227 pci_id->device, pci_dev)) {
228 /* If bus/slot specified, check them. */
230 if (bus != pci_dev->bus->number
231 || slot != PCI_SLOT(pci_dev->devfn))
235 if (pci_id->subvendor != PCI_ANY_ID) {
236 if (pci_dev->subsystem_vendor != pci_id->subvendor)
239 if (pci_id->subdevice != PCI_ANY_ID) {
240 if (pci_dev->subsystem_device != pci_id->subdevice)
244 if (((pci_dev->class ^ pci_id->class) & pci_id->class_mask) != 0)
247 devpriv->pci_dev = pci_dev;
251 printk("no %s found!\n", thisboard->fancy_name);
256 printk("bug! cannot determine board type!\n");
262 * Initialize dev->board_name.
264 dev->board_name = thisboard->name;
265 printk("%s ", dev->board_name);
267 /* Enable device and reserve I/O spaces. */
269 if ((ret=pci_enable_device(pci_dev)) < 0) {
270 printk("error enabling PCI device!\n");
273 if ((ret=pci_request_regions(pci_dev, PC236_DRIVER_NAME)) < 0) {
274 printk("I/O port conflict (PCI)!\n");
277 devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
278 iobase = pci_resource_start(pci_dev, 2);
281 if ((ret=pc236_request_region(iobase, PC236_IO_SIZE)) < 0) {
285 dev->iobase = iobase;
288 * Allocate the subdevice structures. alloc_subdevice() is a
289 * convenient macro defined in comedidev.h.
291 if ((ret=alloc_subdevices(dev, 2)) < 0) {
292 printk("out of memory!\n");
296 s = dev->subdevices+0;
297 /* digital i/o subdevice (8255) */
298 if ((ret=subdev_8255_init(dev, s, NULL, iobase)) < 0) {
299 printk("out of memory!\n");
302 s = dev->subdevices+1;
303 dev->read_subdev = s;
304 s->type = COMEDI_SUBD_UNUSED;
305 pc236_intr_disable(dev);
307 unsigned long flags = share_irq ? IRQF_SHARED : 0;
309 if (comedi_request_irq(irq, pc236_interrupt, flags,
310 PC236_DRIVER_NAME, dev) >= 0) {
312 s->type = COMEDI_SUBD_DI;
313 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
316 s->range_table = &range_digital;
317 s->insn_bits = pc236_intr_insn;
318 s->do_cmdtest = pc236_intr_cmdtest;
319 s->do_cmd = pc236_intr_cmd;
320 s->cancel = pc236_intr_cancel;
323 if (thisboard->bustype == isa_bustype) {
324 printk("(base %#lx) ", iobase);
326 printk("(pci %s) ", pci_name(pci_dev));
329 printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
334 printk("attached\n");
341 * _detach is called to deconfigure a device. It should deallocate
343 * This function is also called when _attach() fails, so it should be
344 * careful not to release resources that were not necessarily
345 * allocated by _attach(). dev->private and dev->subdevices are
346 * deallocated automatically by the core.
348 static int pc236_detach(comedi_device *dev)
350 printk("comedi%d: %s: remove\n", dev->minor, PC236_DRIVER_NAME);
352 pc236_intr_disable(dev);
354 if (dev->irq) comedi_free_irq(dev->irq, dev);
355 if (dev->subdevices) {
356 subdev_8255_cleanup(dev, dev->subdevices+0);
359 if (devpriv->pci_dev) {
362 pci_release_regions(devpriv->pci_dev);
363 pci_disable_device(devpriv->pci_dev);
365 pci_dev_put(devpriv->pci_dev);
366 } else if (dev->iobase) {
367 release_region(dev->iobase, PC236_IO_SIZE);
374 * This function checks and requests an I/O region, reporting an error
375 * if there is a conflict.
377 static int pc236_request_region(unsigned long from, unsigned long extent)
379 if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
380 printk("I/O port conflict (%#lx,%lu)!\n", from, extent);
387 * This function is called to mark the interrupt as disabled (no command
388 * configured on subdevice 1) and to physically disable the interrupt
389 * (not possible on the PC36AT, except by removing the IRQ jumper!).
391 static void pc236_intr_disable(comedi_device *dev)
395 comedi_spin_lock_irqsave(&dev->spinlock, flags);
396 devpriv->enable_irq = 0;
397 if (devpriv->lcr_iobase)
398 outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase+PLX9052_INTCSR);
399 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
403 * This function is called to mark the interrupt as enabled (a command
404 * configured on subdevice 1) and to physically enable the interrupt
405 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
407 static void pc236_intr_enable(comedi_device *dev)
411 comedi_spin_lock_irqsave(&dev->spinlock, flags);
412 devpriv->enable_irq = 1;
413 if (devpriv->lcr_iobase)
414 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase+PLX9052_INTCSR);
415 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
419 * This function is called when an interrupt occurs to check whether
420 * the interrupt has been marked as enabled and was generated by the
421 * board. If so, the function prepares the hardware for the next
423 * Returns 0 if the interrupt should be ignored.
425 static int pc236_intr_check(comedi_device *dev)
430 comedi_spin_lock_irqsave(&dev->spinlock, flags);
431 if (devpriv->enable_irq) {
433 if (devpriv->lcr_iobase) {
434 if ((inl(devpriv->lcr_iobase+PLX9052_INTCSR)
435 & PLX9052_INTCSR_LI1STAT_MASK)
436 == PLX9052_INTCSR_LI1STAT_INACTIVE) {
439 /* Clear interrupt and keep it enabled. */
440 outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase+PLX9052_INTCSR);
444 comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
450 * Input from subdevice 1.
451 * Copied from the comedi_parport driver.
453 static int pc236_intr_insn(comedi_device *dev,comedi_subdevice *s,
454 comedi_insn *insn,lsampl_t *data)
461 * Subdevice 1 command test.
462 * Copied from the comedi_parport driver.
464 static int pc236_intr_cmdtest(comedi_device *dev,comedi_subdevice *s,
473 cmd->start_src &= TRIG_NOW;
474 if(!cmd->start_src || tmp!=cmd->start_src)err++;
476 tmp=cmd->scan_begin_src;
477 cmd->scan_begin_src &= TRIG_EXT;
478 if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++;
480 tmp=cmd->convert_src;
481 cmd->convert_src &= TRIG_FOLLOW;
482 if(!cmd->convert_src || tmp!=cmd->convert_src)err++;
484 tmp=cmd->scan_end_src;
485 cmd->scan_end_src &= TRIG_COUNT;
486 if(!cmd->scan_end_src || tmp!=cmd->scan_end_src)err++;
489 cmd->stop_src &= TRIG_NONE;
490 if(!cmd->stop_src || tmp!=cmd->stop_src)err++;
494 /* step 2: ignored */
500 if(cmd->start_arg!=0){
504 if(cmd->scan_begin_arg!=0){
505 cmd->scan_begin_arg = 0;
508 if(cmd->convert_arg!=0){
509 cmd->convert_arg = 0;
512 if(cmd->scan_end_arg!=1){
513 cmd->scan_end_arg = 1;
516 if(cmd->stop_arg!=0){
523 /* step 4: ignored */
531 * Subdevice 1 command.
533 static int pc236_intr_cmd(comedi_device *dev,comedi_subdevice *s)
535 pc236_intr_enable(dev);
541 * Subdevice 1 cancel command.
543 static int pc236_intr_cancel(comedi_device *dev,comedi_subdevice *s)
545 pc236_intr_disable(dev);
551 * Interrupt service routine.
552 * Based on the comedi_parport driver.
554 static irqreturn_t pc236_interrupt(int irq,void *d PT_REGS_ARG)
556 comedi_device *dev=d;
557 comedi_subdevice *s=dev->subdevices+1;
560 handled = pc236_intr_check(dev);
562 comedi_buf_put(s->async,0);
563 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
564 comedi_event(dev,s,s->async->events);
566 return IRQ_RETVAL(handled);